* Add documentation for renaming a province
Documents the files that need to be modified when renaming a province
and explains how province names flow from the server to the client.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Rename Fluria to East Faluria and Faluria to West Faluria
Updated all province name references:
- province_map.tsv: Both province rows and all neighbor references
- MapDescription.scala: LLM prompt geography description
- centroids.json: Client map rendering metadata
- RuntimeValidatorTest.scala: Test strings
- Renamed hex map files: Fluria.e0mj -> East_Faluria.e0mj,
Faluria.e0mj -> West_Faluria.e0mj
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Override (faction hover/notification): always black — visible against
any faction color
- Targeted (right-click): deep purple-red — distinct from red faction
- Selected (left-click): luminance-adaptive — dark factions get white
highlight, bright factions get black
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When hovering a faction in the Factions table or viewing a faction
notification, borders between provinces of the same faction are now
suppressed so the faction's territory appears as one contiguous
highlighted region.
Adds a cross-province ID map (computed once at startup alongside the
distance map) that stores which province is on the other side of each
pixel's nearest border, plus a 256x1 highlight group lookup texture
that the shader checks to skip highlight borders between same-group
provinces.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The tile PNGs in static/tiles/ are LFS-tracked but the Docker build
workflow checks out with lfs: false, embedding LFS pointer files
instead of actual images. Add a targeted git lfs pull for just the
admin server tile images (~736KB) before the Bazel build.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The selectCaller method was load-balancing across all available LLM
providers by selecting whichever caller had the fewest in-flight
requests. This caused requests to be distributed roughly evenly across
Gemini, Claude, and OpenAI instead of preferring the primary provider
(Gemini) and only falling back on capacity limits.
Now selectCaller first tries to find a caller from the primary provider
with available capacity, and only falls back to other providers when the
primary has no capacity.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Make province borders resolution-independent using fwidth()
Use screen-space UV derivatives to compute texel-to-pixel ratio in the
shader, so border widths specified in screen pixels remain visually
consistent regardless of render resolution.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Reduce default border width from 0.7 to 0.5 screen pixels
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The go:embed directive used `static/*` which only matches direct
children. Change to `all:static` so subdirectories like `static/tiles/`
are included in the binary.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Copy representative PNG tile images from the Unity assets into the admin
server static directory and render them clipped to hex shapes on the
canvas. Falls back to flat CSS colors if an image fails to load.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Update province border width from 0.35 to 0.7 and adjust UI element
positioning in the Gameplay scene.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Reassign 1,242 pixels from a small disconnected blob of Parnia (pid 41)
in the southwest to Ingia (pid 32), which surrounds it.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Hand-tune province label positions for border-adjacent provinces
Manual adjustments on top of the algorithmic positioning for provinces
where labels were too close to borders:
- Berkorszag: +30px right
- Kezonoria: +20px right, +5px up
- Grytrand: +15px right
- Oscasland: +20px up
- Oryslia: 10px left
- Pozia: 5px up
- Chia: 5px right
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Further tweak Kezonoria and Pozia label positions
Kezonoria: +25px up total, Pozia: +15px up total (increasing y = up in
Unity's coordinate system).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replace Gaussian-weighted blend with simpler, more predictable approach:
find all pixels with >= 70% of max possible edge clearance, then pick the
one closest to the geometric centroid. This keeps labels well inside province
boundaries while staying visually centered. Most labels barely move from
their current positions; labels near narrow borders (Kezonoria, Grytrand)
get nudged further inside.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add .e0mj map files as a layer in the admin server Docker image
and pass --maps-dir=/app/maps to enable the web-based map editor
added in #6186.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When a game is rewound past a battle, truncateTo() cleared in-memory
shardokHistory but left .e0s files on disk. On restart, these orphaned
files got lazy-loaded when the game state still referenced the battle,
causing Eagle to re-send stale battle data to Shardok.
This caused a production crash loop where Eagle kept sending an old
Motcia battle (with pre-fix map data baked into the .e0s file) on
every restart, even after the map fix was deployed.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replace geometric centroid (mean of pixels) with a distance-transform-based
approach that finds the point inside each province maximally distant from
edges, weighted by proximity to the visual center. This fixes labels that
bled into neighboring provinces or extended over water (Berkorszag, Chia,
Pozia).
Also adds --update-centroids mode to generate_map.py for re-running label
positioning without losing hand-tuned styling fields.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Uses Docker Engine API over mounted Unix socket to restart the active
Eagle container directly from the admin UI. Includes confirmation dialog
and graceful degradation when Docker socket is unavailable.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replaces the Unity-based Shardok map editor with a browser-based editor
in the admin console. Supports terrain painting, modifiers (castle/bridge),
starting positions, weather config, undo/redo, and file download.
Enabled via --maps-dir flag pointing to the .e0mj directory.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Useful when Eagle needs to reload game state from persistence
(e.g., after a rewind) without a full blue-green deployment.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Validates that no map has attacker starting positions that overlap
with defender starting positions. This overlap causes the AI's
GameStateGuesser to misplace guessed defender units, leading to
command count mismatches during SET_UP.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Motcia's attacker starting position index 0 had 7 of 10 positions
overlapping with defender starting positions. This caused the AI's
GameStateGuesser to place a guessed defender unit on an attacker
starting position during SET_UP, blocking it and creating a command
count mismatch (27 vs 30) that crashed the server in a loop.
Moved attacker index 0 positions east into the plains (columns 3-5)
away from the defender's castle compound at (1,0)/(2,0)/(2,1).
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The guessed state produces 27 PLACE_UNIT_COMMANDs vs 30 real during SET_UP.
This adds target coordinates to the command dump, identifies the specific
missing positions per unit, checks what the guessed state thinks is occupying
them, and dumps all guessed state units for full visibility.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The assert at ShardokAIClient.cpp:174 was crashing the server with
no diagnostic info when guessed and real command counts diverged.
Now logs both counts, player, round, and a side-by-side dump of
each command (type + actor unit) before throwing an exception that
the server can catch and recover from.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The HeroGenerator was being passed to EngineImpl before heroes were
consumed from it during game creation. The randomHeroes() method
consumed heroes from the generator pool but then discarded the updated
generator with .map(_._1). This meant the engine started with a
generator that still contained heroes that had already been spawned.
When new heroes spawned during gameplay, they would duplicate the
heroes already in the game.
Fix: Thread the updated HeroGenerator through the game creation flow
and pass the final generator (after all initial heroes are consumed)
to createEngine().
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When a user enters a game via StreamGameRequest, they were remaining in
the lobbyUsers map, causing unnecessary lobby updates to be broadcast to
them and triggering repeated disk reads of games.e0es. Now users are
removed from the lobby when they start streaming a game. They are re-added
when they send EnterLobbyRequest upon returning to the lobby screen.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Change tutorial reinforcements to appear after round 5
Adjusts timing for better tutorial pacing.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix tutorial reinforcement hero ID verification
When tutorial reinforcements cause a battle to end, the verification in
ResolveBattleAction was failing because it expected only the originally
sent hero IDs but received the reinforcement hero IDs (100, 101, 102) as
well. This adds reinforcementHeroIds to ShardokBattle and includes them
in the expected heroes set during verification.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Apply a subtle cool blue-gray tint to province colors when a blizzard
event is active, so white snowflakes are visible on light provinces.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Increase snowEmitRate from 15 to 45 (3x) in both the prefab and code
default, and raise maxParticles from 300 to 900 to accommodate the
higher emission rate.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When clicking a reserve unit during combat, _selectedGridIndex was
cleared unconditionally, losing track of which hex unit is the actor
for the Reinforce command. Now only clear the selection during setup
phase. Also add null guards in RedrawCommandOverlays to prevent NRE
when SelectedCoords or the unit at those coords is null.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Smooth province borders and reduce default border width
Switch distance-to-border computation from Chebyshev (L∞) to Chamfer
(Euclidean approximation) using √2 diagonal weights, producing smooth
circular contours instead of blocky square stepping. Enable bilinear
texture filtering for additional sub-texel smoothing. Reduce default
border width from 2.0 to 1.5 texels for a cleaner look.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Reduce default border width to 0.75 texels
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Make default borders much thinner (account for double-sided drawing)
Both provinces on each side of a boundary draw their own border, so the
visual width is ~2x the configured value. Reduce width to 0.35 texels
(~0.7 total) and tighten the AA ramp from 0.75 to 0.5 for a true
hairline border.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix border width: update serialized scene value to 0.35
The scene's serialized defaultBorderWidth (2.0) was overriding the C#
default at runtime via Awake(). Update the Gameplay.unity scene to match.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Warp province boundaries for organic look
Apply domain warping to rawGray.gz.bytes using Gaussian-filtered noise
displacement fields. Only land pixels near province boundaries are
affected; ocean and coastline pixels are preserved. Adds a reusable
warp_boundaries.py script with configurable amplitude, scale, and seed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Rubber-band spawned heroes (for struggling factions) now appear as
Residents instead of Travelers. This makes them more likely to stay
in the province and be recruited by the faction they were spawned to
help. Added optional unaffiliatedHeroType parameter to
UnaffiliatedHeroAppearedAction to support this.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Instantiate was called with worldPositionStays=true, causing items to
retain their prefab world position instead of being laid out by the
parent layout group. Changed to false to match running/waiting games.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Sadar Rakon's battalions increased by 30% size
- Training increased from 60 to 80
- Makes the tutorial battle more challenging
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Instead of hardcoding specific (row, column) coordinates in the proto
from Eagle, pass an attacker_starting_position_index that Shardok
resolves against the hex map. Uses index 6 for the tutorial battle
on Onmaa.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add PENDING_REINFORCEMENT units to game state during tutorial setup
Tutorial reinforcement units (defined in TutorialBattleConfig events)
were never added to the initial game state, so ExecuteReinforcementsAction
could not find any units to activate. Now SetUpController() extracts
CommonUnit protos from reinforcement events, converts them via ConvertUnit(),
and adds them with PENDING_REINFORCEMENT status so they exist in the game
state when the trigger fires.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Exclude PENDING_REINFORCEMENT units from PlacedUnitsForPlayer
PlacedUnitsForPlayer returned all non-RESERVE_UNIT units, which
incorrectly included PENDING_REINFORCEMENT units. This caused them
to appear during the initial unit placement phase. Now they are
excluded alongside RESERVE_UNIT so they remain invisible until
activated by the tutorial trigger.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Preserve PENDING_REINFORCEMENT status in SetupInitialGameState
SetupInitialGameState was overwriting every unit's status to
RESERVE_UNIT, which erased the PENDING_REINFORCEMENT status set
during tutorial game setup. Now units that are already marked
PENDING_REINFORCEMENT retain their status.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The games.e0es file stores factionLeaderCache with resolved names,
but gamesForWithoutBlocking() was ignoring this cache and always
showing "[Loading...]" for unloaded games. Now uses the cached
leader info when available, only falling back to placeholder when
the cache is missing or has no resolved name.
This eliminates the "Loading..." delay in the lobby after deployment.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Stage proto outputs in a temp directory and rsync with --checksum
to preserve timestamps on unchanged files. Previously, rm -rf on
the output directory forced Unity to reimport all protos (~7 min
of script recompilation) even when nothing changed.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Replace province fill-color strobing with border-width strobing
Province highlights (selected, targeted, commands available) now strobe
border thickness instead of fill color, making them visible on small
provinces and dark faction colors. A runtime distance-to-border texture
enables variable-width borders with one extra texture sample per fragment.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Restore fill-color strobing, use black border for selection, remove map_borders.png
- Restore fill-color strobing alongside border-width strobing
- Change selected province border color from white to black for contrast
- Remove map_borders.png and make ClickDetector overlay transparent
(borders now rendered by shader)
- Rename MapBWImage to ClickDetector to reflect its purpose
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replace the prod/QA environment dropdowns (connection panel and lobby) with
simple "Connect to QA" buttons that connect directly to localhost:40032 over
plain HTTP with no auth. Buttons are editor-only and hidden in builds.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add charge icon sprite asset and use inline in dialogue
Create TMP sprite asset for the charge horse icon and add it to
the fallback chain. Update charge tutorial instruction text to
show the icon inline instead of describing it as "horse icon".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add profession icon sprite assets for dialogue use
Create TMP sprite assets for Mage, Necromancer, Engineer, Paladin,
Ranger, and Champion profession icons. Added to the fallback chain
on the primary dragoons sprite asset. Available as
<sprite name="Mage"> etc. in rich text.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When --skip-auth is passed to Eagle server, all requests are
auto-authenticated as 'local-dev' user with admin privileges.
This bypasses JWT validation entirely for local testing.
WARNING is printed to stdout when this flag is active.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Vigor (99) was higher than constitution (91), causing a validation
error during battle resolution. Set vigor to match constitution.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Scale battalion type sprites from 1.0 to 1.25 for better readability
in dialogue instruction text.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add inline battalion type icons to dialogue instruction text
Build a TMP_SpriteAsset at runtime from EagleCommonTextures battalion
textures, enabling <sprite name="LightInfantry"> etc. in rich text.
The unit types panel now shows each battalion's icon next to its name.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix read-only spriteCharacterTable/spriteGlyphTable assignment
Use existing lists via .Add() instead of assigning new ones — these
properties are read-only in TMP 3.x.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix non-readable texture error in sprite asset builder
Copy battalion textures to readable copies via RenderTexture blit
before packing into atlas, since the originals may not have Read/Write
enabled in their import settings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove runtime TMP_SpriteAsset builder in favor of editor-created asset
The runtime approach fought TMP's initialization — property getters
trigger UpgradeSpriteAsset() which crashes on freshly created instances.
A pre-built sprite asset assigned in the Inspector is the intended workflow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Only trigger Start Fire tutorial when target has an enemy unit
The start_fire_available event was firing whenever the command existed,
even when targeting empty hexes. Now checks that the command's target
cell contains a non-friendly unit before triggering the dialogue.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
CANCELLED status is transient and will be handled by reconnection logic,
so there's no need to report it to Sentry alongside UNAVAILABLE.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add inline battalion type icons to dialogue instruction text
Build a TMP_SpriteAsset at runtime from EagleCommonTextures battalion
textures, enabling <sprite name="LightInfantry"> etc. in rich text.
The unit types panel now shows each battalion's icon next to its name.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix read-only spriteCharacterTable/spriteGlyphTable assignment
Use existing lists via .Add() instead of assigning new ones — these
properties are read-only in TMP 3.x.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix non-readable texture error in sprite asset builder
Copy battalion textures to readable copies via RenderTexture blit
before packing into atlas, since the originals may not have Read/Write
enabled in their import settings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove runtime TMP_SpriteAsset builder in favor of editor-created asset
The runtime approach fought TMP's initialization — property getters
trigger UpgradeSpriteAsset() which crashes on freshly created instances.
A pre-built sprite asset assigned in the Inspector is the intended workflow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add inspector-assigned sprite asset for dialogue instruction icons
Replace runtime sprite builder with a TMP_SpriteAsset field on
DialoguePanelController. Sprite assets are created in the editor
and wired via Inspector — the intended TMP workflow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix sprite vertical alignment and adjust panel background color
Increase m_HorizontalBearingY from 128 to 230 on all sprite assets
so icons align with adjacent text instead of sitting below it.
Update instruction panel background for better icon readability.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Changed agility from 70 to 75 (minimum threshold for archery).
Updated stat sum from 430 to 435.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add battle tutorial dialogues with Old Marek
Narrative dialogue panels for the Shardok tactical combat tutorial:
- Placement phase: enemy forces (red text), unit types, placement instructions
- Battle running: victory conditions, hold position / End Turn advice
- Archery: purple outlines on targets, longbow effectiveness vs armor
- Melee: Shift-click and Melee button instructions
- Charge: horse icon on move destinations, move-and-charge explanation
- Thunderstorm: prevents archery, extinguishes fires
- Start Fire: setting hexes ablaze when adjacent to enemy
Supporting changes:
- Panel positioning (top/default) via panelPosition field on DialogueScript
- Completion tracking prevents dialogues from re-triggering
- New trigger events: archery_available, melee_available, start_fire_available, thunderstorm
- Register CommitButton and EndTurnButton as tutorial targets
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Clean up repetitive dialogue text in battle opening
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The ReinforcementsAction was missing playerId and startingPositions, so
the C++ side had no positions to place units at. Set playerId to 1
(defender) and use 3 positions from attacker starting position list 7
on the Onmaa map.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Implement The Eagle's appearance in tutorial
When Bridget loses all provinces in tutorial mode, The Eagle (Fracture
Covenant) appears and takes control of provinces 32 and 6, displacing
any existing occupants to adjacent friendly provinces.
- Add TutorialStrategicEvents to check for tutorial-specific events
- Add EagleAppearsAction to create The Eagle faction with battalions
- Add TutorialEagleAppears and TutorialDisplacement ActionResultTypes
- Hook tutorial events into EngineImpl.withUpdateChecks
- Update visibility in BUILD files for tutorial package access
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Move tutorial code from library to service to fix layering violation
The tutorial code was in library/tutorial/ which violated the architecture
where library/ should be pure game logic without proto/service dependencies.
Changes:
- Move EagleAppearsAction and TutorialStrategicEvents to service/tutorial/
- Add applyActionResults method to Engine trait for service-layer events
- Hook tutorial events into GameController.withHandledEngineAndResults
- Update BUILD.bazel visibility rules for the new location
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Rename TUTORIAL_EAGLE_APPEARS to TUTORIAL_FACTION_APPEARS
More generic name for potential reuse with other factions.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The Hetzner server uses IPv6-only networking with NAT64 for IPv4. The first
docker pull often fails because the NAT64 gateway hasn't warmed up. Add a
curl pre-warm to the DO registry and retry docker pull up to 3 times with
a 10s backoff.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Instead of changing PENDING_REINFORCEMENT units to RESERVE_UNIT and triggering
a placement phase, directly place them as NORMAL_UNIT at available starting
positions. This gives the player immediate control of reinforcement units.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Assembly-CSharp.csproj is auto-generated by Unity and already matched by
the *.csproj gitignore pattern, but was still tracked from a prior commit.
GeneratedProtos.meta is a Unity meta file for a generated directory.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add narrative dialogue tutorial system with Old Marek opening
Replace mechanical modal tutorials with in-world character dialogue.
Old tutorial content is suppressed (early return in RegisterAll) but
preserved for reference. DialogueManager loads JSON scripts from
Resources/Dialogues/, plays them via DialoguePanelController with
speaker headshots, and integrates with the existing highlight system.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add province highlighting, strobe animation, and dialogue fixes
- Add province bounding box highlight via MapController proxy RectTransform
- Fix province highlight Y coordinate (byte array y=0 = bottom, matching anchors)
- Add strobe/pulse animation to all highlight borders
- Replace cross-canvas highlighting with child-of-target border approach
- End dialogue on battle transition to prevent highlight persistence
- Fix instruction text to match actual button label ("Battle!")
- Add highlightProvince field to DialogueStep for map province highlighting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- King (Bregos Fyar) is now faction 2 (was incorrectly 1)
- The Eagle (faction 1) is reserved for later
- Player (Sadar Rakon) remains faction 3
- Bridget remains faction 4
Also fixed GamesManager to assign player to faction 3 and
AI to factions 2 and 4.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add factionId and heroLoyalty fields to SetFaction proto message.
Tutorial parameters now specify:
- factionId: explicit faction ID for each faction
- heroLoyalty: loyalty value for heroes in that faction
This moves configuration out of hardcoded Scala into the JSON file,
making it easier to adjust tutorial setup.
Faction config:
- Sadar Rakon (Player): factionId=3, heroLoyalty=90
- Bregos Fyar (King): factionId=1, heroLoyalty=85
- Bridget: factionId=4, heroLoyalty=85
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Faction IDs:
- King (Bregos Fyar) gets faction ID 1
- Eagle reserved at faction ID 2
- Player (Sadar Rakon) gets faction ID 3
- Other factions get ID 4+
Hero and province setup:
- Player heroes loyalty set to 90
- Provinces 14, 31, 39 start with 50 gold and 2000 food
- Tarn's army marches to Onmaa; Aldric the Overlooked stays to hold Nikemi
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Shardok now returns proper gRPC status codes (INVALID_ARGUMENT for client
errors, INTERNAL for server errors) instead of re-throwing exceptions that
resulted in opaque UNKNOWN status. Eagle's ShardokInterfaceGrpcClient now
captures these and other errors via Sentry.captureException at all error
points, skipping transient UNAVAILABLE errors handled by reconnection.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Instead of creating reinforcement units at event time (which crashes the
ActionResultApplier's "battalion got larger" validation), pre-place them at
battle creation with a new PENDING_REINFORCEMENT status. When the tutorial
reinforcement event fires, simply change their status to RESERVE_UNIT.
Key changes:
- Add PENDING_REINFORCEMENT = 10 to UnitStatus enum in unit.fbs
- ActionResultApplier now reads status from changed unit bytes instead of
hardcoding NORMAL_UNIT, so reinforcement units correctly get RESERVE_UNIT
- PlaceUnitCommand, PlaceHiddenUnitCommand, and ReinforceCommand now
explicitly set NORMAL_UNIT status (previously relied on applier default)
- TutorialBattleController finds PENDING_REINFORCEMENT units and changes
them to RESERVE_UNIT, removing the UnitConversions dependency
- All exhaustive UnitStatus switch statements updated with new enum value
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Adjust tutorial event timing
- Reinforcements now appear after round 6 (was 5)
- Tarn's flight now triggers after round 7 (was 5)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Replace Alakanda with Elena Fyar in reinforcements
Sadar Rakon is already a Champion, so swapping in Elena Fyar
(Paladin) for more profession variety.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove Tarn flee events from tutorial
Removing both flee triggers (units lost and after round 7) -
keeping the battle to run its natural course.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Adjust tutorial battle unit sizes for realism
- Double the defender's longbowmen (200 -> 394)
- Add slight variance to all unit sizes to make them look battle-worn:
- Defender Light Infantry: 300 -> 293, 308
- Attacker Heavy Cavalry: 600 -> 592
- Attacker Heavy Infantry: 500 -> 507
- Attacker Longbowmen: 300 -> 291
The non-round numbers suggest these units have already been through combat.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Replace Colin with Old Marek the Learned in tutorial
Old Marek will be used extensively in the tutorial, so adding
him as one of the player's starting heroes.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Defer hexGrid.SetUp() by one frame (EnqueueForNextUpdate instead of
Enqueue) so Unity's layout system fully settles after the Shardok
container activation. Previously, mapArea reported a stale size on
the setup frame, causing HexGrid.Update() to detect a size change
and trigger RebuildGrid() on the next frame — visible as the map
snapping to a different size/position.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Wire GameStateView.GameType through IGameModel so the client can
read the game type (Normal vs Tutorial). Also apply NewGameType
from GameStateViewDiff when present.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
After round 5, the player's TutorialBattleConfig includes three reinforcement
units configured as CommonUnits:
- Hedrick the Hedge-Merchant with 600 longbowmen (75 training/armament)
- John Ranil with 500 knights (80 training/armament)
- Alakanda with 600 heavy infantry (75 training/armament)
These troops are better equipped than the player's starting forces.
Note: This only configures the reinforcements in the TutorialBattleConfig proto.
The C++ Shardok-side implementation to actually spawn the units in battle is
being done separately.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add a reinforcement placement phase to tutorial battles so that when
reinforcement events fire, the receiving player can place new units
before combat resumes. This introduces a new REINFORCEMENT_PLACEMENT
game state that pauses the turn flow until all reinforcement units
are placed.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sort lobby running games by CreatedTimestampMillis with GameId as
tiebreaker for existing games that share timestamp 0.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add created_timestamp_millis field to RunningGame (internal storage)
and GameInfo (client API) protos. Thread the timestamp through
ControllerInfo, GamePlayerInfo, and all lobby query paths in
GamesManager. Set timestamp on game creation (both regular and
tutorial). Existing games default to 0 (proto3 default).
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The Verify workspace step was added to debug the sparse-checkout issue,
which is now fixed by the Clean workspace step. Also removes clean:true
which runs git clean -ffdx and destroys bazel-* symlinks unnecessarily.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Fix tutorial battle flee action player ID
The FleeAction was using playerId=1 (defender/player) instead of
playerId=0 (attacker/Tarn). This caused the player's units to flee
instead of Ikhaan Tarn's units.
In Shardok battles:
- playerId=0 is the attacker
- playerId=1 is the defender
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add eagle_hero_ids to FleeAction for hero-specific fleeing
Extends FleeAction proto to support targeting specific heroes by their
Eagle hero ID. When eagle_hero_ids is specified, only units with matching
attached heroes will flee.
In the tutorial, this makes only Ikhaan Tarn flee when triggered, rather
than all of his units.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Enable --incompatible_enable_proto_toolchain_resolution and
--prefer_prebuilt_protoc so Bazel downloads a pre-built protoc
binary from official protobuf releases rather than compiling it.
Protoc plugins and the runtime library still build from source.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The gh CLI isn't installed on all self-hosted runners. Replace with
curl + python3 which are available everywhere.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When multiple commits are merged to main in quick succession, intermediate
builds can be skipped if a newer docker_build run is already queued.
This avoids wasting runner time on builds that will be immediately
superseded, while never cancelling a build or deploy that's in progress.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When multiple commits are merged to main in quick succession, intermediate
builds can be skipped if a newer docker_build run is already queued.
This avoids wasting runner time on builds that will be immediately
superseded, while never cancelling a build or deploy that's in progress.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The tutorial game creation was not calling withPopulatedPregeneratedTexts(),
causing hero names to show as "Hero" instead of their actual names because
the name text IDs couldn't be resolved.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add GameType enum to track tutorial vs normal games
- Add GameType enum (NORMAL, TUTORIAL) in common proto and Scala model
- Store gameType in GameState and pass to GameStateView for client
- Add newGameType field to ActionResult for setting game type on creation
- Mark tutorial games as TUTORIAL in TutorialGameCreation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add newGameType field to GameStateViewDiff
Ensures game type changes are sent to clients via diffs, not just
the full GameStateView.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove default value from GameState.gameType parameter
Force callers to explicitly specify gameType when constructing GameState,
making the type requirement more explicit and preventing accidental defaults.
Updates all test files and InMemoryHistory to explicitly pass GameType.Normal.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Self-hosted runners can get stuck in sparse-checkout mode from previous
deploy jobs. The actions/checkout@v4 sparse-checkout disable has a bug:
it writes core.sparseCheckout=false to .git/config.worktree, then
immediately unsets extensions.worktreeConfig, causing git to fall back
to .git/config where core.sparseCheckout is still true.
Fix: explicitly set core.sparseCheckout=false in .git/config and
remove the worktree config files entirely before checkout.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The deploy job in docker_build.yml used sparse-checkout on [self-hosted, bazel]
runners. This left the workspace in sparse-checkout mode, causing subsequent
jobs on the same runner to fail with "not invoked from within a workspace"
because MODULE.bazel was missing.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Move Ikhaan Tarn and province 31 into Bregos Fyar's King's Loyalists faction
- Add new Vengeance faction with Bridget and 300 light infantry in Kojaria (province 39)
- Fix startingTrusts to target player faction (was targeting self after reorganization)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add TutorialGameCreationTest to verify tutorial game can be created
without exceptions
- Fix TutorialGameCreation to properly set provinceOrders from JSON
config using ProvinceOrderTypeConverter
- Add test visibility for battalion_types resource
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Aligns pinned versions with what the dependency graph was already resolving,
eliminating the --check_direct_dependencies warnings.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Provinces 14 (player) and 31 (enemy) were missing the "orders" field,
causing a validation error: "Ruling player present, but province orders
not set".
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add Tutorial button to lobby for launching tutorial games
- Add tutorialButton field to ConnectionHandler for Unity to wire up
- Add CreateTutorialGame() method that creates a game with isTutorial=true
- Modify _internalCreateGame() to accept isTutorial parameter and include
it in the CreateGameRequest proto message
- Wire up tutorial button click handler in SetupLobbyUI()
The Tutorial button will create a single-player game where Eagle sets up
the hardcoded tutorial scenario (defending Onmaa from Ikhaan Tarn's attack).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Hide tutorial button for non-nolen users
Temporary gate while tutorial is in development.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove defensive null checks on Inspector fields in lobby setup
If these fields aren't wired in the editor, we want a
NullReferenceException so the problem is immediately obvious.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Updates tutorial_parameters.json to use Vellus Kade (who already exists
in heroes.tsv) as the ruler of province 40, instead of the non-existent
"Spartacus the Younger". Uses battalion names from game_parameters.json.
Also removes swornBrotherNames for Bregos Fyar in tutorial - Vellus Kade
is just a vassal.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The Windows build covers shared C#/proto compilation. Mac only needs
to build on PRs when Mac-specific config changes (workflow, build
scripts, Sparkle, code signing, etc.). Mac still builds on every
push to main.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
These scripts managed an rsync-based Library/ cache to /tmp/eagle0/ but
are never called from any workflow. Library/ caching now relies on
clean: false preserving the working directory between self-hosted runs.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Bee's incremental compilation handles content-only .cs modifications
fine. The stale DAG errors that originally motivated unconditional Bee/
deletion are caused by structural changes (files moved/renamed/deleted),
not content edits. This saves significant rebuild time on asset-only
changes like FBX meta edits or texture updates.
The last-built commit SHA is saved after each successful build and
compared on the next run to detect structural C# changes.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Split bridge prefabs into permanent and constructed styles
Pre-existing bridges now use stone/wooden models while engineer-built
bridges use hastily-constructed models, selected at render time via
the existing _bridgeBuilderLocations tracking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Scale up bridge models to better fill hex tiles
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
HeroGenerator.getHero() returns a new generator after consuming a hero
from the pool, but this updated generator was never stored back in
EngineImpl. This caused duplicate heroes to appear across rounds since
the same pool was being used repeatedly.
Changes:
- Add PhaseAdvancementResult to hold both results and optional updated generator
- Add resultsWithGenerator() to PerformUnaffiliatedHeroesAction
- Update RoundPhaseAdvancer to return PhaseAdvancementResult
- Add recursiveTransformWithGenerator() to EngineImpl to store updated generator
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Azure Artifact Signing at ~$10/mo eliminates the SmartScreen
"unknown publisher" warning on Windows builds.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
With the CDN TTL increased to 1 hour, iOS addressables updates need
active cache purging to be visible to clients promptly.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The Unity DeepLinkPostProcessor depends on UNITY_IOS being defined at
script compilation time, which can fail if the editor starts with a
non-iOS build target. Set the key via PlistBuddy in the archive script
as a reliable fallback that doesn't depend on Unity preprocessor state.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Instead of sleeping 60s for CDN cache to expire, actively purge the
DigitalOcean Spaces CDN cache before notifying clients of new builds.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Both jobs now run on a dedicated [self-hosted, macOS, testflight]
runner (halfdan), so the Xcode project stays on disk between jobs.
This removes the tar/upload/download/extract cycle through GitHub's
artifact storage, saving ~5-13 minutes per TestFlight build.
Requires adding the 'testflight' label to the halfdan runner.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Git sparse checkout cone mode only supports directories. Add
sparse-checkout-cone-mode: false to allow the docker-compose.prod.yml
file pattern.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The cleanup job on ubuntu-latest already deletes these artifacts with
if: always(). The deploy job's copy was redundant.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Move sleep 60 + client notify from self-hosted runners to ubuntu-latest
jobs in mac_build and unity_build, freeing expensive runners sooner
- Skip eagle_build and shardok_build on main pushes (redundant with
docker_build and shardok_arm64_build respectively, kept for PRs)
- Add lfs: false to bazel_cache_cleanup (only runs bazel clean)
- Add sparse checkout + lfs: false to docker_build deploy job (only
needs docker-compose.prod.yml, nginx/, and scripts/)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Build-only workflows (bazel_test, eagle_build, shardok_build) cancel
superseded runs for both PRs and main pushes.
Deploy workflows (mac_build, unity_build, ios_addressables,
shardok_arm64) cancel superseded PR builds but queue main pushes to
avoid interrupting in-progress deployments.
docker_build already had concurrency control.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>