Documents analysis of why client notification generators currently
require per-action-result game state diffs, and proposes a solution
to enable diff batching by moving display data to server-side.
Deferred for now as current performance is acceptable.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Archive step now skips signing (CODE_SIGN_IDENTITY="-")
- This avoids "UnityFramework does not support provisioning profiles" error
- Export step handles all signing with proper certificate and profile
- Added signingCertificate to export options
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Change UNIVERSALLY_VISIBLE_TYPES from Vector to Set for O(1) contains
- Replace O(n²) Vector concatenation with O(n) ListBuffer in filterForPlayer
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Fixes "xcode-select: error: tool 'xcodebuild' requires Xcode" when
the build machine's active developer directory is set to Command
Line Tools instead of Xcode.app.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The unrequestedTextHandler was being called BEFORE the game was added to
gameControllerInfos. This caused isGameValid(eagleGameId) to return false
for the game being loaded, causing all its LLM requests to be skipped
as "game deleted".
The fix adds the game to gameControllerInfos BEFORE calling the text
handlers, then updates it again after text handling completes.
Also add eagleGameId (in decimal and hex) to LLM request logging.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Logs each unrequested text being processed with its type, ID, and
requestedAfterHistoryCount. Also logs the result of each request
(submitted, bypassed, deferred, or waiting on dependency).
This helps diagnose why certain LLM requests are repeatedly triggering
disk loads for old game states.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
TryRestoreSessionAsync() fires OnLoginSuccess on success, which triggers
ConnectWithOAuth() via the OnOAuthLoginSuccess handler. The calling code
was also calling ConnectWithOAuth() directly when restore returned true,
causing _createConnection() to be called twice in quick succession.
This caused the first connection to be disposed while still connecting,
leading to ObjectDisposedException and slow reconnection recovery.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Skip validation when replaying persisted action results
Create separate appliers:
- validatingApplier: for new results (e.g., new game creation)
- replayApplier: for replaying persisted results from storage
Persisted results were already validated when first applied, so
re-validating them during replay is unnecessary overhead.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add logging to track when stateAfter loads from disk
Logs the requested count, persisted count, and how far back in history
the request is going. This helps diagnose performance issues with LLM
prompt generation that requires loading historical game states.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The previous implementation used Vector with `:+` append in a foldLeft,
which is O(n) per append, giving O(n²) total time for n results.
Changed both formAwrs and formAwrsFromScala to use:
- ListBuffer for O(1) amortized append
- foldLeft with tuple to track (buffer, currentState)
This should significantly improve performance when replaying persisted
game history.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When multiple runners on the same machine try to sign simultaneously,
they were conflicting on the shared keychain names (build.keychain,
ios-build.keychain). This caused errSecInternalComponent errors.
Changes:
- mac_build.yml: Use build-${run_id}.keychain
- ios_testflight.yml: Use ios-build-${run_id}.keychain
- codesign_mac_app.sh: Support KEYCHAIN_NAME env var with fallback
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When a command POST fails due to disconnect, the client would get stuck
after reconnecting:
1. PostCommand() sets LastPostedToken before POST succeeds
2. POST fails due to DeadlineExceeded, command kept in pending queue
3. After reconnect, server sends update with same token
4. HandleAvailableCommands() skips update because token == LastPostedToken
5. Client stuck with no commands despite server saying YourTurn
Fix: Instead of skipping when token matches LastPostedToken and we have
no commands, accept the commands. The server will return BAD_TOKEN if
we try to re-post a successfully processed command. But if our POST
failed, we need these commands to proceed.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add Map Editor lighting data and reflection probe assets
- Add missing VassalRisesDetailsNotificationGenerator.cs.meta
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add EAGLE0_BUILD_DIR environment variable to workflows, defaulting to
/tmp/eagle0-${github.run_id}. This allows multiple runners with the
unity-mac label to run builds in parallel without path conflicts.
Changes:
- Workflows set EAGLE0_BUILD_DIR based on run ID
- Build scripts use EAGLE0_BUILD_DIR with fallback to /tmp/eagle0
- Library cache remains shared (beneficial for build speed)
To add a second parallel runner:
1. Set up a new runner with the same 'unity-mac' label
2. GitHub Actions will automatically distribute jobs to available runners
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The cleanup job needs this permission to delete intermediate
xcode-project artifacts. Without it, the gh api DELETE call
fails with HTTP 403.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Update bundle identifier to net.eagle0.eagle (matches provisioning profile)
- Set appleDeveloperTeamID in Unity project settings
- Add CODE_SIGN_IDENTITY="Apple Distribution" to use distribution cert
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Enable PleaseRecruitMe from prisoners
Prisoners can now trigger PleaseRecruitMe, allowing them to request
to join the faction that holds them. The LLM prompt includes context
about their prisoner status, which faction captured them, and when
they were captured.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix BUILD deps and use correct API for prisoner prompt
- Add missing BUILD dependencies for event_for_hero_backstory_trait and
unaffiliated_hero
- Use TextGenerationSuccess instead of non-existent TextGenerationResult.pure
- Use GeneratorUtilities.dateString for date formatting
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Reuse the same credentials already used for Mac notarization instead
of requiring a separate App Store Connect API key.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- New workflow_dispatch-only workflow for iOS TestFlight builds
- build_unity_ios.sh: Builds Unity iOS player (generates Xcode project)
- archive_ios.sh: Archives and exports IPA using xcodebuild
- upload_testflight.sh: Uploads IPA to TestFlight via App Store Connect API
Required secrets:
- IOS_CERTIFICATE: Apple Distribution certificate (.p12, base64)
- IOS_CERTIFICATE_PWD: Certificate password
- IOS_PROVISIONING_PROFILE: App Store provisioning profile (base64)
- APP_STORE_CONNECT_API_KEY: JSON with key_id, issuer_id, and key
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Additional layout improvements
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Wire up ambient volume slider
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Improve rain particle randomization and wind scaling
- Add per-particle speed variation (0.7x to 1.3x) to break up "sheet" effect
- Scale wind offset proportionally to rain speed so angle stays consistent
- Add rainWindMultiplier setting to Inspector for easy tuning
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Replace the Health link that navigated to a separate page with an
inline status indicator that auto-refreshes every 10 seconds using
htmx. This provides at-a-glance health visibility without requiring
navigation.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Consider alliances when vassals decide to mobilize
Change chosenMobilizeIfAdjacentEnemyCommand to use FactionUtils.hostileNeighbors
instead of ProvinceUtils.adjacentHostiles. The latter treats any province owned
by a different faction as hostile, causing vassals to mobilize (and withhold
supplies) even when bordered only by allied provinces.
Now vassals will correctly send supplies to their faction head when their only
neighbors are allies or have truces, rather than unnecessarily mobilizing.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Delete misleading ProvinceUtils.adjacentHostiles method
This method was named "adjacentHostiles" but actually returned provinces
owned by any different faction, ignoring alliance/truce relationships.
It had only one production usage which was just fixed to use the correct
FactionUtils.hostileNeighbors method instead.
Deleting to avoid future confusion.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add tests for FactionUtils.hostileNeighbors
Test coverage for:
- Returns hostile (no relationship) neighbors
- Excludes allied provinces
- Excludes truce provinces
- Excludes unruled provinces
- Excludes own faction's provinces
- Returns empty for province with no neighbors
- Correctly filters mixed neighbor types
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Replace the last unlicensed sound effect with Negative Effect 04.wav
from the purchased Magic Spells Sound Effects LITE pack.
This completes the asset audit - all required items are now resolved.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Wrap ensureGameLoaded in try-catch to prevent one corrupted/broken game
from blocking all game loading for a user. On failure:
- Log loudly to stderr with CRITICAL prefix and stack trace
- Send exception to Sentry for monitoring
- Return false so other games can continue loading
This ensures players can still access their other games even if one
game's save data is corrupted or fails to deserialize.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add fullscreen toggle, resolution dropdown, and F11 shortcut
- Add fullscreen toggle to settings panel
- Add resolution dropdown (same as connection canvas)
- F11 key toggles fullscreen from anywhere
- Resolution change preserves current fullscreen/windowed mode
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Wire up fullscreen toggle and resolution dropdown in Unity
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add ambient volume setting for weather sounds
- Add ambientSlider to SettingsPanelController with static AmbientVolume property
- Weather effects (rain, thunderstorm, blizzard) respect ambient volume
- Volume updates in real-time when slider is adjusted during playback
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add distant thunder sound effect
Source: Freesound #351526 by LittleRainySeasons (CC BY 4.0)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add more thunder sound effects
- thunder_loud.mp3 - mokasza (CC BY 4.0, Freesound #810746)
- thunder_crack.wav - OneSoundToRuleThemAll (CC BY 4.0, Freesound #238796)
- thunder_clap.wav - FreqMan (CC BY 4.0, Freesound #32544)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Wire up thunder sounds and ambient volume slider in Unity
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When all human factions are eliminated or have no provinces ruled by
faction leaders, the game now ends automatically instead of continuing
to run AI commands indefinitely.
Changes:
- Add endGame() method to Engine trait and EngineImpl to create a
GameEnded action result
- Add allHumansDefeated() check in GameController.performAiCommands
- Check both before the AI loop (handles game load case) and after
each batch of AI commands (handles AI destroying human factions)
- Handle edge case where game state has no factions yet (initialization)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add resolution change detection to hex grid
Detects when the mapArea size changes (e.g., resolution change) and
rebuilds the grid with new metrics. Preserves terrain textures while
recreating all UI elements with correct sizes for the new resolution.
- Track mapArea size and detect changes in Update()
- RebuildGrid() destroys and recreates cells with new HexMetrics
- OnGridRebuilt event notifies ShardokGameController to refresh labels
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update Unity scene and remove unused turnHistoryButtonText
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Adds notification generator and dispatcher registration for the VassalRises
notification added in #5642. Displays appropriate messages when a vassal
rises to lead a faction after all leaders are killed or imprisoned.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add sound effect hooks to WeatherEffectAnimator
Audio clips for weather sounds (assign in Inspector):
- rainLoopSound: gentle pitter-patter for normal rain
- thunderstormLoopSound: more intense rain for thunderstorm
- thunderSound: one-shot thunder clap (plays on lightning flash)
- blizzardLoopSound: intense wind sound
Volume controls:
- loopVolume: volume for looping sounds (default 0.5)
- thunderVolume: volume for thunder (default 0.8)
Sound behavior:
- Rain: plays rainLoopSound continuously
- Thunderstorm: plays thunderstormLoopSound + thunderSound on each flash
- Snow: no sound
- Blizzard: plays blizzardLoopSound continuously
- Sun/Clouds: no sound
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Support multiple thunder sounds with random selection
- Change thunderSound field to thunderSounds array
- Each lightning flash picks a random sound from the array
- Allows assigning Lightning Spelll 01-08 for variety
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add rain loop sound effect from Freesound
- rain_loop.ogg by aesqe (CC BY 4.0)
- Source: https://freesound.org/people/aesqe/sounds/37618/
- Rain falling on clay roof tiles, loopable
- Added attribution to docs/ASSET_AUDIT.md
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Document CC BY 4.0 sound effects for weather and runaway
Added to ASSET_AUDIT.md:
- rain_loop.ogg by aesqe (Freesound #37618) - for rain effect
- blizzard_wind_loop.wav by nsstudios (Freesound #651540) - for blizzard
- runaway.mp3 by Yap_Audio_Production (Freesound #218997) - replaces unlicensed file
Note: Files must be downloaded manually from Freesound (requires login).
Only 1 unlicensed sound effect remaining: failure_horn.mp3
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove stray download file
* Add rain and blizzard wind sound effects
- rain_loop.ogg by aesqe (CC BY 4.0, Freesound #37618)
- blizzard_wind_loop.wav by nsstudios (CC BY 4.0, Freesound #651540)
Location: Assets/Shardok/Sounds/
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Replace runaway.mp3 with licensed version
MedievalArmyRunningLoop by Yap_Audio_Production (CC BY 4.0)
Source: https://freesound.org/people/Yap_Audio_Production/sounds/218997/
This resolves the licensing issue for runaway.mp3.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Wire up weather sound effects in scene
- Assign rain_loop.ogg to rainLoopSound
- Assign blizzard_wind_loop.wav to blizzardLoopSound
- Configure thunder sounds array
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add vassal rises to leader when faction has no available leaders
When all faction leaders are killed or imprisoned, the strongest vassal
(by HeroUtils.power) is now promoted to faction leader instead of
destroying the faction. This prevents "zombie factions" that have
provinces and armies but can't act due to imprisoned leaders.
Changes:
- Add VassalRises notification and VassalRisesMessage LLM request types
- Add proto definitions for new types in notification and LLM protos
- Add proto converters for VassalRises types
- Create VassalRisesPromptGenerator for LLM narrative generation
- Modify CheckForFactionChangesAction to detect imprisoned leaders and
promote vassals when no available leaders remain
- Generate appropriate notifications and LLM messages for vassal rises
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Enhance VassalRises prompt with previous leader details
Add previousLeaderIds to VassalRisesMessage to track fallen/imprisoned
leaders. The prompt generator now includes detailed descriptions for
each previous leader including:
- Their name and full backstory via HeroDescriptionGenerator
- Status: KILLED IN ACTION or IMPRISONED in [province] by [faction]
This gives the LLM more context to generate a message that properly
acknowledges and references the fallen/captured leaders.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add weather visual effects to Shardok battles
Adds full-screen weather effects based on the current weather conditions:
- SUN: Subtle brightness overlay
- CLOUDS: Slight dimming overlay
- RAIN: Falling rain particles with wind influence
- THUNDERSTORM: Heavy rain + periodic lightning flashes
- SNOW: Falling snowflakes with drift animation
- BLIZZARD: Heavy snow with strong horizontal wind
The WeatherEffectAnimator creates particles on a dedicated canvas
overlay and updates them continuously. Wind speed from the weather
data influences particle movement.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add procedural sprite generation and pre-made sprite assets
- Add PNG sprites for rain drops and snowflakes in Assets/Shardok/Sprites/
- Add procedural sprite generation as fallback if sprites not assigned
- Update tooltip to indicate sprites are optional
- Clean up generated textures in OnDestroy to prevent memory leaks
The component now works out of the box without requiring any assets
to be manually assigned. Sprites will be generated procedurally if
the inspector fields are left empty.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove procedural sprite generation, fix canvas render mode
- Remove procedural sprite generation code (use pre-made PNGs instead)
- Force canvas to Screen Space - Overlay mode so particles fall
correctly relative to the screen instead of the 3D world
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add Weather Canvas and wire up WeatherEffectAnimator in scene
- Add Weather Canvas with Screen Overlay for weather effects
- Wire up rainDropSprite and snowFlakeSprite references
- Connect weatherEffectAnimator to ShardokGameController
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix particle positioning to use screen coordinates
- Anchor particles to bottom-left (0,0) so anchoredPosition is in
screen pixel coordinates
- Use Screen.width/height directly instead of canvas rect size
- Remove unused _canvasRect field
This ensures particles appear correctly on screen regardless of
canvas RectTransform configuration.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix particle positioning using canvas rect size
- Use canvas RectTransform rect.size instead of Screen dimensions
- This properly accounts for CanvasScaler settings
- Anchor particles to center (0.5, 0.5) and position relative to center
- Wrap-around logic uses half-width/height from center
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Double particle count and size for all weather effects
- Rain: 100→200 particles, 2x12→4x24 size
- Thunderstorm: 200→400 particles
- Snow: 80→160 particles, 3-8→6-16 size
- Blizzard: 250→500 particles
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Remove PregeneratedText proto and unused service handling
Now that clients no longer request pregenerated texts (they're sent via
normal ClientTextStore streaming), remove the unused proto messages:
- PregeneratedTextRequest/Response from eagle.proto
- pregenerated_text.proto file entirely
- Empty case handler in EagleServiceImpl.scala
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove pregenerated_text.proto reference from protos.csproj
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Replace all icons in Assets/free_icons/ (unknown license) with
equivalents from purchased Asset Store packs and delete the folder:
- blizzard.png → 16_blizzard_nobg.png (4000_Fantasy_Icons)
- rain.png → 12_Magic_rain_nobg.png (4000_Fantasy_Icons)
- thunderstorm.png → 27_Storm_nobg.png (4000_Fantasy_Icons)
- wind.png → 23_Light_blow_nobg.png (4000_Fantasy_Icons)
- thermometer.png → startFire.png (existing licensed asset)
These icons were only used in Map Editor (dev tool).
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Replace the free_icons/snow.png (unknown license) with
16_blizzard_nobg.png from 4000_Fantasy_Icons (purchased Asset Store
pack) in all locations:
- Gameplay.unity (Province Info blizzard indicator)
- Dominion Wide.prefab
- Dominion Two Rows.prefab
- Map Editor.unity
Delete the now-unused snow.png file.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Pregenerated texts are now populated into each game's ClientTextStore
and sent via normal text streaming (see #5630). This removes the
separate PregeneratedTextRequest/Response mechanism.
- Remove PregeneratedText import from EagleServiceImpl
- Replace PregeneratedTextRequest handler with no-op for backward compatibility
- Remove getPregeneratedClientText method from GamesManager
- Remove unused pregenerated_text_scala_proto dependency from BUILD
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Update asset audit to reflect current state
- 2 sound effects remaining to replace (failure_horn.mp3, runaway.mp3)
- List the 8 weather icons in free_icons folder
- Reorganize action items into Must Replace / Low Priority / Resolved
- Update recommendation section
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Delete unused weather icons (cloud.png, sun.png)
These icons were not referenced anywhere in the codebase.
Update asset audit to reflect 6 remaining weather icons.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Remove client-side pregenerated text mechanism
Pregenerated texts (hero names, backstories) are now sent to clients
via normal ClientTextStore streaming (thanks to #5630), so the separate
pregenerated text request/response mechanism is no longer needed.
- Remove ClientPregeneratedText.cs class and its Unity component
- Remove PregeneratedTextRequest sending from ConnectionHandler
- Remove PregeneratedTextResponse handling from PersistentClientConnection
- Remove ILobbySubscriber.ReceivePregeneratedTextUpdate method
- Update ClientTextProvider.GetTextEntry to not check pregenerated cache
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add resolved hero names to AvailableLeader for lobby display
The previous commit removed client-side pregenerated text storage, but
lobby UI (hero dropdowns for Create/Join game) still needed hero names.
This fix has the server resolve names and send them in AvailableLeader.name,
so the client can display them immediately without text lookups.
- Add name field to AvailableLeader proto message
- Server resolves names from ClientTextStore (running games) or
PregeneratedClientTextStore (new game options)
- HeroDropdownController uses PreResolvedNames when available
- RunningGameItem/WaitingGameItem use leader.Name with fallback
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The river crossing warning is only relevant when assaulting enemy
territory. When marching to a province owned by the same faction,
there's no assault so the river crossing isn't a concern.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When creating or loading a game, copy the relevant pregenerated texts
(hero names, backstories, initial chronicle) from the global store into
the game's ClientTextStore as complete texts.
This ensures:
- Games are self-contained and immune to TSV file changes
- Texts are saved with the game and sent to clients normally
- Old games get the pregenerated texts they need on load
This is step 1 of removing the separate pregenerated text mechanism.
The global store is still used as a lookup source, but texts are now
stored in the regular completeTexts map for each game.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Layout tweaks to game list prefabs and Gameplay scene
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix provider icon bug and handle invalid stored accounts
Bug fixes:
1. Provider icon bug: Session transfer was storing "session_transfer" as
the provider instead of using the actual provider (discord, twitch,
etc.) from the response. Now uses response.User.Provider.
2. User not found: When stored account validation fails with "user not
found" (NotFound gRPC status), the invalid account is now removed from
storage instead of just clearing the current selection.
3. NullReferenceException: When _createConnection() fails (no valid token),
_internalConnectEagle() was continuing to use _persistentClientConnection
which was null. Now returns early if connection creation failed.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When suggesting a display name from the OAuth provider's username,
sanitize it to meet our validation requirements:
- Replace spaces with underscores
- Remove invalid characters (only allow alphanumeric + underscore)
- Collapse multiple underscores into one
- Trim leading/trailing underscores
- Truncate to 20 characters max
- Return empty if result is less than 3 characters
This prevents users from seeing a pre-filled invalid name that they
would need to manually fix.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
After a user completes the invitation flow (OAuth sign-in, display name
selection), send them a welcome email with:
- Download instructions for Windows and Mac
- Download links for both platforms
- Deep link button to complete sign-in automatically
- Info about which provider/account they used
Also extends session transfer code expiration from 5 minutes to 1 hour
to give users time to install the game before the deep link expires.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When minimizing the game for OAuth browser flow, we were storing
fullscreen mode but not resolution. When restoring, we only set
Screen.fullScreen=true, which uses whatever windowed resolution
Unity had stored (often 1280x720).
Now we:
- Store both fullscreen mode AND resolution (width/height) before exiting
- Use Screen.SetResolution() to restore both together
This affects both WindowFocusManager (OAuth flow) and ConnectionHandler
(new user sign-in flow).
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Remove confirmation dialog for deleting non-pending invitations (already
redeemed/expired/revoked, no need to confirm)
- Reload page after delete actions instead of htmx partial updates (the
search endpoint returns rows with different columns than the accounts page)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Update the sign-in tutorial text to:
- Mention the "Complete Sign-In with Eagle0" button on the download page
- Clarify they should use the same account they created
Also exit fullscreen when showing the auth panel to new users (no stored
accounts) so they can see the browser with the sign-in link, then restore
fullscreen after successful login.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Root cause: HandleStartingState unconditionally overwrites ChronicleEntries.
During startup, multiple StartingState messages can arrive - a stale one
(e.g., from stateAfter(1) early in the game before chronicles existed)
arriving after a current state would wipe out existing entries.
Fix:
1. Don't overwrite existing chronicle entries with an empty list, since
chronicles are historical records that accumulate (never disappear)
2. Close the chronicle canvas if entries are cleared while visible
(defense-in-depth + correct UX behavior)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>