RegisterAll() had an early return followed by all the original tutorial
content definitions — unreachable since the narrative dialogue system
replaced them. Remove the dead code, keeping only the empty method stub.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
FearAnimator maintained its own _activeObjects list, Track(), Untrack(),
and OnDestroy() that duplicated base class TrackedEffectAnimator's
tracking. Since all objects were created via CreateTrackedObject() (which
adds to the base's _trackedEffects), but Track() was never called,
_activeObjects was always empty — making FearAnimator's Untrack() calls
no-ops and its OnDestroy() cleanup redundant.
Remove the dead code and let the base class handle all tracking.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The tutorial battle is too hard for new players. Buff the defender's
starting longbowmen and two reinforcement battalions by ~20% to make
the battle easier, staying within battalion type capacity limits.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Handle ShardokBattleResetResponse in Unity client
Route the battle reset message through PersistentClientConnection and
EagleGameModel to the ShardokGameModel, which clears its mutable state
and signals the controller. The controller clears overlays, labels, and
the game-over UI so the restarted battle rebuilds normally from new
setup results.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add Old Marek dialogue on battle reset
When a tutorial battle resets, Old Marek comments on a strange feeling
of deja vu and encourages the player to try a different approach.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Wait for Eagle confirmation before allowing battle exit
Previously, the Shardok controller showed "Exit Battle" as soon as the
game reached Victory state and immediately cleaned up the model. This
meant a subsequent ShardokBattleResetResponse had nothing to reset.
Now the model stays alive after Victory, and "Exit Battle" only appears
once Eagle confirms removal via RemovedBattleIds. If a battle reset
arrives instead, the model is still intact for ResetForNewBattle().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix GetPlayerName crash with negative playerId after battle reset
ResetForNewBattle sets CurrentPlayer to -1, which passed the
"playerId < players.Count" check but threw on list access.
Add a lower bounds check.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Reset battle tutorials on battle reset so they re-fire on retry
Adds ResetBattleTriggerFlags() for combat-only trigger flags and
ResetCompletedScriptsById() for selective dialogue reset. Strategic
tutorial progress is preserved.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Add receiveBattleReset to BattleUpdateReceiver trait
- Handle BattleResetResponse in ShardokInterfaceGrpcClient streaming
- Implement receiveBattleReset in GamesManager (delegates to controller)
- Add no-op implementation in CustomBattleManager
- Add postBattleReset to GameController: resets shardok history and
notifies human clients via ShardokBattleResetResponse
- Add sendBattleReset to HumanPlayerClientConnectionState
- Add withResetShardokResults to FullGameHistory/PersistedHistory/InMemoryHistory
- Add shardok_battle_scala_proto dependency to BUILD.bazel
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Add IsTutorialBattleEnabled() and DidDefenderLose() to ShardokEngine
- Add WaitResult enum (GAME_OVER, DISCONNECTED, BATTLE_RESET) replacing
bool return from WaitForUpdatesAndPush
- Add OnBattleReset() to StreamSubscriber interface
- Detect tutorial loss in WaitForUpdatesAndPush and return BATTLE_RESET
- In SubscribeToGame, loop on BATTLE_RESET: tear down the old game,
restart from serialized NewGameRequest, and stream fresh state
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Fix settings panel not showing on first Escape press
SettingsPanelController lives on an inactive panel prefab, so Start()
only runs the first time the panel is activated. When ToggleActive()
activated the panel, Start() would immediately set _active=false and
hide it again, swallowing the first Escape press. The slider
initialization in Start() also triggered volume change callbacks,
causing an audible volume shift.
Sync _active with the panel's current state in Start() instead of
forcing it to false.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove defensive null checks on Inspector fields in SettingsPanelController
Per project convention, Inspector fields should throw NullReferenceException
if not linked rather than silently skipping code.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove tileBorderWidth serialized fields from Settings Panel prefab
These fields were removed from SettingsPanelController.cs; clean up the
prefab to avoid Unity serialization warnings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Fix Shardok battle race condition — hex tiles sometimes never render
ImageForTerrainTracker and TacticalAssetLoader were plain C# singletons
whose LoadAsync() coroutines ran on ShardokGameController (which gets
deactivated). ConnectionHandler.Start() deactivates the shardokContainer,
killing any in-flight coroutines. The singletons' _loadStarted guard
then prevented reloading, leaving IsLoaded permanently false and
SetUpGameAfterLoad() spinning forever at its while-loop.
Convert both to MonoBehaviour singletons meant to live on a persistent
GameObject (Main Camera). They now own their own Awake() → LoadAsync()
lifecycle and are immune to container deactivation. Remove the redundant
StartCoroutine kicks from ShardokGameController and EagleGameController.
Requires adding ImageForTerrainTracker and TacticalAssetLoader components
to Main Camera in Gameplay.unity (scene change not included in this
commit).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add ImageForTerrainTracker and TacticalAssetLoader to Main Camera
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
xcodebuild archive can reset the user keychain search list during long
builds, dropping the temporary CI keychain that holds the Apple
Distribution signing cert. Re-add and unlock the keychain right before
the export step so xcodebuild -exportArchive can find the certificate.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Gitea's mirror-sync API is async — the GitHub webhook triggers it but
returns 200 before the sync completes. CI runners can start git lfs pull
before new LFS objects are available on Gitea. Extract LFS fetch into a
shared script with retry logic (5 attempts, 15s backoff) to bridge the
gap.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
RemoveController was using std::shared_lock (read lock) while calling
erase() on the runningControllers map. This is undefined behavior
since other threads could be concurrently reading the map. Use
std::unique_lock (write lock) instead.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add hippogryph beast effect using DragonEffect flight system
Reuses DragonEffect.cs (flight/ground state machine) with a custom
HippogryphMapAnims controller mapping 15 animation states to the
PROTOFACTOR Hippogriff asset. Includes PBR materials, 12 animation
FBXes, and tuned flight parameters (scale 8, fly height 12).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Reduce hippogriff texture maxTextureSize to 512
The Addressable bundle build was failing during ArchiveAndCompress
because the hippogriff textures were imported at 4096x4096 (default
from the asset pack). For a map-view beast effect, 512 is plenty.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix texture .meta files stored as LFS pointers instead of YAML
The broad directory-level LFS pattern in .gitattributes was catching
.meta text files. Replace with a global *.jpg pattern (other binary
formats already have global patterns) and re-add the 8 texture .meta
files as regular blobs so Unity can read them on CI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add BattleResetResponse proto messages for tutorial battle reset
Add BattleResetResponse to the internal Shardok interface (oneof case 4
in GameStatusResponse) and ShardokBattleResetResponse to the client-facing
Eagle API (oneof case 8 in GameUpdate). These signal that a tutorial battle
should reset after the defender loses.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add no-op handler for BattleResetResponse in exhaustive match
Prevents -Werror build failure from non-exhaustive pattern match
after adding the new oneof case. Full handling in follow-up PR.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Each xcodebuild archive creates a new ~3.6GB Unity-iPhone-<random>
folder in DerivedData that was never cleaned up, accumulating on
the build runner.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add black panther, rhinoceros, gorilla, hyena, leopard, warthog, emu,
kangaroo, and tasmanian devil to the custom animations table. Remove
them from the "not yet set up" section and note which remaining pack
animals are not in beasts.tsv.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Engineers standing on a bridge tile can attempt to destroy it. The
player selects an adjacent traversable, unoccupied tile as the
destination. A D100 success roll determines if the bridge is destroyed.
Whether the roll succeeds or fails, the engineer moves to the selected
tile. Bridges are no longer valid Reduce targets.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add Unity client support for Blow Bridge command
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Improve Blow Bridge UX: icon, sound, animation position and rendering
- Change command icon from catapult (reduce.png) to manabomb
(Engineering_25_manabomb.png)
- Use explosion impact sound (Fire Spelll 02) for attempt, structural
breakage sound for success, failure horn for failure
- Fix animation position: use sourceGridIndex (bridge hex) instead of
targetGridIndex (engineer's destination) since the engineer starts ON
the bridge and jumps to an adjacent hex
- Preserve cached source position for BlowBridge results so the server's
post-move actor location doesn't override the bridge hex
- Replace hammer/axe attempt animation with debris explosion
(AnimateRepairFailed) matching the destructive nature of the action
- No visual animation on failure, just the negative sound
- Add zPosition parameter to AnimateRepairFailed so BlowBridge debris
renders in front of the bridge 3D object (at Z=-5)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When the AI thread throws an exception, instead of re-throwing (which
calls std::terminate and silently kills the thread), store the error
message, set an atomic flag, and wake up waiting subscribers. The gRPC
stream subscriber detects the failure and returns INTERNAL status to
Eagle, which triggers Sentry notification. The error message includes
game ID, player ID, and round number for easier debugging.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
ToPlayerInfoProto() was not copying the cannot_become_outlaw flag
when converting flatbuffer PlayerInfo to proto for the AI's
GameStateView. This caused the AI to always think outlaw was
available, generating extra BECOME_OUTLAW_COMMAND entries and
triggering a command count mismatch crash.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Each beast has TSV stats, FBX animation converted to Generic, animator
controller with idle/walk/eat/attack states, effect prefab with tuned
spawn parameters, and Addressables registration. Models sourced from
Africa Animals Pack V1 (black panther, rhinoceros), Africa Animals Pack
Low Poly V2 (gorilla, hyena, leopard, warthog), and Australia Animals
Pack V1 (emu, tasmanian devil, kangaroo).
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add lion and velociraptor beast effects
Add animated lion (male + lioness) and velociraptor province beast effects
with Generic animation controllers. Includes only the specific model files
needed from Africa Animals Pack V1 and Dino Pack Low Poly V1.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix lioness animation with per-prefab animator controller
The lioness wasn't animating because the shared LionMapAnims controller
references clips from the Lion FBX, whose bone paths (LionSkeleton/...)
don't match the Lioness model (LionessSkeleton/...). Create a separate
LionessMapAnims controller referencing Lioness FBX clips and use the
per-prefab animatorControllers array to assign each model its own
controller. Also add the standalone Lion/Lioness prefabs that LionEffect
references.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add BLOW_BRIDGE_COMMAND (38), BLOW_BRIDGE (80), and
BLOW_BRIDGE_FAILED (81) enum values for the upcoming
Blow Bridge engineer command.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Import the Africa Animals Pack and wire up hippopotamus as an AnimalEffect
beast. Switch the hippo FBX import to Generic animation type for Mecanim
compatibility and enable loopTime on looping clips. AnimalEffect now strips
legacy Animation, Rigidbody, and Collider components and adds an Animator
when prefabs lack one, supporting asset packs that ship with legacy setups.
Document the per-animal setup workflow for future Africa Animals Pack imports.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add animated windsock wind direction & speed indicator to Shardok battle view
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Redesign wind indicator as animated arrow, remove weather from round info
Replace windsock (pole + sock) with a simpler arrow that points in the
wind direction, shakes with layered sine waves for organic feel, and
grows thicker at higher speeds. Fix NW/SW direction mapping swap. Remove
weather text from round info label since visual indicators now convey
this. Show "No Wind" in speed label when calm.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Remove GraphicRaycaster from Weather Canvas (no interactive elements)
- Set raycastTarget=false on Odds Panel and Button Tooltip images
(display-only elements that don't need click handling)
- Use Color.clear instead of null when clearing bg highlights in HexGrid
to preserve mesh fill triangles
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Use ID-based lookup instead of object equality when diffing unaffiliated
heroes. The old code used Vector.contains (full object equality), so any
field change (profession, recruitmentInfo, etc.) caused the hero to
appear in both removed and added lists. If ordering or batching led to
the add arriving before the remove was processed, the client threw
"Duplicate unaffiliated hero" InvalidOperationException.
Now builds a Map by heroId for comparison, which correctly handles all
three cases: truly removed, truly added, and changed-in-place (emitted
as remove+add by ID so the client replaces the old entry).
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
BattleFilter was setting shardokGameId to None for restricted
non-participant observers. This broke GameStateViewDiffer's
removedBattleIds computation, which uses flatMap(_.shardokGameId)
and silently drops None entries. The result: battle animations
were added to the client but could never be removed.
myPlayerId = None already prevents non-participants from joining
the Shardok game, so hiding the game ID served no purpose and
broke battle removal tracking.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When a Shardok game ends (victory/defeat), the game model was
removed from ShardokGameModels but the battle was left in
ShardokBattles (OutstandingBattles). The only other removal path
was RemovedBattleIds in Eagle diffs, which may arrive late or not
at all for auto-resolved battles the player never entered.
This left the battle animation running on the strategic map
indefinitely after the battle ended.
Fix: also remove from ShardokBattles when processing ended games
in the Shardok update path.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
SwapModel returns early when both old and new model have available
commands, skipping mapController.Model assignment and all province
effect updates. This meant battle animations persisted after a
battle ended if commands were available at the time.
Fix: call mapController.UpdateBattleEffects before the early return
so battle start/end animations are always kept in sync with the
model, even when the full model swap is skipped.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When a province changed hands after a battle, UpdateBattleEffects
identified the defender by looking up the current ruling faction of
the defender province. After conquest, the attacker's faction matched
the province owner and was skipped, while the old defender was
processed — with OriginProvinceIds containing the defender province
itself, producing key (Wichel, Wichel) and an animation at the
province center.
Fix: skip any origin that equals the defender province. This can
never produce a meaningful animation (midpoint = province center)
and eliminates the dependency on current province ownership for
defender identification.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The failure check treated "no longer allied" as a failure condition, but
that's the success condition. Now: failure only if the target faction no
longer exists. Also added a general fulfillment check so the quest succeeds
even if the inline check in BreakAllianceResolutionHelpers doesn't fire
(e.g. when the ambassador is imprisoned).
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Non-involved players can no longer observe tactical Shardok combat. They
still see the battle-in-progress animation on the strategic map and learn
the outcome, but won't receive individual Shardok actions or be offered
the observation UI. Allied non-participants can still observe.
Controlled by the RestrictBattleObservation setting (default 1/on). Set
to 0 in the admin console to revert to the old behavior.
Also syncs settings.tsv with LLM settings that were previously only in
BUILD.bazel, and fixes the settings generator to include string_setting
dep and correct battalion_type_loader deps.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The settings generator had two issues:
- Missing string_setting dep when String-type settings exist
- Wrong battalion_type_loader deps (proto target instead of model state)
Also syncs settings.tsv with LLM settings (llmProvider, openAiModelName,
claudeModelName, geminiModelName) that were previously only manually
added to BUILD.bazel, and updates the openai_model_name target to match
the generator's snake_case convention.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Set cannotBecomeOutlaw for tutorial opening battle
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix: only set cannotBecomeOutlaw for the first battle, not all tutorial battles
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Instead of fixed directions (March slides right, Send Supplies launches
upward), both animations now compute the direction to the destination
province from the game state diff and animate toward it.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add -500 trust toward the King (faction 2) for both Bridget (faction 4)
and Hedrick (faction 5) so the King's AI never sends them invitations.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add cannot_become_outlaw flag to per-faction battle request
When set on a faction's PlayerSetupInfo, the BecomeOutlaw command
is suppressed for that faction's units. Default false preserves
existing behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove default values for cannotBecomeOutlaw, require explicit specification
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add tutorial Mobilization phase start logic (#6478)
When starting a tutorial at Mobilization or EagleAppears, fast-forward
the game state past the opening battle to a plausible mid-game state
where Sadar controls two provinces and King's satellites are vacated.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Replace dynamic tutorial phase setup with JSON resource loading
Add an extractor tool and loader to store pre-computed tutorial phase
transition results as JSON resources instead of computing them at
runtime from game state. This eliminates fragile dependencies on
knowing exact hero IDs and post-battle state deltas.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Populate expansion_results.json with battle resolution data
Extracted from a tutorial playthrough: actions 4-7 covering battle
resolution, province held, Tarn disappearance, and end of resolution
phase. Actions 0-3 (GAME_START through FOOD_CONSUMED) are already
produced by the initial game creation flow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Pretty-print expansion_results.json
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Increase Sadar's three starting battalions by 15% (460→529, 270→311,
346→398) to give the player a stronger opening battle position.
Raise Luke the Prank-tricker's Sadar faction bias from 80 to 110.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Increase all spatial fields (icon sizes, distances, heights, spreads,
radii) by approximately 1.3x across all province action animation types.
Durations, counts, angles, and colors are unchanged.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Match the Send Supplies behavior: goldLeftLabel and foodLeftLabel turn
red when the amount remaining in the origin province would exceed its
gold or food capacity.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Validates all maps to ensure starting positions are not placed on
impassable terrain (water, river, mountain) and that attacker starting
position sets don't overlap with each other.
Kojaria currently fails both checks and needs a map editor fix.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When an AI unit flees and ends the battle, the GameStateViewDiff
removes the unit from UnitsById before ModelUpdated processes the
history. The sound-playing code required the actor to be found in
UnitsById to determine animation coordinates, silently skipping
the entire sound block when the lookup failed.
Add a fallback else branch that plays the sound without animation
when the actor unit has been removed from the model.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The previous "battle_shout" sound felt more like rioting than
feasting. Replace with a group victory cry from freesound.org
(chripei, CC BY 4.0) that better conveys celebration.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Stop label updates from clearing hex background highlights
SetUnitInfoLabels and SetOverlayLabel were calling SetCellBaseColor
(which sets Geometry.Color = Color.clear) and hexMesh.Triangulate on
every invocation. These methods manage TMP_Text labels, not hex
geometry, so the SetCellBaseColor and Triangulate calls were
unnecessary. Removing them eliminates O(N²) Triangulate calls per
selection change (one per cell, each iterating all cells).
ClearOverlays now explicitly resets Geometry.Color for cells that had
a BackgroundHighlightColor and does a single Triangulate when needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Defer hex background highlight updates to HexGrid.Update for atomic mesh rendering
ClearOverlays was eagerly triangulating hexMesh (clearing background
highlights) while overlayMesh borders were only updated in Update().
This caused the two meshes to render out of sync for one frame when
Update order put HexGrid before MouseHandler. Now both meshes are
updated atomically in Update(), and ClearOverlays just clears the
data. Also reduces hexMesh.Triangulate calls from O(N) per overlay
cell per frame to at most one.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix overlay label/image flash by deferring all cleanup to Update
ClearOverlays was eagerly clearing labels and follow-up images while
the overlay border mesh was only updated in HexGrid.Update(). When
Unity's script execution order put HexGrid.Update before
MouseHandler.Update (which changed after the Shardok prefab
extraction in #6485), the labels/images were cleared mid-frame but
not re-set until next frame — causing a visible one-frame flash on
every unit selection.
Now ClearOverlays just clears the data list. Update() owns all visual
state: it clears stale labels, images, and background highlights from
the previous frame, sets new ones from current overlayCells, and
triangulates both meshes — all atomically regardless of script order.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Animation objects are parented to mapContainer, not the animator. When
the Map deactivates mid-animation (e.g. entering a battle), coroutines
die but the partly-faded sprites linger. Track all objects created by
CreateUIObject and destroy any survivors in OnDisable.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add elephant/mammoth beast effect using Animal Pack Deluxe v2
Wire up African and Indian elephant prefabs from the newly purchased
Animal Pack Deluxe v2 as a proper beast effect. Mammoths reuse the
same elephant models. Includes a custom ElephantMapAnims controller
to remap v2 animation state names to AnimalEffect's expected states.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix elephant animation: use v2 pack's own controller, fix prefab refs
- Use the v2 pack's African_elephant_anim_controller directly instead
of our custom ElephantMapAnims (which couldn't resolve FBX clips)
- Fix animalPrefabs fileIDs to match actual root GameObjects
- Regenerate animation FBX .meta files for Unity 6 compatibility
The v2 controller's state names don't fully match AnimalEffect's
expected names (idle A/attack A vs idle/attack), so animations are
partial. Next step: remap state names to get all 4 animations working.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Rename v2 controller states to match AnimalEffect expectations
Rename 'idle A ' → 'idle' and 'attack A' → 'attack' in the v2 pack's
African_elephant_anim_controller so all 4 animation states work with
AnimalEffect's Animator.Play() calls.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Action results arriving from the server while the Map game object is
inactive (e.g. during a battle) are queued and replayed 1s after the
Map reactivates, so the player sees what happened while they were away.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The full AICommandFilter was only applied during lookahead (inside
FindBestCommand), not at the root level of IterativeSearch. This
caused the AI to repeatedly choose METEOR_TARGET_COMMAND because the
re-target filter never ran on the top-level command list.
Adds FilterLoopingCommands — a minimal filter that only removes
genuinely never-useful commands (meteor re-target/cancel when a target
is already set). The full aggressive filter stays in lookahead only,
where pruning is a performance optimization. Applying the full filter
at the root could miss moves that look bad locally but prove
worthwhile with deeper search.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
METEOR_START uses ActionCost::usesAll which drains AP to 0. The
MeteorTargetCommandFactory then unconditionally generates a
METEOR_CANCEL command (cost 1 AP) for the mage in TARGET state.
When the AI evaluates this command, MutatingSpendActionPoints throws
because 1 > 0.
Two fixes:
1. Set meteorCancelActionPointCost to 0, matching #6454's fix for
meteorTargetActionPointCost.
2. Filter out meteor re-target/cancel in AICommandFilter when the
mage already has a target. Re-targeting exists for human misclick
correction; the AI commits to its first choice and could otherwise
loop endlessly re-targeting at 0 AP cost.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Mammoth had human-bandit-like stats (likelihood 0.10, power 1.5,
economy devastation) instead of large-animal stats. Update to be
rarer than elephants with appropriate power, count, and devastation.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Extract Command Panel into prefab
Move the Command Panel hierarchy (37 command sub-panels) from
Gameplay.unity into its own prefab at Assets/Eagle/. This is the
largest single component in the scene at ~126K lines. Scene-level
cross-references (eagleGameController, connectionHandler, hexGrid,
shardokContainer, etc.) are preserved as PrefabInstance overrides.
Reduces Gameplay.unity from ~232K to ~102K lines (56% reduction).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Extract 35 command sub-panels into individual prefabs
Break down Command Panel.prefab (126K lines) into 35 individual
prefabs under Assets/Eagle/CommandPanels/. Command Panel itself
becomes a thin container (~6K lines) with PrefabInstance references.
Largest sub-panel (March Panel) is 12.7K lines; smallest are ~230
lines. All are independently editable and produce focused diffs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Move the Shardok Container hierarchy (Shardok Canvas + Weather Canvas)
from Gameplay.unity into its own prefab at Assets/Shardok/. External
field references (mainCamera, eagleCanvas, soundManager, audioClipSource)
are preserved as scene-level PrefabInstance overrides.
Also adds UNITY_POST_PROCESSING_STACK_V2 scripting define symbols.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Eliminates 3 duplicate instances by adding a static Instance property,
replacing all GetComponentInParent lookups and serialized field references
with EagleCommonTextures.Instance. Removes the duplicate components from
ConnectionCanvas, Shardok Container, and Eagle Canvas in the scene, and
moves the single remaining instance to Main Camera so it's guaranteed to
have Awake() called regardless of which canvases are active.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Extract Custom Battle Panel into prefab
Remove baked-in Custom Unit Row instances from the prefab since
EventBasedTable.Awake() destroys them and recreates from rowPrefab
at runtime. Also remove stale SimpleFileBrowser references from
eagle0.sln.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Convert Custom Battle Panel prefab from LFS to regular git
This prefab is actively edited line-by-line and benefits from normal
git diff/blame/merge. LFS is better for opaque binary assets.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add missing Unity .meta files, HoneyBadger import artifacts, and fix_import.py
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove HoneyBadger import artifacts and gitignore FBX auto-generated files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Remove the SimpleFileBrowser plugin and its usage in CustomBattleHandler
(custom map loading and game request loading via file dialogs). Remove
the Custom Map Toggle, Load Button from the Custom Battle Panel, and
the obsolete Tutorial Lite button from the Lobby Panel prefab.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The Settings Panel prefab starts inactive, so Start() never runs until
the user opens it. This caused persisted values (province label font
size, audio volumes, tooltip behavior) to not be applied until Escape
was pressed, producing a visible font resize.
Split initialization: ApplyPersistedSettings() loads PlayerPrefs and
applies to game systems early via SettingsKeyListener.Start() on the
always-active PersistentCanvas. The deferred Start() just syncs UI
sliders/toggles to match the already-applied values.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Replace tutorial buttons with phase dropdown in ConnectionHandler
Replace the two separate tutorial buttons (tutorialButton, skipBattleTutorialButton)
with a TMP_Dropdown + Button combo so any tutorial phase can be selected from the lobby
without adding more buttons for each new phase.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix C# TutorialPhase enum names to match protobuf codegen
C# protobuf strips the common prefix, so the enum values are
TutorialPhase.OpeningBattle (not TutorialPhaseOpeningBattle), etc.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Wire tutorial phase dropdown and start button in Gameplay scene
Add missing using directive for Net.Eagle0.Eagle.Common namespace
and re-wire tutorialPhaseDropdown and startTutorialButton to the
Lobby Panel prefab instance.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Reduces Gameplay.unity by ~13k lines by extracting three connection UI
panels into standalone prefabs with scene-level overrides for callbacks
that reference the scene-level ConnectionHandler.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Longbowmen can now target enemies at range 3 when shooting in the
direction the wind is blowing (±1 hex sector), provided wind speed
meets the configurable `longbowWindBonusRangeMinSpeed` threshold
(default 20 mph). The 20 mph threshold reflects the point where wind
meaningfully extends arrow range, below the 35 mph blizzard threshold
where archery is already disabled.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Replace tutorial booleans with TutorialPhase in CreateGameRequest
Replace `is_tutorial` + `skip_tutorial_battle` booleans with a single
`TutorialPhase tutorial_start_phase` field, making the API extensible
for future tutorial phases (Mobilization, etc.) without adding more
boolean flags.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add service visibility to tutorial_phase_converter BUILD target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Keep old tutorial boolean fields in CreateGameRequest proto
Keep is_tutorial and skip_tutorial_battle fields so the Unity build
doesn't break. They'll be reserved in the client-side PR.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Extract Settings Panel into prefab to reduce Gameplay.unity size
The Settings Panel hierarchy (~11k lines) was inline in the scene file,
making Gameplay.unity harder to diff and merge. Extract it into a
standalone prefab with scene-level overrides for cross-references.
Fix 15 event callback targets that Unity zeroed out during extraction
(sliders, toggles, buttons all needed re-pointing to the
SettingsPanelController inside the prefab).
Add SettingsKeyListener on PersistentCanvas to handle Escape/F11 keys,
since the SettingsPanelController now lives on the (sometimes inactive)
prefab root and can't run Update() when hidden.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove scene overrides that zeroed out prefab event callbacks
The prefab instance in Gameplay.unity had overrides that set 6 event
callback targets to null, overriding the correct values in the prefab.
Remove these overrides so the prefab's own targets take effect.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add tiger and lion beast effects using ithappy Animals FREE pack
Import the free ithappy Animals FREE asset pack and create tiger/lion
beast effects. The tiger model is reused at 1.7x scale for lion.
A custom TigerMapAnims controller maps ithappy's idle/walk/idle_rare/run
clips to AnimalEffect's expected idle/walk/eat/attack state names.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove lion effect (scaled tiger is not convincing)
Lion falls back to generic vultures until a proper model is found.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix ithappy files stored as LFS objects breaking Unity import
The broad ithappy/** LFS rule was storing text YAML files (.controller,
.mat, .prefab, .anim, etc.) as LFS objects, causing Unity to fail with
"File may be corrupted" errors. Remove the rule and re-add files so text
files are stored as regular git objects while binaries (.fbx, .png) are
still covered by per-extension LFS rules.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Strip third-party scripts/physics from animal prefabs on spawn
ithappy Tiger_001 prefab includes CharacterController, CreatureMover,
and MovePlayerInput components that conflict with AnimalEffect's own
movement system. DestroyImmediate these after instantiation to prevent
"CharacterController.Move called on inactive controller" errors.
Also wires up tigerEffectPrefab in Gameplay.unity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Move beast effect prefabs to Addressables for CDN delivery (#6373)
Replace 33 serialized GameObject fields in ProvinceBeastsController with
Addressables.LoadAssetsAsync label-based loading ("beast-effects").
Prefabs stay in Assets/Eagle/Effects/ but are now delivered via CDN at
https://assets.eagle0.net/addressables/[BuildTarget] instead of being
bundled in the app binary. This removes ~80-100 MB of 3D models,
textures, and animations from the initial app download.
Loading strategy:
- Eager load in Start() (last priority — after music)
- Game state that arrives before loading completes is stashed and
applied when ready
- If loading fails (offline, no cache), beasts simply don't render
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add tiger and elephant beast effects using Animal Pack Deluxe
Route "tiger" and "elephant" beast names to dedicated TigerEffect and
ElephantEffect prefabs in ProvinceBeastsController. Prefabs to be wired
up in the Unity editor using existing Animal Pack Deluxe assets.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Register TigerEffect in Addressable beast-effects group
The TigerEffect prefab was added in the add-tiger-effect branch but was
never registered in the Beast Effects Addressable group, so LookupPrefab
returned null at runtime. Also removes a duplicate "tiger" switch case
from the merge.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix AnimalEffect component removal errors on spawn
Instantiate prefabs inactive to prevent CharacterController stepOffset
validation errors, and remove MonoBehaviours in reverse component order
to respect [RequireComponent] dependency chains before removing
CharacterController.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix ithappy Animals_FREE material to use Built-in Standard shader
The material shipped with a URP Lit shader reference, causing solid pink
rendering on Built-in render pipeline. Swap to Built-in Standard shader
and remove the URP version tracker MonoBehaviour.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix tiger animations by using standalone .anim clips
The ithappy Animals_FREE FBX was imported with avatarSetup: 0 (no
avatar), so its embedded animation clips had no bone binding data.
Switch TigerMapAnims.controller to reference the standalone .anim files
which have explicit transform path bindings. Also build a Generic avatar
at runtime for prefabs missing one, and set up the Animator before
activation so it initializes with correct avatar and controller.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix duplicate Start/LoadBeastEffects from rebase
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove unused ithappy Animals_FREE assets
Only the tiger model, texture, material, and animations are needed.
Removes all other animals (chicken, deer, dog, horse, kitty, penguin),
demo scenes, skyboxes, scripts, floor material, animation controllers,
and render pipeline conversion files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove CharacterController and deleted scripts from Tiger prefab
The prefab still referenced CreatureMover and MovePlayerInput scripts
that were deleted in the previous commit, causing missing script errors
in the Unity editor.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update Tiger_001 prefab after Unity re-serialization
Unity re-serialized the prefab after we removed the CharacterController
and missing script components. This is the clean editor-saved version.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add battle-in-progress animation on strategic map
Display looping weapon-clash effects between defender and attacker origin
provinces while battles are active. Follows the ProvinceFestivalController
pattern for centroid loading and effect lifecycle management.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Load battle sound from sfx-combat addressables instead of inspector field
Use the "melee" clip from the "sfx-combat" addressable label, matching
the pattern used by ProvinceActionAnimator and SoundManager.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Tune execution animation duration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix battle effect: orientation, sound, two weapons, test provinces
- Fix upside-down weapons by flipping weapon2 Y-scale (mirroring)
- Play melee clip as one-shot at each impact instead of continuous loop
- Use two different weapon sprites (sword + axe) for visual variety
- Change test ContextMenu to use adjacent provinces 2 and 5
- Add Gameplay.unity scene wiring
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add arrow volleys, fix weapon orientation, fix audio pattern
- Add arrow volley animations flying between province centers with
parabolic arc, random direction, and archery sound
- Fix weapon rotation: per-weapon offsets (sword blade down = +90,
mace head up = -90) so both swing business-end first
- Fix arrow sprite rotation offset (-135 for strategic map UI space)
- Fix audio: move playback to controller using Action callbacks,
matching ProvinceActionAnimator pattern (same object owns AudioSource
and calls PlayOneShot)
- Load audio in OnEnable with Dictionary<string,AudioClip> like
ProvinceActionAnimator
- Add configurable volume and rotation offset inspector fields
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add meta files for new battle effect scripts and addressable settings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Expose attacker army origin provinces in the ShardokBattlePlayerInfo view so the
Unity client can draw battle-in-progress animations between defender and attacker
origin provinces on the strategic map.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Split captured hero recruitment into distinct ActionResultTypes and add captured hero animations
HandleCapturedHeroesCommand previously used RecruitHeroes (21) for both successful
and failed captured hero recruitment, making them indistinguishable on the client.
Add CapturedHeroRecruited (162) and CapturedHeroRecruitmentRefused (163) so the
Unity client can play the correct animation for each outcome. Also add province
animations for all 6 captured hero dispensations (recruited, refused, imprisoned,
exiled, executed, returned).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add sound effects for captured hero animations
Map each captured hero ActionResultType to an appropriate sound clip,
reusing the same clips as the animations they share patterns with.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
HandleCapturedHeroesCommand previously used RecruitHeroes (21) for both successful
and failed captured hero recruitment, making them indistinguishable on the client.
Add CapturedHeroRecruited (162) and CapturedHeroRecruitmentRefused (163) so the
client can play the correct animation for each outcome.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Fix tutorial duplicate hero bug by changing Tarn from Fled to Outlawed
PR #6464 set fleeProvinceId to None for the tutorial attacker army, but
TutorialBattleAutoResolve still marked Tarn as Fled. With no flee
province, unitReturned() returned true for Fled, triggering the
shattered army path which created outlaws for ALL attacker heroes —
duplicating the heroes already created via defenderCP's newOutlaws.
Changing Tarn to Outlawed avoids the shattered army path entirely while
preserving the tutorial storyline: Tarn escapes capture and doesn't
appear in HandleCapturedHeroes, then EagleAppearsAction picks him up.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update TutorialTarnDisappearsAction to handle Outlawed status
With Tarn now marked as Outlawed (instead of Fled), he ends up as an
unaffiliated hero in the battle province rather than in an incoming
army to Nikemi. Update the disappears action to remove him from
unaffiliated heroes so he's properly hidden until EagleAppearsAction.
Also remove the now-dead fled/incoming-army logic and unused MovingArmy
import.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Set speakerImagePath directly in tutorial_strategic.json for John Ranil
and Elena Fyar instead of relying on dynamic resolution, which can fail
due to timing issues with the Eagle model.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add tutorial indicator to lobby running game items
Add a tutorialIndicator GameObject field to RunningGameItem that is
shown/hidden based on the game_type from the server. Pass GameType
through from ConnectionHandler so the client can distinguish tutorial
games in the lobby list.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Wire tutorial indicator in Running Game prefab
Connect the Tutorial TextMeshPro label to the tutorialIndicator
field on RunningGameItem so it shows/hides based on GameType.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Adds one-shot visual animations at province centers for all remaining
player commands: Rest, Train, OrganizeTroops, ArmTroops, RecruitHeroes,
Trade, OrdersIssued, SentSupplies, SuppressBeasts (success/fail),
Divined, March, SwearBrotherhood (success/fail), ControlWeather, Alms,
HeroGift, ApprehendOutlaw, Recon, ExileVassal, and StartEpidemic.
Each animation includes inspector-tunable parameters, a ContextMenu test
method, and a sound effect mapping wired through the actionSounds array.
21 audio clips added to the sfx-combat Addressables group.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Plumb the existing GameType enum (NORMAL/TUTORIAL) through to the lobby
response so clients can distinguish tutorial games from normal games.
- Add game_type field to RunningGame (persistence) and GameInfo (API) protos
- Add gameType to GamePlayerInfo case class and populate it in all 4
construction sites (gamesFor cached/full-load, gamesForWithoutBlocking
loaded/unloaded) plus save()
- Pass gameType through to GameInfo in EagleServiceImpl
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The tutorial attacking army had fleeProvinceId set to its origin province, which
allowed Tarn's forces to flee during the opening battle. Since canFleeHeroIds was
always Set.empty, the flee logic fell back to fleeProvinceId.isDefined (true).
Fix by setting fleeProvinceId = None for the tutorial army, and remove the unused
canFleeHeroIds field entirely from ShardokBattle, its proto, converter, and all
call sites.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add TutorialPhase to GameType ADT to track tutorial progression
Converts GameType from a simple Normal/Tutorial enum into an ADT where
Tutorial wraps a TutorialPhase (OpeningBattle, Expansion, Mobilization,
EagleAppears, Complete). This separates "what kind of game" from "what
phase of the tutorial", enabling phase-aware event logic and future
ability to start at a specific phase.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix C# tutorial check to match on Tutorial specifically
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use TUTORIAL_PHASE_ prefix for proto enum values
Follow Google's protobuf enum naming best practice of prefixing all
values with the enum name.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Pale yellow/white particle chain connects necromancers to their
controlled undead units. Shimmers in and out when not actively
controlled this round, becomes solid translucent when controlled.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Increase destinationScale from 45 to 60 for a wider cone at the target.
Track the mage's meteor phase per beam and apply a 3x speed multiplier
to vibration and shimmer during the Cast phase.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add sound effect for meteor start animation
Map ActionType.MeteorStart to Fire Spelll 01 from the magic spells
sound pack so the meteor preparation animation has an accompanying
magic charging sound.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add sound effect for meteor target animation
Map ActionType.MeteorTarget to Fire Spelll 02 so the targeting
crosshairs animation also plays a fire magic sound.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
SetupLobbyUI ran at startup before login, so IsAdmin was stale.
Re-evaluate admin-gated button visibility in OnOAuthLoginSuccess.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add a feast animation to ProvinceActionAnimator that spawns food items
(drumstick, beer, wine, grapes, cheese, bread) tossed into the air on
parabolic arcs with spin and fade. Plays a battle_shout cheering sound.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Skip auto-selecting mage unit that already has a meteor target
When a mage already has a CastTarget set, don't auto-select them
via SelectAppropriateDefaultCommand. The player can still manually
click the unit to re-target, but we no longer default to showing
commands for a unit that has already targeted.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Find untargeted mage when multiple have MeteorTarget available
Instead of checking the first unit with MeteorTargetCommand and
skipping the entire command type if that unit already has a target,
search through all available commands of the type to find a unit
that hasn't targeted yet.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Wire ProvinceActionAnimator in Gameplay scene
Add ProvinceActionAnimator component to the scene with Inspector
references and action sound configuration for Improve animations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update ProvinceActionAnimator scene wiring
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Log game ID, player ID, round, and stack trace to stderr before
re-throwing, so AI thread crashes are diagnosable without a debugger.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add province action animations to Eagle strategic map
Play one-shot animations at province centers when action results arrive.
Starting with the Improve action, which shows a hammer-strike animation
with sparks. Sound effects loaded from sfx-combat Addressables by name.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix hammer head alignment and restore rotation defaults
Offset tool position upward by half the tool size so the hammer head
(bottom of rotated image) aligns with the province center where sparks
appear. With Image+RectTransform the pivot is at center, unlike
SpriteRenderer which uses the sprite's imported pivot — without the
offset the middle of the hammer sat on the province.
Restore hammerBaseRotation=180 and hammerFlipHorizontal=true to match
ToolAnimator defaults.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Set hammer rotation default to 90, fix sound loading robustness
Change hammerBaseRotation default to 90 to match tested working value.
Sound effects were silently failing — the Addressables coroutine started
in Awake() could be interrupted if the GameObject was deactivated during
scene setup. Now uses lazy-load pattern: retries loading on first play
attempt if clips aren't ready. Added diagnostic logging for load
success/failure and missing clip names.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove defensive null checks on inspector fields
Let missing inspector wiring throw NullReferenceException immediately
rather than silently skipping. This was hiding the sound bug — the old
guard clause bailed before any logging could fire.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix sound loading: move coroutine to OnEnable, remove silent null guard
The LoadSoundEffects coroutine started in Awake() was being killed when
the GameObject gets deactivated during scene setup. Moving it to
OnEnable() ensures it retries on reactivation. Also removes the
_loadedClips null guard so failures are visible via NullReferenceException
rather than silently skipping sound playback.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Play metal strike sound per hammer impact, add to Addressables
Switch from repair sound to Metal Weapon Hit Stone 2_1 for Improve
animation. Sound now plays once per hammer strike at impact rather
than once at the start of the animation. Added the clip to the
sfx-combat Addressables group.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
PR #6450 allowed meteor re-targeting, but it fails at runtime because
the initial target uses ActionCost::usesAll which drains all AP to 0,
leaving nothing for the re-target. Setting the target cost to 0 fixes
this: usesAll with minimum 0 always succeeds (0 >= 0), still drains
remaining AP (ending the mage's turn), and removes the vigor penalty.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Previously province 6 (Soria) had battalions but no heroes, and province
stats were hardcoded only for province 32. Now The Eagle's faction matches
the normal game: Tarn defects from faction 2, random vassals are generated
(5 for province 32, 4 for province 6), per-province stats are correct,
faction is hostile to Bregos with trust -100, and battalion names use
"Tarn's" prefix for province 6.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The validateAtMostOneAttackingAlliance check was firing during
UncontestedConquest phase, before PerformUncontestedConquestAction could
resolve the situation (largest army conquers). Skip the validation during
UncontestedConquest, matching the existing AttackDecision exemption.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add is_admin field to UserInfo proto and populate it through Go auth
handlers, Scala fallback handler, and C# client storage. Replace the
hardcoded display name check with the server-provided admin flag.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Previously, once a mage selected a meteor target, the command was
locked in with no way to correct a misclick. Now the target/cancel
commands remain available after targeting, but as optional (not
required to end turn), so the player can re-target, cancel, or
simply end their turn keeping the current target.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
All two-stage actions now have distinct success/failure animations.
Remove the completed status tracking table and animation descriptions,
keeping only the 7-step wiring guide for adding new two-stage actions.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add distinct success/failure animations for BraveWater
Split the shared BraveWater animation into two-stage success/failure variants:
- Success: Strong upward splash with bright droplets, expanding ripples, blue-white flash
- Failure: Droplets rise partway then fall back, darkening color, foam churns turbulently
Updates ShardokGameController enum, mapping methods, and dispatch.
Adds AnimateBraveWaterSuccess/AnimateBraveWaterFailed to WaterEffectAnimator.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add BraveWater success/failed test buttons to AnimationTestController
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use distinct splash sound for BraveWater success
Attempt uses braved_water (wading in), success now uses splash
(satisfying water crossing) so the two phases sound different.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Make BraveWater failure animation more dramatic and violent
Rewrote AnimateBraveWaterFailedEffect with 4 phases: high promising rise
(14 large particles with wide spread), brief stall with shuddering and
color flickering at peak, violent cubic-acceleration crash back down past
center with particles growing on impact, and aggressive turbulent churn
with large oscillations darkening to murky water.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add distinct success/failure animations for DismissUnit
Split the shared DismissUnit animation into two-stage success/failure variants:
- Success: Golden particles rise upward with warm golden glow pulse
- Failure: Particles expand then snap back inward, turning red with rejection flash
Updates ShardokGameController enum, mapping methods, and dispatch.
Adds AnimateDismissSuccess/AnimateDismissFailed to DismissAnimator.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add DismissUnit success/failed test buttons to AnimationTestController
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Double the spread width of DismissUnit success and failure effects
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use undead_break_control sound for DismissUnit failure
The undead have broken free — use the dramatic control-broken sound
instead of the generic failure horn.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Scale up DismissUnit effects: attempt 50% wider/taller, particles 2x
Attempt: particle spread multiplied by 1.5x, rise height 1.5x.
Success and failure: particle sizes doubled (3-6 to 6-12, 3-5 to 6-10).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix attempt particles: use visible sizes (4-8) instead of near-zero (0-4)
The original Random.Range(4f, 0f) produced values 0-4 with many near
zero, making particles invisible. Now uses 4-8 for consistently visible
particles that shrink to zero as they drift.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add distinct success/failure animations for RaiseDead
Split the shared RaiseDead animation into two-stage success/failure variants:
- Success: More figures rise quickly with bright green glow and pulse
- Failure: Figures rise partway then collapse back, glow flickers green to grey
Updates ShardokGameController enum, mapping methods, and dispatch.
Adds AnimateRaiseDeadSuccess/AnimateRaiseDeadFailed to RaiseDeadAnimator.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add RaiseDead success/failed test buttons to AnimationTestController
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use distinct success sound for RaiseDead (undead_grew instead of raise_undead)
Attempt uses raise_undead (eerie summoning), success now uses
undead_grew (undead power surge) so the two phases sound different.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Make RaiseDead failure more dramatic and use impactful success sound
Failure: figures now rise 75% of the way (was 40%), fully visible at
near-full opacity, stall with flickering glow shifting green to grey,
then get yanked back down with cubic acceleration. Much more visible
contrast between "almost worked" and sudden collapse.
Success sound: use holy_wave_damage for a darker, more impactful surge
instead of undead_grew.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add distinct Scout success/failure animations
Success: cone fully extended, glow pulses bright and expands at target.
Failure: cone extends partway, hits a dark barrier with red flash,
cone recoils, eye dims and shrinks to nothing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add Scout success/failed test buttons to AnimationTestController
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use boo sound for Scout failure instead of generic horn
Scout failure plays "boo" — a disappointed reaction when the scout
can't see through the barrier.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Rework Scout failure animation as dark reversal of attempt
Replace the red-barrier-and-recoil failure animation with a reversal:
glow disk at target turns dark, then cone and glow retract back toward
the source eye, which darkens and shrinks away. Also change failure
sound from boo to generic negative effect horn.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove leftover merge conflict marker from TWO_STAGE_ANIMATIONS.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add distinct FreezeWater success/failure animations
Success: crystals solidify with bright white flash and intense shimmer.
Failure: crystals form then crack, flicker, and shatter outward as
spinning shards fading from icy blue to dull grey.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add FreezeWater success/failed test buttons to AnimationTestController
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use splash sound for FreezeWater failure instead of generic horn
Ice shatters and reverts to water — splash conveys the ice losing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Make freeze success 30% bigger in both spread and crystal scale
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add distinct ExtinguishFire success/failure animations
Success: heavy water deluge with thick rising steam cloud.
Failure: droplets evaporate mid-fall, fire flares back with
orange/red burst.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add ExtinguishFire success/failed test buttons to AnimationTestController
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Make ExtinguishFire failure more dramatic with fire flare-up
When extinguish fails, also trigger FireEffectAnimator.AnimateFireSuccess
so the fire blazes back up with tall flickering flames and rising embers
alongside the evaporating water droplets.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use distinct sounds for ExtinguishFire attempt/success/failure
- Attempt: splash (water thrown at fire)
- Success: fire_extinguish (satisfying sizzle/hiss)
- Failure: raging_fire (fire flares back up)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Captured units' battalions are destroyed during BattleEnded processing,
but the multi-victor branch was storing those destroyed IDs in
AftermathClaimantUnit. This caused invalid battalion references when
claimants withdrew. Filter them out when building claimants and
notFledDefenders, and exclude them from armySize calculation.
Also update ALLIED_VICTORY_BATTLE_RESOLUTION.md to reflect PR 6429
changes (LastAllianceStanding for attackers, allied co-attacker added
to winners for solo castle holder) and document the dead-ally
GameOverResponsePopulator bug fixed in PR 6435.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Remove ClipRectMeshUnlit shader replacement for 3D beast models
The shader replacement multiplied texture color by vertex colors, turning
honey badger white stripes red. RendererViewportClipper already handles
viewport clipping for all 3D models generically, making the shader
approach redundant and harmful.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Set render queue on beast models to render above province texture
The old ClipRectMeshUnlit shader used Queue=Transparent+100 and
ZTest Always to draw above the province colour layer. Now that models
keep their original shaders, set mat.renderQueue = 3100 in
RendererViewportClipper.Awake() to match.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix spelling: colour -> color
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Reserve removed fields (ally_victory=2, draw=3) and delete the
DrawType enum to match the Eagle-side proto cleanup. Update C++
conversion code: ALLY_VICTORY now maps to victory, DRAW now throws.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When your alliance wins a battle but your own units were destroyed,
you should still get a victory — not an internal error. Change the
else-if branch from throwing to assigning victory for allied
non-winners.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The buttonTooltip and oddsPanel were setting transform.position/localPosition
using screen-space coordinates directly, which placed them incorrectly when
the canvas uses Screen Space Camera mode. Convert screen positions to canvas
world space via ScreenPointToWorldPointInRectangle.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When GetGameHistory returns a non-empty error field (game failed to load),
the admin server now transparently falls back to GetRawGameHistory. The UI
shows a warning banner and uses raw-rewind instead of normal rewind. This
lets admins view and fix broken games without any manual switching.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Fix allied attacker victory conditions
Allied attackers couldn't win by elimination because LastAllianceStanding
wasn't in their victory conditions. When one attacker held all castles,
the ally was treated as a loser because they weren't in winning_shardok_ids.
- Add LastAllianceStanding to attacker victory conditions in RequestBattlesAction
- Add mutually-allied co-attackers to winning_shardok_ids when one player holds all castles
- Add tests for both fixes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Extract PopulateGameOverResponse into testable library and add tests
Move PopulateGameOverResponse and its helpers (IncludeUnitProtoInReturn,
FromUnitFb, FromInternalStatus) from EagleInterfaceGrpcServer.cpp into a
separate GameOverResponsePopulator library so they can be unit tested
directly. Add 7 tests covering victory/loss assignment, allied player
validation, unit filtering, and status conversion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Shardok now emits Victory for all winners instead of distinguishing
AllyVictory. Remove the AllyVictory case from EndGameCondition, simplify
all isWinOrAllyVictory checks to isVictory, reserve the ally_victory
proto field, and update the allied victory documentation.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When a game's action history contains a broken action result, the game
can't load because formAwrs throws during state replay. This means the
admin console can't view history or rewind to fix it.
These two new RPCs operate directly on persisted chunk files, bypassing
the formAwrs state-replay pipeline:
- GetRawGameHistory: reads ActionResult protos from .e0a chunks and
returns them as JSON without replaying game state
- RawRewindGame: truncates chunk files to a target action count without
loading the game into memory
Also adds an error field to GetGameHistoryResponse so load failures are
reported instead of returning empty results.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
PR #6425 removed DrawType from the Eagle common proto but didn't
update the C++ conversion code in EagleInterfaceGrpcServer, causing
a linux-aarch64 cross-compile failure.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Every battle must have exactly one player with WIN_AFTER_MAX_ROUNDS.
This adds startup validation in StartGame and replaces the unreachable
DRAW result with a ShardokInternalErrorException.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Draws are dead code left over from removed Free For All battles. Shardok
battles always have exactly one defender with WinAfterMaxRounds, so if
max rounds is exceeded the defender wins — a draw is never produced.
- Remove DrawType enum and Draw case from EndGameCondition
- Remove isDraw method and all isDraw checks in unitReturned
- Remove Draw proto conversion code from ShardokBattleConverter
- Remove DrawType from victory_condition.proto (Eagle common only)
- Add internalRequire validation that exactly 1 player has
WinAfterMaxRounds when building shardokPlayers
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Fix tooltip positioning for Screen Space Camera canvas
HoveringTooltip.SetPosition() was mixing world-space and screen-space
coordinates. With Screen Space Camera, GetWorldCorners returns world
coordinates (not screen pixels), and the tooltip was placed at the
wrong position. Now routes all positioning through screen space using
RectTransformUtility, with the correct canvas plane z for projection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use world-space positioning instead of screen-space roundtrip
Work directly in world space using lossyScale for offsets and
canvas GetWorldCorners for overflow, avoiding broken coordinate
space conversions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Convert tooltip providers to pass screen-space Rects
The camera looks downward, so GetWorldCorners returns points where
the visual "height" is along z, not y. A 2D Rect from world coords
loses the z axis entirely (height=0). Fix by converting world corners
to screen space in the providers via WorldToScreenPoint, then
converting back to canvas world space in HoveringTooltip via
ScreenPointToWorldPointInRectangle for final placement.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Players with no active games were being automatically thrown into a
new 7-faction game. Remove this behavior so they always land in the
lobby and can choose what to do.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Fix allied battle victory: treat AllyVictory as winner in ResolveBattleAction
The multi-victor battle aftermath code (added in #6411) was dead code because
the winner/loser partition used `isVictory` which excludes `AllyVictory`.
Allied winners were classified as losers, so only one faction ever "won"
and the province was awarded immediately without a claim decision.
Changes:
- Add `isWinOrAllyVictory` to EndGameCondition
- Use it for the winner partition, backstory, and quest fulfillment
- Fix `unitReturned` so AllyVictory heroes stay at the battle province
as claimants instead of being sent home
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update tests to expect multi-winner behavior for allied victories
Tests previously expected AllyVictory units to be sent home. With
the partition fix, allied winners now stay as claimants for the
BattleAftermath claim decision.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The auto-bounce results set hostile armies to Withdrawing status, but
WithdrawnArmiesReturnHomeAction received the original provinces where
those armies were still AwaitingDecision. This caused a validation
error when transitioning to DefenseDecision because non-allied factions
remained in hostileArmies. Apply bounce status changes to the province
list before passing to WithdrawnArmiesReturnHomeAction so it properly
removes the bounced armies.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When multiple non-allied factions attack the same occupied province and
none makes an AttackDecision, they all remain AwaitingDecision. The
existing auto-bounce logic only triggers when an army has Attacking
status. Add a new branch that detects non-allied undecided factions and
bounces all of them to Withdrawing, preventing the
validateAtMostOneAttackingAlliance runtime error.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Three correctness fixes for the battle aftermath decision system:
- Cap withdrawal gold/food at what each faction brought into battle (broughtGold/broughtFood)
- Auto-keep last undecided claimant when nobody has kept yet (prevents broken province state)
- Serialize PendingConquestInfo to proto so server restarts during BattleAftermath don't lose state
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Destroy previous label container before creating new ones when
OnMapLoaded fires multiple times. Add OnDestroy to unsubscribe
from the event.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add FinalizeAftermathAction to handle the all-decided case (run
ProvinceConqueredAction for keeper, create withdrawal armies, clear
pendingConquestInfo), fix hero-battalion pairing by replacing separate
heroIds/battalionIds vectors with paired AftermathClaimantUnit, and
remove dead destroyedBattalions field from MultiVictorBattleSetupAction.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add BattleAftermathDecisionCommandSelector for Unity client
Adds the UI component for the battle aftermath decision system. When
allied factions jointly conquer a province, each winning faction chooses
to Keep Province or Withdraw (with destination and supply sliders).
The selector follows the AttackDecisionCommandSelector pattern with
toggle group, province dropdown, and gold/food sliders. It is inert
until the server command infrastructure sends the corresponding
available command.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add missing EagleGUIUtils using for GUIUtils.DarkColoredProvinceName
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Wire up BattleAftermathDecisionCommandSelector in Gameplay scene
Add the command selector as a child of CommandPanelController with
all Inspector fields connected: toggles, toggle group, withdraw
container, destination dropdown, gold/food sliders and labels, info
text, and button image. Unity events wired for ToggleChanged,
GoldSliderChanged, and FoodSliderChanged.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Wire up the full command pipeline for battle aftermath decisions so
winning allied factions can choose Keep Province or Withdraw after a
multi-victor assault.
- Add proto messages for available/selected command (field 41) and command type
- Add Scala model types: CommandType, AvailableCommand, SelectedCommand cases
- Add proto converters for all new command types
- Add AvailableBattleAftermathDecisionCommandFactory (sequential by army size)
- Add BattleAftermathDecisionCommand (keep triggers conquest, withdraw creates army)
- Add AutoResolveBattleAftermathAction (last claimant auto-keeps)
- Add BattleAftermathDecisionCommandChooser for AI (always keeps when possible)
- Update AvailableCommandsFactory and RoundPhaseAdvancer for aftermath phase
- Update CommandFactory routing and AIClient integration
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Remove Map Editor scene from build to eliminate duplicated hex tile assets
The Map Editor scene directly references hex tile textures via RawImage
components, pulling ~4.6 MB of textures into the player build that are
already served via Addressables from CDN.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove Map Editor button from connection screen
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove EditorButtonClicked and unused SceneManagement import
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Delete Map Editor scene and MapEditorController
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Enable the toggle GameObject in the scene but hide it at runtime
via Application.isEditor, so it only appears in the settings panel
when running inside the Unity Editor. The checked state still comes
from PlayerPrefs as before.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When multiple allied attackers win a province assault, defer province
assignment by storing PendingConquestInfo on the province instead of
immediately running ProvinceConqueredAction. This lays the groundwork
for the battle aftermath decision command (next PR).
- Add PendingConquestInfo, AftermathClaimant, and AftermathDecision types
- Add pendingConquestInfo field to ProvinceT/ProvinceC/ChangedProvinceC
- Branch ResolveBattleAction for multi-victor case via MultiVictorBattleSetupAction
- Add ActionResultType values for aftermath actions
- Update proto converters to handle new province field
- Remove unused validate method from ResolvedEagleUnit (breaks dep cycle)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add distinct success/failure animations for Repair
Repair previously used the same hammer-strike animation for both attempt
and result. Now it follows the StartFire/Fear two-stage pattern: attempt
shows hammer strikes, success shows a golden shimmer beam rising, and
failure shows debris collapsing with a dust puff.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Increase two-stage animation delay to let attempt animations finish
The delay cap between attempt and result phases was 0.5s (or 0.3s with
no sound), but the hammer animation runs ~0.6s and fire runs ~0.8s.
Raised the cap to 0.8s and the no-sound fallback to 0.6s so result
animations don't overlap with attempt animations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add distinct repair success sound and make debris explosive
Sound: Wire up a "repair_success" clip for the result sound so it
doesn't repeat the hammer attempt sound. Falls back to the hammer
clip until the audio asset is added to sfx-combat.
Debris: Replace gentle falling animation with a violent explosion —
pieces burst outward from center with physics-driven trajectories
(initial velocity + gravity), fast spin, an initial flash burst,
and a drifting dust cloud.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use "Positive Effect 6" as repair success sound
Add the clip to the sfx-combat addressable group and load it as the
repair success result sound so it plays a distinct chime instead of
repeating the hammer attempt sound.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When multiple allied players each occupy different critical tiles (castles)
such that all castles are covered, this now triggers a HOLDS_CRITICAL_TILES
victory for all occupants — provided they all have the victory condition set
and are mutually allied. Previously only a single player holding all castles
could trigger this win condition.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add distinct success/failure animations for StartFire
Split StartFire into three-stage visuals following the Fear pattern:
attempt shows ignition, success shows fire blazing up (taller/brighter
flames, hotter colors, outward burst), failure shows fire dying out
(shrinking flames, cooler colors, smoke puff).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Move StartFire doc to repo-root docs/TWO_STAGE_ANIMATIONS.md
Update the existing doc instead of creating a duplicate in the Unity
client directory.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Thread client_id from StreamGameRequest through EagleServiceImpl →
GamesManager → GameController → HumanPlayerClientConnectionState.
Update withHumanClient eviction logic to always evict connections with
the same client_id (same device reconnecting/refreshing), while keeping
connections from different client_ids alive (multi-device support).
Cancelled connections for the same faction are still cleaned up.
Changed client_id type from int64 to string for future extensibility
(e.g. embedding device name, platform, or version).
This fixes duplicate updates when a single client calls
StreamOneGameAsync for a refresh on the same gRPC stream, which caused
the "Duplicate unaffiliated hero" crash.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add a stable string client_id to StreamGameRequest so the server can
distinguish same-device reconnects from multi-device connections. The
C# client generates a GUID per PersistentClientConnection instance and
sends it with every StreamOneGameAsync request. The server currently
ignores this field; server-side eviction logic follows in a subsequent
PR. Using string instead of int64 for future extensibility (e.g.
embedding device name, platform, or version).
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Guard UpdateUnaffiliatedHeroSelections against count mismatch by
rebuilding the table when the model changes between rebuilds.
Keep the throw on server-side duplicate as a safety net.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add post-Eagle tutorial events: Ranil & Elena depart and form factions
After the Eagle appears, John Ranil departs the player's faction 2 rounds
later and Elena Fyar departs 4 rounds later. At 7 rounds after Eagle,
both form their own factions in isolated provinces.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add tests for TutorialHeroDepartureAction and TutorialHeroFactionsAction
Tests cover action result creation, province selection, hero changes,
battalion/faction creation, unaffiliated hero removal, and edge cases
(missing heroes, no suitable province, hero in different province).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix Eagle hero missing backstoryVersions causing validation failure
EagleAppearsAction.createEagleHero was creating the hero without
setting backstoryVersions, which defaults to Vector(). The runtime
validator requires backstoryVersions.nonEmpty for all heroes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix Eagle appearance crash: split entity creation from province changes
The action result applier processes changedProvinces before newHeroes,
so when EagleAppearsAction put both in a single ActionResultC, the
province change tried to look up the Eagle hero (via
ProvinceUpdateHelpers.applyRulingAndBattalions) before it existed in
the game state, causing a HashMap key-not-found exception.
Split into two sequential action results: first creates the faction,
hero, and battalions; second applies the province changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix duplicate Eagle hero: only place in province 32, not both provinces
The Eagle hero was added to rulingFactionHeroIds for both provinces 32
and 6, violating the one-hero-per-province invariant and causing a
validation crash on game load. Province 6 now gets the Eagle's faction
and battalions but not the hero itself.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replace blind `Add(AddedUnaffiliatedHeroes)` with per-hero loop that
throws InvalidOperationException when a hero already exists in the
province's unaffiliated list. This surfaces the root cause of duplicate
rows in the free heroes table instead of masking it.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When two clients connected for the same faction (e.g., iPad + Unity
editor), withHumanClient evicted all existing same-faction connections.
Now only cancelled connections are removed, allowing multiple active
clients to coexist with independent update streams.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Fix province effects missing when CDN centroids arrive after model
Since #6396 moved centroids.json to CDN, it loads asynchronously and
often arrives after MapController.Model is set. All province effect
controllers (festival, epidemic, blizzard, drought, flood, beasts)
silently skipped spawning because _provinceCentroids was empty.
Re-apply effects in OnProvinceMapLoaded when the model is already set.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix province effects missing when CDN centroids arrive after model
Since #6396 moved centroids.json to CDN, it loads asynchronously and
often arrives after MapController.Model is set. All province effect
controllers silently skipped spawning because _provinceCentroids was
empty.
Add lazy centroid loading to each controller's update method so they
load centroids on-demand from ProvinceIDLoader.CentroidsJsonText. This
eliminates the callback ordering dependency with OnMapLoaded. Also
re-apply effects in OnProvinceMapLoaded when model is already set.
Remove defensive null checks on Inspector-wired controller references
in UpdateProvinceEffects — missing wiring should fail loudly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sequential _colorIndex caused all firework instances to show correlated
color sequences. Use Random.Range per launch for independent colors.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Reduce sync mismatch grace period (60s→5s), HTTP/2 keepalive interval
(15s→5s), and add per-command response timeout (10s) to dramatically
cut recovery time when the client misses a game update.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
GamesManager.save() writes games.e0es to S3 synchronously on the gRPC
executor thread via CompletableFuture.join(), blocking all user
operations for the duration of the upload. JFR profiling showed this
blocking for 28-65ms per command, with potential for much longer spikes.
Wrap the top-level S3Persister in AsyncS3Persister, matching what
per-game persisters already do in LocalGamePersisterCreation.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The exportArchive step was relying on the Xcode-Token in the keychain
for App Store Connect authentication. This token expires periodically,
breaking headless CI builds. Pass the App Store Connect API key (already
used by the upload step) to the export step as well.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Fetch centroids.json from CDN via EagleMapInfo instead of baked TextAsset
ProvinceIDLoader now fetches both the map data and centroids JSON from
CDN using SHA256 hashes from the server-provided EagleMapInfo. OnMapLoaded
fires only after both arrive and validate. All controllers subscribe to
OnMapLoaded for centroid data instead of loading in Awake().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Guard MapController.ProvinceFromPoint against null map bytes
OnMapLoaded now fires later (after both map + centroids arrive),
so Update() can run before _mapIdBytes is set.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The SHA256 hash is used as the content-addressable filename for CDN
fetches, making map_name redundant. Reserve field 1 in the proto to
prevent accidental reuse.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Extends EagleMapInfo with centroidsSha256 so the client can validate
centroids.json after CDN download, matching the existing rawGray pattern.
Bumps migration to v3 so existing saves get backfilled.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Centralize centroidsJson reference in ProvinceIDLoader
All 7 province controllers now read centroidsJson from ProvinceIDLoader
instead of each having their own Inspector-linked TextAsset field.
This prepares for making centroids server-driven in the future.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Wire up provinceIDLoader references and remove stale centroidsJson links
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The province ID map is now fetched from CDN at runtime, so it no longer
needs to be a Unity TextAsset. Move it to the resources directory
alongside other game data files and delete the .meta file.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Load province map dimensions and hash from server-provided EagleMapInfo
Replaces hardcoded map dimensions (3786x1834) across 8 Unity client files
with server-driven values from the EagleMapInfo proto added in #6381.
ProvinceIDLoader no longer auto-loads in Awake() — instead, EagleGameController
calls Initialize(EagleMapInfo) on first GameStateView, which looks up the map
asset by name, validates SHA256 integrity, and fires OnMapLoaded for dependents
(MapController, ProvinceBorderDistanceGenerator).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Keep rawGrayCompressed as direct Inspector field instead of map lookup
The server provides dimensions and SHA256 but the client only has one
map asset — no need for a name-based lookup table.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fetch province map from CDN instead of bundled TextAsset
ProvinceIDLoader now uses ResourceFetcher.mapFetcher to download the
map data at runtime rather than reading a baked-in TextAsset. The server
provides the map name, dimensions, and SHA256 via EagleMapInfo.
Also fixes:
- Duplicate `using Net.Eagle0.Eagle.Common` in EagleGameController
- ProvinceLabelsController timing: defers CreateLabels to OnMapLoaded
to avoid division by zero when mapWidth/mapHeight are still 0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use eagle0-assets CDN for map fetcher instead of headshots CDN
Make ResourceFetcher base URI an instance field so each fetcher can
point at a different CDN. Map data is at:
https://eagle0-assets.sfo3.digitaloceanspaces.com/strategic-maps/
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use SHA256 as CDN fetch path instead of map_name
Content-addressable: the SHA256 hash serves as both the CDN filename
and the integrity check, so map_name is not needed for fetching.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* new sha
* Update EagleMapInfoMigration to read from game_parameters.json
Replace hardcoded map info with GameParametersUtils.defaultExpandedGameParameters
so the migration stays in sync with the config file. Bump version to 2 to
re-migrate existing saved games with the new map hash.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When Eagle returns an error during command processing (e.g. battalion
over-capacity validation), the warmup tool now logs the server error
message and dumps its full state (game ID, province, hero, token,
command) instead of silently timing out.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add smooth_boundaries.py for natural province boundary smoothing
Post-processing tool for rawGray.gz.bytes that replaces jagged
province boundaries with smooth, natural-looking ones using:
1. Majority-filter smoothing (iterative mode filter on boundary pixels)
2. Fragment cleanup (BFS expansion preserving ocean-separated islands)
3. Domain warping (Gaussian displacement for organic character)
Includes automated verification (province preservation, neighbor
adjacency, connectivity) and before/after visualization output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Apply boundary smoothing to province map
Run smooth_boundaries.py --seed 42 to smooth jagged province boundaries.
Uses pixel-based adjacency instead of TSV for neighbor verification.
Updates centroids.json with new label positions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add targeted boundary smoothing for West Faluria borders
Adds --targeted-smooth mode to smooth_boundaries.py that applies a
bilateral majority filter to specific province border pairs. Smooths
the 40-37 (West Faluria/Yuetia) and 40-11 (West Faluria/Garholtia)
boundaries to reduce bubble protrusions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Redraw West Faluria borders via manual edit with roughened edges
Hand-edited province boundaries for West Faluria (40) expanding into
Usvol (33) and Hofolen (17), then applied localized domain warp to
roughen the new borders for a natural look. Added rawgray_editor.py
for PNG export/import workflow and roughen_borders.py for localized
border roughening.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Old saved games predate the EagleMapInfo proto fields and will break
once the client stops hardcoding map info. This adds a migration
framework that patches saved game files at server startup, plus the
first migration (version 1) to backfill eagleMap on all existing games.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
EagleMapInfo (map name, dimensions, SHA256) was hardcoded in GameStateViewFilter.
Now it flows through the event-sourcing pipeline: JSON config → GameParameters proto
→ ActionResultC → ActionResultApplierImpl → GameState → GameStateViewFilter, matching
the existing gameType pattern. The proto definition moves from views/ to common/ so
both internal and views packages can reference it.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Server now sends map metadata (name, dimensions, SHA256) to clients
via the EagleMapInfo field. Clients will initially ignore this unknown
field until a follow-up client PR adds CDN-based map fetching.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Laufarvia (3) and West Faluria (40) are listed as neighbors in the TSV
but are 113 pixels apart on the actual map -- they haven't been adjacent
since an earlier map revision. Remove the stale adjacency from both
provinces' neighbor/neighborPositions/neighborNames fields.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Weather effect controllers (flood, drought, blizzard) each independently
decompressed the same 6.9 MB rawGray texture. Replace with a shared
reference to ProvinceIDLoader, eliminating 3x duplicate decompression.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Move combat sound effects to Addressables for CDN delivery
All 41 SoundManager AudioClip serialized fields replaced with
Addressables loading via single "sfx-combat" label. Clips are loaded
asynchronously and matched by name to build the ActionType dictionary.
Sound effects gracefully degrade (no sound) if loading hasn't completed.
GauntletThrowAnimator.impactSound and WeatherEffectAnimator audio clips
retain their serialized Inspector references (separate from SoundManager).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Route weather and gauntlet sounds through SoundManager
Move WeatherEffectAnimator's rain/thunder/blizzard audio clips and
GauntletThrowAnimator's impact sound to load via SoundManager instead
of serialized Inspector fields. This routes all game audio through
Addressables for CDN delivery.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Some terrain labels may load successfully but return 0 textures (e.g.
when Addressable bundles haven't been built yet or CDN cache is stale).
Return null instead of crashing on images[random % 0].
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Move tactical combat assets to Addressables for CDN delivery
Fire effect prefab, permanent bridge models (5), and constructed bridge
models (4) are now loaded via Addressables instead of serialized Inspector
fields. TacticalAssetLoader singleton pre-loads in EagleGameController.Start()
so assets are cached before the first battle. Empty arrays as defaults
provide graceful degradation if loading hasn't completed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix HexGrid NRE when cells not yet initialized, re-render on late load
HexGrid.Update() could NRE when cells array was null (grid not yet set
up due to async asset loading). Added null guard matching the pattern
used in all other HexGrid methods.
Also added LoadTacticalAssetsAndRefresh coroutine so bridges and fire
effects re-render if tactical assets finish loading after the initial
grid draw.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix IndexOutOfRange when tactical assets load before model setup
The LoadTacticalAssetsAndRefresh coroutine called SetModifiers() when
Model was non-null but _randomNumberForCellIndex was still empty. Guard
on the list being populated; the normal model-update paths will call
SetModifiers() once setup completes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove SetModifiers() call from asset loading coroutine
The coroutine's SetModifiers() crashes because _randomNumberForCellIndex
is populated in row-order but MapCoordsToGridIndex flips rows, so the
list indices don't match. This was a latent bug exposed by async loading.
SetModifiers() is already called from ModelUpdated() on every model
update, so modifiers will render correctly without the coroutine call.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Wait for Addressables to load before setting up hex grid
SetUpGame() called GetImageForTerrain() immediately, but after moving
hex tiles to Addressables the textures load asynchronously. In custom
battles the shardok container activates and SetUpGame runs on the same
frame, before textures are ready — crashing with NullReferenceException
and leaving no hex grid visible.
Fix: split SetUpGame into a sync preamble + SetUpGameAfterLoad coroutine
that waits for IsLoaded. Also change _randomNumberForCellIndex from List
to int[] indexed via MapCoordsToGridIndex so indices match SetModifiers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Move 118 hex tile PNGs from Assets/Resources/Hex Tiles to
Assets/Hex Tiles and register them as Addressables with 22
per-directory labels (hex-plains, hex-forest, hex-winter-hills,
etc.). This removes them from the app binary so they're hosted on
CDN and only redownloaded when content changes.
ImageForTerrainTracker is now a shared singleton that loads textures
via Addressables.LoadAssetsAsync instead of Resources.LoadAll. The
load kicks off in EagleGameController.Start() (strategic scene) so
textures are cached before the player's first tactical battle.
ShardokGameController and MapEditorController reuse the same instance
with a load guard that prevents double-loading.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replace 33 serialized GameObject fields in ProvinceBeastsController with
Addressables.LoadAssetsAsync label-based loading ("beast-effects").
Prefabs stay in Assets/Eagle/Effects/ but are now delivered via CDN at
https://assets.eagle0.net/addressables/[BuildTarget] instead of being
bundled in the app binary. This removes ~80-100 MB of 3D models,
textures, and animations from the initial app download.
Loading strategy:
- Eager load in Start() (last priority — after music)
- Game state that arrives before loading completes is stashed and
applied when ready
- If loading fails (offline, no cache), beasts simply don't render
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Previously the Tutorial button was gated to nolen/tarn but the Tutorial
Lite (skip-battle) button was editor-only. Now both buttons appear for
testers on any platform (iOS, macOS, etc.), and Tutorial Lite also
remains visible in the editor.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The nightly iOS TestFlight build already builds and uploads iOS
addressables as part of its pipeline. The standalone workflow added
unnecessary complexity and forced platform switches on the unity-mac
runner. Manual TestFlight dispatches cover any need for immediate
addressable updates.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Two fixes for iOS deep link handling:
1. CI: Remove UIApplicationSceneManifest from the generated Info.plist
during archive. Unity 6 adds this key, which opts the app into the
scene-based lifecycle. iOS then routes deep links through
UISceneDelegate instead of UIApplicationDelegate, but Unity doesn't
implement that path, so deep link URLs are silently dropped.
2. OAuthManager: Don't call Application.Quit() on iOS — it's documented
as having no effect. Instead, let the app go to background when the
browser opens and handle the deep link callback when iOS brings it
back via OnDeepLinkActivated.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
BuildMacPlayer and BuildWindowsPlayer called BuildAddressables() without
first switching the active build target. On self-hosted runners where
Unity state persists between runs, this caused Addressables to be built
for whatever platform ran last (e.g. iOS), resulting in incompatible
bundles at runtime.
Extract SwitchBuildTarget helper from BuildiOSAddressables and call it
in all three build methods before building Addressables.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When a stored account's token expires and refresh fails, automatically
initiate a fresh OAuth login with the same provider instead of just
showing a brief error. On startup, show "Session expired" message
instead of silently clearing the status text.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Set roundIdJoined=1 for heroes with per-hero loyalty overrides, pushing
their earliest possible departure back by one month. This gives the
player more time before The Tumbler, Aldric the Overlooked, and similar
low-loyalty heroes leave.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
SubscribeToGame only caught NewGameException, so any other exception
(e.g. file-not-found during map loading) propagated uncaught through
gRPC as a generic "UNKNOWN" error with no logging. Now matches the
PostCommand/PostPlacementCommands pattern: catches ShardokClient,
ShardokInternal, and std::exception with stderr logging and proper
gRPC status codes.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Previously, FromPath silently returned garbage data when a file didn't
exist (tellg() returns -1, leading to std::bad_alloc from size_t(-1)).
Now throws std::runtime_error with the file path for clear diagnostics.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Have Ranil and Elena explain Improve/Alms instead of Marek
Replace Marek's combined Improve/Give Alms tutorial step with two
separate steps: John Ranil explains province improvement (highlighting
the Improve button) and Elena Fyar explains giving alms (highlighting
the Alms button). Each step notes that any hero can use the command
but the specialist gets a bonus.
Register each command button as a dynamic tutorial target in
CommandButtonPanelController so individual buttons can be highlighted.
Target IDs are derived from the button tooltip (e.g. "ImproveProvinceButton",
"GiveAlmsButton").
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix speaker portraits, highlight visibility, and notification timing
- Resolve speaker headshots from the Eagle model (strategic map) when
the Shardok model is unavailable, fixing blank portraits for Ranil
and Elena in post-battle dialogue
- Add override sorting Canvas (sortingOrder 999) to dialogue highlights
so they render above sibling UI elements like adjacent command buttons.
Increase default thickness from 3 to 5 and minimum strobe alpha from
0.2 to 0.5 for better visibility
- Delay tutorial_rebuild_support trigger until all notifications are
dismissed, so the Marek/Ranil/Elena dialogue sequence starts cleanly
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use public GameModel accessor instead of private Model
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix NRE: Canvas already adds RectTransform, use GetComponent
Adding a Canvas component automatically creates a RectTransform, so
AddComponent<RectTransform>() returned null. Use GetComponent instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Re-check deferred tutorials when notifications are dismissed
CheckTutorialRebuildSupport only ran during OnModelUpdated, but after
dismissing notifications no model update occurs. Add OnAllDismissed
callback to NotificationPanel, and have TutorialTriggerRegistry
subscribe to it so deferred tutorials fire once notifications clear.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Filter nightly iOS TestFlight by relevant paths
Instead of rebuilding whenever any file changes, compare changed files
against paths that actually affect the iOS build (C#, protobuf, resources,
C++ plugins, scripts, CI config). Scala, Go, and test-only changes no
longer trigger unnecessary nightly builds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove src/main/cpp/ from iOS-relevant paths
Shardok C++ code is server-side only. Native Unity plugins come from an
external Bazel dep (@net_eagle0_unity_godice), not the in-repo C++ code.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The Eagle faction is created mid-game via EagleAppearsAction but
withHandledEngineAndResults only removed AI clients for destroyed
factions — it never added AI clients for newly created ones. The
Eagle faction appeared on the map but never took any turns.
After filtering out removed AI clients, detect new factions from
action results and register AI clients for any that aren't already
human-controlled or AI-controlled.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Adds a check-changes job that compares HEAD against the last successful
scheduled run. If the SHA matches, the build is skipped. Manual
workflow_dispatch triggers always build regardless.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Flee and retreat commands have no target in their protobuf (only actor),
so the Unity client treated them as untargeted and called
PerformUntargetedCommand without triggering any animation or sound.
Add PlayUntargetedCommandAnimation which looks up the animation type
for the command, finds the nearest enemy to use as the flee direction,
and calls PlayAnimationAndSound with the two-stage flow so both the
attempt and result animations play correctly.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The game type check compared GameType (non-optional) against
Some(GameType.Tutorial), which always evaluates to true in Scala,
causing checkForEvents to always return empty.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Runs at 9 PM Pacific (5 AM UTC) every night, alongside the existing
manual workflow_dispatch trigger.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Adds a JSON-configurable faction bias that gets applied when a tutorial
hero becomes unaffiliated (imprisoned/exiled). Currently used to give
Luke the Prank-tricker a +30 bias toward Sadar's faction, making him
more willing to join after capture.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Include province name in ready-to-join hero tutorial dialogue
Marek now says which province the recruitable hero is in, e.g.
"there's a hero in Onmaa who's ready to join our cause."
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Advise taking most gold and food when expanding
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Rework tutorial pacing: delay expansion until taxes, add town tutorials
- Change "support reached 40" dialogue to congratulate and encourage
continued development until January instead of immediately pushing expansion
- Move expansion instructions to the "taxes collected" dialogue
- Add tutorial_ready_to_join: fires when a free hero with WouldJoin status
appears in the player's province, instructs them to Travel and Recruit
- Add tutorial_in_town: fires when player enters town (ReturnCommand
available), explains all town activities and that Return advances the month
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Refine tutorial expansion advice and remove redundant Give Alms
Remove Give Alms from the support tutorial instructions (already covered
earlier). Update the expansion panel to advise taking most heroes
(especially those with professions) and leaving only one or two behind.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add second hero departure tutorial when King loses another follower
Fires tutorial_hero_departed_again on the second month a King's hero
departs (must be a different month than the first departure). Marek
reacts with alarm that the King is losing authority across the realm.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Adjust second hero departure dialogue to hint at something strange
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Rewrite second departure dialogue in Marek's voice
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Raise loyalty from 30 to 35 for distant provinces (15% departure
chance) and from 25 to 30 for Nikemi/Pieksa (20% departure chance).
Heroes were departing too early even with the roundIdJoined fix.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
LoadedHeroConversion.toHeroC was not setting roundIdJoined, leaving it
as None. This meant hero.roundIdJoined.exists() returned false in
PerformHeroDeparturesAction, so the MinimumRoundsBeforeLoyaltyDegrades
check was silently skipped for all tutorial heroes. Move the
roundIdJoined = Some(0) assignment into toHeroC and remove the now-
redundant set in NewGameCreation.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Polytope Studio characters (rebels, peasants, knights, etc.) use a
complex zone-coloring shader that can't be replaced by ClipRectMeshUnlit.
ShaderUtils.ReplaceWithClipRect now skips materials without _MainTex and
returns false. When any material is skipped, the effect attaches a new
RendererViewportClipper component that clips by toggling renderer.enabled
based on the global _ViewportClipRect/_PopupClipRect each frame.
Also force vertex color alpha to 1 in ClipRectMeshUnlit to prevent 3D
mesh models with zero vertex alpha from becoming invisible.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add a roundIdJoined check to PerformHeroDeparturesAction so heroes
that joined within MinimumRoundsBeforeLoyaltyDegrades rounds cannot
depart. This mirrors the existing loyalty degradation gating in
NewYearAction.
Also raise tutorial hero loyalty from 25 to 30 for outer King's
provinces (20% departure chance), keeping 25 for Nikemi and Pieksa
which are adjacent to Onmaa (25% departure chance).
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Adds Inspector wiring for the skip_tutorial_battle button in lobby prefabs
and includes PopupClipper.cs.meta for the popup effect clipping component.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The .bazelrc flags prefer_prebuilt_protoc and
incompatible_enable_proto_toolchain_resolution were set but the
prebuilt binary repos were never made available. Add the required
use_extension/use_repo calls so Bazel can actually find and use the
prebuilt protoc instead of compiling it from source.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Adds editor-only "Skip Battle Tutorial" button that creates a tutorial
game with skip_tutorial_battle=true. Fixes CheckTutorialBattleEnded to
detect server-side auto-resolved battles (no RunningShardokGameModels
were ever observed) and fire tutorial_battle_ended normally.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When skip_tutorial_battle is set in CreateGameRequest, the server
creates the tutorial game normally then immediately resolves the
opening battle with a synthetic BattleResolution — defender wins,
Tarn flees, other attackers captured, defender battalions at ~70%.
The existing ResolveBattleAction pipeline runs so all post-battle
logic executes authentically without Shardok involvement.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Render hero backstory popup above map effects
The hero popup panel was rendering behind 3D effects (dragons, animals,
monsters) because effects use sortingOrder=103 / renderQueue=4000 while
the popup was on the root Canvas at sortingOrder=0. Add an override
sorting Canvas (sortingOrder=200) to the popup panel at startup so it
draws above all map effects.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Lower effect renderQueue so popup Canvas override can draw above them
The effects used renderQueue=4000 (Overlay), which draws after all
standard UI regardless of Canvas sortingOrder. Changed the
ClipRectMeshUnlit shader queue from Overlay to Transparent+100 and
removed explicit renderQueue=4000 from effect classes. The shader's
ZTest Always + ZWrite Off + sortingOrder=103 is sufficient to render
above the map without needing the Overlay queue.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use shader-based popup masking instead of Canvas override sorting
Canvas override sorting doesn't affect MeshRenderer draw order in
Screen Space - Overlay mode. Instead, use the same clip rect pattern
as viewport clipping: PopupClipper broadcasts the popup's screen-space
bounds as _PopupClipRect, and all effect shaders discard fragments
inside it. The popup rect is cleared on disable so effects render
normally when the popup is hidden.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add popup clip rect to weather shaders (drought/flood overlay)
The ProvinceWeatherShader and ProvinceWeatherMapShader render weather
overlays as UI elements using custom shaders, so they also need the
_PopupClipRect discard check to avoid rendering over the hero popup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
ViewportClipper broadcasts the viewport's screen-space bounds as a global
shader property (_ViewportClipRect). Each effect shader discards fragments
outside those bounds, preventing particles, 3D models, and sprites from
overflowing the map viewport during zoom/pan.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When a quest grants a free hero, both the quest notification and
PleaseRecruitMe command arrive in the same batch. Both panels call
SetAsLastSibling() during setup, but PleaseRecruitMe was set up last,
hiding the notification behind it. After the blocking command switch,
re-raise the notification panel if it has pending info so the player
sees the quest completion first.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Self-hosted runners persist .git/lfs/objects/ between runs, and LFS
objects are now served from a local Gitea mirror on the LAN. The
actions/cache step was downloading ~1.8 GB from GitHub on every run,
adding ~35s and consuming transfer quota unnecessarily.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add Flee as two-stage animation with captured-while-fleeing variant
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Move TWO_STAGE_ANIMATIONS.md to docs/
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Move TWO_STAGE_ANIMATIONS.md to repo root docs/
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Wire up flee attempt and run away sound effects in Unity scene
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add Flee Captured test button to AnimationTestController
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Split Flee into three distinct animations: attempt, success, captured
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix test buttons to play full two-stage sequence for result animations
Result variant test buttons (Fear Success/Resisted, Flee Success/Captured,
Duel/Declined) now play the complete attempt→result sequence instead of
only the attempt sound.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Make flee attempt animation more visible and cap two-stage delay at 0.5s
Flee attempt now uses full dust burst and multiple blur trail afterimages
instead of minimal dust and single blur. Two-stage delay between attempt
and result capped at 0.5s in both PlayTwoStageSequence and
PlayTwoStageSoundSequence (affects other players' actions and test buttons).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Tune flee animation parameters in Unity scene
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When a battle ends mid-animation, coroutines stop but GameObjects parented
to the hex grid survive and leak. This extracts the Track/OnDestroy pattern
(originally fixed in FearAnimator for the skull persistence bug) into a
shared base class and applies it to all 22 Shardok animators.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The King was consistently getting overwhelmed in the early game. Add a
Royal Lancers heavy cavalry unit (800 troops) to province 37 to give
him a stronger opening position. Hero count already matches at 7.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
HeroGenerator.getHero returned NoNameRequest for pregenerated heroes,
so no name text request was created when they appeared mid-game. The
name existed in the in-memory pregenerated store but was never written
to SQLite, and getCompleteTextsAccessibleTo only queries SQLite. The
client received the hero's nameTextId but never the actual text,
showing "Hero" as a fallback.
Now getHero assigns an hn_ nameTextId and returns FixedName, so the
name goes through the normal FixedHeroName pipeline: added to SQLite
by foldInLlmRequests, immediately resolved by UnrequestedTextHandler,
and delivered to the client like any other hero name.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
FearAnimator creates GameObjects (skull, tendrils, shield, etc.) as
children of the hex grid and only cleans them up at the end of each
coroutine. If the battle ends mid-animation, the coroutine is
interrupted and cleanup never runs, leaving orphaned sprites visible
in subsequent battles.
Track all created GameObjects and destroy them in OnDestroy().
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When rewinding a game and replaying to the same battle, the Shardok
server returned cached results instead of fighting fresh. The C++ server
kept completed game controllers in runningControllers indefinitely, so
ControllerForGame returned the old controller and SubscribeToGame
immediately sent back game_over_response with stale results.
Fix: detect rewind by checking for an existing game + NewGameRequest +
knownResultCount == 0, then replace the old controller with a fresh one.
Also cancel pending Eagle-side battle subscriptions on rewind.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Quest creation only checked if neighbors were owned by a different faction,
ignoring alliances. Fulfillment logic counted allied neighbors as secured.
This mismatch caused quests to be offered and immediately fulfilled. Now
creation also checks for alliances, matching the fulfillment logic.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Take the lower of the Inspector-configured count and the actual beast
count from BeastsEvent.Count. Effect classes now initialize in Start()
instead of Awake() so SpawnBeastsEffect can cap the count field after
Instantiate but before creatures are spawned.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
RoundPhaseConverter maps Unrecognized(13|14|15) to UncontestedConquest
instead of throwing, allowing saved games from removed FFA phases to
load. CommandTypeConverter maps Unrecognized(12) to None.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Remove Free-For-All battles, replace with sequential AttackDecision
When multiple hostile factions march on the same province, instead of a
separate FFA decision/battle phase, the AttackDecision phase now handles
them sequentially: armies sorted by troop count (largest first), only
the largest undecided army gets the command. After someone advances,
allied armies can join while hostile armies are auto-bounced (occupied)
or can contest (unoccupied).
Phase flow: HostileArmySetup → UncontestedConquest → AttackDecision →
DefenseDecision → Battle (removed FreeForAllDecision,
FreeForAllBattleRequest, FreeForAllBattleResolution phases).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove FFA protos, simplify unoccupied province attack logic
- Remove FFA proto fields from available_command, selected_command,
command_type, round_phase, and army protos (mark as reserved)
- Remove corresponding converter match arms (ArmyConverter,
SelectedCommandConverter, CommandTypeConverter, RoundPhaseConverter)
- Simplify isMyTurnToDecide: hostile armies are always blocked when
another army has advanced (no more unoccupied province special case)
- Replace preBattleConquestResults with uncontestedConquestResults:
handles single/allied/hostile armies on unoccupied provinces with
conquest + bounce of hostile AwaitingDecision armies
- Add 6 tests covering tiebreak, hostile blocking, allied joining,
auto-bounce, and uncontested conquest scenarios
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove FFA references from Unity C# client
Delete FreeForAllDecisionCommandSelector and its .meta file,
remove FFA tutorial entry from TutorialContentDefinitions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix alliance check and simplify uncontested conquest logic
Fix exists→forall bug in AvailableAttackDecisionCommandFactory: armies
must be allied with ALL advancing armies, not just any one. Move all
unoccupied province conquest into UncontestedConquest phase (largest army
always wins) and remove redundant logic from EndAttackDecisionPhaseAction.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Improve army tiebreaking: troop count, then power, then pseudo-random
When choosing the "largest army" (for attack decision ordering and
uncontested conquest), tiebreak by total unit power after troop count,
then by factionId XOR randomSeed for a pseudo-random final tiebreak
that varies across games instead of always favoring lower factionIds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Materialize LINQ chain to avoid multiple enumeration in
HandleOverlays, simplify redundant return in HandleLongHover,
and replace always-matching UnitId type pattern with var.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When a non-defender attacker lost a multi-faction battle, their surviving
units were silently dropped. ProvinceConqueredAction only processed the
winner's and defender's units, so a third-party loser's Normal units were
not returned (unitReturned=false for Loss), not captured (not a defender),
and not outlawed — they just vanished. Fixed by changing the filter from
defenders-only to all non-winning players.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The AllyVictory check in PopulateGameOverResponse compared the current
player's own ID (shardokPid) against their own allies list — which is
always false since a player is never listed as their own ally. Fixed by
using the lambda's `pid` parameter (the winning player's ID, previously
unused) instead of `shardokPid`.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
GamesManager.leaderCache looked up faction heads with
gameState.heroes(faction.factionHeadId), which throws
NoSuchElementException if the hero died in battle and was moved to
killedHeroes. Use getOrElse fallback to killedHeroes, matching the
pattern used elsewhere (HeroInfoUtilities, ChronicleEventTextGenerator).
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When an army retreats from battle, its return destination is set at march
time. If that province is conquered while the army is fighting, the
retreating army arrives at a now-hostile province, causing a "Mismatched
factionIds" validation error. Fix by detecting these stranded armies in
PerformProvinceMoveResolutionAction and shattering them (heroes become
outlaws) instead of letting them fall through to hostile army processing.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When reconnecting due to idle timeout, deadline exceeded, or normal
stream end, keep the displayed connection status unchanged instead of
briefly showing "Connecting..." or "Retry in Xs".
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The two animalPrefabs entries had their fileIDs swapped between the
scorpion_prefab and yellow_fattail_scorpion_prefab GUIDs, causing
MissingReferenceException at runtime.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Matches the existing Trade command selector behavior where food/gold
amounts turn red when they exceed the province's storage cap.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Two-stage actions from the opponent were playing the attempt sound
sequenced correctly but only one animation immediately. The attempt and
result animations were not sequenced.
Fear and Duel exposed this most visibly since they have distinct result
animations (FearSuccess/FearResisted, Duel/DuelDeclined), but the
pattern was wrong for all two-stage actions.
Replace the separate PlayTwoStageSoundSequence + PlayAnimation calls with
a unified PlayTwoStageSequence coroutine that sequences both animations
and sounds: attempt animation+sound → wait → result animation+sound.
Add AttemptAnimationType() to map result animation types back to their
attempt type (FearSuccess/FearResisted → Fear, Duel/DuelDeclined →
DuelChallenge). For actions where attempt and result use the same
animation, the result animation phase is skipped.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Decouple non-Mac builds from Xcode via BAZEL_NO_APPLE_CPP_TOOLCHAIN
Set BAZEL_NO_APPLE_CPP_TOOLCHAIN=1 in .bazelrc so apple_support skips
Xcode detection (xcode-locator) entirely for Scala/C++/Go builds.
Only mactools builds (Mac/Sparkle) re-enable the Apple CC toolchain.
This means Xcode version changes no longer affect Eagle, Shardok, test,
auth, docker, or Unity Windows builds — no expunge, no cache invalidation,
no rebuild. The sync script and its expunge/cache-key logic are now scoped
to mactools only and removed from all non-Mac CI workflows.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Revert BAZEL_NO_APPLE_CPP_TOOLCHAIN; keep Apple CC with stable environ
BAZEL_NO_APPLE_CPP_TOOLCHAIN=1 broke builds because apple_support
registers platforms (e.g. macos_x86_64) that require Apple CC toolchains
for resolution — even in non-Mac builds.
Instead, keep Apple CC enabled but ensure its repo rule never
re-evaluates on non-Mac builds by keeping DEVELOPER_DIR pinned in
common:macos. The sync script only runs for mactools builds and
overrides DEVELOPER_DIR there.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 16:08:28 -08:00
2519 changed files with 474511 additions and 307720 deletions
# This switches Unity to iOS target and builds addressables for CDN upload
set -euxo pipefail
# Read Unity version from project file
UNITY_VERSION=$(grep "m_EditorVersion:" src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt | head -1 | sed 's/m_EditorVersion: //')
# Use runner-specific build directory if EAGLE0_BUILD_DIR is set, otherwise default
| hippogryph | DragonEffect | Heroic Fantasy Creatures Full Pack Vol 2 | Hippogriff_PBR (w/ HippogryphMapAnims controller) |
### Human-type beasts
@@ -143,16 +159,84 @@ Ordered by spawn likelihood (most common first):
| Beast | Likelihood | Notes |
|---|---|---|
| mammoth | 0.10 | Fantasy/prehistoric |
| tiger | 0.10 | Big cat |
| elephant | 0.04 | Large animal |
| hippogryph | 0.04 | Fantasy creature |
| hippopotamus | 0.04 | Large animal |
| lion | 0.04 | Big cat |
| chimpanzee | 0.04 | Primate |
| velociraptor | 0.02 | Dinosaur |
| unknown | 0.00 | Intentional fallback |
## Using low-poly animal packs (Africa Animals, Dino Pack)
Assets from these packs (by the same author) require extra setup because they ship with **Legacy** animation import and include physics components that conflict with AnimalEffect. The workflow below applies to all of them:
- Africa Animals Pack Low Poly V1 (Lion, Lioness, Black Panther, Crocodile, Elephant, Giraffe, Rhinoceros, Tiger, Zebra)
1.**Switch FBX import to Generic animation type.**
In the `.fbx.meta` file (location varies by pack):
- Change `animationType: 1` to `animationType: 2`
- Change `avatarSetup: 0` to `avatarSetup: 1` (newer format) — older metas without `avatarSetup` will auto-create an avatar at runtime
- Set `loopTime: 1` on looping clips (idle, walk, eat, run) but NOT one-shot clips (attack, death, hurt)
Unity will re-import the FBX with Mecanim-compatible clips when it next opens.
2.**Create an AnimatorController** in `Assets/Eagle/Effects/` named `<Animal>MapAnims.controller`.
Use the same 4-state pattern (idle, walk, eat, attack) as ElephantMapAnims. Reference the animation clips by their fileIDs from the FBX meta's `internalIDToNameTable` (type 74 entries) or `fileIDToRecycleName` (older format, type 7400000+). These IDs are stable across import type changes. Note: if the FBX names its idle clip `idle1`, create the controller state as `idle` but reference the `idle1` clip by fileID.
3.**Create an AnimalEffect prefab** in `Assets/Eagle/Effects/` named `<Animal>Effect.prefab`.
Reference the pack's prefab as the animal prefab, and wire up the new AnimatorController. Multiple color/sex variants from the same or similar FBX can share one controller (e.g., Lion and Lioness). AnimalEffect will automatically strip the pack's legacy Animation, Rigidbody, and Collider components at spawn time, and add an Animator if the prefab doesn't already have one.
4.**Register in ProvinceBeastsController** with a case in `GetEffectPrefab()` and a test ContextMenu method.
5.**Mark the effect prefab as Addressable** with the `beast-effects` label in the Unity editor (Window > Asset Management > Addressables > Groups).
### Notes
- The packs include both SingleTexture and TextureAtlas variants; use SingleTexture for best visual quality.
- Hippos have water-specific animations (idlewater, deathwater) that could be used for future water-province effects.
- The packs' prefabs include Rigidbody/BoxCollider components intended for gameplay use. AnimalEffect strips these automatically since it controls position directly.
### Available animals not yet set up as beasts
These animals exist in the imported packs and could be set up as in-game beasts using the workflow above:
**Africa Animals Pack V1** (already imported):
| Animal | Good fit for | Notes |
|---|---|---|
| Giraffe (x2 variants) | giraffe beast | Tall model, may need larger scale |
| Zebra | zebra beast | Herd animal; good with higher animalCount |
(Black Panther, Rhinoceros, Crocodile, Elephant, and Tiger already have effects.)
**Africa Animals Pack V2** (already imported):
| Animal | Good fit for | Notes |
|---|---|---|
| Antelope | antelope/gazelle beast | Fast-moving; good with higher moveSpeed |
(Gorilla, Hyena, Leopard, Warthog, and Hippopotamus already have effects.)
**Dino Pack Low Poly V1** (already imported):
| Animal | Good fit for | Notes |
|---|---|---|
| Brontosaurus | brontosaurus/sauropod beast | Very large; needs big scale, low count |
| Pteranodon | pteranodon/flying beast | Has fly animation; could use DragonEffect-style flight |
| Stegosaurus | stegosaurus beast | Large herbivore |
| T-Rex | tyrannosaurus beast | Iconic predator; good as a solo beast |
| Triceratops | triceratops beast | Large herbivore; good for tough beast |
(Velociraptor already has an effect.)
**Australia Animals Pack V1** (already imported):
| Animal | Good fit for | Notes |
|---|---|---|
| Koala | koala beast | Peaceful; good for low-threat province events |
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.