Commit Graph
100 Commits
Author SHA1 Message Date
613cf529bc Rename Fluria to East Faluria and Faluria to West Faluria (#6201)
* 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>
2026-02-22 11:23:54 -08:00
ca38c1df3a Use context-appropriate highlight colors for province borders (#6202)
- 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>
2026-02-22 08:25:46 -08:00
13777a1e83 Skip same-faction borders during faction highlighting (#6200)
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>
2026-02-22 08:09:28 -08:00
782dc2135a Fetch LFS tile images before admin server Docker build (#6199)
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>
2026-02-22 08:05:25 -08:00
a23ba79190 Fix LLM provider selection to prefer primary provider (#6198)
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>
2026-02-21 21:45:05 -08:00
a1dcb8e932 Make province borders resolution-independent (#6196)
* 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>
2026-02-21 21:41:56 -08:00
328fa44bf4 Fix static file embedding to include subdirectories (#6197)
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>
2026-02-21 21:33:44 -08:00
4f0ecf66c6 Use hex tile images in map editor instead of flat colors (#6195)
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>
2026-02-21 21:23:02 -08:00
9a014b38c4 Tweak strategic map UI layout and border width (#6194)
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>
2026-02-21 18:17:59 -08:00
f6ed3d2bee Merge disconnected Parnia blob into Ingia (#6193)
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>
2026-02-21 18:12:39 -08:00
d92d37ea68 Hand-tune province label positions (#6192)
* 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>
2026-02-21 18:11:41 -08:00
f5ed0de040 Improve label positioning with clearance-threshold algorithm (#6187) (#6191)
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>
2026-02-21 17:56:28 -08:00
8ff56d7d28 Enable map editor in production admin server deployment (#6190)
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>
2026-02-21 17:53:20 -08:00
66e8f2f605 Delete orphaned .e0s battle files on game rewind (#6189)
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>
2026-02-21 17:53:03 -08:00
9c557873c4 Fix province label positions using pole-of-inaccessibility algorithm (#6188)
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>
2026-02-21 14:16:17 -08:00
ee8dfba000 Add "Restart Eagle" button to admin console (#6187)
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>
2026-02-21 13:41:22 -08:00
98b456d760 Add web-based map editor to admin console (#6186)
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>
2026-02-21 13:39:17 -08:00
413df8bdff Add eagle-restart script for quick active instance restart (#6185)
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>
2026-02-21 13:21:09 -08:00
c1463ca9c5 Add CI test for attacker/defender starting position overlap (#6184)
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>
2026-02-21 10:27:27 -08:00
d4572c2108 Fix attacker/defender starting position overlap on Motcia map (#6183)
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>
2026-02-21 08:36:50 -08:00
223e059e72 Add detailed coords and occupant logging to AI command mismatch diagnostic (#6182)
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>
2026-02-20 21:27:51 -08:00
5077981d41 Replace AI command count assert with diagnostic error logging (#6181)
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>
2026-02-20 21:09:16 -08:00
f352d7aca8 Fix duplicate heroes by threading HeroGenerator through game creation (#6180)
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>
2026-02-20 20:27:50 -08:00
c85ce61bef Remove users from lobby when entering a game (#6179)
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>
2026-02-20 20:02:21 -08:00
1ccd5784ee Fix tutorial reinforcement hero ID verification (#6171)
* 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>
2026-02-20 19:46:35 -08:00
5814fb31e5 Tint province background during blizzard (#6178)
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>
2026-02-20 19:43:09 -08:00
ddfd735e15 Triple blizzard intensity on Eagle strategic map (#6177)
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>
2026-02-20 19:42:54 -08:00
d9506fc0a0 Double default province border width from 0.35 to 0.7 (#6176)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 19:12:27 -08:00
1f0da81ed3 Fix reinforce command crash and broken overlay (#6175)
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>
2026-02-20 19:02:25 -08:00
64a15dd616 Smooth province borders and reduce default width (#6174)
* 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>
2026-02-20 19:01:22 -08:00
ad58e4cac2 Make rubber-banding hero spawns appear as residents (#6173)
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>
2026-02-20 19:01:04 -08:00
d9e5b47e7f Fix available games not displaying in lobby list (#6172)
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>
2026-02-20 19:00:45 -08:00
a8842b0cd3 Change tutorial reinforcements to appear after round 5 (#6169)
Adjusts timing for better tutorial pacing.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-20 17:55:12 -08:00
044819cd99 Increase tutorial defender unit size and training (#6167)
- 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>
2026-02-20 16:29:00 -08:00
6471a889a1 Use attacker starting position index for tutorial reinforcements (#6166)
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>
2026-02-20 13:42:33 -08:00
115948eea3 Add tutorial reinforcement units to initial game state (#6165)
* 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>
2026-02-20 12:58:15 -08:00
58e674d3fd Use cached leader info for unloaded games in lobby (#6164)
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>
2026-02-20 10:54:07 -08:00
c94d41952c Avoid unnecessary Unity reimport of generated proto files (#6163)
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>
2026-02-20 10:29:35 -08:00
3b536f93e2 Replace province fill-color strobing with border-width strobing (#6162)
* 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>
2026-02-20 09:49:22 -08:00
d0a07febe7 Replace environment dropdowns with Connect to QA buttons (#6161)
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>
2026-02-20 08:15:36 -08:00
10dbff3738 Enlarge dialogue sprites and add charge/profession icons (#6160)
* 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>
2026-02-20 07:23:09 -08:00
78e130b9dd Add --skip-auth flag for local QA testing (#6159)
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>
2026-02-20 07:15:20 -08:00
e9d724ed88 Fix Waylaid Julius vigor exceeding constitution (#6157)
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>
2026-02-20 06:53:10 -08:00
8cf22c0bd0 Increase inline sprite size by 25% (#6158)
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>
2026-02-20 06:52:50 -08:00
80d8927d6a Fix Start Fire tutorial trigger and clean up sprite asset approach (#6156)
* 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>
2026-02-19 22:28:19 -08:00
c75f7d8dda Suppress CANCELLED gRPC errors in Sentry reporting (#6155)
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>
2026-02-19 22:15:29 -08:00
cc710e0f6b Add inline battalion type icons to dialogue text (#6153)
* 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>
2026-02-19 22:15:16 -08:00
377c1fe726 Increase Sadar Rakon's agility to enable archery in tutorial (#6154)
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>
2026-02-19 22:12:59 -08:00
17d2a72ea9 Add battle tutorial dialogues with Old Marek (#6152)
* 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>
2026-02-19 21:27:17 -08:00
91dab8a28b Add playerId and starting positions to tutorial reinforcements config (#6151)
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>
2026-02-19 21:22:40 -08:00
ebf9a3934f Implement The Eagle's appearance in tutorial (#6149)
* 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>
2026-02-19 21:19:09 -08:00
5cd1b372cc Fix flaky Hetzner deploy by pre-warming NAT64 and retrying docker pull (#6150)
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>
2026-02-19 20:56:32 -08:00
d543967b44 Place tutorial reinforcements directly on battlefield instead of using placement phase (#6147)
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>
2026-02-19 20:43:36 -08:00
69789dd22e Untrack Assembly-CSharp.csproj and gitignore GeneratedProtos.meta (#6148)
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>
2026-02-19 20:41:00 -08:00
f5c24e68c8 Add narrative dialogue tutorial with Old Marek (#6146)
* 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>
2026-02-19 20:37:45 -08:00
c00acc991b Fix tutorial faction IDs to match normal game (#6145)
- 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>
2026-02-19 20:32:55 -08:00
1a9479aa17 Move tutorial faction config to JSON (#6144)
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>
2026-02-19 20:22:36 -08:00
3a5eb1f760 Tutorial setup improvements (#6142)
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>
2026-02-19 20:15:26 -08:00
3eaeb031b2 Send Shardok exceptions to Sentry via Eagle (#6143)
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>
2026-02-19 20:12:26 -08:00
1f29de9012 Pre-place reinforcement units with PENDING_REINFORCEMENT status (#6141)
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>
2026-02-19 19:12:34 -08:00
6640e423b7 Adjust tutorial event timing (#6140)
* 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>
2026-02-19 18:07:58 -08:00
1b18bb090f Adjust tutorial battle unit sizes for realism (#6139)
* 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>
2026-02-19 17:29:34 -08:00
c6cb8b4c48 Fix visible map redraw when entering Shardok battle (#6138)
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>
2026-02-19 17:24:34 -08:00
adminandGitHub 8f205e5e8e Gate tutorials on TUTORIAL game type (#6137) 2026-02-19 08:16:03 -08:00
b3b0de6b15 Expose GameType in client game model (#6136)
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>
2026-02-19 07:11:23 -08:00
4605492f32 Add reinforcement heroes to tutorial battle config at round 5 (#6135)
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>
2026-02-19 07:10:54 -08:00
7626a4a958 Implement tutorial reinforcements with placement phase (#6134)
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>
2026-02-19 07:10:30 -08:00
d07c3237f2 Sort running games by creation date in lobby (client) (#6132)
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>
2026-02-18 21:40:14 -08:00
ad7b3fd0d1 Sort running games by creation date in lobby (server) (#6131)
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>
2026-02-18 21:17:08 -08:00
8b64872a51 Remove diagnostic step and unnecessary clean:true from bazel_test (#6123)
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>
2026-02-18 16:44:45 -08:00
17f9521780 Fix tutorial battle flee action and make only Tarn flee (#6128)
* 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>
2026-02-18 16:43:56 -08:00
2a3ad4cad4 Use pre-built protoc binary instead of compiling from source (#6129)
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>
2026-02-18 16:26:22 -08:00
5dfd9a7515 Fix skip-if-superseded: use curl instead of gh CLI (#6127)
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>
2026-02-18 13:31:35 -08:00
2021ee2ae8 Skip intermediate docker builds when newer runs are queued (#6126)
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>
2026-02-18 13:27:38 -08:00
37b4039f59 Skip intermediate docker builds when newer runs are queued (#6125)
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>
2026-02-18 12:52:55 -08:00
6e1a462c2c Populate hero names in tutorial game ClientTextStore (#6124)
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>
2026-02-18 12:36:42 -08:00
84f165ea1d Add GameType enum to track tutorial vs normal games (#6121)
* 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>
2026-02-18 12:29:41 -08:00
faaac39051 Add workspace cleanup and diagnostics to bazel_test workflow (#6122)
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>
2026-02-18 10:46:39 -08:00
57d35e085a Remove sparse checkout from deploy job to prevent corrupting shared runners (#6120)
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>
2026-02-18 07:06:50 -08:00
4325385544 Adjust tutorial factions: Ikhaan Tarn joins King's Loyalists, add Vengeance (#6119)
- 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>
2026-02-18 06:46:08 -08:00
adminandGitHub 88781d779b Revert "Add persistent Bazel server keepalive for CI runners (#6096)" (#6118)
This reverts commit cccbbb9d37.
2026-02-17 21:23:36 -08:00
b8e1e0d8b5 Add test for tutorial game creation (#6117)
- 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>
2026-02-17 21:23:01 -08:00
023503e831 Bump rules_rust 0.60.0 → 0.68.1 and apple_support 1.23.1 → 1.24.1 (#6116)
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>
2026-02-17 21:10:26 -08:00
3058538772 Add missing orders field to tutorial provinces (#6115)
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>
2026-02-17 21:04:18 -08:00
fe72e870a2 Add Tutorial button to lobby for launching tutorial games (#6044)
* 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>
2026-02-17 21:04:02 -08:00
69da124203 Use Vellus Kade in tutorial instead of missing Spartacus the Younger (#6113)
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>
2026-02-17 18:33:09 -08:00
67fad55d47 Skip Mac build on PRs unless Mac-specific files change (#6114)
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>
2026-02-17 17:41:17 -08:00
a879243fd8 Remove unused persist_library.sh and restore_library.sh (#6112)
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>
2026-02-17 16:54:16 -08:00
a8ded5df45 Only clear Bee/ cache when C# files are added/deleted/renamed (#6111)
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>
2026-02-17 16:50:22 -08:00
9fbc409df9 Split bridge prefabs into permanent and constructed styles (#6110)
* 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>
2026-02-17 16:04:25 -08:00
ac260323c0 Fix HeroGenerator state threading to prevent duplicate heroes (#6078)
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>
2026-02-17 13:45:31 -08:00
3a897b6241 Add Windows code signing to small eagle TODO nice-to-haves (#6109)
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>
2026-02-17 13:25:19 -08:00
161fa78b42 Add CDN cache purge to iOS workflows after addressables upload (#6108)
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>
2026-02-17 12:40:45 -08:00
8dc9d12fdf Set ITSAppUsesNonExemptEncryption in archive script for reliable compliance bypass (#6107)
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>
2026-02-17 12:28:42 -08:00
df105c9e31 Replace CDN sleep with cache purge for faster client notifications (#6106)
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>
2026-02-17 12:26:52 -08:00
d62120b833 Eliminate Xcode project artifact round-trip in iOS TestFlight workflow (#6105)
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>
2026-02-17 11:34:29 -08:00
de1ad34005 Fix docker_build deploy sparse checkout for individual files (#6104)
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>
2026-02-17 11:23:45 -08:00
5f366c04ed Remove duplicate artifact cleanup from mac_build deploy job (#6103)
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>
2026-02-17 11:20:46 -08:00
dfec602889 Misc CI optimizations: notify offload, skip redundant builds, fix LFS (#6102)
- 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>
2026-02-17 11:10:11 -08:00
3f83cf2c45 Add concurrency groups to prevent redundant CI builds (#6101)
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>
2026-02-17 11:03:32 -08:00