Files
eagle0/DEPROTO_PLAN.md
T
c1756cb36b Add protoless ProvinceViewFilter overload with faction filtering (#5034)
- Add filteredProvinceView(ProvinceT, ScalaGameState, FactionId) overload
- Use protoless FactionUtils.hasAlliance, Visibility.hasFullVisibility, and ProvinceUtils.incomingOthers
- Add helper methods: fullProvinceInfoScala, maybeIncomingAttackersScala, unaffiliatedHeroInfoScala
- Handle reconned provinces directly from Scala FactionT.reconnedProvinces

GameStateViewFilter improvements:
- Eliminate GameStateConverter.toProto() call in Scala overload
- Use new ProvinceViewFilter Scala overload for faction filtering
- Convert battalionTypes and chronicleEntries to proto only at output boundary

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 22:16:44 -08:00

10 KiB

Deproto Migration Plan

This document tracks the migration from protobuf types to native Scala models inside the Eagle game engine.

Architectural Decisions

  1. Keep proto for persistence: Yes - protobuf is used for persisting game state
  2. Keep proto for Shardok communication: Yes - protobuf is used for Eagle-Shardok gRPC communication
  3. Use Scala views inside the library: Yes - use native Scala types like ProvinceView, FactionView, HeroView, etc. within the library code

Recent Completed Work

GameState Round-Trip Elimination (PRs #4913, #4914, #4915)

Eliminated wasteful Scala→proto→Scala conversions in the hot path:

  1. LLM Pipeline (#4913): LlmRequestWithGameState now uses Scala GameState instead of proto. All ~38 prompt generators updated to use Scala model types (FactionT, HeroT, ProvinceT).

  2. ActionWithResultingState Caching (#4914): Added precomputedScalaState: Option[GameState] to cache Scala state when available, avoiding fromProto() conversion in stateAfter().

  3. PostResults Simplification (#4915): Changed PostResults.gameState from proto to Option[GameState] (Scala), eliminating toProto() calls when creating PostResults.

Migration Pattern

The codebase follows a Legacy pattern* for separating proto-dependent and protoless code:

  • Protoless utilities: FactionUtils, HeroUtils, ProvinceUtils, ProvinceDistances, etc.
  • Proto-dependent utilities: LegacyFactionUtils, LegacyHeroUtils, LegacyProvinceUtils, LegacyProvinceDistances, etc.

When migrating a file:

  1. Create a Legacy* version containing the proto-dependent methods
  2. Keep the original file name for protoless methods
  3. Update callers to use the appropriate version based on their context

Migration Status

Fully Protoless (no proto imports)

Utilities:

  • FactionUtils - has protoless ownedNeighbors method
  • ProvinceDistances - split into protoless + LegacyProvinceDistances
  • SwornBrotherChooser - fully protoless (removed bestChoiceProto)

Command Selectors (all use native GameState):

  • AllianceOfferCommandSelector
  • AlmsCommandSelector
  • AttackCommandChooser
  • ExpandCommandSelector
  • HeroGiftCommandSelector
  • ImproveCommandSelector
  • MarchTowardProvinceCommandChooser - in AI folder, uses native GameState (callers convert)
  • OrganizeCommandSelector
  • RansomOfferHelpers
  • SeekMoreLeadersCommandChooser - in AI folder, uses native GameState
  • TruceOfferCommandSelector
  • TrustForDiplomacy

Quest Command Selectors (all protoless):

  • AllianceQuestCommandChooser
  • AlmsAcrossRealmQuestCommandChooser
  • AlmsToProvinceQuestCommandChooser
  • DismissSpecificVassalCommandChooser
  • GiveToHeroesAcrossRealmQuestCommandChooser
  • GiveToHeroesInProvinceQuestCommandChooser
  • ImproveQuestCommandChooser
  • QuestCommandChooser
  • TruceCountQuestCommandChooser
  • TruceWithFactionQuestCommandChooser

Fully Protoless

  • AIClientUtils - has protoless overloads (takenHeroIdsForMarchTowardFocus, mostPowerfulHeroes)
  • AttackCommandChooser - uses Scala GameState and BattalionViewC (Scala view type)
  • BattalionPower - has estimatedPower(BattalionViewC) for recon data with optional stats
  • CommandChoiceHelpers - fully protoless, uses Scala GameState throughout
  • ProvinceGoldSurplusCalculator - fully protoless (callers use converters)
  • HeroSelector - fully protoless (removed dead minimallyFatiguedHeroesProto)

AI Layer COMPLETE

All AI and command chooser code is now fully protoless:

  • AttackDecisionCommandChooser - uses Scala GameState
  • CommandChooser - trait uses Scala GameState
  • FulfillQuestsCommandSelector - uses Scala GameState
  • MidGameAIClient - uses Scala GameState internally

Still Using Proto GameState (Boundary Code)

These files use proto GameState because they're at system boundaries:

View Filters (client projection):

  • view_filters/GameStateViewFilter - has Scala overload, uses Scala sub-filters
  • view_filters/ProvinceViewFilter - has Scala overloads for some methods
  • view_filters/FactionViewFilter - has Scala overload
  • view_filters/HeroViewFilter - has Scala overload
  • view_filters/BattalionNameFilter - has Scala overload
  • view_filters/BattleFilter - has Scala overload

Legacy Utilities (to be deprecated):

  • LegacyProvinceDistances, LegacyFactionUtils, LegacyHeroUtils, etc.
  • Used by code that still needs proto GameState

Persistence/Action System:

  • ActionResultTApplier, ActionResultProtoApplier - apply results to proto state
  • ActionWithResultingState - caches both proto and Scala state

Shardok Interface (gRPC boundary):

  • ShardokInterfaceGrpcClient, ShardokInterfaceProxy - must use proto for C++ communication

Next Steps

Phase 1-3: AI Layer COMPLETE

The entire AI decision-making layer is now protoless.

Phase 4: View Filters COMPLETE

The view_filters package migration is complete:

Completed:

  • GameStateViewFilter - added Scala GameState overload (uses Scala sub-filters)
  • ProvinceViewFilter - already has Scala overloads for server-side views
  • HumanPlayerClientConnectionState - updated to pass Scala GameState directly
  • HeroViewFilter - added Scala overload
  • FactionViewFilter - added Scala overload
  • Visibility - added Scala overloads

Still Using Proto:

  • BattalionNameFilter - has Scala overload
  • BattleFilter - has Scala overload
  • ActionResultFilter - uses proto internally (boundary code)

Strategy:

  1. Add Scala GameState overloads to view filter methods
  2. Update callers to pass Scala GameState where available
  3. Eventually deprecate proto versions

Phase 5: Legacy Utility Cleanup (IN PROGRESS)

Remove Legacy* utilities by migrating remaining callers:

  1. Identify callers of each Legacy* util
  2. Update callers to use protoless versions
  3. Delete Legacy* files when no longer needed

Deleted (no production callers):

  • LegacyProvinceDistances - deleted (no callers)
  • LegacyBattalionSuitability - deleted (no callers)
  • LegacyFoodConsumptionUtils - deleted (no callers)
  • LegacyHandleRiotUtils - deleted (no callers)

Refactored to Thin Wrappers (delegating to protoless versions):

  • LegacyRansomValidity - already a thin wrapper delegating to RansomValidity
  • LegacyRecruitmentOdds - refactored to delegate to RecruitmentOdds

Parallel Implementations (proto mirrors protoless):

  • FactionUtils / LegacyFactionUtils - both have matching APIs; LegacyFactionUtils used by boundary code (24+ callers)
  • HeroUtils / LegacyHeroUtils - both have matching APIs; LegacyHeroUtils used by boundary code (10 callers)
  • ProvinceUtils / LegacyProvinceUtils - both have matching APIs; LegacyProvinceUtils used by boundary code (20 callers: availability factories, view filters)

Parallel Implementations (awaiting migration of callers):

  • BattalionUtils / LegacyBattalionUtils - both have matching core methods; LegacyBattalionUtils used by boundary code (4 callers)
  • BattalionViewFilter / LegacyBattalionViewFilter - protoless version exists; Legacy used by view filters, action appliers (3 callers)
  • BattalionTypeFinder / LegacyBattalionTypeFinder - protoless version exists; Legacy used by validators (1 caller: RuntimeValidator)

Recent Caller Migration

CheckForFulfilledQuestsAction - migrated to use protoless BattalionTypeFinder:

  • Changed battalionTypes parameter from proto Vector[BattalionType] to Scala Vector[BattalionType]
  • Updated callers (EngineImpl, EndVassalCommandsPhaseAction) to pass Scala types directly
  • Eliminated wasteful BattalionTypeConverter.toProto() conversions

ExpandedUnaffiliatedHeroUtils - added Scala overload:

  • New overload takes Scala GameState and UnaffiliatedHeroT directly
  • Added UnaffiliatedHeroConverter.unaffiliatedHeroTypeToProto() helper for efficient enum conversion
  • Proto overload retained for backward compatibility

AvailablePleaseRecruitMeCommandFactory - eliminated wasteful proto conversions:

  • Now uses ExpandedUnaffiliatedHeroUtils.expandedUnaffiliatedHero(ScalaGameState, UnaffiliatedHeroT) directly
  • Removed GameStateConverter.toProto() and UnaffiliatedHeroConverter.toProto() calls
  • Factory is now fully protoless internally (still returns proto types for API boundary)

ProvinceViewFilter - added Scala overload with faction filtering:

  • New filteredProvinceView(province: ProvinceT, gs: ScalaGameState, factionId: FactionId) overload
  • Uses protoless FactionUtils.hasAlliance, Visibility.hasFullVisibility, and ProvinceUtils.incomingOthers
  • Handles reconned provinces directly from Scala FactionT.reconnedProvinces (already Scala type)
  • Added helper methods: fullProvinceInfoScala, maybeIncomingAttackersScala, unaffiliatedHeroInfoScala
  • Events still converted to proto at the end (ProvinceView.knownEvents uses proto events)

GameStateViewFilter - eliminated GameStateConverter.toProto() call:

  • Scala overload now fully protoless internally
  • Uses the new ProvinceViewFilter Scala overload with faction filtering
  • Converts battalionTypes and chronicleEntries to proto only at output boundary

Key Files

Protoless Model Types

  • src/main/scala/net/eagle0/eagle/model/state/game_state/GameState.scala - native Scala GameState
  • src/main/scala/net/eagle0/eagle/model/state/province/ProvinceView.scala - province view type
  • src/main/scala/net/eagle0/eagle/model/state/faction/FactionView.scala - faction view type
  • src/main/scala/net/eagle0/eagle/model/state/hero/HeroView.scala - hero view type

Proto Converters

  • src/main/scala/net/eagle0/eagle/model/proto_converters/game_state/ - converts between proto and Scala types

Notes

  • The AI client code (src/main/scala/net/eagle0/eagle/ai/) is now fully protoless
  • Proto GameState is still needed at boundaries: persistence, gRPC to Shardok
  • PerformUnaffiliatedHeroesAction and the LLM pipeline use protoless GameState
  • GameStateViewFilter Scala overload is now fully protoless internally (converts to proto only at output)
  • ProvinceViewFilter has Scala overloads for all three modes: no filtering, faction filtering, and withdrawn-from view