New quest command choosers:
- SpendOnFeastsQuestCommandChooser: Selects FeastCommand when quest active
- RestProvinceQuestCommandChooser: Rests in target province
- SendSuppliesQuestCommandChooser: Sends food to target province
- ExecutePrisonerQuestCommandChooser: Executes specific prisoner
- ReturnPrisonerQuestCommandChooser: Returns prisoner to faction
- StartEpidemicQuestCommandChooser: Starts epidemic in target province
- SwearBrotherhoodQuestCommandChooser: Swears brotherhood with target hero
Also adds Execute and Return helper methods to ManagePrisonersCommandSelector.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
SendSuppliesCommand was missing quest fulfillment logic. This adds
the incrementing of SendSuppliesQuest progress using the existing
QuestFulfillmentUtils pattern from RestCommand.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add narrow layout support for March command battalions
- Add battalionsColumnNarrow view for narrow screen layouts
- Add availableBattalionsContentNarrow to EventBasedUnitSelector for dual population
- Add PopulateBattalionsContent() helper to populate both content areas
- Toggle between original and narrow battalion views based on aspect ratio
- Keep heroes column always visible (user may march from different province)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix selectionChanged event binding on March unit selector
Clear the erroneous binding to AddTargetedBattalion - the original
SelectionChanged method was removed in 2022 and the binding should
be empty.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Remove the separate Overlay Canvas and move overlay content into the
main Shardok Canvas. This eliminates the need to duplicate view hierarchy
between the two canvases.
- Remove overlayCanvas reference from HexGrid
- Remove Start() that was setting overlayCanvas.overrideSorting
- Move overlay content to be a sibling after hex grid in Shardok Canvas
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add quest fulfillment logic to ControlWeatherCommand:
- StartDroughtQuest is fulfilled when a Mage uses ControlWeather to start
a drought in the target province
- StartBlizzardQuest is also now fulfilled via the same mechanism
- Both quest types were previously marked as "action quest - fulfilled in
ControlWeatherCommand" but the implementation was missing
Changes:
- ControlWeatherCommand now extends ProtolessSequentialResultsAction instead
of ProtolessSimpleAction to support returning quest fulfillment results
- Added QuestFulfillmentChecker and allProvinces parameters to ControlWeatherCommand
- Updated CommandFactory to pass new parameters
- Updated tests to use .results instead of .immediateExecute
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add quest creation logic for StartDroughtQuest, following the same pattern
as StartBlizzardQuest:
- Requires a Mage in the faction
- Targets provinces reachable by Control Weather (faction provinces + neighbors)
- Filters out provinces that already have a drought
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Implement prisoner management quest choosers that enable the AI to
complete ReleasePrisonerQuest, ExilePrisonerQuest, and
ReleaseAllPrisonersQuest.
New components:
- ManagePrisonersCommandSelector: Reusable selector for prisoner
management commands (release, exile, return)
- ReleasePrisonerQuestCommandChooser: Releases specific prisoner
- ExilePrisonerQuestCommandChooser: Exiles specific prisoner
- ReleaseAllPrisonersQuestCommandChooser: Releases any available
prisoner to make progress toward quest completion
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
HexMetrics: Properly center the grid vertically by setting center.y to
0.75*OuterRadius. Previously the grid center ended up at -0.75*OuterRadius
instead of 0, requiring a compensating Y=32 offset on GameObjects.
Added detailed comments explaining the centering math for future reference.
HexGrid: Remove the OuterRadius/2 vertical offsets from overlay elements
(text labels, unit type images, profession images, secondary images,
terrain modifier images) that were compensating for the old centering bug.
Unity: Set Hex Grid and Overlay Mesh Y positions to 0.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Update Shardok layout for widescreen support
Unity scene changes for Shardok layout variants.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Set Weather Canvas screen overlay alpha to 0 in editor
The overlay color is set programmatically at runtime, so setting it
transparent in the editor makes it easier to work with the scene.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update Shardok layout
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update Shardok layout
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add overlay container spacer references for widescreen layout
Add overlayContainerTopSpacer and overlayContainerLeftSpacer references:
- Top spacer active on normal/narrow screens (with top row)
- Left spacer active on widescreen (with left column)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add vertical offset to overlay elements for new layout
Move all overlay elements (labels, unit type images, profession images,
secondary images, terrain modifier images) up by metrics.OuterRadius/2
to align with hex cells in the new layout group structure.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Simplify Shardok mesh and hierarchy structure
- Remove -90 rotation requirement from HexMesh by swapping Y/Z coordinates
- HexMesh now uses Y for vertical position, Z for depth (was reversed)
- HexMetrics corners updated to match new coordinate system
- Replace gridCanvas with gridTransform reference in HexGrid
- Update all animators to work with simplified hierarchy
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Consolidate HexGrid onto HexMesh GameObject
- Merge HexGrid component onto the HexMesh GameObject (renamed to Hex Grid)
- Remove redundant separate Hex Grid GameObject
- Move Overlay Mesh under Overlay Content to match Hex Grid hierarchy
- Fix Overlay Mesh transform to match Hex Grid (Y=32, no rotation)
- Update component references
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Implement TotalDevelopmentQuestCommandChooser that enables the AI to
complete TotalDevelopmentQuest by improving the province until the
total development reaches the quest target.
The chooser:
- Prioritizes repairing devastation if total devastation >= 4
- Otherwise improves the province's lowest development stat (economy,
agriculture, or infrastructure)
- Uses existing ImproveCommandSelector for command generation
Also reorders quest choosers in FulfillQuestsCommandSelector:
- Move TotalDevelopment right after Improve (higher priority)
- Move Alliance to end of list (lower priority)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When the primary LLM provider returns a 5xx error (like 503 Service
Unavailable), the system now automatically fails over to backup providers.
Key changes:
- Add ServerError case to ExternalTextGenerationError for 5xx detection
- OkHttpSseListener now creates appropriate error types based on HTTP status
- ApiKeys gains hasOpenAI/hasAnthropic/hasGemini and availableProviders methods
- LlmResolver tracks provider health with circuit breaker pattern:
- 3 consecutive 5xx failures marks provider unhealthy for 5 minutes
- Automatic failover tries providers in order: primary → openai → claude → gemini
- All failover events are logged with [LLM] prefix
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add StartDroughtQuest similar to StartBlizzardQuest and StartEpidemicQuest.
This is PR1 of the 3-PR pattern for new quests (types only).
Changes:
- Proto: Added StartDroughtQuest message and field to QuestDetails oneof
- Scala: Added StartDroughtQuest case class
- Converter: Added toProto/fromProto cases
- Fulfillment: Added case (returns false - action quest)
- Failure: Added case (returns false - never fails)
- LLM prompts: Added descriptions for DivineMessage and QuestEnded
- DivineCommand: Added province ID extraction for notifications
Note: Also added missing StartEpidemicQuest cases in DivineCommand.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
On stream disconnect, scheduleReconnect was passing None for newGameRequest
instead of using the stored request from pendingBattles. This caused an
infinite error loop when Shardok lost the game state (e.g., after a pod
restart) because it couldn't recreate the game without the map path.
The fix retrieves the stored NewGameRequest and passes it on reconnect,
allowing Shardok to recreate the game from scratch if needed.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The UI is too small on iPhone screens. Change targetDevice from 2
(Universal) to 1 (iPad only) so the app only appears in the App Store
for iPad users.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Replace deprecated altool (removed in Xcode 14) with xcodebuild
-exportArchive using App Store Connect API authentication.
Changes:
- upload_testflight.sh: Use xcodebuild with destination=upload
and API key authentication instead of altool
- ios_testflight.yml: Pass xcarchive path and use new API key secrets
Required new GitHub secrets:
- APP_STORE_CONNECT_API_KEY_ID
- APP_STORE_CONNECT_API_ISSUER_ID
- APP_STORE_CONNECT_API_KEY (contents of .p8 file)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Documents which quests the AI proactively attempts to complete via
FulfillQuestsCommandSelector and which quests are not handled.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Previously, Feast was only available when at least one hero had vigor < constitution
or loyalty < 100. This prevented players from completing SpendOnFeastsQuest when
all heroes were already at full stats.
Now Feast is available whenever the province has enough gold.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Override TargetedProvince to allow selecting the Move destination by
right-clicking a province on the map. When a valid move destination is
clicked, the Move toggle is enabled and the dropdown is set to that
province.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Fix RestProvinceQuest not incrementing when resting
RestProvinceQuest is a ComponentQuest that should increment progress when the
player uses the Rest command in the target province. The increment logic was
missing.
Changes:
- RestCommand: Added quest increment logic to check for RestProvinceQuest
where targetProvinceId matches the resting province
- CommandFactory: Pass factionProvinces to RestCommand.make
- BUILD.bazel: Added quest and unaffiliated_hero dependencies
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix RestCommandTest to pass factionProvinces parameter
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The "secure borders" quest (BorderSecurityQuest) now counts provinces owned
by allies (actual alliances, not just truces) towards fulfillment. Previously,
only provinces directly controlled by the quest faction would count.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When a recon succeeds, the quest counter wasn't being updated. Added logic to
PerformReconResolutionAction to increment quest progress using the existing
QuestFulfillmentUtils.withCountersIncremented pattern.
- ReconProvincesQuest: increments by 1 for any successful recon
- ReconSpecificProvincesQuest: increments by 1 if the reconned province is a target
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The failure check had inverted logic - it was checking if the target
province is owned by the same faction (which is always true at creation)
instead of checking if we no longer own it. Now uses the same logic as
RestProvinceQuest.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
ImproveCommand was not updating quest progress when repairing devastation.
Added quest progress tracking similar to how FeastCommand handles
SpendOnFeastsQuest - increments componentsFulfilled for any unaffiliated
hero with a RepairDevastationQuest across all faction provinces.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add hero backstory popup on hover for free heroes
Add LongHoverRowChanged handler to FreeHeroesTableController to show
the hero backstory popup when hovering over unaffiliated heroes, matching
the behavior of resident heroes in HeroesAndBattalionsPanelController.
Unity wiring needed:
- Wire popupPanel, popupPanelDetailsController, popupPanelBackstory refs
- Set LongHoverRowChangedHandler on unaffiliatedHeroesTable to call
FreeHeroesTableController.LongHoverRowChanged
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Wire up free hero backstory popup in Unity scene
Connect popup panel references and LongHoverRowChangedHandler for
unaffiliated heroes table.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add TableRowHoverDetector to UnaffiliatedHeroRow prefab
Required for hover detection to work on free hero rows.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Blizzard quests (StartBlizzardQuest) now require the faction to have a
Mage hero, and epidemic quests (StartEpidemicQuest) require a Necromancer.
This matches the profession requirements for the corresponding actions.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Instead of throwing NoSuchElementException when a userId is not found
in userIdToFactionId (which can happen after server restart), send an
UNAUTHENTICATED error to the client so it knows to reconnect.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Defer mesh collider assignment to the next frame to avoid PhysX cooking
errors during initial Shardok setup. The error only occurs on the first
battle after launch, suggesting a timing issue with mesh initialization.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Update Please Recruit Me quest UI layout
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update Ransom Offer Panel UI layout
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update Alliance, Break Alliance, and Truce offer panel layouts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Quest descriptions now show both the current province count and months
completed, e.g., "Maintain 3 provinces with Develop orders for 4 months
(2/3 provinces, 1/4 months)"
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Convert Manage Prisoners to button-style toggles
- Add ToggleGroup and ConfigureToggle for consistent toggle styling
- Remove individual RawImage icon references
- Use CanvasGroup alpha for disabled state styling (35%)
- Default selection priority: Release > Move > Exile > Execute > Return
- Move dropdown only visible when Move option is selected
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update Ransom Panel layout
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add reconSpecificProvincesQuests to QuestCreationUtils
- Creates quests with 2-4 specific province IDs to recon
- Targets provinces not controlled by the faction
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add SpendOnFeastsQuest generation
- Add spendOnFeastsQuests to QuestCreationUtils that creates quests
requiring 2-4 feasts spending 200-500 gold total
- Modify FeastCommand to track quest progress when feasts are held
- Pass factionLeaderProvinces to FeastCommand from CommandFactory
- Add quest_fulfillment_utils, quest, and unaffiliated_hero deps
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix FeastCommandTest to pass factionLeaderProvinces parameter
The FeastCommand.make method was updated to require a factionLeaderProvinces
parameter for quest progress tracking, but the test file wasn't updated.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Simplify SpendOnFeastsQuest to track total gold spent
- Remove factionLeaderProvinces parameter from FeastCommand
- Track gold spent (componentCount = totalGold, increment by goldCost per feast)
- Only check current province for quest progress
- Simplify quest creation (single random for totalGold target)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Change SpendOnFeastsQuest target gold range to 500-1000
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Check each notification's field associations individually while ignoring
affectedProvinceIds, which varies based on random quest assignment. This
prevents the test from failing every time a new quest type is added.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add repairDevastationQuests to QuestCreationUtils
- Creates quests requiring repair of 30-100 devastation points
- Update test expectations for changed random sequence
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add ReconProvincesQuest generation
- Add reconProvincesQuests to QuestCreationUtils
- Creates quests requiring reconnaissance of 3-6 provinces
- Update test expectations for changed random sequence
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix DivineCommandTest expected affectedProvinceIds
The random sequence changed with the addition of reconProvincesQuests,
causing the quest generation to produce different quests.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1. Speed up check_build_deps.sh:
- Cache expensive `bazel query deps(...)` results
- Reuse cached deps for all three checks instead of running
separate bazel query commands
- Use grep filtering on cached results instead of bazel intersect
2. Run lint job in parallel with test job:
- Split bazel_test.yml into separate lint and test jobs
- Jobs run concurrently, reducing total CI time
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Documents why SparklePlugin is built in a separate workspace due to
rules_swift version conflicts, and provides a checklist for when/how
to reintegrate it into the main workspace.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add RestProvinceQuest generation
- Add restProvinceQuests to QuestCreationUtils
- Creates quests requiring 2-4 months of rest on faction provinces
- Update test expectations for changed random sequence
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix GrandArmyQuest test to use >= instead of >
The test was using `> 5700` but with the changed random sequence from
adding RestProvinceQuest, the generated value is exactly 5700. Using
`>=` is still valid since the test checks that the quest requests an
army larger than the current count.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add Crack Down UI
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add button-style toggles to Free For All Decision selector
Match the Improve command selector pattern with ToggleGroup and
CanvasGroup for proper disabled state styling.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Convert Attack Decision to button-style toggles
- Add ToggleGroup and ConfigureToggle for consistent toggle styling
- Replace individual icon/slider/label references with tributeContainer
- Use CanvasGroup alpha for disabled state styling (35%)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Adjust Attack Decision spacing
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Rename ReconSpecificProvincesQuest to ReconProvincesQuest and add new ReconSpecificProvincesQuest
- Rename existing ReconSpecificProvincesQuest (count-based) to ReconProvincesQuest
- Add new ReconSpecificProvincesQuest that tracks specific province IDs to recon
- Update proto definitions, Quest.scala, QuestConverter.scala
- Update LLM prompt generators for both quest types
- Update CheckForFulfilledQuestsAction comment
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update C# client for ReconProvincesQuest rename and new ReconSpecificProvincesQuest
- Update DisplayNames.cs with renamed and new quest types
- Update UnaffiliatedHeroRowController.cs to handle both quest types
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Layout improvements to Issue Orders panel
- Update tutorial to explain focus province receives excess vassal supplies
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add sendSuppliesQuests to QuestCreationUtils
- Creates quests to send 1000-3000 food to other faction provinces
- Only available if faction has at least 2 provinces
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add startEpidemicQuests function to QuestCreationUtils to generate
StartEpidemicQuest for provinces that don't already have an epidemic.
Update DivineCommandTest expectations for new random sequence.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add display names and quest descriptions for:
- StartEpidemicQuest: "Start Epidemic" - Start an epidemic in {province}
- SpendOnFeastsQuest: "Host Feasts" - Host feasts costing X gold
- SendSuppliesQuest: "Send Supplies" - Send X food to {province}
- RestProvinceQuest: "Rest Province" - Keep {province} at rest for X months
- ReconSpecificProvincesQuest: "Recon Provinces" - Recon X provinces
- RepairDevastationQuest: "Repair Devastation" - Repair X devastation
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add bug report system enhancements
- Add "Report Bug" button on exception popup with pre-populated info
- Add prompt to include recent exceptions when opening bug report manually
- Add screenshot capture button in bug report panel with Discord upload
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Simplify exception handling: auto-include instead of prompting
Remove exception prompt UI and automatically include recent exceptions
in the bug report description when opening from settings menu.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Wire up bug report UI components in Gameplay scene
- ErrorHandler: reportBugButton, bugReportPanel reference
- BugReportPanelController: screenshot button, status text, preview
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix screenshot preview layout: hide container not just RawImage
Add screenshotPreviewContainer reference to hide/show the entire
container with LayoutElement, so layout group collapses the space
when no screenshot is present.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Wire screenshotPreviewContainer in Gameplay scene
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add new quest type definitions
Add proto messages, Scala case classes, and converters for:
- StartEpidemicQuest: Start an epidemic in a target province
- SpendOnFeastsQuest: Spend gold on feasts (component-based)
- SendSuppliesQuest: Send food supplies to a province (component-based)
- RestProvinceQuest: Keep a province in rest orders (component-based)
- ReconSpecificProvincesQuest: Recon a target number of provinces
- RepairDevastationQuest: Repair devastation across provinces
Also adds failure conditions for StartEpidemicQuest, SendSuppliesQuest,
and RestProvinceQuest. Other quests have no specific failure conditions.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add StartEpidemicQuest to CheckForFulfilledQuestsAction
Fix pattern match exhaustivity warning by adding case for
StartEpidemicQuest (action quest - fulfilled in ControlWeatherCommand).
Also add clarifying comment for other new ComponentQuest types.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add LLM prompt cases for new quest types
Add pattern match cases for StartEpidemicQuest, SpendOnFeastsQuest,
SendSuppliesQuest, RestProvinceQuest, ReconSpecificProvincesQuest,
and RepairDevastationQuest to both DivineMessagePromptGenerator
and QuestEndedGeneratorUtilities.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Bazel 8 changed module extension repository naming from `~~` and `~` to
`++` and `+`. Update workflows to find crane dynamically instead of
using hardcoded paths that break with Bazel version changes.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Upgrade rules_java to 9.3.0 (enables JDK 25 toolchain support)
This adds an explicit dependency on rules_java 9.3.0, which includes
support for JDK 25 remote toolchains. The build continues to use
Bazel's embedded JDK 24 by default, but JDK 25 toolchains are now
available for future use.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Mark MCTS basic test as flaky
The shardok_mcts_ai_basic_test uses Monte Carlo Tree Search which
has inherent non-determinism. Tests like PrefersArcheryOverEndTurn
and DoesNotPreferStartFireWhenNotBeneficial assert on AI decisions
that may vary between runs depending on random exploration paths.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add client UI support for ApprehendOutlawQuest (PR 2/3)
- DisplayNames.cs: Added "Apprehend Outlaw" display name
- UnaffiliatedHeroRowController.cs: Added quest description with
dynamic hero name lookup for the target outlaw
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Enable ApprehendOutlawQuest generation (PR 3/3)
Add quest generation for ApprehendOutlawQuest. The quest is generated
when there are outlaws in the faction's territory:
- Finds all outlaws across all faction-controlled provinces
- Creates one quest candidate per outlaw
- Quest requires apprehending that specific outlaw
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- DisplayNames.cs: Added "Apprehend Outlaw" display name
- UnaffiliatedHeroRowController.cs: Added quest description with
dynamic hero name lookup for the target outlaw
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Move SparklePlugin to separate Bazel workspace to resolve rules_swift conflict
The problem: grpc 1.76.0.bcr.1 requires rules_swift 3.x, but rules_apple 4.x
requires rules_swift 2.x. These have different compatibility levels, causing
bzlmod resolution to fail when both are in the same workspace.
The solution: Move SparklePlugin (the only thing using rules_apple) to a
separate Bazel workspace at `sparkle_workspace/`. This workspace has its own
MODULE.bazel with only rules_apple and Sparkle dependencies, completely
isolated from the grpc/flatbuffers dependency tree.
Changes:
- Create sparkle_workspace/ with isolated MODULE.bazel
- Move SparklePlugin source files to sparkle_workspace/
- Update build_sparkle_plugin.sh to build from subworkspace
- Remove rules_apple from main MODULE.bazel
- Add single_version_override for rules_swift 3.1.2 (for grpc/flatbuffers)
All 315 tests pass.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add WORKSPACE.bazel to sparkle_workspace to prevent parent workspace detection
Without this file, Bazel may walk up the directory tree and find the parent
workspace's MODULE.bazel, causing dependency conflicts.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This target was a development utility that's no longer used.
Removing it helps reduce rules_apple dependencies.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add ApprehendOutlawQuest type (PR 1/3)
Add a new quest type where unaffiliated heroes require the faction to
apprehend a specific outlaw currently in their territory. The quest:
- Proto: Added ApprehendOutlawQuest message with outlaw_hero_id field
- Scala: Added case class with quest fulfillment logic
- Failure: Quest fails if the outlaw is no longer in faction territory
- Fulfillment: Completed when ApprehendOutlawCommand targets the outlaw
- Updated ApprehendOutlawCommand to check for quest fulfillment and
return multiple results (main result + quest completion if matched)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix ApprehendOutlawQuest failure condition
Only fail if the hero is no longer an outlaw anywhere. Moving to a
different province is fine - quest remains valid. Quest fails only
when someone else apprehends them.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Enable WinBattlesQuest generation (PR 3/3)
Adds WinBattlesQuest to the available quest generators. The quest
requires winning 2-4 battles (randomly determined).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix DivineCommandTest for new quest type random sequence
Adding WinBattlesQuest changed the random sequence during quest creation,
causing hero 12 to get a quest with a province target. Updated test
expectation to match the new (correct) notification output.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add WinBattlesQuest type (PR 1/3)
Adds a new quest type where a hero asks the player to win X battles
(where X is 2-4). This is a ComponentQuest that tracks progress.
- Proto: Add WinBattlesQuest message and field
- Quest.scala: Add WinBattlesQuest case class extending ComponentQuest
- QuestConverter: Handle WinBattlesQuest toProto/fromProto
- ResolveBattleAction: Increment quest counter when winning battles
- CheckForFulfilledQuestsAction: Quest is fulfilled via ComponentQuest
handling when componentsFulfilled >= componentCount
This quest never fails.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add WinBattlesQuest cases to prompt generators
The exhaustive pattern matching on Quest types requires adding cases
to all prompt generators when a new quest type is added.
Also updates CLAUDE.md to require running full test suite before pushing.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- BorderSecurityQuest: Creates one quest per border province (provinces
with neighbors controlled by other factions)
- WinBattleOutnumberedQuest: Simple quest with no parameters
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Adds quest generation for BetrayAllyQuest, which is available when
the faction has at least one ally.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Instead of a generic "betray any ally" quest, BetrayAllyQuest now
targets a specific faction with `targetFactionId`. This makes the
quest more meaningful and allows players to plan strategically.
Changes:
- Proto: Add target_faction_id field to BetrayAllyQuest message
- Quest.scala: Change from case object to case class with targetFactionId
- QuestConverter: Handle new targetFactionId field
- CheckForFailedQuestsAction: Update failure logic to check if specific
target faction no longer exists or is no longer an ally
- BreakAllianceResolutionHelpers: Check if broken alliance matches
quest's target faction for fulfillment
- DivineMessagePromptGenerator & QuestEndedGeneratorUtilities: Update
quest descriptions to mention specific ally name
- UnaffiliatedHeroRowController (C#): Display specific ally name in UI
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Fix reconnecting status stuck after sleep/wake
Race condition: when device wakes from sleep, CheckForIdleTimeout and
HandleStreamingCall both try to initiate reconnection, leading to
conflicting state updates and duplicate retry timers.
Fix: Cancel thread token in CheckForIdleTimeout before disposing the
streaming call, and check the token in HandleStreamingCall exception
handlers to skip ScheduleReconnect if another path is handling it.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix threadToken scope to be accessible in catch blocks
Move threadToken declaration outside try block so exception handlers
can check if the token was cancelled.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Updates .bazelrc to use Java 21 for compilation.
Requires PR #5865 (URL deprecation fix) to be merged first, otherwise
the build will have deprecation warnings from the old URL constructor.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Adds quest generation for SwearBrotherhoodWithHeroQuest, which targets
vassal heroes in the province who are not already faction leaders.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Adds C# client display support for:
- SwearBrotherhoodWithHeroQuest: "Swear brotherhood with {heroName}"
- BetrayAllyQuest: "Break an alliance with an ally"
- BorderSecurityQuest: "Control all provinces bordering {provinceName}"
- WinBattleOutnumberedQuest: "Win a battle while outnumbered"
Changes:
- DisplayNames.cs: Add quest type strings
- UnaffiliatedHeroRowController.cs: Add quest descriptions in SetQuestText and ShortQuestString
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Adds server-side support for:
- SwearBrotherhoodWithHeroQuest: fulfilled when player swears brotherhood with the target hero
- BetrayAllyQuest: fulfilled when player breaks an alliance with an ally
- BorderSecurityQuest: fulfilled when all neighboring provinces of the target province are controlled
- WinBattleOutnumberedQuest: fulfilled when winning a battle while having fewer troops
Each quest has proto definitions, Scala case classes, converter logic, fulfillment/failure
checks, and LLM prompt generators. PR 2 will add client UI and PR 3 will add quest generation.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Adds two utility functions for checking hero faction membership:
- heroIsInFaction(heroId, factionId, heroes): Efficiently checks if a
specific hero belongs to a faction by checking hero.factionId
- heroIdsInFaction(factionId, heroes): Returns Set of all hero IDs
belonging to a faction
These functions check the hero's factionId field, which is the
authoritative source for faction membership. Heroes retain their
factionId when in a province, in a moving army, or imprisoned. They
lose it when exiled, become outlaws, or depart.
This is more reliable than checking province.rulingFactionHeroIds
which misses heroes in moving armies.
Includes comprehensive tests for various scenarios:
- Heroes in provinces
- Heroes in moving armies (retain factionId)
- Imprisoned heroes (retain factionId)
- Exiled heroes (factionId cleared)
- Outlaws (factionId cleared)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Upgrade Eagle server and JFR sidecar from Java 21 to Java 25.
Java 25 is the latest LTS release (September 2025). Scala 3.7.2
fully supports JDK 25.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Initial backstory: 50 → 100 words
- Growth per update: 20 → 50 words
- Soft cap: 150 → 300 words
This allows battalion histories to build up more narrative over time
while still keeping them from growing unbounded.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Upgrade Eagle server and JFR sidecar from Java 17 to Java 21.
Benefits:
- Better garbage collection (Generational ZGC available)
- General JVM performance improvements
- Foundation for future virtual threads adoption
- Supported until 2031
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Use JsonConvert.ToString() from Newtonsoft.Json for proper JSON string
escaping instead of manual character replacement. This handles all
control characters and edge cases correctly.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Eagle and admin server use different S3 buckets:
- Eagle: eagle0 bucket (game data) - uses DO_SPACES_* secrets
- Admin: eagle0-assets bucket (What's New) - uses ACCESS_KEY_ID/SECRET_KEY secrets
The previous change (#5847) broke Eagle by giving it credentials that only
have access to eagle0-assets. This fix:
- Reverts Eagle to use DO_SPACES_* secrets (eagle0 bucket access)
- Adds ADMIN_S3_* env vars for admin server (eagle0-assets bucket access)
- Admin container receives ADMIN_S3_* values as DO_SPACES_* env vars
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The Eagle server only needs to run Java, not compile it. Using the JRE
base image instead of JDK reduces the image size from ~273MB to ~120MB.
The JFR sidecar still uses JDK because it needs jcmd for JFR dumps.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
TrySetCanceled() can trigger synchronous continuations that re-enter
the lock (C# locks are reentrant) and call Remove() on the dictionary
while the foreach is still iterating, causing InvalidOperationException.
Fix by snapshotting values and clearing the dictionary before cancelling
the TaskCompletionSources.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The docker_build workflow was using DO_SPACES_ACCESS_KEY/SECRET_KEY
secrets which don't have write access. Use ACCESS_KEY_ID/SECRET_KEY
instead, which are the same secrets that work for CI builds.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add quest creation logic to QuestCreationUtils.scala:
- Only available if faction's provinces have at least 3 prisoners
- Returns ReleaseAllPrisonersQuest when condition is met
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add QuestTypeString case in DisplayNames.cs
- Add ShortQuestString case in UnaffiliatedHeroRowController.cs
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add ReleaseAllPrisonersQuest that requires the faction to release all
prisoners held in their provinces. The quest fails if any prisoner is
executed, moved, or traded away in ransom.
- Add proto definition and Scala case object
- Add proto converter for serialization
- Add fulfillment check (succeeds when 0 prisoners/moving prisoners)
- Add failure handling in ManagePrisonersCommand (Execute, Move)
- Add failure handling in RansomResolutionHelpers (ransom acceptance)
- Add LLM prompts for divine message and quest ended narratives
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The controller was on the What's New Panel itself, which starts disabled.
This meant Awake() wouldn't run until the panel was first enabled, but
the WhatsNewManager tries to call Show() before that happens - resulting
in the modal blocker appearing without the panel.
Moving the controller to the always-active Settings object ensures it
initializes properly at startup.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Enable battalion diversity quest generation (PR 3/3)
Adds quest creation logic for BattalionDiversityQuest to QuestCreationUtils.
Quest availability:
- Only if province currently has 1-2 battalion types
- This encourages players to diversify their army composition
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Clarify PR dependency structure in quest docs
PR 2 (client) and PR 3 (generation) both depend on PR 1 (types),
but are independent of each other. They can be developed in parallel
and merged in either order after PR 1.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The existing cleanup only removed dangling images. Now also removes
images older than 24h to prevent disk space exhaustion while keeping
recent images available for rollback.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The S3 API requires credentials with bucket access, but the file is
publicly accessible at assets.eagle0.net. Use HTTP GET for reading
(no credentials needed) and keep S3 API only for writing.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>