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>