Move ShardokBattle boundary conversion to ShardokInterfaceGrpcClient (#5471)

* Remove stale proto_converter deps from library/ BUILD files

Library code should not depend on proto_converters - those belong at
the service layer boundary. Removed 9 stale proto_converter deps that
were no longer used by any Scala code.

The only remaining proto_converter dep is shardok_battle_converter,
which is actually used by ResolveBattleAction.scala (to be fixed in
a follow-up PR).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Move ShardokBattle boundary conversion to ShardokInterfaceGrpcClient

Previously, BattleResolution contained a proto ShardokBattle, requiring
ResolveBattleAction in library/ to depend on ShardokBattleConverter.
This violated the boundary principle where proto conversions should
happen at the service layer, not in library code.

This change:
- Updates BattleResolution.battle to use Scala ShardokBattle
- Moves the proto-to-Scala conversion into ShardokInterfaceGrpcClient
- Removes ShardokBattleConverter dependency from library/ code
- Updates tests to use the scalaBattle() helper for conversion

This completely removes proto_converters dependencies from library/
(the last one was shardok_battle_converter).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add linter check for library/ proto_converters boundary

Adds Rule 3 to check_build_deps.sh that verifies library/ code does not
depend on proto_converters. Proto conversions should happen at service
boundaries (ShardokInterfaceGrpcClient, EagleServiceImpl, etc.), not in
library code.

Also updates baseline from 167 to 75 proto deps reflecting recent cleanup.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-20 06:27:57 -08:00
committed by GitHub
co-authored by Claude Opus 4.5
parent 8975514e7c
commit 6e7611d547
12 changed files with 84 additions and 48 deletions
+1
View File
@@ -250,6 +250,7 @@ Also updated:
- [x] Hostility enum migrated to Scala
- [x] Engine API returns Scala types (ActionResultView)
- [x] No "proto creep" into business logic
- [x] **Linter enforces library/ cannot depend on proto_converters** (`scripts/check_build_deps.sh`)
---
+2 -2
View File
@@ -1,3 +1,3 @@
# BUILD dependency baseline - updated Sun Jan 18 16:17:22 PST 2026
# BUILD dependency baseline - updated Mon Jan 19 22:38:21 PST 2026
# Do not increase these numbers - only decrease!
library_proto_deps=167
library_proto_deps=75
+24
View File
@@ -75,6 +75,27 @@ check_library_depends_on_proto() {
fi
}
# Rule 3: library/ should not depend on proto_converters
# Proto conversions should happen at service boundaries, not in library code
check_library_depends_on_proto_converters() {
echo -e "${YELLOW}Checking: library/ should not depend on proto_converters...${NC}"
violations=$(bazel query 'deps(//src/main/scala/net/eagle0/eagle/library/...) intersect //src/main/scala/net/eagle0/eagle/model/proto_converters/...' 2>/dev/null | grep "^//" || true)
if [ -n "$violations" ]; then
count=$(echo "$violations" | wc -l | tr -d ' ')
echo -e "${RED}VIOLATION: library/ depends on $count proto_converters targets:${NC}"
echo "$violations"
echo ""
echo "Proto conversions should happen at service boundaries (ShardokInterfaceGrpcClient,"
echo "EagleServiceImpl, etc.), not in library code."
return 1
else
echo -e "${GREEN}✓ No proto_converters dependencies in library/${NC}"
return 0
fi
}
# Count proto deps for tracking deproto progress
count_proto_deps() {
echo -e "${YELLOW}=== Proto dependency counts ===${NC}"
@@ -132,10 +153,12 @@ case "$MODE" in
--ci)
check_main_depends_on_test || EXIT_CODE=1
check_library_depends_on_proto || EXIT_CODE=1
check_library_depends_on_proto_converters || EXIT_CODE=1
;;
--strict)
check_main_depends_on_test || EXIT_CODE=1
check_proto_baseline || EXIT_CODE=1
check_library_depends_on_proto_converters || EXIT_CODE=1
;;
--update-baseline)
update_baseline
@@ -143,6 +166,7 @@ case "$MODE" in
*)
check_main_depends_on_test || EXIT_CODE=1
check_library_depends_on_proto
check_library_depends_on_proto_converters || EXIT_CODE=1
echo ""
count_proto_deps
;;
@@ -1550,7 +1550,6 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_battalion_concrete",
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_hero_concrete",
"//src/main/scala/net/eagle0/eagle/model/action_result/types",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/shardok_battle:shardok_battle_converter",
"//src/main/scala/net/eagle0/eagle/model/state:army",
"//src/main/scala/net/eagle0/eagle/model/state:battle_revelation",
"//src/main/scala/net/eagle0/eagle/model/state:combat_unit",
@@ -17,7 +17,6 @@ import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedPro
import net.eagle0.eagle.model.action_result.concrete.{ActionResultC, ChangedHeroC, StatAbsolute}
import net.eagle0.eagle.model.action_result.types.ActionResultType.BattleEnded
import net.eagle0.eagle.model.action_result.ActionResultT
import net.eagle0.eagle.model.proto_converters.shardok_battle.ShardokBattleConverter
import net.eagle0.eagle.model.state.{BattleRevelation, BattleRevelationType, MovingArmy, Supplies}
import net.eagle0.eagle.model.state.battalion.BattalionT
import net.eagle0.eagle.model.state.game_state.GameState
@@ -269,10 +268,9 @@ case class ResolveBattleAction(
battle: ShardokBattle,
battleResolution: BattleResolution
): Vector[ActionResultWithResultingState] = {
val battleFromResolution = ShardokBattleConverter.fromProto(battleResolution.battle)
internalRequire(
battle == battleFromResolution,
s"Battles do not match! Sent $battle but resolved $battleFromResolution"
battle == battleResolution.battle,
s"Battles do not match! Sent $battle but resolved ${battleResolution.battle}"
)
verifyHidsOutMatchHidsIn(battle, battleResolution)
@@ -631,19 +629,18 @@ case class ResolveBattleAction(
outstandingBattles: Vector[ShardokBattle],
resolvedBattle: BattleResolution
): Option[ShardokBattle] = {
val resolvedBattleScala = ShardokBattleConverter.fromProto(resolvedBattle.battle)
val obo = outstandingBattles
.find(_.shardokGameId == resolvedBattleScala.shardokGameId)
val obo = outstandingBattles
.find(_.shardokGameId == resolvedBattle.battle.shardokGameId)
.orElse {
printf(
s"Failed to locate outstanding battle ${resolvedBattleScala.shardokGameId}; may already be resolved"
s"Failed to locate outstanding battle ${resolvedBattle.battle.shardokGameId}; may already be resolved"
)
None
}
if obo.exists(ob => ob != resolvedBattleScala) then {
if obo.exists(ob => ob != resolvedBattle.battle) then {
throw new EagleInternalException(
s"Battles do not match! ${obo.get} vs $resolvedBattleScala"
s"Battles do not match! ${obo.get} vs ${resolvedBattle.battle}"
)
}
obo
@@ -8,6 +8,8 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/model/proto_converters:__pkg__",
"//src/main/scala/net/eagle0/eagle/model/proto_converters:__subpackages__",
"//src/main/scala/net/eagle0/eagle/service:__pkg__",
"//src/main/scala/net/eagle0/eagle/shardok_interface:__pkg__",
"//src/test/scala/net/eagle0/eagle/library/actions/impl/action:__pkg__",
],
exports = [
"//src/main/protobuf/net/eagle0/eagle/internal:shardok_battle_scala_proto",
@@ -14,6 +14,7 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/model/proto_converters/shardok_battle:__pkg__",
"//src/main/scala/net/eagle0/eagle/model/state/game_state:__pkg__",
"//src/main/scala/net/eagle0/eagle/service:__pkg__",
"//src/main/scala/net/eagle0/eagle/shardok_interface:__pkg__",
"//src/test/scala/net/eagle0/eagle/library/actions/impl/action:__pkg__",
],
deps = [
@@ -10,8 +10,7 @@ scala_library(
],
deps = [
":resolved_shardok_player",
"//src/main/protobuf/net/eagle0/eagle/internal:shardok_battle_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/model/state/shardok_battle",
],
)
@@ -109,6 +108,9 @@ scala_library(
"//src/main/scala/net/eagle0/eagle:__subpackages__",
"//src/test/scala/net/eagle0/eagle:__subpackages__",
],
exports = [
"//src/main/protobuf/net/eagle0/common:victory_condition_scala_proto",
],
deps = [
":resolved_eagle_unit",
"//src/main/protobuf/net/eagle0/common:victory_condition_scala_proto",
@@ -158,6 +160,7 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/model/proto_converters:battalion_converter",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/faction",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/shardok_battle:shardok_battle_converter",
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
"//src/main/scala/net/eagle0/eagle/model/state/battalion/concrete",
"//src/main/scala/net/eagle0/eagle/model/state/hero",
@@ -1,6 +1,6 @@
package net.eagle0.eagle.shardok_interface
import net.eagle0.eagle.internal.shardok_battle.ShardokBattle
import net.eagle0.eagle.model.state.shardok_battle.ShardokBattle
case class BattleResolution(
battle: ShardokBattle,
@@ -27,6 +27,7 @@ import net.eagle0.eagle.library.util.faction_utils.FactionUtils
import net.eagle0.eagle.library.util.hero.HeroUtils
import net.eagle0.eagle.model.proto_converters.faction.FactionConverter
import net.eagle0.eagle.model.proto_converters.hero.HeroConverter
import net.eagle0.eagle.model.proto_converters.shardok_battle.ShardokBattleConverter
import net.eagle0.eagle.model.proto_converters.BattalionConverter
import net.eagle0.eagle.model.state.unit_status.UnitStatus
import net.eagle0.shardok.api.placement_command.PlacementCommand
@@ -170,7 +171,7 @@ class ShardokInterfaceGrpcClient(
}
val _ = resolutionReceiver.receive(
BattleResolution(
battle = battle,
battle = ShardokBattleConverter.fromProto(battle),
resolvedPlayers = gameOverResponse.userInfos.map(ui =>
ResolvedShardokPlayer(
eagleFid = ui.eagleFactionId,
@@ -915,6 +915,7 @@ scala_test(
"//src/main/scala/net/eagle0/eagle/model/proto_converters:battalion_converter",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/shardok_battle:shardok_battle_converter",
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/hero",
@@ -36,7 +36,7 @@ import net.eagle0.eagle.internal.faction_relationship.FactionRelationship.Relati
import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.hero.Hero
import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.internal.shardok_battle.{ShardokBattle, ShardokPlayer}
import net.eagle0.eagle.internal.shardok_battle.{ShardokBattle as ShardokBattleProto, ShardokPlayer}
import net.eagle0.eagle.internal.shardok_battle.ShardokBattle.BattleType
import net.eagle0.eagle.internal.supplies.Supplies
import net.eagle0.eagle.internal.unaffiliated_hero.{CapturedHero, UnaffiliatedHero}
@@ -51,6 +51,7 @@ import net.eagle0.eagle.library.util.IDable
import net.eagle0.eagle.library.util.IDable.mapifyFactions
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
import net.eagle0.eagle.model.proto_converters.hero.HeroConverter
import net.eagle0.eagle.model.proto_converters.shardok_battle.ShardokBattleConverter
import net.eagle0.eagle.model.proto_converters.ActionResultProtoConverter
import net.eagle0.eagle.model.proto_converters.BattalionConverter
import net.eagle0.eagle.model.state.unit_status.UnitStatus
@@ -78,6 +79,12 @@ class ResolveBattleActionTest
): net.eagle0.eagle.model.state.game_state.GameState =
GameStateConverter.fromProto(protoGs)
// Helper to convert proto ShardokBattle to Scala for BattleResolution
private def scalaBattle(
protoBattle: ShardokBattleProto
): net.eagle0.eagle.model.state.shardok_battle.ShardokBattle =
ShardokBattleConverter.fromProto(protoBattle)
// Extension to add resultsOfExecute to ResolveBattleAction for backwards compatibility
// Note: validation is disabled because test data doesn't include all required fields
implicit class ResolveBattleActionTestOps(action: ResolveBattleAction) {
@@ -427,7 +434,7 @@ class ResolveBattleActionTest
)
)
private val shardokBattle = ShardokBattle(
private val shardokBattle = ShardokBattleProto(
defenderProvince = defenderProvinceId,
battleType = BattleType.BATTLE_TYPE_ASSAULT_PROVINCE,
roundStarted = 6,
@@ -514,7 +521,7 @@ class ResolveBattleActionTest
scalaGameState(startingGameState),
Vector(
BattleResolution(
battle = shardokBattle,
battle = scalaBattle(shardokBattle),
resolvedPlayers = resolvedPlayersWithAttackerVictory
)
)
@@ -572,7 +579,7 @@ class ResolveBattleActionTest
scalaGameState(startingGameState),
Vector(
BattleResolution(
battle = shardokBattle,
battle = scalaBattle(shardokBattle),
resolvedPlayers = resolvedPlayersWithAttackerVictory
)
)
@@ -593,7 +600,7 @@ class ResolveBattleActionTest
scalaGameState(startingGameState),
Vector(
BattleResolution(
battle = shardokBattle,
battle = scalaBattle(shardokBattle),
resolvedPlayers = resolvedPlayersWithAttackerVictory
)
)
@@ -609,7 +616,7 @@ class ResolveBattleActionTest
scalaGameState(startingGameState),
Vector(
BattleResolution(
battle = shardokBattle,
battle = scalaBattle(shardokBattle),
resolvedPlayers = resolvedPlayersWithAttackerVictory
)
)
@@ -677,7 +684,7 @@ class ResolveBattleActionTest
),
Vector(
BattleResolution(
battle = battleWithExtraHero,
battle = scalaBattle(battleWithExtraHero),
resolvedPlayers = resolvedPlayersWithOutlaws
)
)
@@ -747,7 +754,7 @@ class ResolveBattleActionTest
scalaGameState(startingGameState),
Vector(
BattleResolution(
battle = shardokBattle,
battle = scalaBattle(shardokBattle),
resolvedPlayers = resolvedPlayersWithOutlawedDefender
)
)
@@ -821,7 +828,7 @@ class ResolveBattleActionTest
scalaGameState(startingGameState),
Vector(
BattleResolution(
battle = shardokBattle,
battle = scalaBattle(shardokBattle),
resolvedPlayers = resolvedPlayersWithOutlawedDefender
)
)
@@ -897,7 +904,7 @@ class ResolveBattleActionTest
),
Vector(
BattleResolution(
battle = battleWithExtraHero,
battle = scalaBattle(battleWithExtraHero),
resolvedPlayers = resolvedPlayersWithOutlaws
)
)
@@ -916,7 +923,7 @@ class ResolveBattleActionTest
scalaGameState(startingGameState),
Vector(
BattleResolution(
battle = shardokBattle,
battle = scalaBattle(shardokBattle),
resolvedPlayers = resolvedPlayersWithAttackerVictory
)
)
@@ -961,7 +968,7 @@ class ResolveBattleActionTest
scalaGameState(startingGameStateWithAllies),
Vector(
BattleResolution(
battle = shardokBattleWithAllies,
battle = scalaBattle(shardokBattleWithAllies),
resolvedPlayers = resolvedPlayersWithAttackerVictoryWithCapturedAllies
)
)
@@ -997,7 +1004,7 @@ class ResolveBattleActionTest
scalaGameState(startingGameStateWithAllies),
Vector(
BattleResolution(
battle = shardokBattleWithAllies,
battle = scalaBattle(shardokBattleWithAllies),
resolvedPlayers = resolvedPlayersWithAttackerVictoryWithAllies
)
)
@@ -1056,7 +1063,7 @@ class ResolveBattleActionTest
scalaGameState(gameStateWithAllyWithoutFleeProvince),
Vector(
BattleResolution(
battle = shardokBattleWithAllyWithoutFleeProvince,
battle = scalaBattle(shardokBattleWithAllyWithoutFleeProvince),
resolvedPlayers = resolvedPlayersWithAttackerVictoryWithAllies
)
)
@@ -1121,7 +1128,7 @@ class ResolveBattleActionTest
scalaGameState(startingGameState),
Vector(
BattleResolution(
battle = shardokBattle,
battle = scalaBattle(shardokBattle),
resolvedPlayers = resolvedPlayersWithCapturedDefender
)
)
@@ -1185,7 +1192,7 @@ class ResolveBattleActionTest
scalaGameState(startingGameState),
Vector(
BattleResolution(
battle = shardokBattle,
battle = scalaBattle(shardokBattle),
resolvedPlayers = resolvedPlayersWithNeverEnteredDefender
)
)
@@ -1214,7 +1221,7 @@ class ResolveBattleActionTest
scalaGameState(startingGameState),
Vector(
BattleResolution(
battle = shardokBattle,
battle = scalaBattle(shardokBattle),
resolvedPlayers = resolvedPlayersWithDefenderVictory
)
)
@@ -1232,7 +1239,7 @@ class ResolveBattleActionTest
scalaGameState(startingGameState),
Vector(
BattleResolution(
battle = shardokBattle,
battle = scalaBattle(shardokBattle),
resolvedPlayers = resolvedPlayersWithDefenderVictory
)
)
@@ -1257,7 +1264,7 @@ class ResolveBattleActionTest
scalaGameState(startingGameState),
Vector(
BattleResolution(
battle = shardokBattle,
battle = scalaBattle(shardokBattle),
resolvedPlayers = resolvedPlayersWithDefenderVictory
)
)
@@ -1283,7 +1290,7 @@ class ResolveBattleActionTest
scalaGameState(startingGameState),
Vector(
BattleResolution(
battle = shardokBattle,
battle = scalaBattle(shardokBattle),
resolvedPlayers = resolvedPlayersWithDefenderVictory
)
)
@@ -1345,7 +1352,7 @@ class ResolveBattleActionTest
scalaGameState(startingGameState),
Vector(
BattleResolution(
battle = shardokBattle,
battle = scalaBattle(shardokBattle),
resolvedPlayers = resolvedPlayersWithFledDefender
)
)
@@ -1422,7 +1429,7 @@ class ResolveBattleActionTest
scalaGameState(startingGameState),
Vector(
BattleResolution(
battle = shardokBattle,
battle = scalaBattle(shardokBattle),
resolvedPlayers = resolvedPlayersWithFledAttacker
)
)
@@ -1502,7 +1509,7 @@ class ResolveBattleActionTest
scalaGameState(startingGameState),
Vector(
BattleResolution(
battle = shardokBattle,
battle = scalaBattle(shardokBattle),
resolvedPlayers = resolvedPlayersWithCapturedAttacker
)
)
@@ -1566,7 +1573,7 @@ class ResolveBattleActionTest
scalaGameState(startingGameState),
Vector(
BattleResolution(
battle = shardokBattle,
battle = scalaBattle(shardokBattle),
resolvedPlayers = resolvedPlayersWithCapturedAttacker
)
)
@@ -1629,7 +1636,7 @@ class ResolveBattleActionTest
status = Attacking()
)
val freeForAll = ShardokBattle(
val freeForAll = ShardokBattleProto(
defenderProvince = defenderProvinceId,
battleType = BattleType.BATTLE_TYPE_FREE_FOR_ALL,
roundStarted = 6,
@@ -1684,7 +1691,7 @@ class ResolveBattleActionTest
),
Vector(
BattleResolution(
battle = freeForAll,
battle = scalaBattle(freeForAll),
resolvedPlayers = resolvedPlayersWithDraw
)
)
@@ -1790,7 +1797,7 @@ class ResolveBattleActionTest
scalaGameState(gsWithAttackQuest),
Vector(
BattleResolution(
battle = shardokBattle,
battle = scalaBattle(shardokBattle),
resolvedPlayers = resolvedPlayersWithAttackerVictory
)
)
@@ -1843,7 +1850,7 @@ class ResolveBattleActionTest
scalaGameState(gsWithAttackQuest),
Vector(
BattleResolution(
battle = shardokBattle,
battle = scalaBattle(shardokBattle),
resolvedPlayers = resolvedPlayersWithAttackerVictory
)
)
@@ -1893,7 +1900,7 @@ class ResolveBattleActionTest
scalaGameState(gsWithAttackQuest),
Vector(
BattleResolution(
battle = shardokBattle,
battle = scalaBattle(shardokBattle),
resolvedPlayers = resolvedPlayersWithAttackerVictory
)
)
@@ -1948,7 +1955,7 @@ class ResolveBattleActionTest
status = Attacking()
)
val battleWithFleeProvince = ShardokBattle(
val battleWithFleeProvince = ShardokBattleProto(
defenderProvince = defenderProvinceId,
battleType = BattleType.BATTLE_TYPE_ASSAULT_PROVINCE,
roundStarted = 6,
@@ -2015,7 +2022,7 @@ class ResolveBattleActionTest
scalaGameState(gameStateWithFleeProvince),
Vector(
BattleResolution(
battle = battleWithFleeProvince,
battle = scalaBattle(battleWithFleeProvince),
resolvedPlayers = resolvedPlayersWithFledAttackers
)
)