The grpc 1.78.0 upgrade eliminated the rules_swift version conflict
that required SparklePlugin to live in a separate Bazel workspace.
Move it back into the main workspace under src/main/objc/.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add giant beast type using 3x-scaled peasant model
Reuse Polytope peasant prefabs at 3x scale (animalScale 45) with a single
wandering giant per province. Uses HumanMapAnims controller for idle/walk/
attack animations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix giant test method to use correct province name (Elekes)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Wire up GiantEffect prefab in Gameplay scene
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Fix Bazel Xcode version caching causing CI build failures
When Xcode updates on self-hosted runners, Bazel's cached
local_config_xcode still references the old version, causing
"xcode-locator <old_version> failed" errors.
Add sync_bazel_xcode.sh which detects Xcode version changes
and runs `bazel sync --configure` to refresh the config without
nuking the build cache. Added to all 8 CI workflows that use Bazel.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use rm of cached repo instead of bazel sync --configure
bazel sync --configure requires WORKSPACE mode and fails with
bzlmod. Instead, delete the stale local_config_xcode from the
output base directly — the next bazel build regenerates it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use bazel clean --expunge instead of targeted repo deletion
Deleting just local_config_xcode is insufficient — the stale Xcode
version is also embedded in cached actions and other toolchain repos.
The only reliable fix is a full expunge. Since Xcode updates are
infrequent, the one-time rebuild cost is acceptable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use action_env to bake Xcode version into cache keys
Instead of expunging the cache (which doesn't help with the remote
cache anyway), write the Xcode build version to .bazelrc.xcode via
--action_env. This makes the version part of every action's cache
key, so stale entries in both local and remote caches are naturally
ignored after an Xcode update — no deletion needed anywhere.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add expunge back alongside action_env for belt-and-suspenders fix
action_env alone isn't enough — local_config_xcode is a cached
repository rule that bakes in the old Xcode version before any
actions run. Need expunge to clear the local toolchain config,
plus action_env to prevent stale remote cache hits afterward.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add Xcode sync to mac_build build-and-sign job
The sync step was only in the deploy job, but build-and-sign calls
bazel via build_protos.sh before that. This runs on the unity-mac
runner which also has the stale Xcode cache.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
It uses the Butcher 2D sprite via ButcherBeastNames HashSet,
but was missing from the documentation table.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
grpc 1.78.0 dropped its rules_swift dependency, resolving the version
conflict (rules_swift 3.x vs 2.x) that forced SparklePlugin into a
separate workspace. This removes the rules_swift single_version_override.
Includes a patch for a known BCR bug (grpc/grpc#41438) where grpc 1.78.0
requests Python 3.14.0b2 but rules_python >= 1.6.0 replaced it with 3.14.
The patch backports the upstream fix (commit 459279b).
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replace the 2D PSD-based DragonRed sprite with a 3D FBX model from 3dFoin
Dragon. The dragon now alternates between flying (circling at altitude with
fly/flyAttack/flyBreathFire/flyFast/flyHit actions) and grounded (wandering
with walk/run/attack/breathFire/whipTail/hit/stand/idle actions) modes.
- Add DragonMapAnims.controller with 15 animation states
- Configure dragon_skin.FBX for Generic animation with avatar
- Rewrite DragonEffect.cs with 4-mode state machine
- Delete old 2D DungeonMonsters2D dragon assets
- Add Dragon/ to LFS rules and clang-format excludes
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add 5 Polytope character archetypes for human beast animations
Replace the single Butcher sprite for all 34 human-type beasts with
5 thematic 3D archetypes using Polytope Studio Lowpoly Medieval
Characters: Knight (6 beasts), Soldier (8), Archer (3), Militia (8),
and Peasant (9). All share a HumanMapAnims controller that retargets
Clown animation clips via Unity's Humanoid system.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Rename humanEffectPrefab to butcherEffectPrefab; route psychopath to it
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Move butcherEffectPrefab under Human Effect Prefabs header
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Wire up Polytope prefabs in scene; rename HumanEffect to ButcherEffect
Wire all 6 human effect prefabs (butcher, knight, soldier, archer,
militia, peasant) in Gameplay.unity scene. Rename HumanEffect.prefab
to ButcherEffect.prefab to match the field rename.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Exclude third-party asset packs from clang-format hook
Skip DungeonMonsters2D, Polytope Studio, RRFreelance-Characters,
and Raccoon directories — these are vendor code that should not
be reformatted.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* LFS-track all files under third-party asset pack directories
Track all files under DungeonMonsters2D/, Raccoon/, Polytope Studio/,
and RRFreelance-Characters/ via Git LFS. These are imported third-party
assets that are never manually edited.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Convert DungeonMonsters2D and Raccoon assets to Git LFS
Re-add all files under DungeonMonsters2D/ and Raccoon/ through
the LFS filter so they're stored as LFS objects going forward.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The warmup-binary artifacts from docker_build were accumulating and
exceeding the 500 MB storage threshold. GitHub's retention-days cleanup
wasn't keeping up with deploy frequency. Add explicit artifact deletion
after deploy, matching what mac_build already does.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add raccoon beast animation using Sketchfab 3D model
Adds Raccoon.fbx (WildMesh 3D, CC BY 4.0) with textures, wires up
raccoonEffectPrefab in ProvinceBeastsController, and updates both
attributions.json files and BEAST_EFFECTS.md.
Editor steps still needed: create RaccoonMapAnims controller, create
RaccoonEffect prefab (duplicate HoneyBadgerEffect), wire in Gameplay.unity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Wire up raccoon animation with Blender-converted FBX
Convert GLB to FBX via Blender (original FBX had no animation data).
Add RaccoonMapAnims controller, RaccoonEffect prefab, materials with
textures, scene wiring, and fix province name (Hella, ID 19).
Known issue: gray sphere artifact under each raccoon.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove Icosphere/hair artifacts from raccoon FBX and update scene
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Smooth 3D animal turning with Quaternion.Slerp
Animals now rotate smoothly toward their target instead of snapping.
UpdateFacing is called every frame during walking, and uses Slerp
with a configurable turnSpeed (default 5) for gradual rotation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Eliminates the duplicate attributions.json by making the Unity copy
a symlink to src/main/resources/net/eagle0/attributions.json, ensuring
a single source of truth that can never diverge.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add honey badger 3D model and wire up AnimalEffect
Import HoneyBadger FBX (63 animations) and texture from Sketchfab.
Add honeyBadgerEffectPrefab field and switch case in
ProvinceBeastsController. Still needs Unity Editor setup: Animator
Controller, effect prefab, and scene wiring.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix honey badger test province name (ID 1 is Shumal, not Onmaa)
Onmaa is province ID 14, already used by TestWildPig.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Wire up HoneyBadgerEffect prefab and animator controller
- HoneyBadgerMapAnims controller with idle/walk/eat/attack states
mapped to HoneyBadger FBX clips
- HoneyBadgerEffect prefab with AnimalEffect referencing the model
- Wired into Gameplay.unity ProvinceBeastsController
- Includes Unity-generated .meta files for imported assets
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix HoneyBadger FBX avatar setup to enable animations
avatarSetup: 0 (No Avatar) prevented the Animator from driving the
skeleton. Changed to avatarSetup: 1 (Create From This Model) to match
how the Animal Pack Deluxe models are configured.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add attribution for recently added Unity asset packs
Add Animal pack deluxe (janpec), 2D Monster Pack Basic Bundle/PSB
(SP1/DungeonMonsters2D), and Honey Badger 3D model (WildMesh 3D) to
ASSET_AUDIT.md. Update 3D model count in summary table.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add 3D model attributions to in-app/website credits
Add Animal pack deluxe, 2D Monster Pack, and Honey Badger
to both server and Unity client attributions.json files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Prevents Claude from combining bash commands with &&, ||, or ;.
Each command must be a separate tool call, which makes permission
rules work correctly per-command.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add clown beast animation using both Clown Pack models
- Add per-prefab animator controller overrides to AnimalEffect
(animatorControllers[] array, indexed to match animalPrefabs[])
- Create ClownMapAnims and FatClownMapAnims controllers mapping
idle/walk/eat/attack states to each model's animation clips
- Create ClownEffect prefab using both Clown and Fat Clown models
- Add clown case to ProvinceBeastsController, remove from HumanBeastNames
- Wire up in Gameplay.unity scene
- Update BEAST_EFFECTS.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix ClownEffect prefab (recreated via Unity editor)
The hand-written prefab YAML was rejected by Unity's PrefabImporter.
Recreated by duplicating the working OgreEffect prefab in the editor
and configuring the AnimalEffect fields there.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Update BEAST_EFFECTS.md: ogre/troll now have custom animation
Reflects the addition of the Orc-Ogre pack AnimalEffect for ogre and
troll beast types (PR #6287). Removes them from the "no custom
animation" list and adds them to the custom animations table.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add complete beast coverage details to BEAST_EFFECTS.md
- Add missing animal effect entries (crab, frog, rabbit, salamander,
scorpion, stag, wild goat, wild pig) to custom animations table
- Add human-type beast breakdown by likelihood with thematic clusters
for potential visual differentiation beyond the generic Butcher prefab
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replace unsafe asInstanceOf casts with pattern matching via helper
methods. This is more idiomatic Scala and will give clearer error
messages if the map contains unexpected types.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Fixes two issues with beast balance:
1. Demons and vampires were impossible to defeat - reduced maxCountMultiplier
from 0.5 to 0.15 so an elite battalion (1000 troops, 100/100 stats) can
just barely defeat them.
2. Clowns had a 39% chance of being deadlier than demons due to their huge
power variance (0.8-50). Added a powerExponent field to beasts.tsv that
controls how the random roll translates to power. Default is 4 (quartic),
clowns use 20 which reduces the chance of deadly clowns to ~10%.
The casualties formula is: casualties = beastsPower² / totalPower
Where beastsPower = count × relativePower
This means overwhelming force results in minimal casualties (intended),
but also means high beast power causes quadratically more damage.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
ProvinceWasSelected accessed Model.AvailableCommandsByProvince and
Model.Provinces without checking if Model was null. This caused a
crash when the user clicked a province before the game model was
received from the server on first load.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Add animatorController override field to AnimalEffect so it can swap
the prefab's controller at runtime (needed because the Orc-Ogre pack
uses state names like Ogre_Idle1 instead of the expected idle/walk/etc)
- Create OgreMapAnims.controller with idle/walk/eat/attack states mapped
to Orc-Ogre FBX animation clips
- Create OgreEffect.prefab using AnimalEffect + controller override
- Add ogre/troll cases to ProvinceBeastsController.GetEffectPrefab()
- Wire OgreEffect in Gameplay.unity scene
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Wire up animations for 8 new beast types
Add effect prefabs and scene wiring for crab, frog, rabbit,
salamander, scorpion, stag, wild goat, and wild pig using
Animal Pack Deluxe 3D models with AnimalEffect controller.
Multi-variant prefabs: frog (3), scorpion (2), rabbit (2),
stag (2), wild goat (2), wild pig (2).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix ContextMenu test labels to use real province names
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix animal effect prefab references to use root GameObjects
Each Animal Pack prefab contains many child objects (bones, meshes, etc).
The effect prefabs were incorrectly referencing the first child object
instead of the root prefab object (the one with m_Father: {fileID: 0}).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Tune animal effect scales per beast type
Adjusted animalScale values from the Unity editor after visual testing:
- Frog, salamander, scorpion, crab: 50 (small creatures need more zoom)
- Rabbit, stag, wild goat, wild pig: 20 (medium animals)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Order beasts without custom animations by how commonly they appear,
making it easier to prioritize which to add next.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Document all three effect types (MonsterEffect, AnimalEffect,
DragonEffect), list all beasts with custom animations vs vulture
fallbacks, and add 3D rendering tips.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add animal wandering effect for bear, boar, crocodile, snake, wolf
New AnimalEffect.cs drives Animal Pack Deluxe 3D prefabs using their
lowercase state names (idle, walk, run, eat, attack) and Y-rotation
for facing direction. Wolverine reuses the wolf prefab.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add animal effect prefabs and scene wiring for bear, boar, crocodile, snake, wolf
Wire AnimalEffect prefabs to ProvinceBeastsController and include the
Animal Pack Deluxe asset pack. RatEffect now has both MonsterEffect
(2D sprites) and AnimalEffect (3D model) for a mixed group.
Add LFS tracking for .tga and .fbx files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix animal facing direction to use full angle rotation
3D models need to rotate Y by the actual movement angle, not just
0/180 degrees. Use atan2 of the XY direction vector so animals face
their actual travel direction.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix animal rendering and remove die animation
Use sortingOrder=103 on 3D mesh renderers instead of Z offset hack
to render animals above the map. Guard against die animation state.
Default scale to 15. Remove unused run state.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Disable depth test on animal renderers to prevent map clipping
When 3D animals rotate to face away, their geometry extends into
positive Z and clips behind the map plane. Setting ZTest=Always
ensures they always render on top regardless of orientation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix animal depth clipping with renderQueue and ZWrite/ZTest overrides
Use renderQueue=4000 (Overlay), disable ZWrite, and set ZTest=Always
via SetFloat (compatible with more shader types) to prevent 3D animal
meshes from clipping behind the map when rotated.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The first hero departure notification in the tutorial has no narrative
context. Old Marek now comments when a non-player faction loses a hero,
noting that Bregos has always inspired loyalty and something deeper must
be at work after the upheaval with Tarn.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add wandering monster animations for beast events on Eagle map
Replace generic vulture effects with animated DungeonMonsters2D monsters
for skeleton, zombie, rat, spider, demon, orc, and human-type beasts.
Each MonsterEffect spawns a group of 3 monsters that wander randomly,
idle, and occasionally attack within a province.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add ContextMenu test actions for monster effects
MonsterEffect: Test All Idle/Move/Attack/SpecialAttack
ProvinceBeastsController: Test spawn for each beast type in Motcia
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Spread test beast spawns across different provinces
Also add "Test All Beast Types" to spawn all 8 at once.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix monster sprite facing direction
DungeonMonsters2D prefabs default to facing left, so invert the
X scale flip logic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove malformed AnimationEvents from SkeletonArcher anims
The asset pack shipped with empty functionName fields on these events,
causing "AnimationEvent has no function name specified" errors at runtime.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Wire humanEffectPrefab in Gameplay scene
The HumanEffect prefab was created but never assigned to the
ProvinceBeastsController Inspector field, so human-type beasts
fell through to generic vultures.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add vampire beast type and remove unused DungeonMonsters2D assets
- Add VampireEffect prefab, code mapping, scene wiring, and test entry
- Remove Bat, Ghost, GhostKnight, and four Golem variants (animations,
characters, PSBs) that aren't mapped to any beast type
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove DragonEffect component from monster effect prefabs
These were created by duplicating the dragon prefab, which left a
DragonEffect component that spawned an extra dragon alongside the
intended monsters.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Update Eagle appearance trigger for Hedrick and Sadar expansion
Replace Bridget (faction 4) with Hedrick (faction 5) as the tutorial
antagonist, and add a second trigger path: Sadar controlling 4+ provinces.
The Eagle now appears when either Hedrick is eliminated OR the player
faction grows strong enough to warrant attention.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Lower weak King province hero loyalty to 20
Makes the heroes in the King's weak provinces much more likely to
defect, as they barely have any loyalty to the King.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Adjust weak King province hero loyalty to 25
Loyalty 20 was causing heroes to depart too quickly.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Horseshoe prints were facing forward (open end leading) instead of
trailing behind the horse. Added 180° rotation for mounted units.
Duel accepted animation was never playing because DuelAccepted wasn't
matched as a result of the pending DuelChallenge command. Added it to
IsResultVariant and cached source/target grid indices in
PendingCommandSound so the animation plays even when units have been
removed from the model by later duel combat results in the same batch.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add tutorial-specific initial chronicle text
The tutorial was using the same initial chronicle text ID as regular
games, but the text content references "The Eagle" character which
doesn't exist in the tutorial scenario. When the LLM tries to generate
a chronicle update, it looks up the previous chronicle entry's text
as a dependency, causing "Unknown dependency for LLM request" errors.
This adds a tutorial-specific chronicle text that describes the
actual tutorial scenario: Sadar Rakon defending Onmaa against
King Bregos Fyar's army led by Ikhaan Tarn.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update tutorial chronicle text wording
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Register SupportField dynamically for tutorial dialogue highlight
The SupportField highlight in the tutorial_rebuild_support and
tutorial_support_deadline dialogues wasn't appearing because the
static Inspector reference on TutorialTargetRegistry was not wired up.
Dynamically register the field from EagleGameController, matching the
pattern used for GoToBattleButton and CommandPanel.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix Elena's reinforcement dialogue referencing Tarn incorrectly
Tarn and the King are still allies at this point, so "half of them
had already fled before Tarn's men even arrived" doesn't make sense.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Vary Ranil's siege dialogue to avoid repeating Marek's phrasing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix highlight not rendering inside layout groups
The dialogue highlight was invisible when targeting UI elements inside
a layout group (e.g. SupportField). The highlight container was being
treated as a layout child, which collapsed its rendering. Adding
LayoutElement with ignoreLayout=true makes it overlay correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Make Reset Tutorials also reset dialogue and trigger state
ResetAllProgress previously only cleared the old tutorial system's
PlayerPrefs state. Now it also clears DialogueManager's completed
script IDs and TutorialTriggerRegistry's one-shot flags, so all
narrative dialogues can replay without restarting the game.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix NRE: add RectTransform before LayoutElement
LayoutElement is a UI component that auto-creates a RectTransform,
so the subsequent AddComponent<RectTransform>() returned null.
Reorder so RectTransform is added first.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The conditional `if po.support > 0` prevented explicitly setting
support to 0. Now support always uses the JSON value directly.
Provinces 37 and 40 already have their support values specified.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Remove the special case that allowed Ikhaan Tarn to flee.
Now all units in the tutorial battle must fight to the end.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When we added Hedrick as an independent faction (ID 5), we didn't
add it to the aiFids list in createTutorialGameForUser. This caused
the game to get stuck waiting for commands from a faction that had
no AI controller.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Override duel_base_accept_odds to 200 (max roll is 100) for tutorial
games so the player's first duel challenge is always accepted,
ensuring the tutorial can teach dueling mechanics reliably.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Split reinforcement dialogue into per-hero events for staggered arrivals
Now that reinforcements arrive at different times (Elena round 5, Ranil
round 6), inspect the arriving unit's profession to fire hero-specific
dialogue triggers instead of one generic event for all reinforcements.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update tutorial dialogue and add capture/duel triggers
- Update Elena Fyar's reinforcement dialogue: escaped captivity in Pieska
- Update John Ranil's reinforcement dialogue: pushed through from Nikemi
- Add enemy hero captured dialogue trigger with dynamic hero name
- Add friendly hero captured dialogue with special cases for Marek/Sadar
- Prioritize duel tutorial over melee when target is not Ikhaan Tarn
- Add GetScriptForTrigger to DialogueManager for dynamic text injection
- Cache unit names for capture detection (units removed before processing)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix capture detection to scan removed units instead of action type
CapturedWhileFleeing only fires for flee-based captures. Heroes also
leave the battle via duels, destruction, etc. Now scans every action
result's GameStateViewDiff.RemovedUnits for hero units in the name
cache, catching all forms of hero removal.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Make Ranil reinforce from Tarn's direction
Elena continues to reinforce from position 6, but John Ranil
now enters the battle from the same starting position as Tarn
and the other attackers.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Delay Ranil's arrival to round 7
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Reduce Sadar's unit sizes by 10%
- Rakon's Loyalists: 511 → 460
- Onmaa Defenders: 300 → 270
- Hunters of the Steppe: 384 → 346
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Refactor beast prefab selection to switch and add extensibility doc
Replace IsDragon boolean check with GetEffectPrefab switch on beast
name string, providing a clear extension point for adding new
beast-type-specific effects. Add docs/BEAST_EFFECTS.md explaining
the architecture and step-by-step process for adding new beast types.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use exact beast name matching from beasts.tsv
Switch from case-insensitive substring matching to exact string
matching on the canonical singularName from beasts.tsv. Update doc
with beasts.tsv reference and candidates for custom effects.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add dragon beast animation on eagle map
When a province has a dragon beast event, show an animated 2D dragon
(DungeonMonsters2D DragonRed) circling the province instead of generic
vultures. The dragon plays its Move animation while orbiting and
periodically triggers Attack with fire rain particles falling below.
- New DragonEffect.cs: instantiates animated dragon prefab, drives
circular orbit with vertical bobbing, flips sprite to face travel
direction, emits fire rain particle bursts on attack
- ProvinceBeastsController: extracts beast name from BeastsEvent,
selects dragonEffectPrefab when name contains "dragon"
- Add DragonRed from DungeonMonsters2D asset pack (dragon-only files)
- Add .psb and .psb.meta to Git LFS tracking in .gitattributes
- Add 2D PSD Importer package dependency
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix dragon animation and improve fire breath effect
Add com.unity.2d.animation package required for SpriteSkin bone
deformation at runtime. Rewrite fire breath as directional cone aimed
at province center using stretch render mode. Continuously enforce Move
animation state to prevent controller defaulting to Idle. Add context
menu test helpers for spawning dragon effects in editor.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add animation variety and disable fire breath for now
Dragon now cycles through Move, Jump, Attack, and SpecialAttack
animations with weighted random selection. Fix IsInTransition check
so action animations play fully before returning to Move. Update
defaults to match tuned Inspector values. Disable fire breath
particle system pending direction/sizing fixes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add Hedrick faction in Kojaria, move Bridget to Alah
- Move Bridget's faction from Kojaria (39) to Alah (18)
- Create new faction for Hedrick the Hedge-Merchant in Kojaria (39)
with 300 light infantry, 23 support, same development as Onmaa
- Remove Hedrick from battle reinforcements (now only Elena and
John Ranil arrive as reinforcements at rounds 6 and 7)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix reinforcement hero IDs and adjust timing
- Update TutorialReinforcementHeroIds to Set(100, 101) to match
the two remaining reinforcement heroes (John Ranil and Elena)
- Change reinforcement timing: Elena arrives after round 5,
John Ranil arrives after round 6
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
ExecuteReinforcementsAction was placing ALL PENDING_REINFORCEMENT
units when any reinforcement event fired, instead of only the units
belonging to that specific event. Match pending units by eagle_hero_id
against the event's CommonUnit list so each event only activates its
own reinforcement.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The updateWith call was only setting gold and food, ignoring the
support, economy, agriculture, and infrastructure values from the
tutorial_parameters.json configuration. This caused Onmaa to have
default values (0 support, 20/20/20 development) instead of the
configured values (23 support, 45/41/42 development).
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Set up 12 King's provinces with single noProfession heroes, no troops,
no resources, and low loyalty to portray the realm falling apart:
- East Faluria, Regigia, Tegrot, Berkorszag, Oscasland, Wichel,
Oriyslia, Mesh, Musland, Motcia: loyalty 50-55
- Nikemi, Pieska: loyalty ~40 (even weaker)
Also refactored the attacking army (Ikhaan Tarn's force) to be
created directly in TutorialGameCreation.scala rather than read
from JSON, since the army is already in transit when the game
starts - it's not actually stationed in Nikemi.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add fear success and fear resisted animations
Fear success: dark cloud engulfs the target, pulses ominously, then
slowly dissipates. Fear resisted: a white flash at the target deflects
the wave back toward the source where it shrinks and fades.
Split Fear into three animation types (Fear attempt, FearSuccess,
FearResisted) so the server result triggers a distinct visual. The
pending command handler now also plays result animations, not just
sounds. Rename IsFailureVariant to IsResultVariant to handle both
success and failure result matching.
Add test buttons for Fear Success and Fear Resisted in the animation
test controller.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Refactor test controller to use real sound+animation flow and fix fear attempt sound
Route most animation tests through TestPlayAnimationAndSound to exercise
the real two-stage sound pipeline. Consolidate duplicate PlayAnimation
methods and remove ?? fear fallback that caused double-play.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix fear sound routing and remove test debug logging
Add FearSuccess/FearResisted to ActionTypeForSound so they play the
correct result sounds. Pass isOwnCommand=true in test path so Fear
exercises the two-stage attempt sound.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Improve fear success and fear resisted animations
Fear success: tendrils start 2x farther out with ease-in spiral, add
ghost skull at 40% opacity filling the hex. Fear resisted: shield sprite
pops up and bashes the skull back toward the source with spin and flash.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Cut defending unit sizes by 25% (681→511, 400→300, 512→384)
- Cut reinforcement unit sizes by 25% with battle-worn variation
(450→443, 375→382, 450→446)
- Double food in Onmaa (2000→4000) to support larger army
- Set Onmaa development stats: 45 economy, 41 agriculture, 42 infrastructure
Reinforcements already arrive one at a time (rounds 5, 6, 7).
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Tutorial game creation was passing an empty pregeneratedHeroes pool to
HeroGenerator, causing a crash when PerformUnaffiliatedHeroesAction
tried to spawn new heroes in provinces (e.g., when building support in
Onmaa).
Changed to use GameParametersUtils.pregeneratedOthers as the pool,
excluding hero names already used in the tutorial to avoid duplicates.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add duel animations and programmatic test buttons
Add gauntlet throw animation for duel challenges (arcing projectile with
dust landing effect) and duel declined animation (dismissive sword sweep).
Wire duel challenge as a two-stage sound so the challenge clip plays on
command and accepted/declined plays on server response.
Refactor AnimationTestController to generate buttons programmatically
from a prefab instead of requiring manual wiring per animation. Remove
redundant private animator fields in favor of direct gameController
access.
Remove black background from Iron-Etched-Gloves sprite for use as the
gauntlet throw projectile.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Make gauntlet throw more aggressive and play sound at impact
Change gauntlet animation from a gentle arc to a violent downward slam
from above the source hex to the lower half of the target hex with
cubic ease-in for an accelerating impact feel.
Move challenge sound into the animator so it plays at impact rather
than when the command is issued. The two-stage system still tracks the
pending command for the accepted/declined result sound.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Change deploy conditions from event_name == 'push' to
event_name != 'pull_request', matching the pattern already used by
the Upload Addressables step. This allows workflow_dispatch to
trigger a full deploy, which is needed for re-deploying without
a code change.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Migrate client update system to SHA-addressed blob storage
Upload game files and installer to blobs/<sha256> with immutable cache
headers, serve manifests with Cache-Control: no-cache so CDN always
revalidates. This eliminates the need for CDN cache purges after builds.
During the transition period, files are also uploaded to their legacy
paths (unity3d/win/<path> and installer/Eagle0.exe) so existing
installers continue to work until users auto-update.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add blob cleanup workflow and fix installer manifest URL
- Add daily scheduled GitHub Action to garbage-collect unreferenced blobs
- Fix installer_build.yml manifest step to use blobs/<sha> URL instead
of the legacy installer/Eagle0.exe path
- Add installer_sha256 to manifest for blob cleanup tool
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Revert installer blob changes — installer staleness is benign
The installer self-updates on next launch, so CDN staleness for
installer/Eagle0.exe is harmless. Keep blob addressing only for
game files where manifest/file mismatch causes download failures.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use manifest comparison instead of HEAD requests for blob dedup
Fetch the previous manifest to determine which SHAs already exist as
blobs, avoiding N HEAD requests per build. Also only upload to legacy
unity3d/win/ paths when the file at that path actually changed, matching
the old behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove legacy unity3d/win/ uploads — installer self-updates first
Old installers fetch the manifest, see a new installer version, and
self-update before downloading game files. The new installer fetches
from blobs/<sha>, so there's no need to keep uploading to the legacy
unity3d/win/<path> paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Set no-cache on installer and whats-new.json uploads
Both are mutable files at fixed CDN URLs that were relying on default
cache behavior. Setting no-cache ensures the CDN always revalidates,
so users get the latest installer on first download and see current
changelog entries immediately.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add unit tests for the Eagle strategic tutorial event system:
- Game type tests: verify only Tutorial games trigger events
- Eagle appearance negative cases: events don't fire when:
- Bridget still has provinces
- Eagle faction already exists
- Idempotence tests: verify no re-trigger after Eagle appears
Also updates visibility in BUILD files to allow test access.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Tests verify the tutorial config structure including:
- 3 reinforcement events with correct IDs
- Round triggers at 5, 6, 7 for Hedrick, Elena, John Ranil
- Correct hero IDs in reinforcement units
- Tarn visibility configured for defender
- Reinforcement heroes and battalions in game state
Also fixes :engine BUILD target to export game_history and
battle_resolution which appear in the Engine interface.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Battalion backstory updates were never generated - only initial backstories
are created. This removes the unused code:
- BattalionBackstoryUpdateAction.scala
- BattalionBackstoryUpdatePromptGenerator.scala
- BattalionBackstoryUpdateRequest from LlmRequestT sealed trait
- BattalionBackstoryUpdateRequest proto message (field 41 reserved)
- Related cases in LlmResolver and proto converter
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When a province is conquered, the new owner can now see the backstory
text of unaffiliated heroes in that province. This matches the behavior
of PerformReconResolutionAction which extends backstory visibility for
ruling heroes to the faction performing recon.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The admin server was fetching whats-new.json from the public CDN URL
(assets.eagle0.net) which caches aggressively. After adding an entry,
the page reload would fetch the stale cached version, making it look
like the entry wasn't saved. Read directly from S3 so the admin server
sees its own writes immediately.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Detect NewRoundAction with month=1 after the tutorial battle has ended.
Old Marek explains what taxes provide (gold and food) and advises on
how to spend them wisely.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The GraphQL layer was a workaround for a token permissions issue that
has since been fixed. The REST /git/refs endpoint works correctly with
fine-grained PATs when contents:write is properly configured. This
removes ~90 lines of GraphQL code.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Right-click (or long-press on mobile) is a well-known RTS convention that
eliminates confusion from having two melee input methods. Left-click remains
the default action (move/archery); right-click triggers melee on adjacent
enemies.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When the player has a stable province (40+ support) but only one province,
Old Marek suggests using March to expand to a neighboring province and
explains the sworn sibling/Warlord control mechanic.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When the TUTORIAL_FACTION_APPEARS action result fires, Old Marek warns
the player about the Fracture Covenant seizing Ingia and Soria, and
introduces The Eagle as a mysterious new threat.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The REST /git/refs endpoint returns 403 with fine-grained PATs even
when contents:write is granted. Switch to the GraphQL createRef
mutation which handles the same permission correctly.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Marty McPolitics (BRV 8) and Aldric the Overlooked (BRV 14) would
decline duel challenges in the tutorial, frustrating players trying
to learn dueling mechanics. Replace them with Luke the Prank-tricker
(BRV 100) and Lucia, Queen of the Marshes (BRV 100) from
generated_heroes.tsv. Both are noProfession and would still lose
to Sadar Rakon.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When the player wins the tutorial battle, multiple enemy heroes can be
captured (Tarn, Edgtheow, Julius, McPolitics). Only the first captured
hero gets a messageId via CapturedHeroPleaGenerator.
When TutorialTarnDisappearsAction removes Tarn (who was first and had
the only messageId), the remaining captured heroes have no messageId.
This causes BattleAftermath to be stuck:
- capturedHeroes.nonEmpty = true (others remain)
- hasAvailablePlayerCommands = false (no messageId means no commands)
Fix by generating a plea for the next captured hero when Tarn is
removed, ensuring BattleAftermath has valid commands to show.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Pace combat tutorials to show one at a time with action between each
Archery, melee, charge, and duel tutorials now fire one at a time during
tactical combat. After a tutorial fires, the next one waits until a battle
action occurs (by either player) before showing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Defer battle-running dialogue to player's first turn and pace start fire
- Move shardok_battle_running trigger from setup→running transition to
the player's first turn in running state, so the dialogue appears when
the player can act rather than during the enemy's opening move
- Update dialogue text: "The enemy is drawing near" instead of "Now the
real fight begins"
- Add start_fire_available to the paced combat tutorial set with lowest
priority
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Instead of all three reinforcement heroes arriving at round 5,
they now arrive one at a time:
- Round 5: Hedrick the Hedge-Merchant (longbowmen)
- Round 6: Elena Fyar (heavy infantry)
- Round 7: John Ranil (heavy cavalry)
This gives the player time to position each new unit strategically
as they arrive.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The TarnNameTextId was hardcoded as "hero_name_ikhaan_tarn" but hero
nameTextIds are generated using MurmurHash3 (e.g., "fhn_d331492f").
This caused the hero lookup to fail, making canFleeHeroIds empty.
When canFleeHeroIds is empty, the fallback logic allows ALL units
with fleeProvinceId set to flee.
Fix by using LoadedHero.nameTextId("Ikhaan Tarn") to generate the
correct hash-based ID, so only Tarn can flee as intended.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add persistHighlight field to DialogueStep. When the final step of a
dialogue has this set, the highlight stays visible after the panel is
dismissed. It clears when the next dialogue trigger fires (e.g. the
player enters the battle) or when a new dialogue starts.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Allows map editor changes to be submitted as GitHub PRs directly from
the admin UI. Uses the GitHub REST API with a fine-grained PAT (stored
as ADMIN_GITHUB_TOKEN secret) to create a branch, commit the map file,
and open a PR -- no git credentials needed on the admin server.
The button only appears when GITHUB_TOKEN is set. The flow saves the
map first, then creates the PR, showing a clickable link to the result.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add ability to specify that certain units should be visible to specific
players from the start of a battle. Used in the tutorial to make Tarn's
unit visible to the player so they can see where he is positioned.
Changes:
- Add visible_to_player_ids field to CommonUnit proto
- Add InitialUnitVisibility message to TutorialBattleConfig proto
- Apply initial visibility in ShardokGamesManager when setting up units
- Set Tarn's unit visible to defender in tutorial config
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Plan post-battle tutorial dialogue in docs
Document two narrative dialogues for after the first tutorial battle:
1. Battle Aftermath - Marek on the close call, Tarn's disappearance,
and recruiting captured lieutenants
2. Rebuild Support - Marek explains Improve and Give Alms to raise
province support before January taxes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Implement post-battle tutorial dialogues and triggers
Add two narrative dialogues after the tutorial battle:
- Battle aftermath: Marek on Tarn's disappearance and captured lieutenants
- Rebuild support: Marek guides player to use Improve and Give Alms
Trigger detection uses lifecycle flags to ensure correct ordering:
tutorial_battle_ended fires when battle is removed and commands arrive,
tutorial_rebuild_support fires after captured heroes phase completes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix rebuild dialogue: any hero can improve/alms, need 40 for taxes
Engineers and paladins are more effective, not the only ones who can
do it. Support threshold of 40 is required to collect taxes at all,
not just for higher revenue.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Highlight SupportField and refine tax instruction text
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add loyalty warning and support deadline tutorial triggers
- November loyalty warning: Marek warns about heroes with <70 loyalty,
highlights the loyalty label, instructs to use Give Gold or Feast
- December support deadline: Marek urges the player to have Elena give
alms immediately if support is still below 40
- Register first vassal's loyalty label as LoyaltyLabel highlight target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Drop 'closer than I'd like to admit' from post-battle dialogue
Marek wouldn't admit it was close — he thought it was hopeless.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use default panel position for strategic dialogues, top for combat
Hard-code panel position based on context: if a Shardok model is active,
use "top" (combat); otherwise use default position (strategic map).
Remove panelPosition overrides from strategic dialogue JSON.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add a separate tutorialStartDate constant so the tutorial starts in May
while regular games continue to start in August.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The updateWith() call in TutorialGameCreation was missing the gold and
food parameters from the tutorial config, causing all provinces to start
with default values (0) and immediately trigger starvation.
Also set reinforcement heroes to start with 80 loyalty.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The AI-generated headshot for The Talismangobbler was too revealing.
Regenerated with a more appropriate image and uploaded to DigitalOcean
Spaces.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Silvio the Smooth (strength 92) was too strong a lieutenant. Marty McPolitics
(strength 24, bravery 8) is a political opportunist who "is no good in a fight"
- Sadar Rakon would clearly defeat him in a duel.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The beforeunload handler wasn't reliably showing a dialog in all
browsers. Add a click listener that intercepts link navigation with
a confirm() dialog when there are unsaved changes.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Set Sadar Rakon's first Light Infantry battalion to size 681 (up from 381)
and armament 75 (up from 60) for a stronger starting force in the tutorial.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
After the tutorial battle resolves, Tarn will either have fled (returning
to Nikemi) or been captured. This creates a TutorialTarnDisappearsAction
that:
- Removes Tarn from captured heroes list if captured
- Removes Tarn from incoming army to Nikemi if fled
- Clears Tarn's faction ID
- Removes Tarn from King's Loyalists leaders list
He remains in the main heroes list but is effectively disappeared.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add tutorial triggers for duel, engineer bombard, and hide
- Duel: fires when ChallengeDuelCommand is available; Marek warns
against dueling Tarn but encourages challenging his lieutenants
- Engineer bombard: fires when John Ranil is within 3 hexes of an
enemy; Ranil explains fortify + reduce workflow
- Hide: fires when Hedrick has HideCommand available; Hedrick
describes slipping into cover
Adds TMP sprite assets for Duel, Fortify, and Hide icons with
fallback chain in dragoons.asset.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix duel tutorial: only champions can duel, loser may be captured
- Marek addresses Sadar directly (you can challenge, not "one of our heroes")
- Instructions clarify that Champions issue duels, target can't be in a castle
- Loser may be captured (not slain), evenly matched duelists may be inconclusive
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Remove verbose descriptions from the unit type instruction panel.
The icons and names are self-explanatory — players don't need to be
told that cavalry are fast or that heavy infantry are slow.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The tile images are isometric with the hex face compressed to roughly
half the image width in height. The old formula (sqrt(3)*width/2 = 222px)
assumed a non-isometric flat-top hex, but the actual hex face is only
~128px tall. This caused the source crop to include transparent space
above and 3D depth below, making tiles appear shifted down with white
gaps at the top of each hex.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The self-hosted runner doesn't have Node.js in PATH, causing the
node --check step to fail with 'command not found'.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add canFleeHeroIds field to ShardokBattle that overrides the army-level
fleeProvinceId logic. When this set is non-empty, only heroes in the
set can flee.
For the tutorial:
- Set canFleeHeroIds to only contain Tarn's hero ID
- Set reinforcement units to canFlee = false
This ensures all units except Tarn are locked into the tutorial battle.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The charge tutorial previously fired for any unit with a charge
destination. Now it only fires when Old Marek the Learned himself
can charge, and the dialogue has him volunteering to lead the charge.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
A git conflict marker was accidentally left in map_editor.js after a
rebase merge, breaking the map editor in production. This adds two
safeguards:
- CI lint job: `node --check` to catch JS syntax errors
- Pre-commit: `check-merge-conflict` hook to catch conflict markers
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When defending reinforcement units (from tutorial) survive a battle,
add them to the defending province's rulingFactionHeroIds and
battalionIds. Previously, these units existed in the game state but
were never assigned to a province after the battle ended.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
A rebase conflict marker was accidentally left in the file, causing
a JavaScript syntax error that broke the map editor entirely.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Fix race conditions in reinforcement hero headshot resolution
Two race conditions prevented headshots from showing for mid-battle
reinforcement heroes:
1. The eagle NewHeroes update and Shardok ChangedReserveUnits update
arrive as separate messages with no guaranteed ordering. If the
Shardok update arrives first, units are created with null headshot
and name data.
2. Streaming hero name text may not have arrived in ClientTextProvider
when the dialogue fires, so name-based matching fails.
Fix: propagate NewHeroes from the eagle model to all running
ShardokGameModels (backfilling any units created with missing data),
and retry headshot resolution each frame in DialogueManager until it
succeeds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use case-insensitive name matching and model display names for dialogue
Replace ResolveHeadshotPath with ResolveSpeaker that:
- Uses case-insensitive name matching (fixes "Hedge-Merchant" vs
"Hedge-merchant" mismatch for Hedrick)
- Returns both the headshot path AND the display name from the model,
so the dialogue shows the canonical hero name from the game data
Remove the per-frame retry mechanism — reinforcement heroes are in the
eagle game state from the start, so there is no race condition.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove unnecessary RegisterNewHeroes propagation
Reinforcement heroes are in the eagle game state from tutorial
creation, so they're already in the ShardokGameModel's dictionaries
when the battle starts. No need to propagate them later.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The tile images contain flat-top hexes (height = sqrt(3)/2 * width),
but our grid uses pointy-top hexes (height = 2/sqrt(3) * width) which
are ~15% taller for the same width. Scale based on the flat-top hex
height within the source image so the hex content fills the full
canvas hex vertically. Excess width is clipped.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The tile images contain flat-top hexes (height = sqrt(3)/2 * width),
but our grid uses pointy-top hexes (height = 2/sqrt(3) * width) which
are ~15% taller for the same width. Scale based on the flat-top hex
height within the source image so the hex content fills the full
canvas hex vertically. Excess width is clipped.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add Silvio the Smooth as a 5th hero in province 31 (before Aldric who
stays behind), and add a light cavalry battalion (size 450) to the
attacking force.
Also give the battalions proper names:
- Doomriders (heavy cavalry)
- The Shardok's Guard (heavy infantry)
- Bowmen of Nikemi (longbowmen)
- Swift Sabres (light cavalry)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Center the tile image on the hex center (center.y - dh/2) instead of
aligning the image top with the hex top vertex (center.y - HEX_SIZE).
The scaled image is taller than the hex and the hex clip handles
cropping the excess.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Remove "cruelty" and "madman" from Tarn description; replace with
erratic/paranoid/inscrutable behavior (he's not evil, just alarming)
- Add dragoons to Marek's unit rundown and instruction panel
- Rewrite reinforcement hero intros to reference backstories and the
strategic situation rather than self-introductions of abilities
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Reinforcement heroes (e.g. John Ranil) arrived mid-battle with no
headshot or name because ShardokGameModel's hero dictionaries were
only populated at battle creation. Add a fallback lookup to the eagle
model's Heroes collection so mid-battle heroes resolve correctly.
Also extract the hardcoded bridge scale (15) to an Inspector field on
HexGrid, defaulting to 22.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>