Discord-bound bug reports now put the description and metadata in the
message content and attach the full recent logs as recent_logs.txt,
instead of shipping everything inside a webhook embed. Webhook-embed
bodies aren't surfaced by the Claude Code Discord plugin's
fetch_messages/push path, so reports couldn't be auto-processed;
plain-text content and a file attachment both flow through cleanly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a third unit-label color in Shardok battles: purple (#8000FF) for a
player's mage that has already picked a meteor target but is still in the
Target state and could retarget on the same turn. Blue/black remain for
"has commands" / "no commands".
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Clear focus province when faction loses ruler to keep state valid
RuntimeValidator requires that every faction's focusProvinceId points
to a province it rules. ProvinceConqueredAction and
EndPlayerCommandsPhaseAction already emit ChangedFactionC with
clearFocusProvinceId for their respective pathways, but other pathways
(hero departures, battle casualties leaving a province unruled, etc.)
reach the applier's fixRulerIfNeeded hook without any faction-side
cleanup. The next validation pass then crashes with "Unowned focus
province for faction ...".
Clear the stale focus alongside fixRulerIfNeeded in the applier so any
ChangedProvinceC that removes or transfers a ruling faction also drops
the now-invalid focus pointers. The negative case (same ruler, just
losing some heroes) is preserved so active focuses aren't dropped
unnecessarily.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Switch to end-of-phase focus cleanup instead of applier side-effect
Reverts the applier-level focus-clearing added in the previous commit.
Modifying factions as a side-effect of applyChangedProvinceC violates
the event-sourcing model: every game state change should be described
by an ActionResult, not implicit in the applier.
New approach, matching the pattern used by other
EndPlayerCommandsPhaseAction-style cleanups:
- RuntimeValidator.validateFactions now only enforces the "focus must
be an owned province" invariant during PlayerCommands. Other phases
may legitimately leave a stale focusProvinceId in transit (e.g.
fixRulerIfNeeded silently clears rulingFactionId after hero
departures or battle casualties).
- EndVassalCommandsPhaseAction is the sole entry point into
PlayerCommands. Its final ActionResultC now carries
ChangedFactionC(clearFocusProvinceId = true) for every faction
whose focus province is no longer ruled by it, so the invariant
holds when the validator re-engages.
- Removes the three applier-level tests that exercised the reverted
side-effect, and adds EndVassalCommandsPhaseActionTest covering the
new cleanup behavior (unruled focus, focus taken by another
faction, and focus still held).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
shatterStrandedArmies only checked whether an incoming army's destination
was not its own faction's territory. Since isFriendlyMove does
`province.rulingFactionId.contains(...)`, unoccupied provinces (None)
also returned false, so any already-retreated army (fleeProvinceId = None)
arriving at an unoccupied province was spuriously shattered with a
"nowhere to withdraw to" notification.
Add an explicit `rulingFactionId.isDefined` guard so arrivals at
unoccupied provinces fall through to the normal uncontested conquest
flow.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
TryPendingCommands' stale-token branches called StreamOneGameAsync to
"refresh state" when a pending command's token was behind the current
token. This sent a SUBSCRIBE(unfilteredCount=N) over the active stream,
which caused the server to replay history.since(N) as a new
ActionResultResponse while normal updates were still in flight.
Those re-delivered ActionResultViews were then applied a second time on
the main thread, which could surface as (e.g.) "Duplicate unaffiliated
hero N in province M" when a VASSAL_EXILED action ran twice.
The refresh is unnecessary: we only reach the stale-token branch because
an ActionResultResponse already delivered the newer token and its
updates were applied. Drop the stale command without re-subscribing.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The divination and quest-ended prompts for SendSuppliesQuest and
SpendOnFeastsQuest both said "over $componentCount months", but
componentCount on these quests equals the total food/gold (since the
counter is incremented by units delivered, not months elapsed). This
produced confusing output like "send 1337 food over 1337 months".
Drop the time clause — these quests are purely cumulative totals.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When an exception propagates out of an async-void lambda enqueued to
MainQueue, it goes through AsyncMethodBuilderCore.ThrowAsync to Unity's
SynchronizationContext, which logs it but cannot undo the partial state
changes already applied by the failing update. Subsequent updates then
apply diffs on top of corrupt state, cascading further errors until the
client is effectively unusable.
Wrap ReceiveGameUpdate at all four call sites in HandleGameUpdate so
exceptions are caught at the source, logged via Debug.LogException
(triggering the in-game ErrorHandler panel) and the file logger, and the
enclosing lambda completes normally. TryPendingCommands and timing logs
still run, keeping the MainQueue pump healthy.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
VelociraptorEffect.prefab's animalPrefabs array references two
prefab GUIDs (8fa2338697777704fafc6374e9750de9 and
df9fda81573442940a2a92a3628b3cbc) from the Dino Pack Low Poly V1
asset bundle that were never committed to the repo in PR #6525.
The pack's Velociraptor.fbx, materials, and textures were added,
but the standalone .prefab files that wrap the fbx with an
Animation component and a MeshRenderer were missing.
The dangling references didn't fail until something in the live
game actually triggered ProvinceBeastsController.SpawnBeastsEffect
for a "velociraptor" beast, at which point AnimalEffect.SpawnAnimals
threw MissingReferenceException on the null array element.
Re-import the two referenced prefabs from Dino Pack Low Poly V1
(PrefabPack/PrefabSingleTexture/VelociraptorP/). The re-imported
prefab GUIDs match exactly, and the root GameObject fileIDs
(7255238702826593904 and 8644578109599837090) match the fileIDs
VelociraptorEffect.prefab already references. Internal references
(VelociraptorC1.mat material, Velociraptor.fbx mesh and animations,
VelociraptorMapAnims.controller) are all already in the repo.
The atlas-texture variant prefabs from the same pack were not
committed since nothing references them.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Multiple MainQueue actions (one per ActionResultResponse and
ShardokActionResultResponse) each call TryPendingCommands as
async void lambdas. When one yields at an await, the MainQueue
Update loop immediately invokes the next action, which re-enters
TryPendingCommands. Because PostRequest synchronously re-adds
the command to _pendingCommands before yielding for WriteAsync,
each parallel invocation re-drains the same buffered command and
writes it to the gRPC stream again. This produces bursts of 10+
identical writes in a single frame, all of which the server
rejects with BAD_TOKEN.
Wrap TryPendingCommands in a non-blocking SemaphoreSlim acquire.
If another invocation is already running, skip and return — any
items added to _pendingCommands while it runs will be picked up
by the next ActionResultResponse handler that fires.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Occasional disconnects are normal and usually recover on the first
retry, so the old behavior of immediately flashing "Reconnecting..."
(and a 2s countdown) on every blip was too noisy.
Now the first retry after a fresh disconnect is "silent": we don't
touch _currentState, so the status UI keeps showing Connected while
the retry attempt runs in the background. Only after the first retry
has itself failed does the ScheduleReconnect path flip state to
Reconnecting, at which point the user sees the countdown (starting
from the 4s second-attempt backoff).
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Introduces the FocusProvinceStorageMultiplier setting (default 1.50) and
applies it to food and gold caps when a province is its ruling faction's
focus province, so focus provinces can stockpile extra reserves.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add delta gradient flash to battle thermometer
When the thermometer ratio changes, overlay a gradient on the delta
region showing the gaining side's color fading toward white-tinted,
making the territory shift visible. Fades out 2.5x faster than the
existing boundary flash line.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix invisible delta gradient by using FullRect sprite mesh
The gradient sprite was using the default SpriteMeshType.Tight which
trimmed out the fully-transparent pixel, producing either an empty or
half-sized mesh. Use SpriteMeshType.FullRect with a 256x1 texture for a
cleaner gradient, and SetAsLastSibling so the overlay renders on top of
the attacker/defender bars. The boundary flash line also reasserts
last-sibling so it stays visible above the gradient.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add diagnostic logging for delta gradient
Temporary logging to diagnose why the gradient overlay isn't visible
in-game. Will be removed after diagnosis.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* DEBUG: render delta gradient as opaque magenta to test positioning
Temporarily replace the gradient sprite with plain opaque magenta and
add canvas renderer state logging to isolate whether the problem is
with the sprite/shader or with positioning/z-order/masking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* DEBUG: use attackerBar sprite, bright green, no fade
Set the delta gradient to use the attacker bar's known-working sprite,
force opaque bright green, and disable the fade coroutine so the image
stays permanently visible. Also log the full transform path to confirm
which thermometer instance is being modified.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* DEBUG: test gradient sprite with permanent green tint
Swap back to the procedural 256x1 gradient sprite while keeping the
permanent no-fade green tint. This isolates whether the gradient sprite
itself renders correctly (gradient visible) or has a mesh/UV issue
(solid green or invisible).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use losing side's color for delta gradient, remove debug scaffolding
The previous implementation tinted the delta region with the *gaining*
side's color, but the bar underneath the delta region is already that
same color — so the gradient was nearly invisible (faction color fading
to a slightly lighter faction color).
Flip it around: use the *losing* side's color. The old owner's
territory briefly lingers in the lost region, then fades to reveal the
new owner's bar underneath. This is a more intuitive visualization of a
territory change.
Also flip the gradient direction so the opaque (losing-color) edge sits
at the old boundary (where the losing side's territory previously
ended), fading toward the new boundary.
Remove the debug logging, green color override, and disabled fade that
were added while diagnosing the earlier rendering issue, and re-enable
the DeltaGradientRoutine fade coroutine.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Flip delta gradient direction
The losing color should be on the side of the delta closer to the
losing side's remaining bar, so it visually points "home" to its own
side. The transparent edge reveals the new owner's bar underneath.
- Attacker gained → losing=defender → opaque on right (near defender).
- Defender gained → losing=attacker → opaque on left (near attacker).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Double the delta gradient fade duration
Previously 0.16s (flashDuration/2.5); now 0.32s (flashDuration/1.25)
so the gradient is easier to see.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
RandomHeroGenerator.createLowPowerHero pre-populates backstoryVersions
with hero_${id}_backstory_0, but UnaffiliatedHeroAppearedAction only
emits a HeroInitialBackstoryRequest when backstoryVersions is empty. The
rubber-band spawn path in PerformUnaffiliatedHeroesAction was relying on
that action to emit the request, so the initial backstory text ID was
orphaned from the start — never registered with ClientTextStore, always
resolving to TextGenerationDependencyUnknown.
Clear backstoryVersions before handing the hero to
UnaffiliatedHeroAppearedAction so it creates a proper
hero_${id}_initial_backstory_$roundId text ID with a matching LLM
request. This restores effectiveBackstory's ability to fall back to the
initial backstory when later updates are legitimately skipped (e.g. for
heroes only visible to AI factions).
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
3.1 Flash-Lite is faster (throughput + TTFT), meaningfully smarter, and
still among the cheapest Gemini options, making it a strict upgrade
over 2.5 Flash-Lite for narrative text generation.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Logs a warning in GameController.withHandledEngineAndResults when a
free hero's most recent backstory is not visible to the province's
ruling faction. Includes game ID, history count, round, and phase
to help trace back to the action that caused the visibility gap.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Retry beast effect Addressable loads on failure
Beast effect prefabs are loaded from a remote CDN via Addressables.
If the download failed (network hiccup, CDN timeout), the effects
were permanently lost for the session with no retry. Now retries up
to 3 times with backoff delays (2s, 5s, 10s). Also releases failed
handles to avoid leaking resources.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Extract AddressableLoader utility with retry logic for all Addressable loads
All Addressable assets load from a remote CDN and a single transient
network failure permanently broke the affected feature for the session.
Extracts retry logic (3 retries with 2s/5s/10s backoff) into a shared
AddressableLoader.LoadWithRetry<T>() utility and updates all 6 callers:
ProvinceBeastsController, TacticalAssetLoader, SoundManager,
ImageForTerrainTracker, BattleInProgressController, ProvinceActionAnimator.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Removed ButtonManagerBasic and UIGradient components from the lobby
AvailableGame prefab (they were no-ops — the button already had a
normal onClick callback). Then deleted all non-texture content from
Modern UI Pack: Scripts, Animations, Editor, Fonts, Scenes, Unused
prefabs, and Documentation. Only Textures/ is retained.
Saves ~34 MB of unused assets.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add editor tool to audit third-party asset pack usage
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Include Addressable group entries in asset usage audit
The previous version only traced dependencies from build scenes, completely
missing assets loaded via Addressables. This caused ~8.5 GB of assets to
be falsely reported as unused. Now traces dependencies from all Addressable
group entries (Beast Effects, Hex Tiles, Tactical Assets, Sound Effects)
in addition to build scenes.
Also logs all USED files per pack for easier analysis.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove Pixel Fonts Megapack from audit pack list
Pack was deleted in PR #6620.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Asset usage audit (scenes + Addressables) confirmed 0 files from this
pack are referenced anywhere in the build. Saves 2 MB.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When an army flees after losing a battle, the flee province is consumed
but was not being cleared. This caused armies arriving at a now-hostile
flee province to get attack decision options instead of being shattered.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The Shardok Container prefab had defender on the left and attacker on
the right, but BattleThermometer code forces the defender bar to the
right and attacker bar to the left. This caused the wide bar to appear
next to the smaller number. Swap the field references so bars and labels
are on matching sides.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Wire up flashDuration on BattleThermometer in the Battle Progress Row
prefab and right-align the attacker troop count label. Includes Unity
editor re-serialization of Emu and peasant prefabs.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
FactionUtils.hostilityStatus treats both truce and alliance as non-hostile,
which caused battle player infos to show Allied hostility for truce holders.
The client then displayed an "Observe" button that threw an exception on click
because the server correctly rejects observation for truce-only relationships.
Fix shardokBattlePlayerInfos to use hasAlliance directly instead of the
general hostilityStatus, so only actual alliances produce Allied hostility.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Previously the flash only triggered when the ratio changed by more
than 0.001. Now it flashes on every update after the first, so each
day tick produces a visible flash even if the ratio barely moved.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
ResolveBattleAction was discarding the original fleeProvinceId for
armies that lost a battle, causing them to shatter instead of retreating
to the province the player designated when dispatching the army.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
postBattleUpdate sent a BattleProgressResponse for every Shardok
action, even when the gated round hadn't changed. In AI-only battles
this floods clients with dozens of identical-day updates per round.
Now only sends battle progress when the gated round actually advances,
reducing redundant updates while still delivering Shardok action
results to participants.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
withHandledEngineAndResults used the 5-parameter overload of
humanClientsAfterPostingResults which did not send battle progress.
This meant non-combatant observers only received battle progress
updates when Shardok sent a GameUpdateResponse, not during Eagle
strategic turn resolution. Switch to the 7-parameter overload to
forward existing battle progress on every client update.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The gated round (lastRevealedRound) used max(previous, current) to
prevent information leakage, but never reset when all battles ended.
This caused new battles to start at the previous battle's final day
(e.g. "Day 29") instead of "Day 0".
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
BattleDay was only set from BattleProgressResponse, so it retained
stale values from previous battles until the first progress update
arrived for the new battle.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
A thin white line briefly flashes at the attacker/defender boundary
whenever the troop ratio changes, giving visual feedback during battle.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Use isDefender flag in Shardok thermometer and game model
Use the new is_defender field from ShardokBattlePlayerInfo instead of
assuming player 0 is always the defender. Updates ShardokGameController
thermometer, ShardokGameModel.IsDefender, EagleGameModel.MakeGameModel,
and CustomBattleHandler player setup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update Gameplay scene layout and soldier prefabs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add is_defender field (field 5) to ShardokBattlePlayerInfo so the
client can identify which players are defenders without assuming
player 0 is always the defender. Populate it from
ShardokPlayer.isDefender in BattleFilter and serialize it in
ShardokBattleViewConverter.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Two fixes for the battle progress thermometer:
1. Swap attacker/defender sides so attacker fills from left and defender
from right, matching the expected layout.
2. Add missing BattleProgressResponse case in PersistentClientConnection's
HandleGameUpdate switch. The server was sending progress updates but
the client silently dropped them, causing the bar to stay at 50%.
Also extract UpdateBattleProgressDisplay helper in EagleGameController
and call it from SwapModel's early-return path so progress updates aren't
skipped when the player has active strategic commands.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
ConfigureToggle used toggle.isOn = false which fires the ToggleChanged
callback. That callback accesses SelectedDecision, which throws when no
toggle is on yet during SetUpUI. Use SetIsOnWithoutNotify instead.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The updatedBattleProgress method assumed player 0 was always the
defender, but RequestBattlesAction builds the players vector with
attackers first and defender last. Use the isDefender flag from the
battle's player list to correctly classify troop counts.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When ConfigureToggle sets isOn=false on a toggle that is the last active
member of a ToggleGroup with allowSwitchOff=false, Unity forces it back
to true. The toggle is then removed from the group but retains the stale
isOn=true state. This causes the wrong option to be sent to the server
(e.g. Recruit when only Imprison/Return/Execute were available).
Fix by removing the toggle from its group before setting isOn=false.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When a battle is first created, participants now see troop counts and an
accurate balance bar immediately instead of a blank 50/50 bar while
waiting for the first Shardok GameUpdateResponse.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- BattleThermometer: accept IList<Color> for attacker colors, dynamically
create extra bar segments for multi-attacker battles
- BattleProgressRowController: look up defender faction from province
RulingFactionId and attacker factions from PlayerInfos, using
LightPlayerColor for faction-correct colors; use DarkColoredProvinceName
for more readable province name text
- ShardokGameController: wrap single attacker color in list for new API
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
ModelUpdated() never unconditionally cleared overlays, so stale highlights
persisted after phase transitions (setup -> running) and after issuing a
command that ends the turn. Add ClearOverlays() at the top of ModelUpdated()
so overlays are always wiped before being selectively re-drawn, and in
EndTurn() for immediate visual feedback when clicking Commit/End Turn.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add battle progress tracking and real-time progress updates
During BATTLE_RESOLUTION phase, track Shardok battle progress (troop counts
and defender/attacker ratios) and push synchronized updates to clients via
a new BattleProgressResponse message. All battles advance together using
gated round progression to prevent information leakage. Allied observers
see troop counts; non-allied players see only the ratio bar.
Key changes:
- Add defender_ratio, troop counts, and winning_faction_id to ShardokBattleView proto
- Add updated_battles to GameStateViewDiff proto
- Add BattleProgressResponse to GameUpdate oneof
- Track battle progress in GameController with synchronized round gating
- Send enriched battle views to clients via HumanPlayerClientConnectionState
- Update GameStateViewDiffer to detect battle updates
- Add GameStateViewDifferTest with 6 test cases
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Move BattleProgressResponse to views layer to fix proto layering
BattleProgressResponse only contains ShardokBattleView objects, which
belong to the views layer. Move it from eagle.proto (api layer) to
game_state_view.proto (views layer) and remove the direct
shardok_battle_view.proto import from eagle.proto.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add battle progress display for non-combatant observers
During BATTLE_RESOLUTION, non-combatant players now see real-time
progress bars instead of a single "Observe Battle" button. Each bar
shows defender/attacker balance with green/red coloring. Allied battles
show troop counts and an Observe button; non-allied battles show only
the bar. When a battle ends, the bar is replaced with Victory/Defeat.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix TextAlignmentOptions.MidlineCenter compile error and meta GUID conflict
MidlineCenter does not exist in Unity 6 TMP; use Center instead.
Also include reassigned .meta GUID to resolve conflict with duel.asset.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add BattleProgressPanel GameObject to Gameplay scene
Wire the panel to EagleGameController.battleProgressPanel with
VerticalLayoutGroup and ContentSizeFitter components.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove BattleInProgressController from BattleProgressPanel GameObject
BattleInProgressController belongs on the map, not the progress panel.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove unused ShardokGameStateView import from GameController
The import triggered -Werror and broke the battle_progress_test build.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Show BattleProgressPanel for all battles, not just observed ones
The panel was only activated for non-combatant observers. Now it shows
whenever there are battles, alongside the "Battle!" button for combatants.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use ShardokBattles instead of RunningShardokGameModels for panel visibility
RunningShardokGameModels requires the client to have connected to the
Shardok game and received a GameRunning status, which may not have
happened yet. ShardokBattles comes from the server game state view and
is available immediately when battles exist.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Refactor BattleProgressPanel to use prefab rows and display battle day
Replace programmatic CreateRow()/PopulateRow() with a prefab-based
approach using BattleProgressRowController. The panel now instantiates
rows from a prefab into a scroll view, displays "Day X" from the
server's gated round, and uses colored province names.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use BattleThermometer in battle progress rows
Replace manual anchor/color logic with the existing BattleThermometer
component, which already handles both troop-count and ratio-only modes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix row container cleanup, observe button visibility, and prefab transforms
Clear stale children from rowContainer on first update. Only show
Observe button for allied battles (not the player's own). Fix bar
rotation/scale in row prefab. Add debug logging for battle progress.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update BattleProgressPanel layout in Gameplay scene
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Replace goToBattleButton with per-row Fight!/Waiting.../Observe buttons
Each battle row now has its own action button: "Fight!" for the
fightable battle, "Waiting..." (grayed) for other own battles,
and "Observe" for allied battles. The global goToBattleButton is removed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove goToBattleButton from scene and adjust battle panel layout
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Adjust battle progress panel layout
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The server already computes lastRevealedRound (the minimum round across
all active battles) for fair reveal timing, but never sent it to the
client. Add current_day field to the proto and pass gatedRound through
sendBattleProgress so clients can display the battle day.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Guard idle and walk Play() calls with HasState(), matching the existing
pattern already used for eat/attack transitions.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add battle progress tracking with proto-based troop counts
Adds real-time battle progress tracking so non-combatant observers can
see defender/attacker troop ratios during BATTLE_RESOLUTION phase.
C++ Shardok extracts current_round and per-player troop counts from the
FlatBuffer game state and sends them as new proto fields on
GameUpdateResponse, avoiding the previous approach of parsing opaque
FlatBuffer bytes as Protobuf on the Scala side.
Scala Eagle tracks progress via BattleProgressTracker, gates round
reveals across concurrent battles, and enriches ShardokBattleView with
defender_ratio and troop counts for allied observers.
Includes tests for both C++ troop count extraction and Scala progress
tracking logic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove default values from ShardokBattleView fields
Make all new fields (defenderRatio, defenderTroopCount,
attackerTroopCount, winningFactionId) required at construction sites
instead of relying on defaults.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Preserve battleProgress across GameController operations
Convert withHumanClient, withAiClient, stopStreaming, aiClientCommands,
and withHandledEngineAndResults from explicit GameController construction
to copy(), so battleProgress and lastRevealedRound are preserved when
clients connect/disconnect or commands are processed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Populates the new proto fields (current_round, player_troop_counts) on
GameUpdateResponse by extracting data from the FlatBuffer game state in
ShardokGameController::GetUpdates(). Counts troops for each player
across NORMAL, RESERVE, and NEVER_ENTERED units.
Updates the OnUpdates callback signature to pass the new fields through
EagleInterfaceGrpcServer to Eagle.
Includes tests verifying troop count extraction for both normal games
and games with reserved slots.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Adds new proto fields to support real-time battle progress updates for
non-combatant observers:
- shardok_internal_interface: PlayerTroopCount message, current_round
and player_troop_counts on GameUpdateResponse
- shardok_battle_view: defender_ratio, troop counts for allied viewers,
winning_faction_id
- game_state_view: updated_battles on GameStateViewDiff,
BattleProgressResponse message
- eagle.proto: battle_progress_response in GameUpdate oneof
- BUILD.bazel: game_state_view_scala_proto target
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Deletes game folders from eagle/archived/ where directory.e0i is over
1 month old. Runs weekly on Sundays at 05:00 UTC with manual trigger support.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The surviving units data was being sent by the server but never displayed.
Wire the EventBasedTable to show heroes and battalions using the same
CombatUnitRowController pattern as AttackDecisionCommandSelector.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
MarkAllAsSeen() assumed entries[0] had the latest date, but entries
in whats-new.json aren't necessarily in chronological order. When a
backdated entry was added at position 0, the saved last-seen date was
older than other entries, causing them to reappear on every sign-in.
Now scans all entries to find the maximum date.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>