* Update ASSET_AUDIT.md: mark Terrain Hexes and Discord logo as verified
- Terrain Hexes: confirmed Unity Asset Store purchase
- Discord logo: complies with Discord brand guidelines
- Updated action items and recommendations to reflect remaining work
Remaining items:
- 6 music tracks (1 unknown source, 5 Audius non-CC license)
- 56 Shardok sound effects (unknown origin)
- 138 StrategyGameIcons (unknown source)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update ASSET_AUDIT.md: Medieval Victory Theme is CC0 Public Domain
Found source: RandomMind on Chosic, CC0 Public Domain license.
No attribution required, free for commercial use.
Remaining items:
- 5 Dima Koltsov/Audius tracks (non-CC license)
- 56 Shardok sound effects (unknown origin)
- 138 StrategyGameIcons (unknown source)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update ASSET_AUDIT.md: Dima Koltsov tracks mostly verified as CC BY 4.0
- No Time for Greatness: CC BY 4.0 (YouTube)
- Warriors of Demacia: CC BY 4.0 (YouTube)
- Valor: CC BY 4.0 (YouTube)
- Forest Queen Tale: Presumed CC BY 4.0 (not found on YouTube)
- Clouds: Presumed CC BY 4.0 (not found on YouTube)
Remaining items:
- 56 Shardok sound effects (unknown origin)
- 138 StrategyGameIcons (unknown source)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update ASSET_AUDIT.md: StrategyGameIcons verified as Asset Store purchase
Confirmed as "Strategy Game Icons" by REXARD from Unity Asset Store.
Remaining items:
- 56 Shardok sound effects (unknown origin)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update ASSET_AUDIT.md: identify 3 sounds from Zombie Monster Undead Collection
Verified from Unity Asset Store purchase:
- raise_undead.mp3
- undead_break_control.mp3
- undead_grew.wav
Also corrected count: 37 audio files (not 56 - was counting .meta files)
34 sound effects still unverified.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update ASSET_AUDIT.md: flag anybody.mp3 and runaway.mp3 for replacement
These two sound effects have licensing issues and must be replaced.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update ASSET_AUDIT.md: also flag burnination.mp3 for replacement
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update ASSET_AUDIT.md: mark remaining sound effects as presumed Asset Store
Owner believes 31 remaining sounds are from purchased Asset Store packs:
- Fantasy Interface Sounds
- Medieval Combat Sounds
- Magic Spells Sound Effects LITE
- Medieval Battle Sound Pack
Only 3 files need replacement: anybody.mp3, burnination.mp3, runaway.mp3
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Replace anybody.mp3 and burnination.mp3 with licensed alternatives
- anybody.mp3: replaced with Positive Effect 6.wav (Magic Spells Sound Effects LITE)
- burnination.mp3: replaced with Magic Element Fire 04.wav (Medieval Combat Sounds)
Still need replacements for:
- failure_horn.mp3
- runaway.mp3
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add bridge rotation based on builder position or adjacent land
Bridges now rotate to face meaningful directions:
- For bridges built by engineers: one end faces the builder's location
- For bridges present at battle start: one end faces adjacent land
Implementation:
- Add rotationDegrees parameter to HexGrid.SetCellModifier3D()
- Track bridge builder locations in _bridgeBuilderLocations dictionary
- Add GetHexNeighbors() to find adjacent hex cells
- Add CalculateBridgeRotation() to compute angle between cells
- Add CalculateBridgeRotationFromLand() to find land for initial bridges
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Simplify bridge rotation to 3-orientation lookup table
Replace complex trigonometry with simple lookup based on hex grid
direction. Only 3 possible orientations exist:
- Horizontal (0°): left ↔ right
- Diagonal up (124°): down-left ↔ up-right
- Diagonal down (56°): up-left ↔ down-right
Add BridgeRotationCalculator with unit tests for all 6 hex directions
from both even and odd rows.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add iOS addressables build workflow
Creates a separate workflow to build and upload iOS addressables to CDN.
This is needed for iOS clients to load remote assets.
- Adds build_ios_addressables.sh script
- Adds ios_addressables_build.yml workflow
- Uploads to https://assets.eagle0.net/addressables/iOS/
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix iOS addressables build: add proto build step
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Document that bridge.png was replaced with AI-generated wooden rope
bridge icon created with ChatGPT/DALL-E 3.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
New 512x512 PNG icon featuring an isometric wooden rope bridge,
better suited for the fantasy game aesthetic. Generated with
ChatGPT/DALL-E, has transparent background.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Replace 2D bridge texture overlay with 3D bridge prefabs for more
visually appealing bridges on water tiles. The system now supports
an array of bridge prefabs for variety, selected deterministically
based on cell index.
- Add TerrainModifier3D field to HexGrid Cell struct
- Add SetCellModifier3D() and ClearCellModifier3Ds() methods
- Change ShardokGameController.bridgeImage to bridgePrefabs array
- Bridge positioned at true cell center using Geometry.AnchoredPosition
- Assign 6 bridge prefabs from TileableBridgePack in Gameplay scene
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
During blue-green deployments, warmup games may be invalidated while
LLM requests are in flight. The responses are correctly ignored, but
the log messages were noisy. Remove them since this is expected behavior.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The mobile shaders in TextMesh Pro/Shaders/ reference these include
files with relative paths but they only exist in Resources/Shaders/.
Copy them here to fix shader compilation errors.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Usage:
eagle-logs # Tail logs (follow mode)
eagle-logs -n 100 # Show last 100 lines and follow
eagle-logs --no-follow -n 50 # Last 50 lines without following
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Replace fire icon with UXWing flame and consolidate duplicates
Replaced the startFire.png icon with a clean flat-style flame from UXWing
(free for commercial use, no attribution required). Consolidated from two
duplicate copies to a single canonical copy in Assets/Images/, updating all
Unity scene references.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update ASSET_AUDIT.md with UXWing flame icon source
Document that startFire.png was replaced with the flame icon from UXWing
(https://uxwing.com/flame-icon/) which is free for commercial use with no
attribution required. Also note the consolidation from two duplicate copies
to one canonical copy.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Remove sudo symlink creation (deploy user lacks passwordless sudo)
- Simplify eagle-exec to read from /opt/eagle0/.active-instance file
- Update deploy-blue-green.sh to write active instance to file
- Keep fallback to checking running containers if file doesn't exist
Users can add their own alias:
alias eagle-exec='/opt/eagle0/scripts/eagle-exec.sh'
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add iOS/mobile support with touch-aware tooltips
- Add melee button to Shardok action bar (button 4 with [SHFT] label)
- Changed MeleeAttackGroup from -1 to 4 to show melee commands as button
- Updated Gameplay.unity to assign melee/charge commands to group 4
- Add touch-aware tooltip system using long-press pattern
- Updated HoveringTooltipTextProvider to detect touch vs mouse input
- On desktop: tooltips appear immediately on hover
- On mobile: tooltips appear after 0.4s long-press
- Created TouchAwareTooltip.cs as reusable component
- Add iOS build method to BuildScript.cs
- Generates Xcode project for building/signing
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add dedicated melee button (button9) for mobile/tablet support
- Add button9 and buttonText9 fields to ShardokGameController
- Change MeleeAttackGroup from 4 to 9 for dedicated button
- Change EndTurnCommandGroup from 9 to 12 (handled separately)
- Update melee/charge commands in Gameplay.unity to group 9
- Update End Turn command to group 12
- Add null checks for unassigned buttons in UpdateActionButtons
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix melee/charge command groups in Gameplay.unity
Set commandGroup to 9 for melee and charge commands so they
appear on the dedicated melee button and indicators work correctly.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Adds a helper script that automatically runs docker exec against the
active Eagle instance (blue or green), determined by checking nginx
config.
Usage:
eagle-exec printenv GEMINI_API_KEY
eagle-exec jcmd 1 VM.flags
eagle-exec sh
The script is deployed to /opt/eagle0/scripts/ and symlinked to
/usr/local/bin/eagle-exec for easy access.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When LlmProvider was set to "gemini", the validation code was missing a
case for extracting the GeminiModelName, causing it to default to an
empty string. This resulted in malformed API URLs like:
https://generativelanguage.googleapis.com/v1beta/models/:streamGenerateContent
which returned 404 errors.
Added the "gemini" case to properly extract GeminiModelName from
settings.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When switching LLM providers in the admin console, if the API key for
the target provider isn't configured, the validation would crash with
ExceptionInInitializerError. This fix:
- Wraps service creation in Try to catch initialization errors
- Logs the error with context (likely missing API key)
- Reports to Sentry for monitoring
- Returns a user-friendly error message
- Falls back to keeping the old setting unchanged
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The LLM settings validation in GameAdminServiceImpl was missing the
"gemini" provider case, causing "Unknown provider: gemini" errors when
trying to switch to Gemini from the admin console.
Changes:
- Add GeminiServiceImpl import
- Add "gemini" case to validateLlmSettings()
- Add "GeminiModelName" to llmSettingKeys set
- Add gemini_service_impl dep to BUILD.bazel
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Replace 13 heroes that referenced real people or copyrighted characters
with original alternatives that preserve gameplay stats and general archetypes:
Real people replaced:
- Jesse "The Body" Ventura → Magnus "The Boulder" Varnok
- Jeb! → Aldric the Overlooked
- Ringo → Tomlin the Tuneful
- Flea → Zander the Restless
Copyrighted characters replaced:
- Darth Plagueis → Voros the Undying
- Deckard Cain → Old Marek the Learned
- Skeletor → Karvax the Fleshless
- Trap Jaw → Ironmaw
- Shu Lien → Mei Shen
- Roger the Shrubber → Hedrick the Hedge-Merchant
- Kriss Kross → The Tumbler
- Daddy Mac → Silvio the Smooth
- Ul (removed Warcraft "Far Seer" reference)
Image files in eagle0-headshots bucket have been renamed to match.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Refactor LLM update interface to use Scala domain types instead of protos
Replace proto types (GeneratedTextRequest, LLMResponse) with Scala domain
type (GeneratedTextRequestT) throughout the LLM streaming interface. This
eliminates unnecessary proto conversions and simplifies the code.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove unused llm_response_scala_proto dependency from llm_resolver
The LLMResponse proto is no longer used since we now pass Scala domain
types through the LLM update interface.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Filter LLM requests for deleted games before sending
Add a game validity check to LlmResolver to prevent sending LLM requests
for games that have been deleted (e.g., warmup games during blue-green
deployments). This avoids wasted API calls and eliminates the "Ignoring
LLM response for deleted game" log messages at startup.
Changes:
- Add LlmResolverGameDeleted result type to LlmResolver
- Add isGameValid callback parameter to LlmResolver constructor
- Check game validity before sending each request
- Handle LlmResolverGameDeleted in UnrequestedTextHandler
- Pass gameControllerInfos.contains check from GamesManager
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Move proto conversion inside TextGenerationSuccess case
The proto conversion is only needed for the success case where we
actually send the request. Moving it avoids unnecessary conversion
for dependency-blocked requests.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Move claude-3-5-haiku to the top of the dropdown since it's the
fastest model for streaming responses (~1.9s TTFT vs ~5.0s for sonnet).
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add ANTHROPIC_API_KEY to docker-compose environment
Required for Claude LLM provider to work in production.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add ANTHROPIC_API_KEY to deployment workflow
Pass the secret to the deploy job and export it for docker-compose.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Only send reasoning_effort for reasoning models (o1, o3)
The reasoning_effort parameter is only valid for OpenAI reasoning models.
Sending it to non-reasoning models like gpt-5-mini causes a 400 Bad Request.
Now only includes reasoning_effort when the model name starts with "o1" or "o3".
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Treat models without -mini/-nano as reasoning models, add gpt-5.1 option
- Change reasoning model detection: models with -mini or -nano suffix are
non-reasoning; all others (gpt-5.1, gpt-5.2, o1, o3, etc.) get reasoning_effort
- Add gpt-5.1 to admin console dropdown options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The previous change incorrectly set both total players and human players
to 1. This restores the intended behavior: 7 total players (1 human + 6 AI)
with 1 human player selected by default.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
OkHttp can call onFailure with a null Throwable when there's an HTTP
error response but no actual exception. Handle this case by:
- Null-checking before calling getMessage()
- Creating a synthetic RuntimeException when t is null
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
JFR profiling revealed that the first client connection after deploy was
experiencing 5+ second delays due to lazy initialization of the AWS S3 SDK
(profile loading, TLS handshake to DigitalOcean Spaces, etc.).
This change adds a warmup() method to S3Utils that:
- Forces initialization of the lazy transferManager
- Makes a lightweight headBucket call to complete TLS handshake
The warmup is called early in Main.scala before the server starts accepting
connections, moving the initialization cost to startup time rather than
first-request time.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The --gpt-model-name CLI argument was removed in the LLM model switching
PR (97b313f6), but docker-compose.prod.yml still passed it, causing the
server to fail with "Unknown option --gpt-model-name".
LLM provider and model are now configured via settings (admin console)
instead of CLI arguments.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add LLM model switching from admin console
Implement the ability to switch LLM provider and model dynamically from the
admin console with validation before applying changes.
Changes:
- Add String setting type support (StringSetting.scala, generator updates)
- Add LlmProvider, OpenAiModelName, ClaudeModelName settings
- Modify LlmResolver to read provider/model from settings dynamically
- Add recreateLlmCallers() to allow hot-swapping LLM configuration
- Add validation in GameAdminServiceImpl that tests API before applying
- Remove gptModelName command line flag (now configured via settings)
- Update AddSettingsResponse proto with success/errorMessage fields
The admin console now shows these settings and validates them by making
a test API call before applying. If validation fails, the error is returned
and settings remain unchanged.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add dropdown UI for LLM settings in admin console
Instead of showing LLM provider and model names as text inputs in the
general settings list, display them as dropdowns with common options:
- LlmProvider: openai, claude
- OpenAiModelName: gpt-5-mini, gpt-5.2, gpt-4.1, gpt-4.1-mini, etc.
- ClaudeModelName: claude-sonnet-4-20250514, claude-opus-4-20250514, etc.
This provides better UX by making it easy to switch between known models
without having to remember exact model name strings.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When loading a game, if a hero's name text (hn_X format) is missing from
the text store, the server would throw an exception requiring the game
to be deleted. This occasionally happened when a hero was created but
the name request was somehow lost before persistence.
Changes:
- New MissingHeroNameRecovery utility: Provides a reusable method to
check heroes for missing name texts and recreate GeneratedHeroName
requests. Can be called from game load or other contexts as needed.
- GamesManager: Uses the new utility during game loading to recover
missing hero names instead of throwing an exception.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add explicit length instructions to prompt (IMPORTANT prefix)
- Add reminder at end of prompt to be selective
- Increase default word count from 200 to 400 (appropriate for 1.5 pages)
- Instruct LLM to focus on 2-3 most significant events
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Clarifies that users can right-click a province on the map or use the
dropdown menu to select a destination.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Default to 1-player game in create game dropdown
- Auto-create 1-player game on first launch (skip lobby)
- Add sign-in tutorial for users without stored accounts
- Add lobby tutorial for users who reach the lobby
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The installer is built by installer_build.yml, not the Docker workflow.
Adding exclusion prevents unnecessary Docker builds when only installer
files change.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Generate 64-bit .syso resource file for the WebView installer build.
The previous resource file was 32-bit and caused link errors with the
64-bit CGO cross-compilation.
Changes:
- Add resource_windows_amd64.syso (64-bit COFF object with icon/version)
- Update genrule to copy the .syso file during build
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Fix genrule variable syntax for manifest public key
Genrules use Make variable syntax $(VAR), not curly brace syntax {VAR}.
The curly brace syntax only works in x_defs for go_binary rules.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Use --action_env to pass MANIFEST_PUBLIC_KEY to genrule
Bazel genrules don't support workspace status variable substitution
in cmd strings. Instead, use --action_env to pass the environment
variable into the sandbox, where it can be referenced as a regular
shell variable ($$MANIFEST_PUBLIC_KEY in the genrule cmd).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Handle missing MANIFEST_PUBLIC_KEY env var in genrule
When building via 'bazel test //src/main/go/...', the genrule runs
without --action_env=MANIFEST_PUBLIC_KEY set. Use bash default value
syntax ${VAR:-} to default to empty string when the env var isn't set,
allowing the build to succeed with an empty public key.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Simplify public key handling - let empty var expand naturally
Bazel doesn't support ${VAR:-} syntax in genrule cmd. Instead, just
use $MANIFEST_PUBLIC_KEY directly - if not set, it expands to empty
string which the installer handles gracefully.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add manual tag to exclude webview installer from wildcard builds
The webview installer genrule requires --action_env=MANIFEST_PUBLIC_KEY
to be set. Adding tags = ["manual"] excludes it from wildcard patterns
like "bazel test //src/main/go/..." so it won't fail in the test workflow.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
PR #5459 accidentally broke Shardok update streaming when refactoring
postBattleUpdate to make Engine protoless. The original code called
withHandledEngineAndResults() which invoked humanClientsAfterPostingResults
to push updates to connected clients. The refactored code updated the
history but skipped client notification.
This caused clients to only receive Shardok updates on initial subscription
or reconnect, not during ongoing gameplay. The heartbeat would detect sync
mismatches (client=X server=Y) but updates weren't pushed.
Fix: Call humanClientsAfterPostingResults in postBattleUpdate to push
Shardok results to connected human clients, restoring the behavior that
was lost during the refactor.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add stamp = 1 to the genrule so that {STABLE_MANIFEST_PUBLIC_KEY}
gets substituted with the actual value from workspace status.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When the client subscribes with ShardokViewStatuses empty (because
ShardokGameModels hasn't been populated yet), the server sends an
initial ShardokActionResultResponse as catch-up but doesn't know to
keep sending updates for that game.
The model is created when processing ShardokActionResultResponse, but
by then the subscription was already sent without it. The server
won't send further updates, causing sync mismatches detected by
heartbeat until the client reconnects.
Fix: When creating a new ShardokGameModel from ShardokActionResultResponse,
re-subscribe to tell the server to include this game in future updates.
This issue was exposed by commit fb2b0beb85 which added
ShardokGameModels.Clear() in HandleStartingState, ensuring models are
always empty when subscription is initially sent after reconnect.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Units are auto-placed in starting positions by default, so the
tutorial should explain how to move them rather than implying
you need to place them from the Unplaced Units panel first.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The manifest public key wasn't being injected properly because:
1. Workspace status variables need STABLE_ prefix for x_defs stamping
2. BUILD.bazel was using {MANIFEST_PUBLIC_KEY} but the variable
wasn't being output with the STABLE_ prefix
This caused the installer to try to base64 decode the literal string
"{MANIFEST_PUBLIC_KEY}" instead of the actual public key value.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When a ShardokGameModel is first created via MakeGameModel, its
GameStatus property is null. It's only set later when history
entries are processed with a non-null gsvDiff.GameStatus.
The ShardokGameModelIsRunning check accessed sgm.GameStatus.State
without a null check, causing a NullReferenceException when
filtering ShardokGameModels. This caused RunningShardokGameModels
to fail and return empty, making the Observe Battle button not appear.
Fix: Add null check before accessing GameStatus.State, matching the
pattern already used in ShardokGameModel.InSetUp property.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When CurrentEagleToken or CurrentShardokToken is null (e.g., during
battles), TryPendingCommands was calling PostRequest which:
1. Adds the command back to _pendingCommands
2. Sends it to the server
3. Server returns BAD_TOKEN
4. Next game update triggers TryPendingCommands again
5. Command is still pending with null token → retry loop
This caused the same command to be posted multiple times, flooding the
server with BAD_TOKEN errors and potentially causing reconnect storms.
Fix: When token is null, add the command back to _pendingCommands
directly without calling PostRequest. The command stays pending until
we have a valid token to compare against.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Rename WebView installer output from EagleInstallerWebView.exe to Eagle0.exe
- Update CI workflow to build and deploy Eagle0.exe
- Update all references in authservice (download URLs, batch scripts, HTML)
- Update installer_build_handler default key and version content
- Update help text and versioninfo.json metadata
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add WebView GUI to Windows installer
Adds a native GUI window to the Windows installer using the webview
library which embeds Microsoft Edge WebView2. The installer now shows
a modern HTML/CSS/JS interface with progress bar and status updates.
Key changes:
- Add llvm-mingw toolchain for macOS → Windows CGO cross-compilation
- Add webview_go dependency for WebView2 integration
- Create UIInterface abstraction with ConsoleUI and WebViewUI impls
- Build tags select stub (non-Windows/non-CGO) vs real WebView impl
- Genrule handles CGO cross-compilation within Bazel sandbox
Build targets:
- eagle_installer_windows_amd64: Pure Go console version (5.8 MB)
- eagle_installer_windows_amd64_webview: CGO WebView GUI (13.7 MB)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix WebView genrule to use Bazel Go SDK
The CI doesn't have Go in PATH, so the genrule needs to use Bazel's
Go SDK explicitly. This change:
- Exposes @go_default_sdk via use_repo in MODULE.bazel
- Adds @go_default_sdk//:files as a srcs dependency to the genrule
- Uses absolute paths for GO and GOROOT so they work after cd
- Filters out Go SDK files when copying source files
- Updates go.mod version to 1.23 to match the Bazel SDK
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Remove continue-on-error from "Run tests" step so GitHub UI expands
the actual failing step instead of "Fail if tests failed".
The log collection and artifact upload steps use `if: always()` so
they still run after test failures.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Change hero selection in several commands to use HeroUtils.fatigue
(constitution - vigor) instead of absolute vigor. This is more
consistent with how other commands select heroes and properly accounts
for heroes having different max vigor (constitution).
Commands updated:
- HandleRiotCrackDown in CommandChoiceHelpers
- TruceOfferCommandSelector
- AllianceOfferCommandSelector
- Recon in MidGameAIClient
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add relationship context and personality guidance to prevent repetitive
"If you mean to kill me, do it swiftly" patterns:
- Detect if hero was previously in captor's faction (defection context)
- Note previous captures by the same faction
- Reference prior battles between the parties
- Use hero's personality words to guide tone
- Explicitly suggest varied emotional approaches and discourage clichés
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Match Unity client layout: left column (Discord, GitHub, Steam),
right column (Google, Apple, Twitch). Includes responsive fallback
to single column on narrow screens.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The library/ code is now fully protoless (zero Scala proto dependencies).
Transitive deps through C++/Go build tools (map generation) are expected.
Changes:
- Delete docs/DEPROTO_PLAN.md - migration complete
- Delete scripts/build_deps_baseline.txt - no longer needed
- Update check_build_deps.sh to enforce zero Scala proto deps in library/
(C++/Go proto deps are allowed as they're build-time tools)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The Windows installer has been rewritten in Go. Remove the old C#/.NET
installer code and the associated CI build script.
Deleted:
- src/main/csharp/net/eagle0/clients/win/ - .NET installer source
- ci/build_eagle_installer_win.sh - old manual build script
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
After the installer updates itself, the old version is left behind as
EagleInstaller.exe.backup. Now on startup, the installer checks for and
removes this backup file.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The perform_province_events_action_test and perform_vassal_commands_phase_action_test
had BUILD.bazel dependencies on proto_matchers but the Scala files don't actually
use ProtoMatchers or equalProto. Remove these dead dependencies.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Remove .NET installer build and old manifest support. The Go installer is
now the only version built and deployed. Both the installer and Unity
builds now update only the v2 manifest at installer/v2/eagle0_manifest.txt.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Two issues fixed:
1. Unity build workflow now updates BOTH manifests (old and v2)
- The v2 manifest was missing all game file hashes because only the
installer workflow was updating it
- Now unity_build.yml calls manifest_manager with both "unity3d" and
"unity3d-v2" to update both manifests
2. Go installer now verifies game exists before claiming "up to date"
- Previously, if manifests matched but files didn't exist, it would
claim success and try to launch a non-existent game
- Now checks if eagle0.exe exists; if not, forces redownload
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Remove all proto imports and proto_converters from ResolveBattleActionTest.
Use native Scala model types (ProvinceC, HeroC, FactionC, BattalionC,
GameState, etc.) and standard ScalaTest matchers instead of ProtoMatchers.
Key changes:
- Replace proto imports with Scala model imports
- Use inside() pattern for type-safe assertions
- Add resultsOfExecute() extension method using ActionResultApplierImpl
- Update BUILD.bazel to remove proto dependencies
- Add test visibility for battalion_view package
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This file was a proto-using duplicate of the test in battalion_suitability/
but was never built (no BUILD target referenced it).
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add console UI with progress bar to Go installer
Adds a polished console-based UI to the Go installer with:
- Box-drawn banner header
- Status messages with ► prefix
- Success messages with ✓ prefix
- Error messages with ✗ prefix
- Warning messages with ⚠ prefix
- Progress bar with █ (filled) and ░ (empty) characters
- File count display during downloads
- Elapsed time on completion
The UI uses pure Go (no external dependencies) so the binary
size remains at 5.4MB.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add application icon to Go installer
- Copy eagle0.ico from .NET installer
- Add versioninfo.json for goversioninfo configuration
- Generate resource_windows.syso with embedded icon and version info
- Update BUILD.bazel to include the .syso resource in Windows build
Binary size increased from 5.4MB to 5.5MB.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix Zone.Identifier removal in .NET installer
File.Exists() doesn't work with Windows Alternate Data Streams (ADS) -
it always returns false. Changed to unconditionally attempt deletion
and ignore errors if the ADS doesn't exist.
This fixes the self-update mechanism where SmartScreen was blocking
the new installer because Zone.Identifier was never actually removed.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Two-stage manifest for clean installer upgrade path
Old .NET installers have a bug where Zone.Identifier removal fails,
blocking SmartScreen from allowing the Go installer to run. This change
creates a two-stage upgrade path:
1. OLD manifest (installer/eagle0_manifest.txt) -> points to .NET installer
- Old .NET installers download new .NET installer (same format, works)
2. NEW manifest (installer/v2/eagle0_manifest.txt) -> points to Go installer
- New .NET installer checks v2 manifest, downloads Go installer
- Go installer also checks v2 manifest for future updates
Changes:
- .NET installer now checks installer/v2/ manifest path
- Go installer now checks installer/v2/ manifest path
- manifest_manager supports "installer-v2" section for v2 manifest
- CI workflow updates both manifests appropriately
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Delete unused AvailableCommandMatcher and SelectedCommandMatcher
These proto-based test matchers were defined but never used by any test.
Removing them eliminates direct proto imports from library test code.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove stale selected_command_matcher dependency
perform_vassal_commands_phase_action_test had a dep on selected_command_matcher
but didn't actually use it.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add console UI with progress bar to Go installer
Adds a polished console-based UI to the Go installer with:
- Box-drawn banner header
- Status messages with ► prefix
- Success messages with ✓ prefix
- Error messages with ✗ prefix
- Warning messages with ⚠ prefix
- Progress bar with █ (filled) and ░ (empty) characters
- File count display during downloads
- Elapsed time on completion
The UI uses pure Go (no external dependencies) so the binary
size remains at 5.4MB.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add application icon to Go installer
- Copy eagle0.ico from .NET installer
- Add versioninfo.json for goversioninfo configuration
- Generate resource_windows.syso with embedded icon and version info
- Update BUILD.bazel to include the .syso resource in Windows build
Binary size increased from 5.4MB to 5.5MB.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix Zone.Identifier removal in .NET installer
File.Exists() doesn't work with Windows Alternate Data Streams (ADS) -
it always returns false. Changed to unconditionally attempt deletion
and ignore errors if the ADS doesn't exist.
This fixes the self-update mechanism where SmartScreen was blocking
the new installer because Zone.Identifier was never actually removed.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The CI runner doesn't have s3cmd installed. Modified installer_build_handler
to accept an optional second argument for the S3 key, allowing it to upload
the Go installer to installer/EagleInstallerMini.exe.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Migrate deferred test files to Scala types, delete unused utilities
- Migrate BattalionTypesTestData.scala to use Scala BattalionType
- Migrate NewRoundActionTest.scala to Scala types (FactionRelationship, MovingArmy, etc.)
- Migrate EndBattleAftermathPhaseActionTest.scala with complete rewrite
- Migrate ResolveBattleActionTest.scala (remove IDable dependency)
- Delete unused impl/package.scala and availability/package.scala
- Remove 48 dangling action_impl_pkg references from BUILD files
- Update DEPROTO_PLAN.md (24 migrated, 1 deferred, 3 deleted)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Delete unused IDable.scala and remove all :idable BUILD references
IDable.scala was listed as a BUILD dependency in 22 targets, but no code
actually imported or used it. The BUILD dependencies were stale.
This completes the library test deproto migration with zero deferred files.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Remove Zone.Identifier to fix Windows SmartScreen blocking
When the installer downloads a new version and launches it programmatically,
Windows SmartScreen blocks the execution because the file has a "Mark of the
Web" (Zone.Identifier alternate data stream) indicating it was downloaded
from the internet.
Remove the Zone.Identifier after download to allow the new installer to
launch without being blocked.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add Zone.Identifier fix to .NET installer, deploy both versions
- Add Zone.Identifier removal to .NET installer's download logic
- Update CI to build both .NET and Go installers
- Deploy .NET installer to installer/EagleInstaller.exe
- Deploy Go installer to installer/EagleInstallerMini.exe
- Manifest points to EagleInstallerMini.exe (Go version)
Flow for existing users:
1. Old .NET installer → auto-updates to fixed .NET installer
2. Fixed .NET installer → downloads EagleInstallerMini.exe, strips Zone.Identifier, updates to Go
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Rewrite Windows installer in Go (73MB -> 5.4MB)
Rewrites the Eagle0 Windows installer from C#/.NET to Go, reducing the
binary size from 73MB to 5.4MB (93% reduction).
The Go implementation provides the same functionality:
- Downloads and verifies game files via manifest
- Ed25519 signature verification for manifest
- SHA256 verification for all downloaded files
- Parallel downloads (8 concurrent slots)
- Self-update mechanism for installer updates
- Console-based progress output
- Invitation code support
Changes:
- Add Go installer source code (main.go, updater.go, config.go)
- Add Bazel BUILD file with Windows cross-compilation target
- Update CI workflow to build Go binary instead of .NET
- Add MANIFEST_PUBLIC_KEY to workspace status for build-time injection
The installer is cross-compiled for Windows from Mac using Bazel.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix CI permission error when copying installer output
Remove any existing installer-output directory before copying
to avoid permission denied errors from previous runs.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Replace proto ActionResult with Scala ActionResultC
- Replace proto Date with Scala Date
- Replace proto RoundPhase with Scala RoundPhase
- Add InMemoryHistory.fromScalaResults() method for building history
from Scala ActionResultT objects without proto conversion
- Update visibility for action_result_concrete to allow library tests
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Corrects error from #5512 - the LLM prompt generators are actually
fully protoless. My grep was matching Scala utility imports like
`net.eagle0.common.JsonUtils`, not proto imports.
Verified with: grep -r "import net.eagle0.eagle.(internal|common|api|views)."
which returns zero matches in src/main/scala/net/eagle0/eagle/library/
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Enable remote catalog building in AddressableAssetSettings
- Switch Default Local Group to use remote build/load paths
- Set CDN URL to https://assets.eagle0.net/addressables/[BuildTarget]
- Update BuildScript to log bundle output location for upload
- Add graceful error handling in SoundManager when music fails to load
(e.g., offline and not cached) - game continues without music
After building, bundles in ServerData/[BuildTarget] are uploaded to the
CDN by CI (see PR #5509).
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Mark Hostility proto migration as complete (all 4 files now use Scala enum)
- Correct status: LLM prompt generators still have 4 files with proto imports
- List specific LLM files and their proto dependencies
- Move LLM generators from "Fully Protoless" to "Remaining Proto Usage"
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add upload_addressables.sh script for Addressables bundles
- Add upload step to Mac and Windows build workflows
- Update mac_build_handler to use eagle0-assets bucket
- Update unity3d_windows_build_handler to use eagle0-assets bucket
- Update installer_build_handler to use eagle0-assets bucket
- Update manifest_manager to use eagle0-assets bucket
All uploads now go to eagle0-assets bucket, accessible via assets.eagle0.net.
Old eagle0-windows bucket can be deleted after DNS TTL expires (~1 week).
Addressables uploaded to:
- https://assets.eagle0.net/addressables/StandaloneOSX/
- https://assets.eagle0.net/addressables/StandaloneWindows64/
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit migrates the GameStateViewFilterTest to use native Scala
model types instead of protobuf types:
- Replace proto GameState with Scala GameState
- Use Scala concrete types: FactionC, HeroC, ProvinceC, UnaffiliatedHeroC
- Use Scala enums: Profession.NoProfession, RoundPhase, etc.
- Update ShardokBattle and ShardokPlayer with all required fields
- Replace proto assertions with field-specific assertions on view types
- Add visibility for shardok_battle to test package
- Add required view dependencies to BUILD.bazel
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This test file was left behind when RansomCommand was refactored into
the DiplomacyCommand system. The file:
- Had no BUILD target
- Used types that no longer exist (RansomAvailableCommand, RansomSelectedCommand)
- Called APIs that no longer exist (RansomCommand.make)
The ransom functionality is now tested in DiplomacyCommandTest.scala
using the current API.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Reapply "Convert SoundManager music loading to Addressables (#5475)" (#5494)
This reverts commit bda787c07e.
* Add BuildScript to build Addressables before player build
The Addressables change (PR #5475) was reverted because the Mac build
failed. The issue was that Addressables content must be built before the
player build, which the CI scripts weren't doing.
This commit:
- Reapplies the Addressables changes (revert of the revert)
- Adds Editor/BuildScript.cs with methods to build Addressables content
before building the player
- Updates CI build scripts to use -executeMethod BuildScript.Build*Player
instead of -buildWindows64Player/-buildOSXUniversalPlayer
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Documents the completion of Phase 10 library test migration:
- 18 test files successfully migrated to Scala types
- 8 files deferred with documented rationale (shared utilities, API
boundary tests, complex proto integration)
- 1 orphaned test file deleted
- Added migration patterns and key conversions reference
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Convert test assertions from using proto ActionResult types to native
Scala types (ActionResultT, ChangedProvinceC, ChangedHeroC, ChangedFactionC,
UnaffiliatedHeroC, etc.). Keep proto types for input data construction
with GameStateConverter.fromProto() conversion.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Convert test to use native Scala model types instead of ScalaPB protos:
- Replace proto GameState, Hero, Province, Faction, Battalion with Scala types
- Use Scala Gender, Profession, UnaffiliatedHeroType, RecruitmentInfo enums
- Use FactionRelationship.RelationshipLevel instead of proto enum
- Use native Scala quest types (WealthQuest, TruceCountQuest)
- Use BackstoryVersion from Scala model
- Remove all proto converters (DateConverter, FactionConverter, etc.)
- Remove 16 proto dependencies from BUILD.bazel
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Convert test to use native Scala model types instead of ScalaPB protos:
- Replace proto Date, Gender, Profession with Scala equivalents
- Use DeferredChange.* for BlizzardStarted, DroughtStarted, etc.
- Use ProvinceOrderType Scala enum
- Use RecruitmentInfo and UnaffiliatedHeroType enums
- Use GameState, ProvinceC, HeroC, FactionC, UnaffiliatedHeroC directly
- Remove 15 proto dependencies from BUILD.bazel
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Convert from proto types with ScalaPB lens syntax to native Scala
models with .copy():
- Replace proto GameState/Province/Faction/Hero with Scala types
- Replace proto FactionRelationship/PrestigeModifier with Scala types
- Replace proto DiplomacyOffer with Scala TruceOffer
- Replace proto UnaffiliatedHero with UnaffiliatedHeroC
- Replace proto RecruitmentInfo/Status with Scala RecruitmentInfo
- Remove GameStateConverter.fromProto() calls
- Remove IDable.mapifyFactions/mapifyHeroes/mapifyProvinces helpers
- Remove 13 proto dependencies from BUILD.bazel
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Tutorial improvements: queue-based system, skip Turn Cycle for AI
Refactored tutorial system to use a queue-based approach:
- Tutorials that can't be shown immediately are queued
- Queue is processed when current tutorial completes or is skipped
- Removes complex interruption logic and pending profession tracking
- Prerequisites checked when dequeuing (may have changed since enqueue)
Turn Cycle tutorial only shows for multiplayer games (>1 human player):
- Added TutorialManager.IsMultiplayerGame static property
- Set from ConnectionHandler when creating/joining games
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix profession tutorial timing after onboarding
- Remove ArePrerequisitesMet check from OnGameEvent so tutorials
can be queued during onboarding even when prerequisites aren't
met yet. ProcessQueue checks prerequisites when showing them.
- Process the tutorial queue when entering a hidden step, allowing
queued tutorials to show while onboarding waits for async events.
- Add re-entrancy guard to ProcessQueue to prevent nested calls
from StartSequence -> StopCurrentSequence -> ProcessQueue causing
tutorials to be started and immediately overwritten.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Replace proto imports with native Scala types:
- Use Date, ProvinceC, HeroC, BattalionC
- Use Army, MovingArmy, CombatUnit, Supplies
- Use BattalionType, BattalionTypeId
- Use GameState directly instead of via converter
Remove 8 proto dependencies from BUILD.bazel.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Replace proto imports with native Scala types:
- Use Date, ProvinceC, FactionC, HeroC, BattalionC
- Use Army, MovingArmy, Supplies for army types
- Use ProvinceEvent subtypes for events
- Use ProvinceEventRolls from action package
- Use GameState directly instead of via converter
Remove 9 proto dependencies from BUILD.bazel.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Replace proto imports with native Scala model types
- Use GameState, ProvinceC, FactionC, Army, MovingArmy, HostileArmyGroup
- Update FactionRelationship and RelationshipLevel imports
- Remove proto dependencies from BUILD.bazel
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Replace proto types with native Scala equivalents:
- GameState, Province, Hero, Faction, Date
- UnaffiliatedHero, RecruitmentInfo, UnaffiliatedHeroType
- Remove IDable helpers, GameStateConverter.fromProto
- Remove 6 proto dependencies from BUILD.bazel
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Replace proto types with native Scala equivalents:
- GameState, Province, Hero, Faction, Battalion
- Army, MovingArmy, HostileArmyGroup, CombatUnit
- Remove 10 proto dependencies from BUILD.bazel
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Migrate PerformHostileArmySetupActionTest to pure Scala types
Replace proto types (GameState, Province, Army, MovingArmy, RoundPhase)
with their Scala equivalents. Construct GameState directly instead of
using GameStateConverter.fromProto. Remove dependency on IDable.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Refactor PerformHostileArmySetupActionTest for clarity
* Update PerformHostileArmySetupActionTest.scala
* Add Inside matcher import for tests
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Migrate FeastCommandTest from proto to Scala ActionResultType
Replace proto ActionResultType.FEAST import with Scala ActionResultType.Feast
and remove proto dependency from BUILD.bazel.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix assertion for actionResultType in FeastCommandTest
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Full migration of test data from proto types to native Scala model types.
Key changes:
- Replace proto Army, CombatUnit, Battalion with Scala equivalents
- Remove ProvinceConverter.fromProto - construct ProvinceC directly
- Remove IDable.mapifyFactions usage
- Remove 10 proto deps, 3 converter deps, and idable from BUILD.bazel
- Add Scala state type dependencies
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Full migration of test data from proto types (Province, Hero, Faction,
Battalion, GameState) to native Scala model types (ProvinceC, HeroC,
FactionC, BattalionC, GameState).
Key changes:
- Replace ScalaPB .update() lens syntax with Scala .copy() methods
- Remove GameStateConverter.fromProto usage
- Remove 6 proto deps from BUILD.bazel
- Add equivalent Scala type dependencies
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
BattalionTypesTestData, impl/package.scala, and IDable.scala are shared
test utilities that use proto types and have cascading dependencies.
These need to be migrated together with their dependent tests.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The questDetailsProto, questProto, and euh variables using proto types
were defined but never used in any test. Removed the dead code and
proto dependencies.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The residentHeroes variable using proto Hero was dead code (created but
never used, alongside commented-out test code). Removed the variable,
the commented code, and the proto dependency.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Remove unused proto imports and dead code (questDetailsProto, questProto).
The test already uses Scala model types; these proto variables were never used.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Remove deleted stock images section (already removed files)
- Document Market Day (RandomMind) and Shopping List (Komiku) as free-to-use
- Flag Medieval: Victory Theme as needing verification
- Flag Dima Koltsov Audius tracks for eventual replacement (Open Music License, not CC)
- Update action items and recommendations
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Mark production library/ code as complete
- Remove detailed history of completed phases
- Add Phase 10: Library Test Migration with 27 files to migrate
- Categorize files by proto import count (light/medium/heavy)
- Include key type conversions and recommended migration order
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Convert SoundManager music loading to Addressables
Replace synchronous Resources.LoadAll calls with async Addressables loading.
This enables music files to be updated independently via Addressables catalog,
reducing download sizes for game updates.
- Add LoadMusicAsync coroutine using Addressables.LoadAssetsAsync
- Load music by label (music-spring, music-summer, etc.) instead of path
- Add MusicLoaded property to check when music is ready
- Guard PlayMusic() against empty clips during async loading
Note: Requires Unity Editor setup to mark music files as Addressable assets
and assign the corresponding labels.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Auto-play music after async loading completes
If a music type was requested before loading finished, start playing
once the music is ready. This ensures music plays even if
CurrentMusicType was set during the async load window.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Prioritize Summer/Autumn music loading, parallelize rest
- Load Summer first (most likely starting season)
- Load Autumn second (next season after Summer)
- Load remaining music (Spring, Winter, Travel, Battle, Victory) in parallel
- Try to start playing after each priority load completes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix: Don't interrupt playing music after async load
TryStartMusicIfNotPlaying() only starts music if:
- Music type was requested (not MusicNone)
- Music isn't already playing
- The requested type's clips are loaded
This prevents interrupting Summer playback when Autumn finishes loading.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix missing Clipboard Button texture in Chronicle canvas
Replace broken Unity built-in sprite reference with btn_icon_copy.png
from GUI Pro Kit. Also change Image Type to Simple with Preserve Aspect
for proper icon rendering.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Revert "Fix missing Clipboard Button texture in Chronicle canvas"
This reverts commit c747e78ea2dbe03ed5215566c6f1a6f2ac8dbdb2.
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Verify code signature before attempting to staple (catches transfer corruption)
- Increase retry attempts from 5 to 10
- Increase wait between retries from 10s to 30s (total wait up to 5 minutes)
- Capture and display stapler error output for better debugging
- Show signature details if verification fails
This addresses intermittent stapling failures due to Apple CloudKit
propagation delays after notarization completes.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Remove stale proto exports from province:province
The province target was exporting province_event_scala_proto,
army_scala_proto, and unaffiliated_hero_scala_proto but no downstream
targets actually needed these through that path. Removing these stale
exports reduces library/ proto deps from 41 to 26.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix unused import and stale test dependency
Remove unused LlmRequestT import and stale unaffiliated_hero_scala_proto
dependency that were exposed by removing the province proto exports.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix test build failures from stale proto dependencies
- Remove unused ProtoMatchers from tests that don't use it
- Add missing unaffiliated_hero_quest_scala_proto dep to quest_fulfillment_utils_test
- Remove stale unaffiliated_hero_quest_scala_proto dep from check_for_fulfilled_quests_action_test
- Keep ProtoMatchers in ResolveBattleActionTest (uses equalProto matcher)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Remove unused eagle_unit deps from resolved_eagle_unit and
province_conquered_action. These files use ResolvedEagleUnit (which
uses Scala types), not EagleUnit (which uses proto types).
This breaks the transitive proto dependency chain, reducing library/
proto deps from 74 to 41.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add Shardok battle tutorials
Adds three introductory tutorials for tactical combat:
1. Victory conditions - explains attacker/defender win conditions
2. Unit placement - how to position units before battle
3. Battle controls - click to select, click to move/attack, ctrl+click for specials
Tutorials chain with prerequisites and trigger at appropriate game states.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Split victory conditions into multi-step tutorial
Expands the victory conditions tutorial into three steps:
1. "Battle!" - introduces hex-grid combat, shows if player is attacker/defender
2. "Attacker Victory" - capture all castles + hold, or eliminate defenders, by Day 31
3. "Defender Victory" - eliminate attackers, or hold out past Day 31 (scattering works)
Adds IsDefender property to ShardokGameModel for role detection.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix profession tutorial bugs
- Fix duplicate hero name appearing multiple times by storing original
descriptions in a dictionary
- Fix Champion description: Duel, not Fear
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Migrate ResolvedShardokPlayer to use native Scala EndGameCondition
instead of the proto type. This removes 1 transitive proto dependency
from library/ (75 → 74).
Changes:
- Add Scala EndGameCondition sealed trait with Victory, AllyVictory,
Draw, and Loss cases
- Add DrawType enum for draw conditions
- Add EndGameConditionConverter in ShardokBattleConverter
- Update ResolvedShardokPlayer to use Scala EndGameCondition
- Move proto conversion to ShardokInterfaceGrpcClient boundary
- Update ResolveBattleAction to use Scala EndGameCondition API
- Update ResolveBattleActionTest to construct Scala EndGameCondition
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Remove stale proto_converter deps from library/ BUILD files
Library code should not depend on proto_converters - those belong at
the service layer boundary. Removed 9 stale proto_converter deps that
were no longer used by any Scala code.
The only remaining proto_converter dep is shardok_battle_converter,
which is actually used by ResolveBattleAction.scala (to be fixed in
a follow-up PR).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Move ShardokBattle boundary conversion to ShardokInterfaceGrpcClient
Previously, BattleResolution contained a proto ShardokBattle, requiring
ResolveBattleAction in library/ to depend on ShardokBattleConverter.
This violated the boundary principle where proto conversions should
happen at the service layer, not in library code.
This change:
- Updates BattleResolution.battle to use Scala ShardokBattle
- Moves the proto-to-Scala conversion into ShardokInterfaceGrpcClient
- Removes ShardokBattleConverter dependency from library/ code
- Updates tests to use the scalaBattle() helper for conversion
This completely removes proto_converters dependencies from library/
(the last one was shardok_battle_converter).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add linter check for library/ proto_converters boundary
Adds Rule 3 to check_build_deps.sh that verifies library/ code does not
depend on proto_converters. Proto conversions should happen at service
boundaries (ShardokInterfaceGrpcClient, EagleServiceImpl, etc.), not in
library code.
Also updates baseline from 167 to 75 proto deps reflecting recent cleanup.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Documents the design for implementing delta patches in the Mac auto-update
system to reduce download sizes from ~200MB to ~10-30MB per update.
Covers:
- Appcast XML structure with sparkle:deltas
- S3 storage layout for app bundles and deltas
- BinaryDelta tool usage
- Migration strategy and error handling
- Storage impact analysis (~3.25GB total for 85% bandwidth savings)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Use run-specific artifact names (signed-mac-app-{run_id}, notarized-mac-app-{run_id})
to prevent race conditions where a previous run's cleanup job might delete
artifacts from a concurrent run.
Root cause: When PR #5465 merged, the previous run (which used the old workflow
that deleted ALL artifacts by name) ran its cleanup job between when the new
run uploaded its artifact (06:13:20) and when it tried to download it (06:18:27).
Fix: Include the GitHub run ID in artifact names so each run's artifacts are
uniquely named and cleanup jobs can only delete their own artifacts.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Library code should not depend on proto_converters - those belong at
the service layer boundary. Removed 9 stale proto_converter deps that
were no longer used by any Scala code.
The only remaining proto_converter dep is shardok_battle_converter,
which is actually used by ResolveBattleAction.scala (to be fixed in
a follow-up PR).
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add Eagle server build to CI
Add eagle_build.yml workflow that builds the Eagle server when Scala
or Eagle proto files change. This ensures build failures in the Eagle
server are caught during PR review, similar to how shardok_build.yml
works for the C++ Shardok server.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix unused dependencies and simplify GameAdminServiceImpl
- Remove unused json4s and settings_loader deps from :service target
- Simplify GameAdminServiceImpl since GamesManager now returns
game_admin proto types directly (no conversion needed)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Read version from the built app's Info.plist instead of calculating
from git commit count at deploy time. This ensures the appcast version
always matches what's actually in the app bundle.
Previously, the deploy step used `git rev-list --count HEAD` which could
differ from the version baked into the Unity build if commits were added
between the build and deploy steps.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add first-encounter profession tutorials
Shows tutorial popup explaining each profession's abilities when player
first encounters a hero with that profession. Covers both strategic
(Eagle) and tactical (Shardok) abilities in natural language.
Triggers when viewing provinces with employed or free heroes.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update mage and paladin profession tutorial text
- Mage: Can create *or end* blizzards and droughts
- Paladin: Giving alms provides much larger support boost
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Now that the admin console uses the GameAdmin service (PR #5460),
remove the duplicate admin endpoints from eagle.proto and their
implementations from EagleServiceImpl.
Changes:
- Remove 14 admin RPC endpoints from Eagle service in eagle.proto
- Remove corresponding message types (AddSettings*, Get*, Convert*,
Reassign*, Rewind*, Import*, Delete*, CheckGame*, DownloadGame*)
- Remove admin method implementations from EagleServiceImpl.scala
- Update GamesManager to import admin response types from game_admin
proto instead of eagle proto
This completes the admin service separation:
- PR #5458: Create GameAdmin service
- PR #5460: Migrate admin console to GameAdmin
- This PR: Remove admin endpoints from Eagle
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
After the GameHistory split, EagleServiceImpl needs to access
shardokPlayerCount from controller.fullHistory instead of
controller.engine.history. Also export full_game_history from
game_controller so downstream targets can see the FullGameHistory type.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
With all proto imports removed from library/ Scala code, these proto
dependencies in BUILD.bazel files are no longer needed. This completes
the deproto milestone for the library/ layer.
- Update DEPROTO_PLAN.md to reflect 0 proto files in library/
- Remove unused proto deps from library/BUILD.bazel, util/, actions/impl/
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Update admin_server.go to call the new GameAdmin gRPC service instead
of Eagle for all game management operations. This is step 2 of the
admin service separation:
- Replace grpcClient (EagleClient) with gameAdminClient (GameAdminClient)
- Update all game admin RPC calls to use gameadminpb types
- Add game_admin_go_proto dependency to BUILD.bazel
- Fix game_admin BUILD.bazel to use go_grpc instead of go_grpc_v2
(v2 only generates gRPC code, not message types)
Auth operations still use authpb (via Eagle's Auth service).
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Create a separate GameAdmin service to handle admin-only game management
operations (RewindGame, DeleteGame, GetRunningGames, etc.) that were
previously exposed through the Eagle service alongside client endpoints.
This is the first step in separating admin and client APIs:
- Creates game_admin.proto with all 13 admin game endpoints
- Implements GameAdminServiceImpl calling the existing GamesManager
- Registers the new service in Main.scala on the same port as Eagle
The admin console will be migrated to use this service in a follow-up PR,
after which the admin endpoints can be removed from eagle.proto.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Go's XML unmarshaler doesn't preserve xmlns attributes correctly.
When the appcast was fetched and re-marshaled, the Sparkle namespace
was being set to empty string, which prevented Sparkle from parsing
version numbers and detecting updates.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Change from com.Shardok-Games.eagle0 to net.eagle0.eagle0 to match
the project's domain (net.eagle0).
Updated in:
- Unity ProjectSettings (applicationIdentifier for Standalone)
- inject_sparkle.sh (CFBundleURLName for URL scheme)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Sparkle's default check interval is 24 hours. If a user launches the app
shortly after a new version is deployed but before their scheduled check,
they won't see the update prompt.
This adds an explicit background check on every startup to ensure users
get updates promptly when a new version is available.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Make Engine API protoless by introducing Scala ActionResultView
Created Scala ActionResultView type to replace proto ActionResultView
in the Engine's public API. Proto conversion now happens at the gRPC
boundary in HumanPlayerClientConnectionState.
Changes:
- Created model/view/action_result/ActionResultView.scala
- Created proto_converters/view/action_result/ActionResultViewConverter.scala
- Updated ActionResultFilter to return Scala ActionResultView
- Updated Engine and EngineImpl to use Scala type
- Updated HumanPlayerClientConnectionState to convert to proto at gRPC boundary
- Removed proto dependencies from library/
This follows the architecture principle that the Engine should vend a
pure Scala model API, with proto conversions happening at the outer
boundary layer (GameController/GamesManager).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Split GameHistory into protoless GameHistory and FullGameHistory
- Create FullGameHistory trait in service/ that extends GameHistory
and adds Shardok-specific methods (shardokPlayerResultsSince,
shardokPlayerAvailableCommands)
- Remove receiveBattleUpdate from Engine trait (moved to GamesManager)
- Update GameController to hold fullHistory: FullGameHistory field
- Update PersistedHistory and InMemoryHistory to extend FullGameHistory
- Add extractFullHistory helper methods for safe type extraction
- Update tests to use FullGameHistory mocks
This continues the deproto effort by isolating proto-dependent
Shardok methods from the library layer's GameHistory trait.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Regenerate ICO file with proper format:
- Use PNG compression for 256x256 size (Windows Vista+ requirement)
- Use BMP format for smaller sizes (16, 24, 32, 48)
- Proper BITMAPINFOHEADER structure for BMP entries
The previous ICO may not have displayed correctly due to format issues.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Reduce build size: optimize Twitch button, remove unused profession icons
1. glitch_flat_purple.png (Twitch OAuth button):
- Reduce maxTextureSize from 2048 to 512
- Enable crunched compression
- Expected savings: ~8 MB
2. Delete Resources/Professions folder:
- These icons were unused (profession textures are assigned via
EagleCommonTextures component, not loaded from Resources)
- Expected savings: ~7 MB (5.3 MB paladin + others)
Total expected savings: ~15 MB
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Move actually-used icons from Unused to InUse folder
22 icons from 4000_Fantasy_Icons/Unused were actually referenced by
Gameplay.unity (for flame effects, UI elements, etc). Moving them
to an InUse folder makes the naming accurate.
Unity tracks assets by GUID, so moving files with their .meta files
preserves all scene/prefab references.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Uses the same eagle head icon that was added to the Unity/macOS app.
Converted from PNG to ICO format with multiple sizes (16-256px).
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Unity creates .meta files for the native plugins which should also
be ignored since the plugins are built at CI time.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Created Scala ActionResultView type to replace proto ActionResultView
in the Engine's public API. Proto conversion now happens at the gRPC
boundary in HumanPlayerClientConnectionState.
Changes:
- Created model/view/action_result/ActionResultView.scala
- Created proto_converters/view/action_result/ActionResultViewConverter.scala
- Updated ActionResultFilter to return Scala ActionResultView
- Updated Engine and EngineImpl to use Scala type
- Updated HumanPlayerClientConnectionState to convert to proto at gRPC boundary
- Removed proto dependencies from library/
This follows the architecture principle that the Engine should vend a
pure Scala model API, with proto conversions happening at the outer
boundary layer (GameController/GamesManager).
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add styled DMG with arrow pointing to Applications
- Add create-dmg as a bazel dependency for creating styled DMG installers
- Add background image with arrow pointing from app to Applications
- Update mac_build_handler to use create-dmg for styled DMG creation
- Update CI workflow to use the new DMG creation process
The DMG now shows a visual arrow guiding users to drag the app
to the Applications folder.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add --skip-jenkins flag for headless CI environment
The AppleScript that positions icons times out in CI environments
without a GUI session. Using --skip-jenkins skips the Finder styling
while still including the background image and Applications link.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Switch from create-dmg to dmgbuild for CI compatibility
dmgbuild generates .DS_Store files programmatically without needing
AppleScript or Finder access, making it work in headless CI environments.
- Remove create-dmg bazel dependency (relied on AppleScript)
- Use dmgbuild Python tool instead (pip install dmgbuild)
- Generates proper icon positions and background without GUI
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix DMG styling: lighter background, adjust icon positions
- Use light gray background so file/folder names are readable
- Move arrow up to align with icon centers
- Move Applications link further right (500 -> 520) to clear the arrow
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix arrow: move left, connect head to shaft properly
- Arrow now starts at x=200 (closer to app icon)
- Arrow ends at x=460 (further from Applications at x=520)
- Head and shaft now overlap for proper connection
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Created Scala ActionResultView type to replace proto ActionResultView
in the Engine's public API. Proto conversion now happens at the gRPC
boundary in HumanPlayerClientConnectionState.
Changes:
- Created model/view/action_result/ActionResultView.scala
- Created proto_converters/view/action_result/ActionResultViewConverter.scala
- Updated ActionResultFilter to return Scala ActionResultView
- Updated Engine and EngineImpl to use Scala type
- Updated HumanPlayerClientConnectionState to convert to proto at gRPC boundary
- Removed proto dependencies from library/
This follows the architecture principle that the Engine should vend a
pure Scala model API, with proto conversions happening at the outer
boundary layer (GameController/GamesManager).
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Remove proto Date from GeneratorUtilities
All callers use Scala Date, so the proto Date overload is unused.
- Remove import of net.eagle0.eagle.common.date.Date
- Remove the proto Date overload of dateString()
- Keep only the Scala Date version
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove proto types from LLM prompt generators
Migrated 8 files to use Scala types instead of proto:
- BattalionDescriptions: uses BattalionTypeId, BattalionT, BattalionView
- ChronicleEventTextGenerator: uses ChronicleEvent sealed trait
- ChronicleUpdatePromptGenerator: removed ChronicleEventConverter
- HeroBackstoryUpdatePromptGenerator: removed BattalionViewConverter, QuestConverter
- QuestEndedGeneratorUtilities: uses QuestT/QuestC types
- QuestFailedPromptGenerator: removed QuestConverter
- QuestFulfilledPromptGenerator: removed QuestConverter
Updated DEPROTO_PLAN.md to accurately reflect completion of Phase 8
(LLM Prompt Generators) and Phase 8b (Hostility Migration).
All non-boundary library code is now protoless.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The IsRunningFromReadOnlyVolume and ShowDMGWarning functions were
not being exported from the native plugin, causing the DMG launch
check to fail silently.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Runs every 6 hours to check total artifact storage. Fails if storage
exceeds 500 MB and lists the largest artifacts for debugging.
Steady-state should be ~50 MB (logs and test results). This acts as
a safety net in case cleanup jobs fail to run properly.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The cleanup step was deleting ALL signed-mac-app artifacts across the
entire repo, not just the current run's. When two main builds ran
concurrently, one build's cleanup would delete the other's artifacts.
Fix: Change cleanup to only delete the current run's artifacts using
the runs/$run_id/artifacts API endpoint instead of deleting all
artifacts matching the name.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Adds a step to the bazel_test workflow that runs check_build_deps.sh
in strict mode. This enforces architectural boundaries:
- src/main should not depend on src/test
- library/ proto dependencies should not exceed baseline
Runs before tests so failures are caught early.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The test tutorials ("You've issued your first command!") were
accidentally left enabled in the Gameplay scene.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Replace Hostility proto with Scala Hostility enum
This removes direct proto imports from library/ for the Hostility type:
- FactionUtils.hostilityStatus now returns Scala Hostility
- ArmyStats.hostility now uses Scala Hostility type
- AvailableAttackDecisionCommandFactory no longer imports proto
- AvailableFreeForAllDecisionCommandFactory no longer imports proto
- AttackDecisionCommandChooser uses Hostility.Enemy instead of proto
- BattleFilter no longer needs protoToScalaHostility converter
- AvailableCommandConverter uses HostilityConverter.toProto
This completes Phase 8b of the deproto migration, reducing direct
proto imports in library/ from 8 files to 4 (LLM prompt generators).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix AvailableCommandConverterTest to use Scala Hostility enum
Update test to use net.eagle0.eagle.model.state.Hostility.Self
instead of proto net.eagle0.common.hostility.Hostility.SELF_HOSTILITY.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add retention-days to all GitHub Actions artifacts
- Use retention-days: 1 for artifacts deployed to external storage
(sysroot, installer, mac app builds)
- Use retention-days: 3 for debug logs and test results
Prevents artifact storage quota from being exceeded.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Delete Mac build artifacts after successful deploy
Automatically deletes signed-mac-app and notarized-mac-app artifacts
after deployment completes. These ~250MB artifacts are only needed to
pass the app between workflow jobs; once deployed, they're redundant.
This prevents artifact storage from accumulating even if retention-days
doesn't expire them quickly enough.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Delete ALL old build artifacts after deploy (not just current run)
Both Mac and Windows installer workflows now delete ALL artifacts
with their respective names after successful deployment:
- Mac: signed-mac-app, notarized-mac-app
- Windows: eagle-installer
This ensures no artifact buildup even from failed/stale runs.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Increase retention for test/build logs to 7 days
These are small files useful for debugging, so keep them longer:
- test.json (~100KB each)
- editor_win.log / editor_mac.log (~60KB each)
- failed-test-logs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Use 5-day retention to match repository maximum
The repository has a 5-day maximum retention policy configured.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Delete old Mac builds from DigitalOcean when pruning appcast
The appcast keeps the last 10 versions, but the old DMG files were never
deleted from S3. Now when items are removed from the appcast, the
corresponding DMG files are also deleted from DigitalOcean Spaces.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add cleanup job that runs even when build fails
Previously, artifact cleanup only happened in the deploy job, which
doesn't run if earlier jobs fail. This left behind large artifacts
from failed builds, eventually hitting the storage quota.
The new cleanup job runs on ubuntu-latest with `if: always()` so it
executes regardless of whether build-and-sign, wait-notarization,
or deploy succeeded or failed.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add BUILD.bazel dependency linting
Adds tooling to enforce architectural boundaries in the codebase:
1. Shell script `scripts/check_build_deps.sh` with multiple modes:
- Default: runs all checks and shows dependency counts
- --ci: fails only on hard violations (src/main depends on src/test)
- --strict: also fails if proto deps in library/ increase from baseline
- --count: just show current dependency counts
- --update-baseline: update the baseline file for tracking
2. Bazel test `//:build_deps_test` for integration with test suite
3. Baseline file `scripts/build_deps_baseline.txt` tracking proto dep count
Current enforced rules:
- src/main must not depend on src/test (enforced now)
- library/ proto dependencies tracked (167 currently, deproto in progress)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update DEPROTO_PLAN.md with accurate proto import inventory
The previous status was inaccurate. This update:
- Corrects summary table: Availability, Command Choice Helpers,
and other utilities still have Hostility proto imports
- Adds Phase 8b documenting Hostility proto migration (4 files)
- Updates detailed inventory showing actual 13 files with direct
proto imports (vs 167 transitive deps from bazel query)
- Corrects success criteria checkboxes to reflect actual status
- Documents the grep command used to verify imports
The 167 proto deps from bazel query are transitive dependencies.
Only 13 files have direct proto imports:
- 5 boundary files (expected to keep proto)
- 4 files using Hostility proto (Phase 8b)
- 4 LLM prompt generator files (Phase 8)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Mark Phase 8 (LLM Prompt Generators) as complete following PR #5429.
All 34 prompt generator files and all request generators now use Scala
LlmRequestT types instead of proto types.
Summary:
- Total proto imports: ~7 (down from 149)
- All remaining proto usage is at system boundaries (gRPC, persistence)
- All success criteria met
- All open questions resolved
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Unity auto-updated audio import settings:
- Serialized version 6 → 8
- Quality setting adjusted
- Preload audio data setting moved
Font SDF asset regenerated.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Convert all remaining LLM prompt generators to Scala types
This converts all remaining prompt generators from proto types to Scala
types (LlmRequestT). Updates include:
- All prompt generators now use LlmRequestT.* instead of proto types
- LlmResolver uses Scala type matches for all generators
- Proto fallback cases removed (now throw exceptions for non-LLM types)
- Updated BUILD files with correct dependencies
- Fixed test file to use Scala types
Note: ChronicleUpdatePromptGeneratorTest still needs to be updated to use
Scala types in a follow-up PR.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix ChronicleUpdatePromptGeneratorTest to use Scala types
Update the test to use LlmRequestT.ChronicleUpdateMessage and related
Scala types instead of proto types.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The deferred flag logic was inverted - we were collecting notifications
where deferred=true (hold for later) instead of deferred=false (send now).
This bug was introduced in commit 8d3bc914ed when migrating from proto
types to Scala types. The original proto-based code used notificationsToDeliver
which already handled the deferred distinction. The new Scala code used
NotificationConverter.toProto() which returns (proto, deferred) tuple,
but the .collect pattern incorrectly filtered for deferred=true.
Affected features:
- Divine command notifications (learning about quests)
- Quest completion/failure notifications
- Diplomacy event notifications (alliance, truce, etc.)
- All other immediate notifications
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add in-app warning when running from Downloads folder
Sparkle auto-update framework cannot update apps running from the
Downloads folder or other transient locations. This adds a native
macOS alert that warns users when they launch the app from an
invalid location and instructs them to move it to Applications.
The check detects:
- /Downloads/ - where macOS downloads files
- /tmp/ or /private/tmp/ - temporary directories
- /.Trash/ - deleted files
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix build: import AppKit for NSAlert
NSAlert is part of AppKit, not Foundation. Changed import to AppKit
which also includes Foundation.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Offer to move app to /Applications and relaunch
Instead of just showing a warning, now offers to:
1. Copy the app to /Applications (removing existing if present)
2. Launch the new copy
3. Terminate the current instance
This provides a seamless experience - user just clicks "Move to
Applications" and the app handles everything automatically.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Switch Mac distribution from ZIP to DMG
Changes the Mac app distribution from a ZIP file to a DMG disk image.
This provides a better installation experience and enables enforcing
proper installation:
1. **DMG with Applications alias**: Users see the app and an
Applications folder alias, making drag-to-install intuitive.
2. **Read-only enforcement**: DMGs are mounted read-only, so if
users try to run the app directly from the DMG, we detect it
and show a blocking dialog telling them to install first.
3. **Sparkle works from any writable location**: Unlike the previous
Downloads-folder check, this approach allows the app to run from
anywhere writable (~/Desktop, ~/Games, /Applications, etc.).
Changes:
- SparklePlugin.m: Replace location warning with read-only volume check
- SparkleUpdater.cs: Update P/Invoke bindings for new functions
- SparkleInitializer.cs: Check for read-only volume instead of path
- mac_build_handler.go: Create DMG instead of ZIP using hdiutil
- invitation_handlers.go: Update download URL and instructions for DMG
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Update BreakAllianceResolutionMessagePromptGenerator to accept
LlmRequestT.BreakAllianceResolutionMessage instead of the proto type.
Add Scala type matching in LlmResolver and update BUILD dependency
from proto to Scala types.
Also updates enum handling from proto DiplomacyOfferStatus to Scala Status.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Update BreakAllianceMessagePromptGenerator to accept LlmRequestT.BreakAllianceMessage
instead of the proto type. Add Scala type matching in LlmResolver and update BUILD
dependency from proto to Scala types.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Update AllianceResolutionMessagePromptGenerator to accept
LlmRequestT.AllianceOfferResolutionMessage instead of the proto type.
Add Scala type matching in LlmResolver and update BUILD dependency
from proto to Scala types.
Also updates enum handling from proto DiplomacyOfferStatus to Scala Status.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
docker_build.yml was missing TWITCH_CLIENT_ID and TWITCH_CLIENT_SECRET
in its env block and export statements. All other OAuth providers
(Discord, Google, GitHub, Apple) were present, but Twitch was omitted
when the provider was added.
This caused Twitch to not appear on the invitation landing page after
Eagle deployments, even though auth_build.yml had the secrets.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Update AllianceOfferMessagePromptGenerator to accept LlmRequestT.AllianceOfferMessage
instead of the proto AllianceOfferMessage type. Add Scala type matching in LlmResolver
and update BUILD dependency from proto to Scala types.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Update TruceResolutionMessagePromptGenerator to accept
LlmRequestT.TruceResolutionMessage and use the Scala Status enum
instead of proto DiplomacyOfferStatus. Add Scala type matching in
LlmResolver.promptGenerator before falling back to proto matching.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Update TruceOfferMessagePromptGenerator to accept
LlmRequestT.TruceOfferMessage instead of the proto TruceOfferMessage
type. Add Scala type matching in LlmResolver.promptGenerator before
falling back to proto matching.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add --no-deps to nginx and admin docker compose commands to prevent
them from cascading to auth. The auth container has secrets (like
TWITCH_CLIENT_ID) that are only available in auth_build.yml, not in
docker_build.yml. Without --no-deps, docker compose would recreate
auth with blank env vars when it detected config changes.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Update DivineMessagePromptGenerator to accept LlmRequestT.DivineMessage
instead of the proto DivineMessage type. Add Scala type matching in
LlmResolver.promptGenerator before falling back to proto matching.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add sworn brotherhood guidance tutorial
- Add guidance_sworn_brotherhood tutorial that triggers when:
- Player has 4+ provinces, OR
- SwearBrotherhood command is available with good candidates (server-determined)
- Explains that sworn siblings become faction leaders for direct province control
- Mentions succession benefit if Warlord falls
Also update guidance_expand to note that vassals manage provinces without
a Warlord or sworn sibling present.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Improve sworn brotherhood tutorial with candidate detection
- Use SwornBrotherChooser logic to identify good candidates:
charisma >= 65, constitution >= 65, loyalty >= 95, ranked by power
- Dynamically update tutorial text to name the specific candidate
- Add 95 loyalty requirement and permanence info to description
- Keep 4+ provinces as alternative trigger condition
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix hero stat access - use int directly, not StatView
Charisma, Constitution, Strength, Agility, Wisdom are int properties,
only Loyalty uses StatView?.Stat pattern.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix TextEntry access - use .Text property for string value
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Use gender-neutral language: Brotherhood → Kinship
- Command header: "Swear Kinship with Hero"
- Tutorial text uses "sworn siblings" and "swear kinship"
- Internal IDs updated: guidance_sworn_brotherhood → guidance_sworn_kinship
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Native plugin (SparklePlugin.m):
- Log app version and build number from Info.plist
- Log feed URL configuration
- Log whether public key is set
- Log auto-check plist setting
- Log current auto-check state after initialization
- Log last update check date
Unity initializer (SparkleInitializer.cs):
- Log Application.version and Unity version
- Log platform and product name
- Log AutomaticallyChecksForUpdates setting
This helps debug why Sparkle updates may not be working.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This is the next step in the incremental migration from proto to Scala types
in the LLM processing pipeline.
Changes:
- LlmRequestWithGameState now takes GeneratedTextRequestT instead of
GeneratedTextRequest (proto)
- LlmResolver.resolveLlmRequests returns Scala types in the result
- Proto conversion happens at boundaries:
- Converting to proto for LLMResponse when streaming
- Converting to proto for receiveStreamingLlmFailure
- Converting to proto in promptGenerator for individual prompt generators
- UnrequestedTextHandler no longer needs to convert to proto when calling
LlmResolver
The prompt generators still expect proto types - they will be updated
incrementally in follow-up changes.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The nginx config is already deployed by docker_build.yml (which has
nginx/** in its trigger paths). Adding it to auth_build.yml was
redundant and broken (the production server isn't a git repo).
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When a user tries to sign in with an OAuth provider but doesn't have an
existing account, the auth service returns InvitationRequired status.
Previously this fell through to the default case and threw a generic
"Unknown OAuth status" exception, providing a poor user experience.
Now displays a friendly message explaining that an invitation is required
to create a new account, and suggests using an invitation link or signing
in with the original provider if they already have an account.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add nginx/nginx.conf and docker-compose.prod.yml to trigger paths
- Pull latest nginx config and restart nginx during deployment
This ensures nginx config changes (like OAuth callback routes) are
automatically deployed when merged to main.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Store Scala types in ClientTextStore instead of proto
This change updates the ClientTextStore layer to store the native Scala
type `GeneratedTextRequestT` in memory instead of the proto type
`GeneratedTextRequest`. Proto conversion now only happens at persistence
boundaries (when saving to/loading from disk).
Key changes:
- ClientText.scala: Changed llmRequest field to use GeneratedTextRequestT
- ClientTextStore.scala: Updated interface to accept Scala types
- ClientTextStoreImpl.scala: Added toProto/fromProto conversion at persistence
- GameController.scala: Removed proto conversion in allNewLlmRequests
- UnrequestedTextHandler.scala: Uses Scala types internally, converts to proto
only when passing to LlmResolver
- GamesManager.scala: Uses Scala types directly when creating UnrequestedClientText
- Updated BUILD files for new dependencies and visibility
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix tests for ClientTextStore Scala types migration
- Update UnrequestedTextHandlerTest to use Scala types instead of proto
- Add GeneratedTextRequestConverter import for proto conversions in test mocks
- Change test requests from FixedHeroName to LlmRequestT.DivineMessage to properly test LLM resolution path
- Add visibility for test packages in BUILD files
- Remove unused proto dependency from generator_utilities_test
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
During blue-green deployment, the old (active) instance should not wait
for the flush marker - it would be waiting for itself to stop, which
causes a 30s timeout warning.
Now compares deployment timestamp to instance start time:
- If deployment started AFTER instance: skip wait (we're the old instance)
- If deployment started BEFORE instance: wait (we're the new instance)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Steam OpenID 2.0 redirects back to /oauth/steam/callback after
authentication, but nginx was not configured to proxy this path to the
auth service, resulting in 404 errors.
Added the location block alongside the existing /oauth/callback and
/oauth/apple/callback blocks.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add GitHub, Apple, Steam, and Twitch to handleLoginStart switch
- Add sign-in buttons with icons for all providers to login.html
- Add CSS styles for new provider buttons
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add Steam and Twitch sign-in buttons to Unity client
- Add steamLoginButton and twitchLoginButton fields
- Add steamProviderIcon and twitchProviderIcon for stored accounts
- Wire up button click handlers in SetupOAuthUI()
- Add icon mapping for stored account display
Note: Unity scene needs Steam/Twitch button GameObjects and icons wired up.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add Steam and Twitch OAuth button assets
- Add Steam and Twitch (glitch) logo icons for OAuth buttons
- Consolidate Discord icon to OAuth Buttons folder
- Remove unused Google OAuth button variants
- Update Gameplay.unity with new button references
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The TWITCH_CLIENT_ID and TWITCH_CLIENT_SECRET were added to
docker-compose.prod.yml but not to the GitHub Actions workflow
that deploys the auth service.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add game state guidance tutorials
Add four contextual tutorials that trigger based on game state analysis:
1. guidance_loyalty_danger - November warning when heroes have low loyalty
Suggests using Feast or Give Gift to boost loyalty before year end
2. guidance_neighbor_danger - Alert when province borders hostile faction
Suggests Organizing Troops or seeking Diplomacy
3. guidance_recruit_heroes - When stable province has recruitable heroes
Suggests using Travel, Divine, and Recruit commands
4. guidance_expand - When player has stable province but only one territory
Suggests using March to claim new provinces
These provide strategic guidance for new players based on their game state.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix compilation errors in guidance trigger checks
- MarchAvailableCommand.OneProvinceCommands to access destination provinces
- FactionRelationshipView.TargetFactionId instead of FactionId
- province.FullInfo.Gold instead of model.CurrentGold
- Net.Eagle0.Eagle.Common.RecruitmentStatus instead of Views namespace
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Use Any() instead of ToList().Count for efficiency
Any() short-circuits on first match rather than materializing entire list.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Simplify recruit heroes gold check
Just check for 100 gold - January was when this would naturally be true,
not an alternative condition.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
docker-compose validates the entire file even when deploying just auth.
Remove the :? validation since docker_build.yml already validates
SHARDOK_ADDRESS explicitly before deploying Eagle.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Document that WarlordRow, VassalRow1, VassalRow2 are registered dynamically
by HeroesAndBattalionsPanelController when hero rows are created from prefabs.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Steam: OpenID 2.0 authentication (no API key required)
- Twitch: Standard OAuth 2.0 with user:read:email scope
- Add TWITCH_CLIENT_ID and TWITCH_CLIENT_SECRET to deployment config
- Add Steam/Twitch buttons to invitation landing page
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add fromProto methods to LlmRequestConverter and GeneratedTextRequestConverter
to enable converting proto types back to Scala types. This is preparation for
storing Scala types in-memory in ClientTextStore, with proto conversion only
happening at persistence boundaries.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Clear all children of storedAccountsContainer and disable them
immediately before destroying (fixes visual duplication from
deferred Destroy)
- Add delete button to StoredAccountButton to remove saved accounts
- Wire up delete button to OAuthManager.RemoveStoredAccount
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Divine reveals what you must do to impress a hero and recruit them,
not their stats/abilities.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Security improvement: Eagle no longer has access to JWT private keys.
- JwtService.scala: Simplified to validation-only (removed signing methods)
- Loads public key from /etc/eagle0/keys/public.pem (shared volume from auth)
- Falls back to extracting public key from JWT_PRIVATE_KEY for backward compat
- AuthServiceImpl.scala: Local fallbacks throw UNIMPLEMENTED (signing in Go auth)
- docker-compose.prod.yml: Removed JWT_PRIVATE_KEY from Eagle, volume now read-only
Auth service unchanged - still bootstraps PEM files from JWT_PRIVATE_KEY env var.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Clicking a selected unit during setup would trigger a placement action
instead of deselecting the unit, because HasPlacementAction returned
true for the unit's own tile. Now the placement check excludes the
currently selected tile, allowing the deselection logic to run.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Change version scheme from git describe (which picked up unrelated tags
like busybox-1.35.0) to commit-count based versions like 1.0.9548.
This gives automatic, always-incrementing version numbers that Sparkle
can properly compare for updates.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Update DEPROTO_PLAN.md with recent completions
- Mark Phase 9 (Utilities) as complete - MapGenerator now protoless
- Mark Phase 10 (History APIs) as complete - PersistedHistory accepts Scala types
- Add PRs #5373, #5378, #5381, #5390 to recent completions
- Update proto import inventory with current counts
- Update success criteria checklist
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Make DiplomacyResolutionLlmRequestGenerator protoless
Replace proto types with Scala equivalents:
- DiplomacyOfferStatus -> Status
- GeneratedTextRequest -> LlmRequestT
- Proto message types -> LlmRequestT enum cases
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Use ditto instead of cp -R to copy Sparkle.framework (preserves bundle structure)
- Skip individual signing of Sparkle's internal XPC services, apps, and executables
- Use --deep flag when signing Sparkle.framework to handle its internal components
- Verify cached Sparkle.framework has proper symlink structure, re-download if corrupted
Fixes "bundle format unrecognized" error during codesigning.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
After investigating a bad deployment where Eagle connected to "shardok"
instead of the actual Hetzner address, found the root cause: GitHub
Actions secrets can occasionally fail to load, causing the heredoc to
expand with default values instead of the actual secrets.
Changes:
- Add validation at deploy start to check critical env vars are present
- Add network connectivity check to Shardok before blue-green deployment
(blocks deployment if Shardok is unreachable)
- Remove the misleading "shardok:40042" default - now SHARDOK_ADDRESS
must be explicitly set, making configuration errors immediately obvious
- docker-compose.prod.yml now uses :? syntax to require SHARDOK_ADDRESS
The env var validation will abort deployment early with a clear error
message if secrets failed to load, rather than deploying with broken
config.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add a new apply overload that takes Vector[ActionResultT] directly,
avoiding proto conversion at the call site. The conversion to proto
for storage still happens internally, but callers like NewGameCreation
can now pass Scala types directly.
Removed the unused proto-based apply overload since all callers that
need proto types use the case class constructor directly.
This makes NewGameCreation fully protoless.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The logout button was wired to QuitButtonClicked in the Unity scene,
causing the app to exit instead of returning to the connection panel.
- Change scene to wire logout button to OnLogoutClicked
- Make OnLogoutClicked public so it's visible in Unity inspector
- Remove redundant programmatic listener setup
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add loading indicator when switching environments in lobby
Shows "Switching environment..." text while waiting for the server
to respond after changing the environment dropdown. Text is cleared
when the lobby data arrives and populates the game lists.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* add lobby connection status and fix 16:10 aspect ratio
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Make NewGameCreation protoless
Convert NewGameCreation to build Scala ActionResultC internally instead of
protobuf ActionResult. Proto conversion now only happens at the boundary
when storing to PersistedHistory.
Changes:
- NewGameCreation.scala: Use ActionResultC, Scala RoundPhase, ChronicleEntry,
FactionC, FactionRelationship instead of proto equivalents
- StartGameActionResultUtils.scala: Change from proto ActionResult to
ActionResultC throughout
- GameParametersUtils.scala: Add provinceWithOverrideScala method
- Add withId methods to HeroT/HeroC and BattalionT/BattalionC traits
- Update BUILD.bazel files with visibility for new_game_creation package
- Update test to use Scala types
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* empty check
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The toProto method was not serializing newProvinces, causing provinces
to be lost when persisting game state. This led to stack overflow during
game creation as the phase advancer would loop infinitely with no provinces.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Export proto types from converters for caller visibility
ActionResultProtoConverter and GameStateConverter return proto types,
so those proto dependencies need to be exported for callers to use
the return values.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix missing proto exports and ServerSetupHelpers warnings
- Export shardok_battle_scala_proto from ShardokBattleConverter
- Export game_state_scala_proto and shardok_battle_scala_proto from GamesManager
- Remove unused ManagedChannelBuilder import from ServerSetupHelpers
- Remove unused default parameter from private newShardokInterface method
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add @unused annotation to parameters that are intentionally unused
(userName and name parameters that need future verification)
- Add missing game_state_scala_proto dependency to BUILD.bazel
- Add CustomBattleManagerTest with tests for CustomBattleGameController
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Change EngineAndResults.results to return Vector[ActionResultT] (Scala) instead of Vector[ActionResult] (proto)
- Update PostResults to use Scala ActionResultT and ShardokBattle types
- Update GameController methods to use Scala types internally
- Add GeneratedTextRequestConverter for Scala-to-proto conversion when needed for LLM requests
- Add recipientFactionIds field to ClientTextVisibilityExtensionT trait
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Remove verbose debug logs that were added during Apple Sign-In debugging:
- Token exchange client_id/redirect_uri
- id_token length
- Parsed user info (id/email)
Error logging is retained for troubleshooting.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Apple inconsistently returns email_verified and is_private_email as
either boolean (true/false) or string ("true"/"false"). Added custom
AppleBool type that unmarshals both formats.
Also added missing claims from Apple's documentation:
- nonce, nonce_supported, c_hash, auth_time
See: https://developer.apple.com/forums/thread/121411🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Log the id_token length and any parse errors to help debug
"Failed to parse user info" errors.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The key is stored as base64-encoded PEM. The previous code base64
decoded it but then tried to use the result directly as key bytes.
The fix: after base64 decoding, PEM decode to extract the actual
key bytes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
MapGenerator now returns Scala ProvinceT instead of proto Province.
GameParametersUtils works with Scala types internally, converting to
proto only in NewGameCreation when needed for ActionResult.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
docker-compose ignores AUTH_IMAGE env var for unknown reasons.
Fix with multiple approaches:
1. Tag pulled image as :latest locally (belt)
2. Pass AUTH_IMAGE explicitly on command line (suspenders)
3. Add debug output to diagnose .env file issues
4. Verify by comparing image digests, not tags
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
BattalionTypeLoader now returns Scala BattalionType instead of proto.
GamesManager and NewGameCreation updated to use Scala types internally,
with conversion to proto only when needed for GameState updates.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Exclude auth paths from docker_build.yml triggers so eagle builds
don't trigger when only auth code changes
- Remove `docker compose up -d auth` from docker_build.yml - auth is
deployed exclusively by auth_build.yml now
- Delete unused deploy/update-env.sh and deploy/env.template
This prevents docker_build.yml from reverting auth to stale images
when it runs after auth_build.yml.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Make GameHistory APIs return Scala types
The GameHistory trait now returns ActionResultWithResultingState (pure
Scala) instead of ActionWithResultingState (proto-based). Internal
storage in InMemoryHistory and PersistedHistory still uses proto types
for file persistence, but the public API is now protoless.
Changes:
- GameHistory.all, since, sinceDate, last, recentResultsForRound now
return Vector[ActionResultWithResultingState]
- withNewResults accepts Scala types and converts to proto for storage
- Added toScala() helper in history implementations
- Updated callers: EngineImpl, EagleServiceImpl, ChronicleEventGenerator,
ActionResultFilter, GamesManager
- SavedGameUtils uses internal ActionWithProtoState for proto reflection
- Fixed Option[Date] comparisons with .forall() pattern
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Delete unused SavedGameUtils
This file was a standalone debugging utility that was never integrated
into the build (BUILD target was commented out). Removing it as cleanup.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The crane+docker-load approach was causing image tagging issues where the
loaded image didn't have the expected registry tag, causing docker-compose
to not find the correct image.
Changes:
- Use simple `docker pull` instead of crane pull + docker load (since
deploy server is already logged into the registry)
- Add verification that the running container is using the expected image
- Fail the workflow if container is running the wrong image
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add comprehensive logging to Apple callback handler
- Fetch GitHub email from /user/emails when not in main response
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When user clicks an OAuth login button:
- Game window minimizes (Windows) or exits fullscreen (macOS/Linux)
- Browser opens and is immediately visible to user
- After auth completes (success or failure), game returns to foreground
- Fullscreen mode is restored if it was enabled
Adds WindowFocusManager utility with platform-specific native calls:
- Windows: P/Invoke to user32.dll (ShowWindow, SetForegroundWindow)
- macOS: Placeholder for native plugin, falls back to fullscreen toggle
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add precomputedScalaActionResult parameter and lazy scalaActionResult property to
ActionWithResultingState, following the same pattern as scalaGameState
- Update GameHistory.withNewResultsScala to preserve Scala ActionResultT to avoid
re-conversion
- Convert ActionResultFilter.includeForPlayer to use Scala types (ActionResultT,
NotificationT, ActionResultType) instead of proto types
- Add UNIVERSALLY_VISIBLE_TYPES_SCALA constant with Scala ActionResultType values
This continues Phase 10 of the deproto migration, moving internal logic to use
Scala types while keeping proto for boundaries.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Use single quotes in .env to handle JSON and special chars
Double quotes don't work when values contain embedded quotes
(like JWT_PRIVATE_KEY JSON). Single quotes treat content literally.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Use exported env vars instead of .env file for deployment
The .env file approach was fragile for complex values like JSON
(JWT_PRIVATE_KEY) and base64 (APPLE_SIGNIN_PRIVATE_KEY).
Export environment variables directly in the deploy script so
docker compose reads them from the shell environment.
Also adds Apple Sign-In credentials to auth_build.yml workflow.
Note: Delete /opt/eagle0/.env on the server before deploying.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fetch GitHub email from /user/emails endpoint
GitHub only returns email in the /user endpoint if the user has made
their email public. For private emails, we need to call /user/emails.
This ensures we get the user's primary verified email even when they
have their email set to private on GitHub.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This PR completes Phase 7 of the deproto migration by making the view
filter and differ components work with Scala types internally, converting
to proto only at boundaries.
## New Scala Types
- `GameStateView` - full game state view
- `GameStateViewDiff` - diff between two game state views
- `ProvinceViewDiff` - diff for province views
- `HeroViewDiff` - diff for hero views
- `FactionViewDiff` - diff for faction views
- `FullProvinceInfoDiff` - diff for full province info
- `ShardokBattleView` - battle view type
- `Hostility` - enum for hostility levels
## New Converters
- `GameStateViewConverter` - converts Scala GameStateView to proto
- `GameStateViewDiffConverter` - converts Scala GameStateViewDiff to proto
- `ProvinceViewDiffConverter` - converts Scala ProvinceViewDiff to proto
- `HeroViewDiffConverter` - converts Scala HeroViewDiff to proto
- `FactionViewDiffConverter` - converts Scala FactionViewDiff to proto
- `ShardokBattleViewConverter` - converts Scala ShardokBattleView to proto
- `HostilityConverter` - converts Scala Hostility to proto
## Updated Components
- `GameStateViewFilter` - now returns Scala `GameStateView`
- `GameStateViewDiffer` - now uses Scala diff types internally
- `ActionResultFilter` - converts Scala diff to proto at boundary
- `HumanPlayerClientConnectionState` - converts Scala GameStateView to proto
## Test Updates
- Updated tests to use Scala `ProvinceOrderType` instead of proto
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Fail fast with a clear error message if a province has an empty
hexMapName when a battle is being created. Previously this would
fail downstream in Shardok with a generic "Must include map path"
error, making it harder to diagnose.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Use single quotes in .env to handle JSON and special chars
Double quotes don't work when values contain embedded quotes
(like JWT_PRIVATE_KEY JSON). Single quotes treat content literally.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Use exported env vars instead of .env file for deployment
The .env file approach was fragile for complex values like JSON
(JWT_PRIVATE_KEY) and base64 (APPLE_SIGNIN_PRIVATE_KEY).
Export environment variables directly in the deploy script so
docker compose reads them from the shell environment.
Also adds Apple Sign-In credentials to auth_build.yml workflow.
Note: Delete /opt/eagle0/.env on the server before deploying.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Include state parameter in OAuth callback redirect
When redirecting from /oauth/callback to /invite/{code}/callback,
include the state parameter so the invitation handler can look up
the OAuth result.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Quote values in .env file to handle special characters
Base64-encoded values contain / characters that break unquoted
.env parsing. Wrap all values in double quotes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Pass APPLE_SIGNIN_CLIENT_ID, APPLE_TEAM_ID, APPLE_SIGNIN_KEY_ID,
and APPLE_SIGNIN_PRIVATE_KEY to the auth container during deployment.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add connectionBackgroundLayer field to ConnectionHandler that can be
linked in the Unity Editor. The layer is hidden when entering the lobby
and shown when returning to the connection screen.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Remove invitation code handling from Unity client
Account creation now happens on the web landing page, so the client
no longer needs to handle invitation codes.
Removed:
- InvitationCodeManager.cs (entire file)
- Invitation code parameter from AuthClient.GetOAuthUrlAsync()
- OAuthStatus.InvitationRequired handling in AuthClient
- OnInvitationRequired event and handlers in OAuthManager
- Invitation code panel UI fields in ConnectionHandler
- OnInvitationRequired and OnSubmitInvitationCodeClicked methods
The display name panel is retained for edge cases where a user
somehow doesn't have a display name set.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix OAuth issues on landing page
- Fix Apple OAuth redirect_uri mismatch in token exchange
The token exchange was using /oauth/callback but the auth request
uses /oauth/apple/callback, causing redirect_uri mismatch error
- Add nginx route for /oauth/apple/callback
Apple OAuth uses form_post response mode which posts to a separate
callback path that wasn't proxied through nginx
- Add credential validation in GetAuthURL
Only show OAuth buttons if provider credentials are configured,
preventing broken auth URLs when client ID/secret are missing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Pass GitHub and Apple OAuth credentials to auth container
The GH_OAUTH_CLIENT_ID/SECRET and Apple Sign-In credentials were
set in GitHub secrets but not passed to the auth service container
in docker-compose.prod.yml, causing the OAuth providers to appear
unconfigured.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update OAuth tests to expect error on empty credentials
The credential validation now returns an error for empty client ID
or client secret, so update the test to expect this behavior.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
RansomOfferHelpers had no production callers - it was only tested.
The ransom offer logic in CommandChoiceHelpers already uses
Scala DiplomacyOptionType.Ransom and Scala RansomOfferDetails.
This removes the last 3 proto imports from command_choice_helpers/.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The doctl --format output was being incorrectly parsed, causing the script
to read size values (18.67 MB) as dates. This resulted in all images being
deleted, including those with the 'latest' tag.
Switch to --output json with jq parsing for reliable field extraction.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Move account creation from Unity client to the web landing page.
Users now complete OAuth sign-in and display name selection in the
browser before downloading the client.
New flow:
1. User visits /invite/{code} → sees OAuth buttons
2. User clicks provider → OAuth flow
3. Existing user: redirect to download page (code not consumed)
4. New user: show display name form → create account → redeem code
5. Download page with platform-specific installer links
Changes:
- Landing page shows OAuth buttons instead of download buttons
- New routes: /invite/{code}/auth/{provider}, /invite/{code}/callback,
/invite/{code}/set-name, /invite/{code}/download
- HMAC-signed cookies for session management between OAuth and form
- 4 new HTML templates: landing, display name form, download, error
- Legacy .bat/.command handlers retained for backwards compatibility
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Pass GH_OAUTH_CLIENT_ID and GH_OAUTH_CLIENT_SECRET through both
auth_build.yml and docker_build.yml to enable GitHub OAuth login.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The stats() method that returns proto ArmyStats has no callers.
Removing it eliminates both proto imports from IncomingArmyUtils.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The providerToString and stringToProvider functions were missing
cases for GITHUB and APPLE providers, causing them to return
"unknown" which resulted in "unsupported provider" errors.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- StatWithConditionUtils: Remove supportSc() and scWithRanges() proto versions
(only Scala versions supportScala() and scWithRangesScala() were being used)
- ProvinceEventUtils: Remove all proto overloads (only Scala overloads used)
- Remove proto dependencies from BUILD.bazel files
Both files are now completely protoless.
Other Utilities: 13 → 9 proto imports (5 → 3 files)
Total: ~84 → ~80 proto imports remaining
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Update ProvinceView.knownEvents from proto ProvinceEvent to Scala type
- Update ProvinceViewConverter to convert events with ProvinceEventConverter
- Update ProvinceViewFilter to work with Scala events internally:
- Remove proto import
- Use Scala overloads of ProvinceEventUtils checkers
- Simplify event filtering logic
- Add visibility for event target to proto_converters/view and model/view
ProvinceViewFilter is now completely protoless - zero proto imports.
View filters directory reduced from 4 proto imports to 3 (in 2 files).
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add GamesManager.isEagleGame(gameId) wrapper for routing decisions
with clear documentation warning against using gameControllerInfos directly
- Fix postCommand routing for ShardokCommand and PlacementCommands
to use isEagleGame() instead of checking empty gameControllerInfos map
- Update streamOneUpdate to use isEagleGame() for consistency
These are the remaining places that had the same bug pattern as the
streamOneUpdate fix (PR #5342): after deployment the map is empty,
causing Eagle games to be incorrectly routed to customBattleManager.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The streamOneUpdate function was checking if the game was already in
gameControllerInfos to decide between gamesManager and customBattleManager.
After deployment (empty map), Eagle games would incorrectly route to
customBattleManager, which doesn't load the game. Then subsequent commands
would fail with "key not found".
Fix: Call ensureGameLoaded first to try loading the game from storage.
If it loads successfully, use gamesManager. If not (game doesn't exist),
fall back to customBattleManager.
Also made ensureGameLoaded public so EagleServiceImpl can call it.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add ensureGameLoaded() calls to:
- postCommand, postShardokCommand, postPlacementCommands
- joinGame case None (for started games)
This handles the case where a command arrives before the game subscription
has loaded the game into memory. The check is cheap (map contains) if the
game is already loaded.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Make HeroViewFilter return Scala types instead of proto
- Create HeroView.scala - Scala case class for the hero view type
- Create HeroViewConverter - converts Scala HeroView to proto
- Update HeroViewFilter to return Scala HeroView
- Update GameStateViewFilter to convert to proto at the edge
- Update AvailableCommandConverter to convert HeroView at the edge
- Update tests to use Scala types
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Consolidate DEPROTO_PLAN.md files and update with current stats
- Move detailed proto import inventory from root DEPROTO_PLAN.md to docs/
- Delete root level DEPROTO_PLAN.md (duplicate)
- Update all proto import counts based on current codebase state:
- Total: ~85 imports remaining (down from 149)
- Command choice helpers: 3 imports in 1 file (was 57 in 17)
- View filters: 4 imports in 3 files
- LLM generators: 56 imports in 34 files
- Add HeroViewFilter to recent completions (PR #5336)
- Update Phase 7 table with current view filter statuses
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add tutorials for all remaining command panels
Add 21 new command tutorials:
- Travel, Return, Diplomacy, Send Supplies, Recon
- Divine, Issue Orders, Control Weather, Swear Brotherhood
- Apprehend Outlaw, Suppress Beasts, Exile Vassal
- Handle Captured Hero, Manage Prisoners, Decline Quest
- Start Epidemic (plague), Handle Riot (3 variants)
- Attack Decision, Free-For-All Decision, Resolve Tribute
All command panels now have tutorials that appear after onboarding.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix tutorial text accuracy and remove duplicates
- Rest: Only restores hero vigor, not troops
- Feast: Adds vigor alongside loyalty, cost based on hero count
- Travel: Goes to town within province, enables various activities
- Organize Troops: Emphasize hiring battalions, mention requirements
- Remove End Turn step (no End Turn button in Eagle)
- Remove duplicate Diplomacy tutorial (command panel version remains)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix Trade, Travel, and Return tutorial descriptions
- Trade: Exchange food/gold within province, market takes cut
- Travel: Mention multiple actions per turn, reference Return
- Return: Opposite of Travel, returns to camp and ends turn
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Make FactionViewFilter return Scala types instead of proto
- Create FactionView.scala - Scala case class for the view type
- Create FactionViewConverter - converts Scala FactionView to proto
- Create FactionRelationshipViewConverter - converts FactionRelationship to proto view
- Update FactionViewFilter to return Scala FactionView
- Update GameStateViewFilter to convert to proto at the edge
- Update tests to use Scala types
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update DEPROTO_PLAN.md with recent progress
- Actions layer now at 0 proto imports (PR #5332)
- ProvinceUtils converted to Scala BattalionType (PR #5333)
- FactionViewFilter returns Scala types (PR #5334)
- Updated summary table and success criteria
- Reorganized remaining work into clear phases
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add contextual tutorials for command panels
When a command panel is shown for the first time (after onboarding
completes), display a tutorial explaining what the command does
and its options.
- Hook CommandSelector.Show() to trigger tutorial events
- Add OnCommandPanelShown() to TutorialTriggerRegistry
- Create tutorials for: Improve, Alms, March, Defend, Rest, Trade,
Feast, Hero Gift, Train, Arm Troops, Organize Troops, Recruit Heroes
- Improve tutorial includes tip about selecting heroes via dropdown
or by clicking in the Resident Heroes panel
All command tutorials require onboarding completion before showing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix command panel tutorial trigger timing and step count display
Two fixes:
1. Move tutorial trigger from Show() to UpdateAvailableCommand()
- Show() only fires when selector changes, not when same command is re-selected
- During onboarding, users explore commands but prerequisites aren't met yet
- After onboarding, re-selecting same command wouldn't trigger Show()
- UpdateAvailableCommand() fires every time a command is selected
2. Use visible step count instead of total step count
- Onboarding has 16 total steps but includes hidden wait steps and tactical steps
- Add VisibleStepCount and GetVisibleStepIndex to TutorialSequence
- Now shows "Step 3 of 15" (visible) instead of "Step 3 of 16" (total)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Mark onboarding complete after strategic portion, fix highlight
Two fixes:
1. Add MarksOnboardingComplete flag to TutorialStep
- When set, marks onboarding complete when that step finishes
- Set on "strategic_complete" step so command tutorials can appear
- Tactical portion continues when battle becomes available
2. Remove highlight from "Province Commands" step
- Was causing yellow box to appear over modal text
- The buttons are explained in the text, no highlight needed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix command panel tutorials: step count, trigger timing, and highlight
- VisibleStepCount now stops at MarksOnboardingComplete step (8 vs 15)
- Allow contextual tutorials to interrupt hidden DisplayMode.None steps
- Restore Province Commands highlighting with HighlightBoundsFromChildren
- Add defensive highlight clearing before showing new modals
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix command tutorial positioning, initial trigger, and switching
- Position command tutorials above CommandPanel using AdjacentTargetPath
- Track last command shown and re-trigger after onboarding completes
- Allow command tutorials to interrupt each other when switching commands
- Same command tutorial won't restart if already showing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Register CommandPanel target and add debug logging
- Add CommandPanel to TutorialTargetRegistry static targets
- Register commandPanel from EagleGameController on tutorial init
- Add debug logging to RetriggerLastCommandTutorial for diagnosis
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix command tutorial timing and switching behavior
- Defer RetriggerLastCommandTutorial until after advancing to hidden step
(was firing while onboarding modal was still active)
- When switching to a command whose tutorial is already completed,
hide the current command tutorial without marking it complete
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Delay onboarding start until first model update
Move StartOnboarding() from SetUpGame() to SwapModel() so it runs after
the first game model is received and UI panels are populated. This
prevents the tutorial from appearing before the province info panel
is visible/positioned.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Link CommandPanel in TutorialTargetRegistry and enable debug logging
- Add CommandPanel reference for tutorial positioning
- Enable tutorial debug logging for testing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Replace proto BattalionType import with Scala version in
ProvinceUtils.availableBattalionTypeIds method. Update test
to use Scala BattalionType with helper function for test data.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Use Scala ActionResultType enum instead of proto enum in
ChronicleEventGenerator. This completes the deproto migration
for the Actions layer (0 proto imports remaining).
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Delete old proto-based CommandSelection class
- Rename ScalaCommandSelection to CommandSelection
- Delete ProtoCommandChooser and ProtoCommandChooserImplicits
- Delete ProtoAvailableCommandSelector
- Update all imports (47 files) to use CommandSelection
- Remove proto dependencies from BUILD.bazel files
- Delete AvailableCommandSelectorTest (tested deleted proto functionality)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Make CommandChoiceHelpers protoless
Convert all command choice helper files to use Scala types instead of
proto types. This eliminates proto dependencies from the command
selection logic in the library.
Key changes:
- Update AvailableCommandSelector, CommandChoiceHelpers, and all
command selector files to use Scala AvailableCommand/SelectedCommand
- Convert CommandSelection to ScalaCommandSelection throughout
- Update action files (EndHandleRiotsPhaseAction,
PerformVassalCommandsPhaseAction, PerformVassalDefenseDecisionsAction)
to work with Scala commands directly
- Update all quest command choosers to use Scala types
- Fix ArmedBattalion Scala definition (battalionTypeId -> newArmament)
- Add exports to combat_unit_selector BUILD.bazel
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Make AI clients protoless
Convert AI client source and test files to use Scala AvailableCommand
and ScalaCommandSelection types instead of proto types.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update CommandChoiceHelpers tests to use Scala types
Convert test files to use Scala AvailableCommand and ScalaCommandSelection
types instead of proto types. Also includes additional source updates for
AvailableCommandSelector and CommandChooser.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix remaining test conversions to Scala types
- Fix CommandChoiceHelpersTest to use DiplomacyAvailable Scala type
- Convert ExpandCommandSelectorTest fixtures from proto to Scala types
- Remove tests that relied on ScalaPB .update() lens syntax (marked with TODO)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix AIClient and GameController to use Scala types consistently
- AIClient now works entirely with Scala command types
- GameController.withPostedCommand accepts Scala SelectedCommand
- postHumanCommand converts proto to Scala at the API boundary
- All 202 tests pass
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Restore 5 missing tests in ExpandCommandSelectorTest
Tests were removed during protoless migration because they used ScalaPB
.update() lens syntax. Rewrote them using .copy() syntax:
- "return nothing if not enough heroes can move to keep balance"
- "move some heroes to a friendly province if there's an imbalance"
- "return a march command with one hero if that's close enough"
- "return a march command with hero count rounding up if possible"
- "keep hero count balanced even if lots are available"
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add tutorial row highlighting for province info, warlord, and vassals
- Highlight entire Province Info panel for province_stats step
- Register hero table rows dynamically for tutorial targeting
- Add WarlordRow target highlighting for heroes_warlord step
- Add VassalRow1+VassalRow2 combined highlight for heroes_vassals step
- Add AdditionalHighlightTargets field for multi-element highlights
- Add HighlightMultiple method to compute combined bounding boxes
- Add GetRowRectTransform helper to EventBasedTable
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Clamp tutorial highlights to stay within screen bounds
Adds screen bounds clamping to both PositionHighlight and
PositionHighlightMultiple methods to prevent highlights from
going off-screen. Uses a 5px margin from canvas edges.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add highlighting for Command Buttons tutorial step
The command_buttons step had AdjacentTargetPath for panel positioning
but was missing TargetGameObjectPath for highlighting. Added both
TargetGameObjectPath and HighlightPulsing to show the highlight.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix highlight bounds to stay within screen
Two fixes for tutorial highlighting:
1. Account for HighlightPadding in screen bounds clamping - the padding
was added to size AFTER clamping, pushing edges off-screen again.
Now the margin includes HighlightPadding so final bounds stay on screen.
2. Add HighlightBoundsFromChildren option for containers where the
RectTransform is larger than visible content. When enabled, highlight
bounds are computed from active child elements instead of the target's
own RectTransform. Used for CommandButtonsPanel where buttons are
85x85 with 5px spacing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Point CommandButtonsPanel to actual button container
Changed the reference to target the button container directly
rather than the larger parent panel.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Tutorial: non-blocking panels with positioning and updated content
- Add TutorialPanelAnchor enum (Center, Left, Right, Top, Bottom)
- Add BlocksInteraction property to TutorialStep for non-modal tutorials
- Update TutorialModalPanel with PositionPanel() method for anchored placement
- Update TutorialUIManager fallback UI to support positioning and non-blocking
- Revise tax/Support content: explain taxes provide both Gold AND Food
- Add vassals tutorial step with loyalty mechanics warning
- Set province-related tutorial steps to non-blocking and right-anchored
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Tutorial: smaller panel, adjacent positioning, Support highlight
- Reduce panel size from 800x550 to 500x400
- Reduce font sizes (title 28, desc 18, progress 14, buttons 16)
- Add AdjacentTargetPath property to position panel next to UI elements
- Add PositionAdjacentTo() method for target-relative positioning
- Highlight Support field during Support tutorial step
- Fix "Give Alms" to say "costs Food" not "costs Gold"
- Fix turn cycle text (no End Turn button - turns end automatically)
- Fix vassals text: "feasts" instead of "victories"
- Emphasize that commands are safe to explore until Commit
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Tutorial: add TutorialTargetRegistry for Unity-linked targets
- Create TutorialTargetRegistry component with serialized fields for UI targets
- Add TargetRegistry reference to TutorialManager
- Update TutorialUIManager to use registry instead of GameObject.Find
- Update TutorialModalPanel to use registry for adjacent positioning
- Registry provides drag-and-drop configuration in Inspector
- Falls back to GameObject.Find for unregistered targets
Supported targets:
- ProvinceInfoPanel, SupportField, AgricultureField, EconomyField,
InfrastructureField, HeroesPanel, CommandButtonsPanel, ImproveButton,
AlmsButton, MarchButton, CommitButton, BattleButton
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Tutorial: support dynamic target registration for prefab buttons
- Remove individual command button fields (ImproveButton, AlmsButton, etc.)
- Remove BattleButton (not needed yet)
- Add RegisterTarget/UnregisterTarget methods for runtime registration
- Command buttons can be registered when instantiated from prefabs
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add TutorialTargetRegistry.cs.meta
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Tutorial: fix Infrastructure description
- Infrastructure improves troop armament and disaster resilience
- Note that all three stats increase storage capacity
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Tutorial: increase panel size to 540x500
Panel was too small, causing title to clip at top and buttons below bottom.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Tutorial: soften Warlord warning text
Changed from "game over" to "protect them" - the full mechanic
is more nuanced and doesn't need to be explained in onboarding.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Tutorial: add debug logging for highlight targeting
Helps diagnose why Support field highlight may not be appearing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add diagnostic logging to debug registry lookup and panel positioning
- Log TutorialManager.Instance and TargetRegistry state
- Log which target is found (static vs dynamic vs not found)
- Log panel positioning anchor and resulting position
- Log step details when Show() is called
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Wire TutorialTargetRegistry to TutorialManager in scene
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix tutorial panel positioning and highlight sizing
- Add screen bounds clamping to prevent panels from going off-screen
- Add Top/Bottom positioning support for adjacent panel placement
- Fix highlight frame using canvas-local coordinates instead of screen coords
- Position Warlord/Vassals panels adjacent to HeroesPanel
- Position Command panel above CommandButtonsPanel with highlight
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Clean up debug logging and remove command button highlight
- Remove diagnostic debug logs from TutorialModalPanel and TutorialTargetRegistry
- Remove command buttons highlight (panel keeps oversized element bounds)
- Keep panel positioning adjacent to CommandButtonsPanel
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The diplomacyOptionTypeToOption method was incorrectly setting goldCost=0
for all diplomacy types (Alliance, Truce, Invitation, BreakAlliance).
This meant gold was never deducted when these commands were executed.
Fix uses the proper settings values for each diplomacy type.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Server-side implementation for GitHub and Apple Sign-In OAuth:
- Add OAUTH_PROVIDER_GITHUB and OAUTH_PROVIDER_APPLE to auth.proto enum
- Add GitHub OAuth config (standard OAuth 2.0 flow)
- Add Apple Sign-In config with JWT client_secret generation
- Handle Apple's POST callback and id_token parsing for user info
- Support per-provider callback URLs (Apple requires /oauth/apple/callback)
Environment variables required:
- GitHub: GH_OAUTH_CLIENT_ID, GH_OAUTH_CLIENT_SECRET
- Apple: APPLE_SIGNIN_CLIENT_ID, APPLE_SIGNIN_KEY_ID, APPLE_SIGNIN_PRIVATE_KEY
(APPLE_TEAM_ID already exists for notarization)
Client UI changes will follow in a separate PR.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Improve onboarding tutorial flow and simplify UI buttons
- Revise onboarding to focus on the single starting province:
- Province stats (Agriculture/Economy/Infrastructure)
- Support importance (40 by January for taxes)
- Faction Head and hero panel
- Command buttons (Improve and Give Alms)
- Turn cycle explanation
- Simplify tutorial modal buttons to just two options:
- "Continue" - advance to next step
- "Skip Tutorial" - skip all remaining steps (onboarding only)
- Remove redundant "Skip" button (was identical to Continue)
- Add note in welcome step about restarting from Settings
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Use "Warlord" instead of "Faction Head" in tutorial
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add lastUpdateAtMillis field to IncompleteClientText to track when data
was last received from the LLM stream. This enables better diagnosis of
stalled incomplete texts by distinguishing between:
- Streams that stalled immediately (small partialLen, secsSinceLastUpdate ≈ secsSinceRequest)
- Streams that received data then went silent (larger partialLen, secsSinceLastUpdate << secsSinceRequest)
This helps verify the hypothesis that HTTP/2 streams can go into a zombie
state where no data, error, or completion is received.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1. Separate cache paths per platform (/tmp/eagle0/Library-mac vs Library-windows)
- Prevents cross-platform contamination if runners share /tmp
2. Only persist cache on successful builds
- Prevents failed builds from poisoning the cache
3. Exclude Library/Bee/ from cache
- Bee contains DAG files with hardcoded paths that become stale
- Prevents "Data at the root level is invalid" XML errors
- ScriptAssemblies and ShaderCache are still cached for speed
Also adds clean: true to Windows Unity workflow.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Two related fixes for handling stale game state after blue-green deploy:
Server (EagleServiceImpl.scala):
- Catch "Token mismatch" exceptions in postCommand and return BAD_TOKEN
status instead of throwing an exception
- Previously the exception caused an RPC error, bypassing the client's
BAD_TOKEN handling which refreshes game state
Client (PersistentClientConnection.cs):
- When WriteAsync times out or fails, dispose the dead connection and
schedule reconnect
- Previously the connection was left in a zombie state (appeared alive
but couldn't communicate)
- Add better logging for write errors
Root cause: After a deploy, the client reconnects but may have stale
game state. When posting a command with an old token, the server threw
an exception instead of returning BAD_TOKEN. The client didn't know to
refresh its state, and the stale command stayed in the retry queue.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Remove GoDice Bluetooth dice integration
The GoDice integration for physical Bluetooth dice was incomplete and
causing Mac build failures due to orphaned .meta files for plugin
binaries that weren't tracked in git.
This commit removes all GoDice-related code:
- Deleted Assets/Bluetooth folder with all dice interface code
- Removed DarwinGodiceBundle.bundle.meta and GoDiceDll.dll.meta
- Removed RollFetcher interface and references from game models
- Removed GoDice settings from SettingsPanelController
- Updated ShardokGameModel to always pass null for rolls (server
generates random rolls when no physical roll is provided)
The feature can be re-added later when there's time to properly
implement and test GoDice integration.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove GoDice Canvas with orphaned script references
Removed the GoDice Canvas GameObject from the scene which contained
components referencing the deleted Bluetooth/RollPanelController scripts.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove GoDice plugin build steps from CI
Since GoDice integration is removed, no need to build the
DarwinGodiceBundle or GoDiceDll plugins in CI.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add native Sparkle plugin to enable Mac auto-updates
The Sparkle framework was being injected into the app bundle, but
nothing was initializing it. This adds:
- Native Objective-C plugin (SparklePlugin.m) that initializes
SPUStandardUpdaterController at runtime
- C# wrapper (SparkleUpdater.cs) for Unity to call the native plugin
- SparkleInitializer.cs uses RuntimeInitializeOnLoadMethod to
automatically initialize Sparkle at app startup
- Build script to compile the plugin as a universal binary
The plugin is weak-linked against Sparkle.framework, which is injected
separately by inject_sparkle.sh during the build process.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Convert Sparkle plugin build from clang to Bazel
- Add Sparkle framework as http_archive dependency in MODULE.bazel
- Add BUILD.sparkle to import the framework
- Add BUILD.bazel for SparklePlugin using macos_bundle rule
- Update build_sparkle_plugin.sh to use Bazel instead of direct clang
- Register Apple CC toolchain extension
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix symbol exports for SparklePlugin native library
The C functions need to be exported with visibility("default") and
explicit linker flags for Unity P/Invoke to find them. Without this,
the bundle binary had no exported symbols.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Re-enable Sparkle auto-update integration for Mac builds
Restores Sparkle integration that was temporarily removed in #5317:
- Restore inject_sparkle.sh script
- Add Sparkle injection step to mac_build.yml
- Re-enable Sparkle signing and appcast updates in deploy step
Combined with native SparklePlugin that initializes Sparkle at runtime.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Build SparklePlugin.bundle before Unity build
The SparklePlugin.bundle.meta file tells Unity to include the plugin,
but the actual bundle needs to be built by Bazel first.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Convert SparklePlugin Info.plist to XML format
Unity's build system requires Info.plist files in XML format,
but Bazel outputs them in binary plist format.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The ArmedBattalion.battalionTypeId field was misnamed - it represents
the armament level to raise troops to, not a battalion type ID.
Renamed to newArmament to match the proto field name and the
semantic meaning.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Remove Sparkle auto-update integration from Mac builds
Temporarily removing Sparkle integration to get Mac builds working:
- Remove inject_sparkle.sh script and workflow step
- Make mac_build_handler's Sparkle private key optional
- Skip Sparkle signing and appcast updates when no key provided
This allows Mac builds to complete without Sparkle. Auto-updates can
be re-enabled later once the basic build pipeline is stable.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Use clean checkout to remove stale SparklePlugin.bundle
The runner had a leftover SparklePlugin.bundle from previous builds
which was causing Unity to fail when trying to process it.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When warmup games (or any games) are deleted while LLM requests are
in flight, the async responses would crash with "key not found" because
GamesManager used direct Map access that throws NoSuchElementException.
This caused two problems:
1. The exception disrupted LLM response processing
2. Other games' text generation could get blocked as a result
Changed three methods to use safe .get() access and gracefully ignore
responses for deleted games:
- receiveStreamingLlmResponses: logs and returns early
- receiveStreamingLlmFailure: logs and returns early
- aiPlayers: returns empty vector
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
DateProtoUtils has no production callers - it's dead code that only
has a test file. The Scala Date type at model/state/date/ is the
preferred way to work with dates in the codebase.
Proto imports in library/ after this change: 143
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
IDable is a test-only utility that provides mapify* helper methods
for converting proto collections to Maps. This moves it from the
main library/ directory to test code.
Changes:
- Inline IDable methods in StartGameActionResultUtils (the only main
code user)
- Move IDable.scala to src/test/scala/net/eagle0/eagle/library/util/
- Update all test BUILD.bazel files to use the test version
Proto imports in library/ after this change: 145
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Delete all proto overloads from IncomingArmyUtils since all callers use
Scala types. This required adding an export to ProvinceOrderTypeConverter
to properly expose the proto type to callers.
Proto imports in library/: 149 → 146
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
ArmyUtils.heroCount and ArmyUtils.troopCount methods are never called.
Delete the file entirely.
Proto imports in library/: 149 → 147
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Document all 149 proto imports in library/ with:
- Proto deps by BUILD.bazel file (for build-level tracking)
- Detailed imports by file/line (for code-level tracking)
- Cleanup priority candidates
This inventory makes it easier to track deproto progress.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The issue was that upload-artifact uploads directory *contents*, not
the directory itself. So uploading eagle0.app resulted in an artifact
containing Contents/... without the eagle0.app wrapper. When downloaded,
this corrupted the .app bundle structure.
Fix by:
- Zip the .app bundle with ditto before uploading (preserves structure
and macOS extended attributes)
- Unzip after downloading to restore the proper .app bundle
- Clean download directories before extracting to avoid stale state
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Fix unity_build.yml and mac_build.yml to trigger on actual client
proto directories instead of non-existent src/main/proto/** path
- Trigger on: common/**, shardok/**, eagle/api/**, eagle/common/**,
eagle/views/** (excludes eagle/internal/** which is server-only)
- Remove build_protos_test.yml as redundant (unity/mac builds run
build_protos.sh)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Delete proto GameState overloads from AvailableResolve*CommandFactory
classes since they now use only Scala types. Move package.scala with
proto helper functions to test directory. Convert break alliance test
to use Scala types.
Reduces proto imports in library/ from 192 to 168.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The client presigner was intended for generating presigned S3 URLs,
but assets.eagle0.net now points directly to the DigitalOcean CDN
(eagle0-windows bucket is public). The presigner was never deployed.
Removed:
- .github/workflows/client_presigner.yml
- src/main/go/net/eagle0/client_download/
- Presigning code from util/aws/s3.go (GetPresignedURL, NewPresigner, Presigner)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The internal/unaffiliated_hero.proto was mistakenly included in the
client proto build. Internal protos should only be used server-side.
No C# code references types from Net.Eagle0.Eagle.Internal namespace.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Prevent multiple tutorials from showing simultaneously
- Don't trigger contextual tutorials while another is already active
- Fix HideOverlay to work even when parent container is inactive
(was silently returning without hiding, causing UI pile-up)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove placeholder TargetGameObjectPath values from tutorials
The placeholder values like "ProvinceUI", "BattleButton", "EndTurnButton"
don't match actual GameObjects in the scene, causing warnings.
Overlays now show centered without targets. TODO comments mark where
to add proper targeting once the actual UI hierarchy is known.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add tutorial content documentation for editing
Creates docs/TUTORIAL_CONTENT.md with:
- Full onboarding sequence (13 steps) with titles, descriptions, triggers
- Strategic contextual tutorials (diplomacy, heroes, weather, prisoners)
- Tactical contextual tutorials (spells, terrain, abilities)
- Display mode reference
- Content guidelines
Edit this doc to refine content, then update TutorialContentDefinitions.cs.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The lastPlayedByUser timestamp was updated in postCommand and
postShardokCommand but save() was not called, so the data stayed
in memory until something else triggered a save.
Add save() calls after updating lastPlayedByUser to ensure the
timestamp is persisted to disk immediately.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Remove redundant proto-based Validator and RuntimeValidator that used
proto types. The Scala versions (ScalaValidator/ScalaRuntimeValidator)
have identical validation logic and are already used by ActionResultApplierImpl.
- Delete proto Validator.scala and RuntimeValidator.scala
- Rename ScalaValidator -> Validator
- Rename ScalaRuntimeValidator -> RuntimeValidator
- Update all imports across library/ and test/ code
- Delete unused TestingNoopValidator
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Bug: When a deploy happened while a user had a command in-flight,
the command could be lost without the user knowing:
1. User posts command with token T
2. PostRequest adds to _pendingCommands, WriteAsync completes locally
3. PostRequest removes from _pendingCommands (TOO EARLY!)
4. Connection dies before server receives command
5. Reconnect - _pendingCommands is empty, command never retried
6. User sees "Processing..." forever
Root cause: WriteAsync completing only means data was written to local
TCP buffers, not that the server received and processed it. The command
was removed from _pendingCommands prematurely.
Fix:
- Don't remove from _pendingCommands after WriteAsync success
- Remove only when server confirms: PostCommandResponse SUCCESS or BAD_TOKEN
- TryPendingCommands already handles stale commands (token advanced)
This ensures commands are retried on reconnect if they weren't confirmed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Eagle server already persists to S3 on every game save via CompoundPersister
when S3Credentials.isEnabled. The deploy script's s3cmd backup was redundant
and caused warnings when s3cmd wasn't installed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Docker builds were failing because jobs could run on any self-hosted
runner, but each runner has a different Bazel output base. When a job
ran on a different runner than previous builds, Bazel's remote cache
reported "cached" but local output files didn't exist.
Fix: Add `bazel` label requirement to all generic Bazel-based workflows.
The specialized Unity/notarization runners don't have this label, so
they won't pick up these jobs.
Workflows updated:
- auth_build.yml
- bazel_test.yml
- build_protos_test.yml
- client_presigner.yml
- docker_build.yml
- installer_build.yml
- shardok_arm64_build.yml
- shardok_build.yml
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add tutorial content definitions (Phase 4)
Create TutorialContentDefinitions.cs with all tutorial content:
Onboarding sequence (13 steps):
- Welcome, map overview, province selection
- Command panel, march command, turn cycle
- Battle intro, enter battle, tactical overview
- Move units, attack enemies, end turn, completion
Strategic contextual tutorials:
- Diplomacy introduction
- Hero recruitment
- Weather control
- Prisoner management
Tactical contextual tutorials:
- Spells: Lightning, Meteor, Holy Wave, Raise Dead
- Terrain: Fire hazards, water crossing
- Abilities: Cavalry charge
Content is defined in code for easy version control and review.
TutorialManager now auto-registers all content on initialization.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix tutorial UI not showing when parent container inactive
Add ActivateParents() to TutorialOverlayController and TutorialModalPanel
to ensure all parent GameObjects are active before showing. This fixes
the error "Coroutine couldn't be started because the game object is inactive".
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The proto-based appliedResults method was never called - all code paths
use the Scala-based appliedResultsScala. This removes the dead code and
renames appliedResultsScala to appliedResults for clarity.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
These files wrapped proto→Scala→proto conversions but were never used
anywhere in the codebase. Removing them as dead code cleanup.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Previously, AvailableCommandConverter.toProto() took a proto GameState and
internally called GameStateConverter.fromProto() to get the Scala GameState
needed for lookups. This caused unnecessary Scala→Proto→Scala round-trips.
This change:
- Updates AvailableCommandConverter.toProto() to take Scala GameState directly
- Updates OneProvinceAvailableCommandsConverter.toProto() similarly
- Updates all callers (GameController, AIClient, action files) to pass
Scala GameState directly instead of converting to proto first
- Updates tests to use Scala GameState
This eliminates proto conversion overhead in the command availability path.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add dedicated self-hosted runners for parallel Unity builds:
- unity-mac: Mac Unity builds and deployment
- unity-windows: Windows Unity builds (cross-compiled on Mac)
- notarize: Notarization waiting (lightweight, doesn't block builds)
Split mac_build.yml into 3 jobs:
1. build-and-sign (unity-mac): Build, sign, submit to Apple
2. wait-notarization (notarize): Wait for Apple, staple ticket
3. deploy (unity-mac): Deploy notarized app
This allows:
- Mac and Windows Unity builds to run in parallel
- Notarization waiting doesn't block other builds
- All runners share the same Mac Mini hardware
New scripts:
- notarize_submit.sh: Submit without waiting, output submission ID
- notarize_wait.sh: Wait for submission ID, staple ticket
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Eliminates unnecessary Scala→Proto→Scala round-trip conversions in the
action result filtering path:
- ActionResultFilter now takes Scala GameState instead of proto
- Uses Scala RoundPhase enum instead of proto RoundPhase
- Removed GameStateConverter.fromProto calls in filteredGameStateDiff
- Updated callers (EngineImpl, HumanPlayerClientConnectionState) to pass
Scala state directly instead of converting to proto first
JFR profiling showed proto conversion taking ~16% of eagle0 time. This
change reduces that overhead in the filtering path by eliminating:
- 1x Scala→Proto conversion per filter call in callers
- 2x Proto→Scala conversions per action result (before/after states)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Use rm -f instead of rm when cleaning up the zip file after notarization.
The zip may already be deleted if a previous step failed and was retried,
causing the script to fail even when notarization actually succeeded.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When clicking "Stop" on JFR recording, a dialog now appears with options:
- Download & Stop: Downloads the recording then stops
- Stop Only: Stops without downloading
- Cancel: Keeps recording
This prevents accidentally losing recordings by clicking Stop without
remembering to download first.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add getScalaAvailablePlayerCommands to Engine interface
Expose a method that returns available commands using Scala types directly,
avoiding proto conversion overhead. This enables AI clients and other internal
callers to work with native Scala types without round-tripping through proto.
The existing getAvailablePlayerCommands method continues to return proto types
for gRPC client compatibility.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Make Engine.getAvailablePlayerCommands return Scala types only
Callers that need proto types for gRPC (like GameController) now
convert using OneProvinceAvailableCommandsConverter. AIClient
converts to proto temporarily until command choosers are migrated.
Changes:
- Engine.getAvailablePlayerCommands returns SortedMap[ProvinceId, ScalaOneProvinceAvailableCommands]
- Removed separate getScalaAvailablePlayerCommands method
- Added toProtoAvailableCommands helper to GameController for gRPC conversion
- Updated AIClient to convert Scala to proto for command choosers
- Updated tests to use SortedMap.empty for mocked commands
- Removed unused proto deps from EngineImpl
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove unnecessary Scala prefix from OneProvinceAvailableCommands imports
No longer need to disambiguate since proto types are only used where needed.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Display last played time in lobby game list (client-side)
- Add lastPlayedField to RunningGameItem for displaying time
- Format time as relative (e.g., "Just now", "5m ago", "2h ago", "3d ago")
- Fall back to date format ("Jan 5") for older times
- Display in user's local timezone
Requires server-side changes from PR #5281 (now merged).
Note: The lastPlayedField TextMeshProUGUI reference needs to be added
to the RunningGameItem prefab in Unity.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Wire up lastPlayedField in RunningGameItem prefab
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When a pending command is dropped because the server's token has
advanced (indicating the command was already processed), the client
now triggers a re-subscription to ensure it has the current game state.
This fixes a race condition during deployment reconnects where:
1. User posts command, UI clears available commands
2. Connection drops during deployment
3. Server processes command, token advances
4. Client reconnects, pending command dropped as "stale"
5. UI was stuck with no commands visible
Changes:
- Add game_id to PostCommandResponse proto for targeted refresh
- Server echoes game_id in SUCCESS and ERROR responses
- Client refreshes specific game subscription when:
- Pending command dropped as stale (token mismatch)
- Server returns BAD_TOKEN response
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Adds [TIMING] logs to identify slow operations during client subscription:
- GamesManager.streamUpdates: logs ensureGameLoaded and filtering time
- HumanPlayerClientConnectionState.streamUpdates: logs shardok filtering,
action result filtering, and game state view filtering
- filteredResultsFrom: logs GameStateConverter.toProto and
ActionResultFilter.filterForOptionalPlayer separately
Logs only appear when operations exceed 10-50ms thresholds to avoid
noise during normal operation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add last_played_by_user map to RunningGame proto for persistence
- Track last played time in ControllerInfo when processing commands
- Update postCommand and postShardokCommand to record timestamps
- Persist and load last played times across server restarts
- Include lastPlayedTimestampMillis in GameInfo lobby response
The client-side display will be added in a follow-up PR.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Shardok now runs exclusively on the Hetzner ARM64 server, deployed via
the shardok_arm64_build.yml workflow. This removes:
- shardok service from docker-compose.prod.yml
- Shardok x86 build/push from docker_build.yml
- SHARDOK_IMAGE from env.template and deploy script
- C++ path trigger from docker_build.yml (handled by ARM64 workflow)
The next deployment will stop and remove any existing shardok-server
container on the DigitalOcean droplet.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
CreateGame on a cold JVM took >90s in production, causing warmup to
fail and abort the deployment. Increase per-operation timeout to 180s.
The overall warmup timeout (--timeout flag) is already 300s, but the
internal per-operation timeout was only 90s.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Change incremental saves from O(n²) to O(n) by saving individual ActionResults
to separate .e0r files immediately, then consolidating into chunks and deleting
the individual files. This eliminates redundant re-serialization of the same
results when building up a chunk incrementally.
Also adds crash recovery support: orphaned .e0r files are loaded and merged
with chunk data on startup, preserving any results written before a crash.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add FilterContext optimization for filteredGameState
Introduces a FilterContext class that pre-computes expensive data once
at the start of filteredGameState, avoiding repeated O(n) and O(n*m)
lookups when filtering game state for player views.
Key optimizations:
- Pre-compute ally pairs as Set for O(1) alliance checks (was O(n))
- Pre-compute prisoner hero IDs as Set for O(1) lookups (was O(heroes*provinces))
- Pre-compute hero-to-province mapping for O(1) lookups (was O(provinces))
- Cache factionLeaderIds to avoid repeated flatMap allocations
Complexity improvements:
- HeroViewFilter: O(heroes * provinces) -> O(heroes + provinces)
- BattalionNameFilter: O(factions²) -> O(factions)
- Overall filteredGameState: eliminates ~148+ repeated Vector allocations
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove unused filter overloads and update tests
- Remove old FactionViewFilter.filteredFactionView 3-param overload
- Remove old ProvinceViewFilter.filteredProvinceView 3-param overload
- Remove old BattalionNameFilter.filteredBattalionNames 2-param overload
- Remove old helper methods no longer needed
- Update FactionViewFilterTest to use FilterContext
- Update ProvinceViewFilterTest to use FilterContext
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Auto-invalidate game cache when flush marker is updated
During warmup, the staging server may cache stale game data that was
loaded before the active server flushed. Instead of exposing an RPC
for cache invalidation (which leaks internal state), the server now
automatically detects when the flush marker is updated and invalidates
any cached games.
Changes:
- GamesManager: Added `invalidateCacheIfFlushMarkerUpdated()` that
checks the flush marker's modification time and clears the cache
if it's been updated since the last check
- Called before checking if a game is in cache, so stale data is
cleared before any attempt to use it
- Removed the InvalidateGameCache RPC (no longer needed)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix user wait window metric to measure actual user impact
The max user wait window was measuring from when nginx switching
started (before recreation) instead of when it completed. This
inflated the metric by ~12s because nginx recreation time was included.
Users can only experience a wait AFTER nginx starts routing to the
staging server, so we now measure from nginx_switch_end (after
recreation completes) to flush_end.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
During warmup, the staging server may cache stale game data that was
loaded before the active server flushed. Instead of exposing an RPC
for cache invalidation (which leaks internal state), the server now
automatically detects when the flush marker is updated and invalidates
any cached games.
Changes:
- GamesManager: Added `invalidateCacheIfFlushMarkerUpdated()` that
checks the flush marker's modification time and clears the cache
if it's been updated since the last check
- Called before checking if a game is in cache, so stale data is
cleared before any attempt to use it
- Removed the InvalidateGameCache RPC (no longer needed)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When the server returns a PostCommandResponse with ERROR status,
disconnect and reconnect using the normal flow. This ensures the
client recovers gracefully from server-side errors during command
processing.
Also logs BAD_TOKEN responses for debugging purposes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Make view_filters completely protoless
Remove all proto type overloads from the view_filters package, forcing
callers to use Scala types exclusively. This is part of the ongoing
effort to reduce proto dependencies in the codebase.
Changes:
- Visibility: Remove proto GameState overloads
- BattalionNameFilter: Remove proto overload (~60 lines)
- HeroViewFilter: Remove proto overloads, use Scala RoundPhase
- ProvinceViewFilter: Remove proto overloads (~280 lines)
- FactionViewFilter: Remove proto overload
- BattleFilter: Remove proto overloads
- ArmyFilter: Remove proto overloads
Also:
- Delete unused ExpandedCombatUnitUtils.scala
- Update AvailableCommandConverter to convert types before calling filters
- Remove outdated view_filter tests that used proto types
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add protoless tests for view_filters
Restore FactionViewFilterTest, HeroViewFilterTest, and ProvinceViewFilterTest
using Scala types (FactionC, ProvinceC, HeroC, GameState) instead of protos.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add comprehensive protoless ProvinceViewFilterTest
Rewrites ProvinceViewFilterTest with full coverage of all original test
cases, using Scala types instead of proto types. Tests cover:
- Devastation and economy values
- Ruler traveling status
- Incoming armies (own/hostile/neutral provinces)
- Unaffiliated heroes
- Incoming supplies visibility
- Reconned views from self/allies
- Most recent reconned view selection
- Killed heroes filtering from reconned views
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The saves directory is owned by root (created by Docker) but the deploy
script runs as the deploy user. Use docker exec to create marker files
from inside a running container that has the saves directory mounted.
- create_deployment_marker: uses active container (running before staging starts)
- create_flush_marker: uses staging container (running after active stops)
- cleanup_markers_on_failure: uses active container (still running on failure)
- remove_stale_deployment_marker: finds any running eagle container
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Enhance tutorial trigger detection for strategic and tactical events
Expands TutorialTriggerRegistry with specific condition detection:
Strategic triggers:
- Diplomacy command availability
- Weather control availability
- Province riots (new)
- Hero recruitment opportunities
Tactical triggers:
- Spell cast detection (lightning, meteor, holy wave, raise dead)
- Ability usage (charge, flanking)
- Terrain encounters (fire, water)
- Spell/ability availability when commands update
Also adds OnTacticalCommandsAvailable hook to ShardokGameController
to detect when special abilities become available.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix tutorial trigger compilation errors
- Use correct field name ControlWeatherSelectedCommand (not ControlWeatherCommand)
- Remove CheckBattleMapFeatures - HexMap doesn't have FireCoords/WaterCoords
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix remaining compilation errors in TutorialTriggerRegistry
- Use ControlWeatherAvailableCommand (not ControlWeatherCommand) for AvailableCommand
- Use FactionId (not Faction) for HeroView
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix all proto field name mismatches in TutorialTriggerRegistry
- Use opac.Commands (not AvailableCommands) for OneProvinceAvailableCommands
- Remove province riot detection (riot status not exposed in ProvinceView)
- Remove FlankAttack (doesn't exist in ActionType)
- Use CrossedWater (not CrossWater) for ActionType
- Use LightningBoltCommand (not LightningCommand) for CommandType
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Fix nginx not picking up config changes during blue-green deploy
Root cause: `docker compose restart nginx` doesn't refresh bind-mounted
volume files. The nginx container keeps using its cached copy of
nginx.conf even after we update the host file with sed.
This caused nginx to keep trying to connect to the old (now deleted)
eagle instance, resulting in 502 errors after deployment.
Fix:
- Use `docker compose up -d --force-recreate nginx` instead of `restart`
This recreates the container, forcing it to read the updated config
- Add verification that nginx picked up the correct backend
- Remove the useless pre-validation (it validated old config in old container)
The progression of failed fixes:
1. `nginx -s reload` - doesn't re-resolve Docker DNS
2. `docker compose restart` - doesn't refresh bind-mounted files
3. `docker compose up -d --force-recreate` - THIS WORKS
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Reorder deploy: switch nginx BEFORE stopping blue to avoid 502s
Previous order (caused 502 errors):
1. Stop blue → nginx still points to blue → 502!
2. Update nginx config
3. Recreate nginx → traffic finally works
New order (eliminates 502 window):
1. Update nginx config
2. Recreate nginx → traffic goes to green (blue still running)
3. Stop blue → flushes state to disk
4. 3-second pause for flush to complete
The stale data race condition is minimized by stopping blue immediately
after the nginx switch. Users reconnecting to green will lazy-load
fresh game data from disk (after blue has flushed).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add flush marker coordination for zero-downtime blue-green deploys
This ensures green never serves stale game data during deployments:
1. Deploy script creates .deployment_in_progress marker at start
2. Green's lazy-load waits for flush marker if deployment in progress
3. nginx switches to green BEFORE stopping blue (zero 502 downtime)
4. Blue stops, flushes state to disk
5. Deploy script creates .flush_complete marker
6. Green's waiting lazy-loads proceed with fresh disk data
Key changes:
- GamesManager.scala: Add waitForFlushMarker() that blocks lazy-load
during deployment until flush marker appears (30s timeout)
- GamesManager.scala: Auto-clean stale markers >5 minutes old
- GamesManager.scala: Add deployment ID correlation in logs [DEPLOY:xxx]
- GamesManager.scala: Report flush marker timeouts to Sentry
- deploy-blue-green.sh: Reorder to switch nginx BEFORE stopping blue
- deploy-blue-green.sh: Add marker file coordination with deployment ID
- deploy-blue-green.sh: Add timing metrics (flush duration, user wait window)
- nginx.conf: Keep variable-based routing (Docker DNS only resolves
running containers, so upstream+backup doesn't work)
Monitoring and observability:
- All deployment-related logs tagged with [DEPLOY:timestamp] for correlation
- Wait duration logged for each lazy-load during deployment
- Flush marker timeouts reported to Sentry for alerting
- Deploy script logs total duration, flush duration, max user wait window
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Convert GameState once upfront in GameStateViewFilter proto overload,
then use the already-converted Scala heroes directly instead of
converting each hero individually.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Use fold with RecruitmentInfo.Unknown as fallback instead of .get
to handle proto UnaffiliatedHero objects where recruitmentInfo
is None.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The proto overloads now convert factions via FactionConverter
and delegate to the Scala implementations, eliminating duplicate
logic.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Changes:
- Proto filteredFactionView now converts types and delegates to Scala version
- Removed ~45 lines of duplicate private methods (filteredRelationshipLevel,
filteredFactionRelationshipView)
This continues the deproto work of consolidating proto and Scala code paths.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add checkboxes next to each game in the admin console Games list,
allowing multiple games to be selected and deleted at once. When
games are selected, a batch actions bar appears with "Delete Selected"
and "Clear" buttons.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Changes:
- Proto filteredHeroView now converts types and delegates to Scala version
- Removed duplicate private methods (heroIsPrisoner, heroUnaffiliatedInProvince,
heroIsOfferedInRansom, includeFullHeroInfo)
- Added Gender.Unknown to Scala enum to preserve GENDER_UNKNOWN on round-trip
- Updated GenderConverter to handle Unknown case
- Fixed test data in AvailableCommandConverterTest to include valid currentPhase
This reduces code duplication while maintaining backward compatibility.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When clicking to move a unit, the animation now follows the actual
path of hexes from the CommandDescriptor's path field instead of
drawing a straight line from origin to destination. This prevents
the animation from showing units moving "over water" when the
actual path goes around it.
Changes:
- MoveAnimator: Add path-based animation method that draws prints
along each segment of the path
- ShardokGameModel: Return full CommandDescriptor from
PerformTargetedCommand instead of just CommandType
- ShardokGameController: Extract path from executed command and
pass to animation system
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
nginx reload doesn't always force DNS re-resolution in Docker. During
blue-green deployment, after updating nginx.conf to point to the new
instance (e.g., eagle-green:40032), nginx -s reload would sometimes
keep trying to connect to the old (now removed) container, causing
502 errors.
A full restart ensures nginx picks up the new upstream correctly.
The ~1-2 second restart time is acceptable for deployment reliability.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The warmup tool was timing out during CreateGame because the per-step
timeout was only 30 seconds. On a cold JVM (freshly started Eagle
instance during blue-green deployment), CreateGame can take longer
than 30 seconds due to:
- JIT compilation not yet warmed up
- First-time class loading
- Game initialization including Shardok communication
Changes:
- Increase per-operation timeout from 30s to 90s
- Increase overall warmup timeout from 60s to 300s (5 minutes)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
CommandDescriptor now includes a repeated Coords path field that
contains all hexes traversed during a move command. This allows
clients to animate the actual path taken rather than a straight
line from origin to destination (which sometimes showed units
moving over water).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This removes the last of the Legacy*Utils wrapper classes, completing the
deproto migration. ProvinceViewFilter now uses ProvinceUtils directly,
with a new resourceCap helper method to calculate gold/food caps from
pre-computed effective development values.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add delete button next to each game in the main games list
- Include confirmation modal with option to delete save files
- Reuses existing /games/{id}/delete endpoint
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1. Add missing jfr and jvm-tmp volumes to eagle-green
- Without these, JFR sidecar can't attach to the JVM when green is active
- JFR recordings wouldn't work either
2. Remove redundant sync_config_files from deploy script
- CI already copies config files via scp before running deploy
- Fetching from GitHub main could cause version mismatches
- Eliminates unnecessary network calls during deployment
3. Skip image pull if already present locally
- CI already pulls images before running deploy script
- Saves time during CI deployments
- Manual deployments still pull if needed
4. Add nginx config validation before reload
- Run nginx -t before nginx -s reload
- Rollback to backup config if validation fails
- Prevents broken config from taking down nginx
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Problems fixed:
1. CI was recreating admin BEFORE blue-green, causing stale .env
2. CI was restarting nginx AFTER blue-green (double restart)
3. Deploy script used slow nginx restart instead of reload
4. Cleanup was blocking the critical path
Changes:
- CI: Only restart shardok before blue-green, let script handle rest
- CI: Remove duplicate nginx restart and fallback path
- Deploy: Use nginx reload (faster) with restart fallback
- Deploy: Update .env before restarting services
- Deploy: Run container cleanup in background
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Begin deleting LegacyFactionUtils - convert first batch of callers
Converts the following callers to use FactionUtils with FactionConverter:
- ShardokInterfaceGrpcClient: isFactionLeader
- BattalionNameFilter: provinces (inlined as filter)
- IncomingArmyUtils: factionsAreMutuallyAllied, factionsAreHostile, hostilityStatus
Remaining files to convert:
- ProvinceViewFilter (hasAlliance, isFactionLeader)
- FactionViewFilter (prestige)
- BattleFilter (hostilityStatus)
- Visibility (hasAlliance)
- ActionResultFilter (hasAlliance)
- EligibleDiplomacyStatuses (hasProvinces)
- AvailableResolveInvitationCommandFactory (provinceCount)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Delete LegacyFactionUtils - complete deproto of faction utilities
Convert all callers of LegacyFactionUtils to use FactionUtils + FactionConverter:
- BattleFilter: Use FactionConverter to get factions vector for hostilityStatus
- FactionViewFilter: Convert faction and provinces for prestige calculation
- ProvinceViewFilter: Convert factions for hasAlliance and isFactionLeader
- Visibility: Convert factions for hasAlliance
- ActionResultFilter: Convert factions for hasAlliance check
- EligibleDiplomacyStatuses: Convert provinces for hasProvinces check
- AvailableResolveInvitationCommandFactory: Convert provinces for provinceCount
Delete LegacyFactionUtils.scala and LegacyFactionUtilsTest.scala.
Update BUILD.bazel files to remove legacy_faction_utils dependencies.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Default is 1MB which is too small for game save uploads.
Set to 50MB to allow large game files.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add environment dropdown to connection panel
Allows switching environments before connecting, useful when selected
environment (e.g. QA) is down and user can't reach the lobby to switch.
- Add connectionEnvironmentDropdown field
- SetupConnectionEnvironmentDropdown() initializes on Start
- OnConnectionEnvironmentChanged() saves preference for next connection
- ShowAuthPanel() syncs dropdown when returning to connection screen
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Wire up connection panel environment dropdown in Unity
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Previously, when command processing threw an exception (e.g., invalid
diplomacy resolution status), the Future would fail silently - no error
was logged, sent to Sentry, or returned to the client. The client would
just wait forever for a response that never came.
Changes:
- Add ERROR status and error_message field to PostCommandResponse proto
- Add .recover handler to postCommand Future in streaming handler
- Log errors to console with SimpleTimedLogger
- Print stack trace for debugging
- Report errors to Sentry for monitoring
- Return PostCommandResponse with ERROR status to client
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Multiple deployments were running simultaneously, causing:
- Container name conflicts ("shardok-server is already in use")
- Corrupted image downloads (short read errors)
- Race conditions with docker compose
This adds a concurrency group so deployments run one at a time.
New deployments queue (cancel-in-progress: false) rather than
canceling running ones to avoid leaving production in a bad state.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The statusFromProto function was throwing IllegalArgumentException for
DIPLOMACY_OFFER_STATUS_IMPRISONED instead of returning the Imprisoned
status. This caused the game to fail when players selected the Imprison
option for diplomacy offers.
The bug was introduced in #5106 when SelectedCommandConverter was added.
The function was only designed to handle Accept/Reject but the Imprison
option was later added as an eligible status.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add REQUIRE_INVITATION_CODE=true to auth service in production.
Without this, anyone could create an account without an invitation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Updates callers to use HeroConverter + HeroUtils instead of LegacyHeroUtils:
- HeroViewFilter: Updated proto overload to convert heroes and use HeroUtils
- ProvinceViewFilter: Added heroIdSortOrderer helper using HeroConverter
- ShardokInterfaceGrpcClient: Updated archeryCapable/startFireCapable calls
- GameStateViewFilterTest: Updated test to use HeroConverter + HeroUtils
Also added new overload for HeroUtils.loyaltyAsStatWithCondition that takes
factionLeaderIds directly for cases where proto code has leader IDs but not
full faction objects.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The CI workflow was unconditionally starting jfr-sidecar (which shares
PID namespace with eagle-blue). When eagle-green is active after a
blue-green deployment, eagle-blue doesn't exist and jfr-sidecar fails.
Fix: Remove the redundant jfr-sidecar startup from CI. The
deploy-blue-green.sh script already handles starting the appropriate
jfr-sidecar (either jfr-sidecar or jfr-sidecar-green) based on which
eagle instance is active.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
With lazy loading, save() merges in-memory games with unloaded games
from storage. When a game was archived or deleted:
1. It was removed from gameControllerInfos
2. save() read games.e0es and found the game
3. Since it wasn't in loadedGameIds, it was treated as "unloaded"
4. The game was written back to games.e0es
This caused FileNotFoundException spam in Sentry when the server tried
to load these archived games (their files no longer exist).
Fix: Track explicitly removed games in removedGameIds and exclude them
from the merge in save().
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add tutorial overlay system with runtime UI construction
Implements TutorialOverlayBuilder to construct overlay UI at runtime,
similar to TutorialCanvasBuilder for modals.
Features:
- TutorialOverlayBuilder creates complete overlay UI hierarchy:
- Background dimmer (semi-transparent)
- Highlight frame with gold border and corner decorations
- Tooltip container with title, description, continue button
- Arrow pointer for visual connection
- TutorialUIManager auto-builds overlay if not assigned
- TutorialOverlayController.OnContinueClicked made public for button wiring
- Test tutorial now includes overlay step for End Turn button
- Updated TUTORIAL_PLAN.md with overlay system completion
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix HideOverlay coroutine error on inactive GameObject
Check if gameObject is active before starting FadeOut coroutine.
HideAll() may be called when overlay is already hidden.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Change Mac installer from .sh to .command extension:
- .command files open Terminal and execute when double-clicked on macOS
- No more asking users to open Terminal and run bash commands
- Updated instructions to reflect simpler flow
- Added "Press Enter to exit" so users can see completion message
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Updated LegacyProvinceUtils.monthlyFoodConsumption to use
BattalionUtils with BattalionConverter and BattalionTypeConverter
to convert proto types to Scala types.
Also added export for model/state/battalion from battalion_converter.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Fix: Move S3 backup to BEFORE stopping old server
Critical fix: The old server may wipe games.e0es on shutdown if it
doesn't have the save() merge fix. The backup must happen BEFORE
stopping to capture valid data.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Admin console shows all games, auto-install s3cmd, restart nginx properly
1. Admin console now shows all games from games.e0es, not just loaded ones:
- Added getAllRunningGamesSummary() to GamesManager
- Updated getRunningGames() to include unloaded games with "[Not loaded]" status
- Clicking into a game triggers lazy loading via getGameHistory()
2. Deploy script improvements:
- Auto-install s3cmd if not present (via pip or apt)
- Auto-configure s3cmd for DigitalOcean Spaces from .env
- Use nginx restart instead of reload to ensure all workers pick up new config
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Updated callers to use BattalionConverter + BattalionViewFilter instead:
- AvailableCommandConverter: proto Battalion → Scala → BattalionView → proto
- ExpandedCombatUnitUtils: proto Battalion → Scala → BattalionView
- ProvinceViewFilter: Scala BattalionT → BattalionView (direct)
Also added required exports and visibility for battalion_view_filter.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Make manifest signature verification blocking
When a public key is configured, the installer now rejects:
- Unsigned manifests (signature required)
- Manifests with invalid signatures
This closes the security gap where a compromised manifest could
point to a malicious installer. The SHA check on the installer
was already blocking, but an attacker could modify the manifest
to include the SHA of their malicious installer.
Behavior:
- No public key configured: allows any manifest (backwards compatible)
- Public key configured + valid signature: proceeds
- Public key configured + missing/invalid signature: BLOCKS update
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Always require valid manifest signature
Remove backwards-compatibility fallback - any installer with this
code will have been built with the public key injected by CI.
Now requires:
- Public key must be configured (fails if missing)
- Manifest must be signed (fails if unsigned)
- Signature must be valid (fails if invalid)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Fix critical bug: save() now merges with existing games.e0es
CRITICAL BUG FIX: With lazy loading, gameControllerInfos only contains
games that have been loaded into memory. The old save() would overwrite
games.e0es with only the loaded games, losing all unloaded games.
This caused complete data loss of user-to-game mappings when:
1. New server started (empty gameControllerInfos)
2. Any operation triggered save()
3. games.e0es was overwritten with empty data
Fix: save() now reads existing games.e0es first and merges:
- Unloaded games are preserved from disk
- Loaded games use fresh in-memory state
Also adds logging to diagnose games.e0es read failures.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add S3 backup of games.e0es during blue-green deployment
After stopping the old container (which flushes state to storage),
create a timestamped backup in S3 before switching nginx traffic.
This provides a recovery point if something goes wrong during deployment.
Backups are stored at: s3://eagle0/eagle/save/backups/games.e0es.<timestamp>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix blue-green deployment dependencies and auth routing
1. docker-compose.prod.yml:
- Remove eagle-blue from nginx depends_on (use EAGLE_ADDR variable instead)
- Remove eagle-blue from admin depends_on (use EAGLE_ADDR variable instead)
- Add TODO note about jfr-sidecar limitation during green deployments
2. nginx/nginx.conf:
- Fix auth.Auth location on port 443 to route to auth:40033 instead of
eagle_backend. This was causing auth failures when clients connected
via the main HTTPS port.
These changes allow blue-green deployments to work without hard
dependencies that cause docker-compose to recreate stopped containers.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add jfr-sidecar-green for blue-green JFR profiling support
- Add jfr-sidecar-green service that shares PID namespace with eagle-green
- Make admin's jfr-sidecar address configurable via JFR_SIDECAR_ADDR env var
- Update deploy script to:
- Start appropriate jfr-sidecar with each eagle instance
- Update .env with EAGLE_ADDR and JFR_SIDECAR_ADDR after switching
- Restart admin service to pick up new addresses
- Clean up old jfr-sidecar when removing old eagle instance
This ensures the JFR button in admin console works regardless of
whether blue or green is the active instance.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Sync config files from GitHub at start of deployment
Downloads docker-compose.prod.yml and nginx.conf from the main branch
before starting deployment. This ensures new services (like jfr-sidecar-green)
are available when the deploy script runs.
- Preserves the current active instance in nginx.conf
- Creates .bak backups before overwriting
- Continues with existing files if GitHub fetch fails
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix: Move S3 backup to BEFORE stopping old server
Critical fix: The old server may wipe games.e0es on shutdown if it
doesn't have the save() merge fix. The backup must happen BEFORE
stopping to capture valid data.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Remove the proto wrapper LegacyRansomValidity and its only caller (the
proto overload of AvailableResolveRansomOfferCommandFactory). Convert
the test from proto types to Scala types, replacing ScalaPB's .update()
lens syntax with helper functions.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Fix critical bug: save() now merges with existing games.e0es
CRITICAL BUG FIX: With lazy loading, gameControllerInfos only contains
games that have been loaded into memory. The old save() would overwrite
games.e0es with only the loaded games, losing all unloaded games.
This caused complete data loss of user-to-game mappings when:
1. New server started (empty gameControllerInfos)
2. Any operation triggered save()
3. games.e0es was overwritten with empty data
Fix: save() now reads existing games.e0es first and merges:
- Unloaded games are preserved from disk
- Loaded games use fresh in-memory state
Also adds logging to diagnose games.e0es read failures.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add S3 backup of games.e0es during blue-green deployment
After stopping the old container (which flushes state to storage),
create a timestamped backup in S3 before switching nginx traffic.
This provides a recovery point if something goes wrong during deployment.
Backups are stored at: s3://eagle0/eagle/save/backups/games.e0es.<timestamp>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix blue-green deployment dependencies and auth routing
1. docker-compose.prod.yml:
- Remove eagle-blue from nginx depends_on (use EAGLE_ADDR variable instead)
- Remove eagle-blue from admin depends_on (use EAGLE_ADDR variable instead)
- Add TODO note about jfr-sidecar limitation during green deployments
2. nginx/nginx.conf:
- Fix auth.Auth location on port 443 to route to auth:40033 instead of
eagle_backend. This was causing auth failures when clients connected
via the main HTTPS port.
These changes allow blue-green deployments to work without hard
dependencies that cause docker-compose to recreate stopped containers.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add jfr-sidecar-green for blue-green JFR profiling support
- Add jfr-sidecar-green service that shares PID namespace with eagle-green
- Make admin's jfr-sidecar address configurable via JFR_SIDECAR_ADDR env var
- Update deploy script to:
- Start appropriate jfr-sidecar with each eagle instance
- Update .env with EAGLE_ADDR and JFR_SIDECAR_ADDR after switching
- Restart admin service to pick up new addresses
- Clean up old jfr-sidecar when removing old eagle instance
This ensures the JFR button in admin console works regardless of
whether blue or green is the active instance.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Sync config files from GitHub at start of deployment
Downloads docker-compose.prod.yml and nginx.conf from the main branch
before starting deployment. This ensures new services (like jfr-sidecar-green)
are available when the deploy script runs.
- Preserves the current active instance in nginx.conf
- Creates .bak backups before overwriting
- Continues with existing files if GitHub fetch fails
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add Ed25519 signature verification for manifest
When a manifest has a signature line (# signature=...), the installer
now verifies it using the embedded public key. If verification fails,
a warning is logged but the update proceeds to allow graceful degradation.
Changes:
- Add NSec.Cryptography NuGet package for Ed25519
- Add VerifyManifestSignature() to parse and verify signature
- Update ReadConfiguration() to support comments in config file
- Call verification when fetching remote manifest
Behavior:
- If no signature: proceeds normally (backwards compatible)
- If no public key configured: logs info, proceeds
- If signature valid: logs success, proceeds
- If signature invalid: logs WARNING, proceeds (graceful degradation)
To enable verification:
1. Generate key pair: go run scripts/generate_manifest_keys.go
2. Add public key to configuration.txt as manifest_public_key
3. Add private key as GitHub secret MANIFEST_SIGNING_KEY (from PR 3)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix: NSec PublicKey is not IDisposable
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Remove LegacyRecruitmentOdds (callers already use Scala RecruitmentOdds)
- Remove LegacyBattalionTypeFinder (inline simple lookup in RuntimeValidator)
Part of ongoing deproto effort to remove proto wrapper classes.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
With lazy game loading (merged in #5223), games are loaded on-demand
when users reconnect after nginx switches traffic. No explicit reload
call is needed - the new server reads fresh state from storage.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Games are now loaded on-demand when a user subscribes or lists their games,
rather than all at startup. This enables true zero-downtime deployments:
1. New server starts with no games loaded (fast startup)
2. Old server stops, flushes all game state to disk
3. nginx switches traffic to new server
4. Users reconnect, triggering fresh game loads from disk
Key changes:
- GamesManager.apply() no longer loads games at startup
- New ensureGameLoaded() method loads a single game from disk on demand
- readRunningGamesFromDisk() reads games.e0es fresh each time to handle
race conditions (e.g., game created just before deployment)
- streamUpdates() calls ensureGameLoaded() before accessing game
- gamesFor() reads games.e0es to find user's games, then loads them
(handles "lost game ID after disconnect" scenario)
- dropGame() tries lazy loading before returning "not found"
- begin() simplified to just connect to Shardok
- Removed ReloadGames RPC (no longer needed with lazy loading)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
FlatBuffers 25.9.23 changed GetMutableObject() on vectors of structs
to return const T* instead of T*. This is a const-correctness
improvement in the library.
Updated all C++ code to handle this change:
- Added const_cast<T*>() wrappers where mutation is needed on owned buffers
- Added helper function GetMutableTerrain() in HexMapUtils for terrain access
- All const_casts are safe because the code owns the underlying mutable buffers
All 112 C++ tests and 209 Scala tests pass.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add optional Ed25519 signing to the manifest_manager. When a signing key
is provided, the manifest is signed and the signature is prepended as
a header comment that clients can verify.
Changes:
- manifest_manager: Accept optional private key file as 3rd argument
- manifest_manager: Sign manifest content and prepend signature line
- installer_build.yml: Pass MANIFEST_SIGNING_KEY to manifest_manager
- unity_build.yml: Pass MANIFEST_SIGNING_KEY to manifest_manager
- Add generate_manifest_keys.go script to create key pairs
The signature line format is: # signature=<base64-encoded-ed25519-signature>
To enable signing:
1. Run: go run scripts/generate_manifest_keys.go
2. Add the private key as GitHub secret MANIFEST_SIGNING_KEY
3. Embed the public key in the installer for verification (PR 4)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Verify installer SHA256 after download
The Windows installer was downloading and launching new installer updates
without verifying the SHA256 hash, which could allow a corrupted or
tampered installer to run. This adds SHA256 verification after download
and before launching the new installer.
- Add expectedSha parameter to DownloadAndLaunchNewInstaller
- Compute SHA256 of downloaded file and compare to manifest value
- Delete the file and fail if SHA doesn't match
- Log verification success on match
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove accidentally committed node_modules cache files
* Add node_modules to .gitignore
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Previously, FetchAndWriteOne() would download entire files into memory,
then compute the SHA256, then write to disk. This doubled memory usage
for each concurrent download.
Now the function streams directly to a temp file while computing SHA256
incrementally using TransformBlock. The temp file is renamed to the
final location only after SHA verification passes.
Benefits:
- Eliminates memory buffering of entire files
- Safer atomic writes using temp file + rename pattern
- Temp files cleaned up on failure
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Apple's CloudKit can have a brief delay after notarization completes
before the ticket is available for stapling. This adds a retry loop
with 10-second delays, up to 5 attempts.
Error was:
CloudKit query for eagle0.app failed due to "Record not found".
The staple and validate action failed! Error 65.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The previous configuration used an upstream block with a static hostname:
upstream eagle_grpc { server eagle-blue:40032; }
This caused nginx -s reload to fail when eagle-blue was stopped because
nginx tries to resolve all upstream hostnames at config load time.
Changed to use a map directive with a variable:
map $host $eagle_backend { default "eagle-blue:40032"; }
grpc_pass grpc://$eagle_backend;
This pattern (already used for auth backend) resolves the hostname at
request time, allowing nginx to reload even when the backend is down.
Requests to a stopped backend will get 502 errors instead of failing
to reload nginx entirely.
Tradeoff: Loses keepalive 100; setting, but deployment reliability
is more important than connection pooling optimization.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Unity's temporary .traceevents files can vanish during rsync, causing
exit code 23 ("partial transfer due to error"). This is acceptable for
the Library cache, so treat exit code 23 as success.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The -s flag expects the private key as a string argument, not a file
path. Changed to -f which correctly reads the key from a file.
Error was: "Failed to decode base64 encoded key data from: /tmp/sparkle_private_key"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
User explicitly stated: "never ever ever ever merge a PR for me.
You create PRs. I merge them."
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Previously, docker_build.yml had 4 separate jobs (build-eagle, build-shardok,
build-admin, build-jfr-sidecar) that competed for runner slots. With 3 runners
and 6+ workflows triggering on main push, these jobs serialized rather than
running in parallel.
Now consolidated into a single `build-all` job that:
- Builds all 4 images with one `bazel build` command (Bazel parallelizes internally)
- Uses 1 runner slot instead of 4, freeing runners for other workflows
- Shares Bazel cache warming across all builds
- Pushes all images sequentially (fast, network-bound)
Expected improvement: Docker Build workflow goes from ~8.5m (4 serialized jobs)
to ~3-4m (1 consolidated job with internal parallelism).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Improves Sparkle sign_update error reporting by capturing stderr, and allows full signing/notarization/deploy pipeline on feature branches via workflow_dispatch.
Add toScala() method to CommandSelection that converts proto-based
command selection to ScalaCommandSelection. This simplifies the action
files that were previously doing manual conversion from proto to Scala
types.
Updated files:
- CommandSelection.scala: Added toScala() method
- EndHandleRiotsPhaseAction.scala: Use toScala() instead of manual conversion
- PerformVassalCommandsPhaseAction.scala: Use toScala() instead of manual conversion
- PerformVassalDefenseDecisionsAction.scala: Use toScala() instead of manual conversion
- Removed unused AvailableCommandTypeMap and SelectedCommandConverter imports
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
GitHub release URLs don't work for private repos without authentication.
Bazel's http_file can't use GitHub auth, so we need a public URL.
Uploaded the busybox binary to DigitalOcean Spaces alongside the sysroots.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add runtime Canvas UI builder for tutorial modal
Implements TutorialCanvasBuilder to construct Canvas-based tutorial modal
UI at runtime, replacing the IMGUI fallback when no prefab is assigned.
- TutorialCanvasBuilder creates complete Canvas UI hierarchy:
- Modal blocker (dark overlay)
- Panel with title, description, icon, progress bar
- Continue, Skip, and Skip All buttons
- Fantasy RPG color scheme matching game style
- TutorialUIManager auto-builds Canvas UI if ModalPanel not assigned
- TutorialModalPanel click handlers made public for external setup
- Updated TUTORIAL_PLAN.md to reflect Canvas UI completion
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix tutorial Canvas UI issues
- Auto-load Stoke font if not assigned (searches Resources and loaded assets)
- Increase panel height (550px) and use flexible spacer for layout
- Fix text truncation by using Overflow mode instead of Ellipsis
- Replace "Province Selected" tutorial with proper welcome intro
- Intro tutorial triggers immediately on game start
- Skip buttons hidden when AllowSkip=false (intro is non-skippable)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove font search logic - use CanvasFont field instead
Font should be assigned in TutorialUIManager inspector (CanvasFont field).
Removed unnecessary auto-search logic from TutorialCanvasBuilder.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix TutorialTestSetup compile errors
- Use HasCompletedTutorial instead of HasSeenTutorial
- Use OnGameEvent instead of TriggerEvent
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Trigger intro tutorial when entering game, not lobby
- Remove immediate trigger from TutorialTestSetup.Start()
- Trigger "game_started" event from TutorialManager.Initialize()
when EagleGameController is passed (actual game entry)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update TUTORIAL_PLAN.md with current status and future work
- Document completed phases (foundation, Canvas UI, triggers, test setup)
- Add Unity setup instructions (font assignments)
- Add future work: lobby tutorial helper, overlay system, hints
- Add lobby tutorial section to planned contextual tutorials
- Update testing instructions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Assign Stoke-Regular-SDF font to TutorialUIManager
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Mark font assignment complete in TUTORIAL_PLAN.md
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Updates Engine.postCommand to accept Scala SelectedCommand instead of
the proto version. The conversion from proto to Scala now happens at
the GameController layer, keeping the Engine interface proto-free.
Changes:
- Engine.scala: Import Scala SelectedCommand instead of proto
- EngineImpl.scala: Remove proto import and converter, use Scala directly
- GameController.scala: Convert proto→Scala before calling engine.postCommand
- Update test files to use Scala SelectedCommand for mock expectations
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The busybox.net server is frequently unavailable or slow, causing CI
builds to fail with download timeouts. This adds a GitHub release
mirror as the primary download source with busybox.net as fallback.
The binary is hosted at:
https://github.com/nolen777/eagle0/releases/tag/busybox-1.35.0
SHA256 verified: 6e123e7f3202a8c1e9b1f94d8941580a25135382b99e8d3e34fb858bba311348
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Allow Mac signing/notarization/deploy on manual workflow triggers
The signing, notarization, and deploy steps were only running on
push events. Now they also run on workflow_dispatch (manual triggers)
when targeting main branch.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add Mac download support to invitation landing page
- Detect Mac vs Windows from User-Agent header
- Add /invite/{code}/install.sh endpoint for Mac shell installer
- Shell script creates invitation.json, downloads app ZIP, extracts to /Applications
- Update HTML template with platform-specific instructions
- Add MAC_INSTALLER_URL environment variable (default: assets.eagle0.net/mac/builds/eagle0-latest.zip)
- Show link to other platform at bottom of page
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Update TCommandFactory and CommandFactory to accept Scala AvailableCommand
and SelectedCommand types directly, eliminating proto dependencies from
the command execution path.
Key changes:
- TCommandFactory.makeTCommand now takes Scala command types
- CommandFactory pattern matching updated for all ~40 command types
- Helper methods updated (attackDecision, improvementTypeMap, etc.)
- Removed proto converter imports from CommandFactory
- Updated vassal/riot phase actions to convert proto→Scala at call sites
- Added exports for Scala command types from t_command_factory
All 175 library tests pass.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The warmup tool sends X-Warmup-User header for authentication during
blue/green deployments. This only worked when connecting from true
localhost (127.0.0.1), but when running warmup from the host machine
to a Docker container, the connection appears to come from the Docker
bridge network (172.17.x.x), causing the warmup header to be ignored.
The CreateGameRequest then fails because the user is unauthenticated
(null username), causing the warmup to timeout.
Fix: Expand the localhost check to also accept Docker bridge network
addresses (10.x.x.x, 172.16-31.x.x, 192.168.x.x) using Java's
isSiteLocalAddress(). These are all private network addresses that
can only come from the same machine or local network.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When defenders scatter (flee from castles), the attacker AI was still
using HOLD_CASTLES strategy. This caused the AI to park units on
empty castles instead of chasing fleeing defenders, even though
eliminating all defenders wins via LAST_PLAYER_STANDING.
The fix adds a new condition to the strategy selector: if defenders
exist but none are on castles, use ATTACK_UNITS strategy to chase
them down.
New strategy selection flow:
1. Consider fleeing (if combat odds are bad)
2. Consider crossing rivers (if needed)
3. If attacker can't hold all castles → ATTACK_UNITS
4. If any defender is on a castle → ATTACK_CASTLES
5. NEW: If defenders exist but none on castles → ATTACK_UNITS
6. If no defenders remain → HOLD_CASTLES
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The signing, notarization, and deploy steps were only running on
push events. Now they also run on workflow_dispatch (manual triggers)
when targeting main branch.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1. Fix crane installation - don't try to mv crane to itself
(tar extracts to cwd which is already /opt/eagle0)
2. Keep crane binary after deploy for blue-green script to use
3. Update blue-green deploy to use crane instead of docker pull
(fixes OCI/Docker digest mismatch issue)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Use absolute path for crane binary and add verification that it
was installed correctly. Also add ls -la output for debugging.
The crane binary was mysteriously disappearing between the Eagle
and Shardok image pulls.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Refactors postCommand to:
- Get Scala commands directly from AvailableCommandsFactory
- Convert proto SelectedCommand to Scala for matching
- Use CommandType-based matching (no more proto dependency in AvailableCommandTypeMap)
- Convert back to proto for CommandFactory (temporary until full migration)
AvailableCommandTypeMap now uses only Scala types - matching is simply:
availableCommands.find(_.commandType == selectedCommand.commandType)
Also adds:
- ScalaCommandSelection case class for future migration of command selectors
- Improved visibility for command converter targets
This is an incremental step toward eliminating proto commands from library/.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Upgrade rules_apple to 4.3.3 and rules_swift to 2.4.0
These are the latest compatible versions (rules_apple 4.3.3 depends
on rules_swift 2.4.0 with compatibility level 2).
Note: rules_swift 3.x uses compatibility level 3 and is not
compatible with current rules_apple versions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix zipApp to handle symlinks in Sparkle.framework
Sparkle.framework contains symlinks like Headers -> Versions/Current/Headers.
The previous code used filepath.Walk which follows symlinks, causing it to
try to read a directory as a file.
Now uses filepath.WalkDir with os.Lstat to detect symlinks and store them
properly in the zip archive.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Revert "Fix zipApp to handle symlinks in Sparkle.framework"
This reverts commit ad2f2e40d4562ced1b4001e13abc95d8901c8f0e.
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Sparkle.framework contains symlinks like Headers -> Versions/Current/Headers.
The previous code used filepath.Walk which follows symlinks, causing it to
try to read a directory as a file.
Now uses filepath.WalkDir with os.Lstat to detect symlinks and store them
properly in the zip archive.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Fix codesign script to sign all Sparkle framework components
Sign XPC services, nested apps (Updater.app), and standalone
executables (Autoupdate) before signing the framework itself.
Apple notarization requires all nested binaries to be signed
with Developer ID certificate and secure timestamp.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add missing path triggers for pull_request in Mac build workflow
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Re-add rules_swift for Mac GoDice plugin build
The DarwinGodiceBundle requires rules_swift to build.
This was inadvertently removed in #5194.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Handles intermittent Docker registry digest mismatch errors like:
"failed commit on ref: unexpected commit digest"
This is a known Docker/containerd issue that can occur due to:
- Registry caching
- Network/proxy issues
- Race conditions during push
Now retries up to 3 times with 5 second delays.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Warmup tool fixes:
- Process GameUpdates while waiting for PostCommandResponse (action
results arrive BEFORE PostCommandResponse, not after)
- Don't wait for SubscriptionAck before ActionResultResponse (they
arrive in reverse order)
- Use recommended_hero_id from ImproveAvailableCommand instead of
hardcoding 0 (which doesn't exist)
- Verify we receive new AvailableCommands after posting command
- Add detailed logging for debugging
Server fix:
- Return PostCommandResponse.Status.SUCCESS instead of default UNKNOWN
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The Mac history editor that used these targets was previously removed.
Cleaning up the unused Swift proto infrastructure.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Unity Mac build scripts (build_mac.sh, build_unity_mac.sh)
- Code signing with Developer ID certificate
- Apple notarization for Gatekeeper compliance
- Sparkle framework injection for delta auto-updates
- Go build handler for S3 upload and appcast.xml generation
- Update InvitationCodeManager.cs for Mac platform paths
- GitHub Actions workflow triggered on main branch pushes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add CommandType enum to replace SelectedCommand in ActionResult and Province
Create a compile-time safe CommandType enum that provides exhaustive matching
when adding new command types. This replaces SelectedCommand in:
- ActionResult.lastCommandTypeForActingProvince
- Province.lastCommand
Key changes:
- New CommandType.scala enum with exhaustive converters from SelectedCommand
and AvailableCommand
- New command_type.proto with 40 command types + UNKNOWN
- CommandTypeConverter for proto<->Scala conversion
- Simplified shouldFollow in AvailableCommandsFactory to compare CommandType
values directly
- Updated RandomStateSequencer.withTCommandAndLastCommand to use CommandType
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add backwards compatibility for CommandType in saved games
Preserve the deprecated SelectedCommand field (field 28) alongside the
new CommandType field (field 43) in action_result.proto. When loading
saved games, first check the new CommandType field; if not set, fall
back to the deprecated SelectedCommand and convert it.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Use parameterized enums for SelectedCommand/AvailableCommand to CommandType mapping
Refactors the dependency direction: instead of CommandType.from(SelectedCommand),
each enum case now has a built-in `val commandType: CommandType` parameter.
Benefits:
- Compile-time safety: can't add a new command without specifying its CommandType
- Simpler access: just call .commandType instead of a converter method
- Better dependency direction: richer types depend on simpler types, not vice versa
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Prevent future accidental pushes directly to main by putting
explicit rules at the very top of the file.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add tutorial test setup with IMGUI fallback UI
Create TutorialTestSetup component that:
- Registers test tutorials programmatically on startup
- Triggers on first province selection, battle entry, and command issued
- Allows testing without Unity Editor asset creation
Add fallback IMGUI modal in TutorialUIManager:
- Renders when no ModalPanel prefab is assigned
- Shows title, description, progress, and action buttons
- Enables end-to-end testing without UI prefab setup
To test:
1. Add TutorialTestSetup component to TutorialManager GameObject
2. Ensure TutorialManager has UIManager reference
3. Play game and select a province to see test tutorial
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Silence missing onboarding sequence warning
Change LogWarning to debug log when no onboarding sequence is assigned.
This is a valid configuration state, not an error.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Improve IMGUI fallback modal styling
- Add FallbackFont field (assign Stoke-Regular.ttf in Unity)
- Increase font sizes: title 24, description 20, buttons 18
- Make modal window larger (600x300)
- Make buttons taller (40px height)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Double IMGUI modal size and font sizes
- Window: 1200x600 (was 600x300)
- Title: 48pt, Description: 40pt, Progress: 32pt, Buttons: 36pt
- Buttons: 200-240x80 (was 100-120x40)
- Add TutorialManager GameObject to Gameplay scene
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add Reset Tutorials button to Settings panel
- Add resetTutorialsButton field to SettingsPanelController
- Add OnResetTutorialsClick() handler that calls TutorialManager.ResetAllProgress()
To wire up in Unity:
1. Add a Button to the Settings panel
2. Assign it to resetTutorialsButton field
3. Set OnClick to SettingsPanelController.OnResetTutorialsClick
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix ambiguous Debug reference
Use UnityEngine.Debug.Log to resolve conflict with System.Diagnostics.Debug
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Apply window style to IMGUI modal title
Pass windowStyle to GUI.Window so title uses 48pt font
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add more spacing between title and description
Increase top spacing from 30 to 60 pixels
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Three fixes:
1. Remove existing warmup binary before scp - Bazel outputs it with
read-only permissions (r-xr-xr-x), causing scp to fail on overwrite.
2. Remove the `docker compose up -d --remove-orphans` step which was
causing "container name already in use by service {}" errors due to
Docker Compose state conflicts.
3. Add `docker container prune -f` as a safer alternative - removes
stopped containers without trying to reconcile compose state.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Prevent NPE when listing games with corrupt faction/leader data.
This defensive fix handles three cases:
- Hero might not exist for the faction head ID
- Faction name might be null
- Leader nameTextId might be null
The warmup tool created games with unexpected null values, causing
the admin console to crash when listing running games.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The invitation landing page is served by the auth service on port 8080,
but nginx wasn't configured to proxy requests to it, resulting in 404.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
scp flattens paths - it was copying scripts/bin/warmup to /opt/eagle0/warmup
instead of /opt/eagle0/scripts/bin/warmup. Fixed by:
1. Creating directory structure on remote first
2. Copying files to their correct locations
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Both docker_build.yml and auth_build.yml now use a shared update-env.sh
script that updates only the variables each workflow is responsible for,
without overwriting values set by other workflows.
Changes:
- Add deploy/env.template with all environment variables
- Add deploy/update-env.sh to safely update individual env vars
- Update docker_build.yml to use update-env.sh instead of rm/recreate
- Update auth_build.yml to use update-env.sh instead of grep/sed chain
- Add FASTMAIL_* vars to docker_build.yml deploy job
This fixes the bug where docker_build.yml was overwriting FASTMAIL env
vars set by auth_build.yml because it recreated .env from scratch.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
jfr-sidecar has `pid: "service:eagle-blue"` to share PID namespace for
JFR profiling. It must be started after eagle-blue exists, not before.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add DeleteUser and DeleteInvitation RPCs to admin.proto
- Add Delete methods to UserService and InvitationService
- Add delete handlers in admin_handlers.go and admin_server.go
- Add delete buttons to users and invitations admin UI templates
- Users can be deleted permanently (with self-deletion prevention)
- Only non-pending invitations (revoked, expired, redeemed) can be deleted
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The warmup binary was being built in build-eagle but each job has its
own checkout, so the binary wasn't available in the deploy job when
we tried to scp it to the droplet.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The mac history editor is no longer needed now that we have the Admin
console for game management.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 22:17:48 -08:00
4974 changed files with 58872 additions and 87706 deletions
**NEVER push directly to main/master.** No exceptions. Not for "small changes." Not for docs. Not ever.
**NEVER merge PRs.** You create PRs. The user merges them. No exceptions.
**ALWAYS use this workflow:**
1. Create a feature branch from origin/main
2. Commit to that branch
3. Create a PR with `gh pr create`
4. Wait for user to merge (DO NOT run `gh pr merge`)
If you catch yourself about to run `git push origin main` or `git push origin <branch>:main`, STOP. You are about to violate a critical rule. Create a PR instead.
If you catch yourself about to run `gh pr merge`, STOP. Only the user merges PRs.
---
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Eliminated wasteful Scala→proto→Scala conversions in the hot path:
1.**LLM Pipeline** (#4913): `LlmRequestWithGameState` now uses Scala `GameState` instead of proto. All ~38 prompt generators updated to use Scala model types (`FactionT`, `HeroT`, `ProvinceT`).
2.**ActionWithResultingState Caching** (#4914): Added `precomputedScalaState: Option[GameState]` to cache Scala state when available, avoiding `fromProto()` conversion in `stateAfter()`.
3.**PostResults Simplification** (#4915): Changed `PostResults.gameState` from proto to `Option[GameState]` (Scala), eliminating `toProto()` calls when creating PostResults.
## Migration Pattern
The codebase follows a **Legacy* pattern** for separating proto-dependent and protoless code:
- **Protoless utilities**: `FactionUtils`, `HeroUtils`, `ProvinceUtils`, `ProvinceDistances`, etc.
- **Proto-dependent utilities**: `LegacyFactionUtils`, `LegacyHeroUtils`, `LegacyProvinceUtils`, `LegacyProvinceDistances`, etc.
When migrating a file:
1. Create a `Legacy*` version containing the proto-dependent methods
2. Keep the original file name for protoless methods
3. Update callers to use the appropriate version based on their context
## Migration Status
### Fully Protoless (no proto imports)
**Utilities:**
- [x]`FactionUtils` - has protoless `ownedNeighbors` method
- [x]`ProvinceDistances` - split into protoless + `LegacyProvinceDistances`
- [x]`FactionUtils` / `LegacyFactionUtils` - both have matching APIs; LegacyFactionUtils used by boundary code (24+ callers)
- [x]`HeroUtils` / `LegacyHeroUtils` - both have matching APIs; LegacyHeroUtils used by boundary code (10 callers)
- [x]`ProvinceUtils` / `LegacyProvinceUtils` - both have matching APIs; LegacyProvinceUtils used by boundary code (20 callers: availability factories, view filters)
**Parallel Implementations (awaiting migration of callers):**
- [x]`BattalionUtils` / `LegacyBattalionUtils` - both have matching core methods; LegacyBattalionUtils used by boundary code (4 callers)
- [x]`BattalionViewFilter` / `LegacyBattalionViewFilter` - protoless version exists; Legacy used by view filters, action appliers (3 callers)
- [x]`BattalionTypeFinder` / `LegacyBattalionTypeFinder` - protoless version exists; Legacy used by validators (1 caller: RuntimeValidator)
### Recent Caller Migration
**CheckForFulfilledQuestsAction** - migrated to use protoless `BattalionTypeFinder`:
- Changed `battalionTypes` parameter from proto `Vector[BattalionType]` to Scala `Vector[BattalionType]`
- Updated callers (EngineImpl, EndVassalCommandsPhaseAction) to pass Scala types directly
"build_file_content": "\npackage(\n default_visibility = [\"//visibility:public\"],\n)\n\nfilegroup(\n name = \"buf\",\n srcs = [\n \"@com_github_bufbuild_buf//:bin/buf\",\n ],\n tags = [\"manual\"], # buf is downloaded as a linux binary; tagged manual to prevent build for non-linux users\n)\n"
| No Time for Greatness | Dima Koltsov | CC BY 4.0 | [YouTube](https://www.youtube.com/watch?v=cQh0OWIFdgM) |
| Warriors of Demacia | Dima Koltsov | CC BY 4.0 | [YouTube](https://www.youtube.com/watch?v=yktSUMJn9ao) |
| Forest Queen Tale | Dima Koltsov | Presumed CC BY 4.0 | Not found on YouTube; other Dima Koltsov tracks are CC BY 4.0 |
| Valor | Dima Koltsov | CC BY 4.0 | [YouTube](https://www.youtube.com/watch?v=uoHYJRPcS2Y) |
| Clouds | Dima Koltsov | Presumed CC BY 4.0 | Not found on YouTube; other Dima Koltsov tracks are CC BY 4.0 |
**Note on Dima Koltsov tracks:** 3 of 5 tracks confirmed CC BY 4.0 via YouTube. 2 remaining tracks (Forest Queen Tale, Clouds) presumed same license but not verified.
---
@@ -129,42 +138,55 @@ All 26 tracks have CC licenses with proper attribution:
| ~~`Assets/Shardok/commandImages/bridge.png`~~ | **REPLACED** (2026-01-23) with AI-generated wooden rope bridge icon (ChatGPT/DALL-E 3, 512x512 PNG). No licensing restrictions - AI-generated for this project. |
|~~`Assets/Images/startFire.png`~~ | **REPLACED** (2026-01-23) with "Flame Icon" from [UXWing](https://uxwing.com/flame-icon/) (free for commercial use, no attribution required). Consolidated duplicate removed. |
| ~~`Assets/Shardok/commandImages/startFire.png`~~ | **DELETED** (2026-01-23) - duplicate removed, all references updated to use `Assets/Images/startFire.png` |
### Shardok Sound Effects
- **Location:** `Assets/Shardok/soundEffects/`
- **Count:** 56 MP3 files
- **Count:** 37 audio files (was incorrectly counted as 56 including .meta files)
Remaining 31 files presumed from Asset Store purchases; 3 verified from Zombie Monster Undead Collection.
4.**Terrain Hexes** - 85 hex tiles of unknown source
- **TODO:** Confirm if this is a Unity Asset Store purchase (owner believes it is)
3.~~**Terrain Hexes**~~ - **VERIFIED** (2026-01-23): Confirmed Unity Asset Store purchase
5.**StrategyGameIcons** - 138 icons of unknown source
- **TODO:** Investigate origin - check Unity Asset Store purchase history
4.~~**StrategyGameIcons**~~ - **VERIFIED** (2026-01-23): Unity Asset Store purchase (REXARD)
6.**AUDIUS music tracks** - Verify Dima Koltsov tracks are licensed for commercial use
5.~~**Medieval: Victory Theme**~~ - **VERIFIED** (2026-01-23): CC0 Public Domain by RandomMind ([Chosic](https://www.chosic.com/download-audio/28492/))
7.**Discord logo** (`Eagle/Discord-Logo-Blurple.png`) - Likely fine for "Login with Discord" button per Discord brand guidelines, but verify usage complies with their terms
6.~~**Dima Koltsov tracks**~~ - **MOSTLY VERIFIED** (2026-01-23): 3 of 5 confirmed CC BY 4.0 via YouTube. 2 remaining (Forest Queen Tale, Clouds) presumed same license.
7.~~**Discord logo**~~ - **OK** (2026-01-23): Usage complies with Discord brand guidelines for "Login with Discord" button
### Already Safe:
@@ -229,12 +249,13 @@ NuGet packages in `Assets/Packages/` all include LICENSE.TXT files:
## Recommendation
Before removing HTTP basic auth:
Before public release:
1.~~Delete or replace the 4 suspicious JPG/JPEG files in `Assets/Eagle/` and `Assets/Images/`~~**DONE**(2025-01-04)
2.Replace clip art images (`bridge.png`, `startFire.png`) with properly licensed alternatives (e.g., from [game-icons.net](https://game-icons.net) CC BY 3.0)
3. Verify source of `Assets/Shardok/soundEffects/` MP3s
4. Verify source of `Assets/Terrain Hexes/` and `Assets/StrategyGameIcons/`
5.If any are from early development with unclear licensing, replace them
1.~~Replace clip art images (`bridge.png`, `startFire.png`) with properly licensed alternatives~~**DONE**- see Section 4
2.~~Verify source of `Assets/Shardok/soundEffects/` MP3s~~**MOSTLY DONE** - 3 files flagged for replacement, rest presumed Asset Store
3.~~Verify source of `Assets/Terrain Hexes/`~~**DONE** - confirmed Asset Store purchase
4.~~Verify source of `Assets/StrategyGameIcons/`~~**DONE** - Unity Asset Store (REXARD)
5.~~Replace Dima Koltsov Audius tracks~~**MOSTLY DONE** - 3/5 confirmed CC BY 4.0, 2 presumed same
6.~~Find source of Medieval: Victory Theme or replace~~**DONE** - CC0 Public Domain by RandomMind
The bulk of your assets (95%+) are properly licensed Asset Store purchases or CC content.
**Protocol buffers should only be used at the edges** — for network serialization (gRPC) and disk persistence. Inside the Eagle game engine, all logic should operate on native Scala models.
| Phase 5: Action Base Classes | **Complete** | All `RandomSequentialResultsAction` and `DeterministicSingleResultAction` converted to T-type base classes |
| Phase 5b: Base Class Cleanup | **Complete** | `RandomSequentialResultsAction` and `DeterministicSingleResultAction` deleted |
| Phase 5c: RoundPhaseAdvancer Actions | **Complete** | All actions called by RoundPhaseAdvancer accept Scala GameState |
`ActionResultApplier` applies `ActionResultT` directly to Scala `GameState`. The legacy `ActionResultTApplierImpl` wraps it and converts to/from proto for callers that still need proto types.
Modify `RandomStateTSequencer` to thread Scala `GameState` throughout (currently converts to proto internally). Then evaluate whether `RandomStateProtoSequencer` is still needed at all.
**Current State**: `RandomStateTSequencer` accepts Scala `GameState` via its `apply()` method but internally converts to proto. All callback methods (`withRandomActionResult`, `withActionResults`, etc.) pass `GameStateProto` to callers, forcing actions that use the sequencer to work with proto types internally.
**Target State**: Create a fully protoless sequencer where:
To enable lightweight mocking of command creation in tests, `TCommandFactory` trait was extracted from `CommandFactory`. This allows tests to mock just the `makeTCommand` method without pulling in all 40+ command dependencies that `CommandFactory` requires.
-`TCommandFactory` - lightweight trait with just `makeTCommand`
Change APIs to vend Scala `GameState` and `ActionResultT` instead of proto versions. `PersistedHistory` converts to proto internally for disk persistence; `InMemoryHistory` doesn't need proto at all.
| Next target | See "Next Candidates" section below |
### Next Candidates
Priority candidates for further deproto work:
1.**More Enum Migrations** - Apply the `DiplomacyOfferStatus` pattern to other proto enums:
- Files importing `net.eagle0.eagle.common.round_phase.RoundPhase` (proto) could use Scala `RoundPhase`
- Files importing `net.eagle0.eagle.common.battalion_type.BattalionType` (proto) could use Scala `BattalionType`
2.**AvailableCommandsFactory Files** - Many still use proto `GameState` internally:
- These files build proto `AvailableCommand` messages but could use Scala types for internal logic
- Convert to accept Scala `GameState`, only convert fields to proto when building the response
3.**History APIs** - `InMemoryHistory` and `PersistedHistory`:
- Change to vend Scala `GameState` and `ActionResultT` instead of proto versions
-`PersistedHistory` converts to proto internally for disk persistence
### Validation
- [x]`ActionResultApplier` created and tested
- [x]`RandomStateSequencer` threads Scala GameState throughout
- [x]`RoundPhaseAdvancer` uses T-types internally
- [x]`ProvinceViewFilter` has Scala overload for server-side use (PR #4752)
- [x]`FactionT.reconnedProvinces` and `ChangedFactionC.updatedReconnedProvinces` use Scala `ProvinceView`
- [x]`ProvinceViewFilter.withdrawnFromProvinceView` has Scala overload
- [x]`CommandChoiceHelpers` uses Scala types ✅
- [x] All action files (52/52) are fully protoless ✅
- [ ]`ProvinceViewFilter` faction-filtered views use Scala types
- [ ] History APIs vend Scala types
- [ ] No `ActionResultProtoConverter.toProto()` calls except at persistence/gRPC boundaries
- [ ] All tests pass
---
## Phase 7: Clean Up Legacy Utilities
### Objective
Remove remaining direct proto imports from utility classes.
### Files to Modify
| File | Status |
|------|--------|
| `CommandChoiceHelpers.scala` | Accepts proto `GameState`; blocks full deproto of `PerformVassalCommandsPhaseAction` and `PerformVassalDefenseDecisionsAction` |
-`/library/util/` - Pure Scala models (except loaders)
-`/model/state/` - Pure Scala models
---
## Open Questions
1.**Persistence Format**: Currently game state is persisted as proto. Should we keep proto for persistence (good for schema evolution) or switch to a different format?
2.**Shardok Integration**: `ResolveBattleAction` communicates with Shardok. Should the Shardok interface use protos (external service) or Scala models?
3.**View Generation**: `GameStateViewDiffer` works with view protos for client updates. Views need Scala models (`ProvinceViewT`, etc.) to allow actions like `EndBattleAftermathPhaseAction` to be fully protoless. The Scala views would be converted to proto only at the gRPC boundary when sending updates to clients.
---
## Success Criteria
### Code Quality
- [ ] Zero proto imports in `/library/actions/` (except boundaries)
- [ ] Zero proto imports in `/library/` utilities (except loaders)
- [ ]`GameStateT` used throughout engine internals
- [ ] Proto usage limited to: `EagleServiceImpl`, loaders, converters, persistence
### Architecture
- [ ] Clear separation: Scala models (internal) vs Proto (boundaries)
- [ ] Converters as the only bridge between domains
This document compares streaming latency (time-to-first-token) and pricing across OpenAI, Anthropic (Claude), and Google (Gemini) models for use in Eagle's narrative text generation.
## Test Methodology
All tests were performed locally using curl with streaming enabled. Each model was tested 3 times with the same prompt:
> "Write a short paragraph about a brave knight who discovers a hidden cave. Make it vivid and descriptive."
Time-to-first-token (TTFT) was measured from request initiation to the first text content appearing in the stream.
Be able to support a small (10-50 user) private alpha, including with strangers.
Larger set of goals in [The Big Eagle TODO](https://docs.google.com/document/d/17RTt3-4Wl2AAVMRLodaC3a84E4de6xuQTWBPvCRM484/edit?pli=1&tab=t.0), but most of that is not necessary for MVP.
## Required
### Gameplay Productionization
- [x]~~All functionality works on production eagle / shardok servers~~
- [x]~~Acceptable latency in all regions~~
- [x]~~Shardok performance similar to QA~~
- [x]~~Error logging & alerting~~
- [x]~~Fix long disconnects on deployments~~
- [ ] Fix the Mac installer
- [x]~~Still not reconnecting after deployments~~
- [ ] Notify about client updates, button to come directly back
- [ ] Generatedtext healing
- [ ] Kill outstanding shardok requests when game is deleted
### Other Productionization
- [x]~~Oauth sign-in~~
- [x]~~Add Google, others?~~
- [ ] User management
- [x]~~Invite codes~~
- [ ] Link accounts
- [x]~~Choose display name~~
- [x]~~Just do account setup from the landing page?~~
- [x]~~Download client directly from DO, avoid basic auth/my home network~~
This document outlines the implementation plan for adding delta update support to the Eagle0 macOS auto-update system using Sparkle's BinaryDelta feature.
### Current State
- Full DMG downloads (~200MB) for every update
-`mac_build_handler.go` creates DMG, signs it, uploads to S3, updates appcast.xml
- Keeps last 10 versions in appcast, deletes older DMGs
- Users must download full app even for small changes
### Goals
- Reduce update download size from ~200MB to ~10-30MB (85% reduction)
- Maintain backward compatibility with full DMG downloads
- Automatic fallback for users who are many versions behind
## Sparkle Delta Update Architecture
Sparkle supports binary delta updates through the `<sparkle:deltas>` element in the appcast. When a user updates, Sparkle:
1. Checks if a delta patch exists from their current version to the new version
2. If found, downloads the smaller delta patch instead of the full DMG
3. Applies the patch locally to create the new app version
4. Falls back to full DMG if no matching delta exists
| 1 | `welcome` | Modal | Auto (game start) | Welcome to Eagle0 | Command your armies, recruit heroes, and conquer provinces in this turn-based strategy game.<br><br>Let's walk through the basics! |
| 2 | `select_province` | Overlay | Completes on: `province_selected` | The Strategic Map | This is your kingdom. Each colored region is a province.<br><br>Tap a province you control (shown in your color) to see what you can do there. |
| 3 | `province_panel` | Modal | Button click | Province Information | This panel shows province details: its name, terrain, any armies present, and the commands available to you.<br><br>Commands let you move troops, recruit heroes, and more. |
| 4 | `try_march` | Overlay | Completes on: `command_issued` | Issue a Command | Try issuing a March command to move your army to an adjacent province.<br><br>Select a destination and confirm the order. |
| 5 | `turn_cycle` | Modal | Button click | The Turn Cycle | Eagle0 uses simultaneous turns. All players give orders at the same time, then turns resolve together.<br><br>When all players are ready, the server processes everyone's commands and shows the results. |
| 6 | `wait_for_battle` | Hidden | Completes on: `first_battle_available` | *(none)* | *(Invisible step - waits for a battle to become available)* |
| 7 | `battle_intro` | Modal | Button click | Battle Time! | When armies collide, you'll fight tactical battles on a hex grid.<br><br>You command individual units - infantry, cavalry, archers, and heroes with special abilities. |
| 8 | `enter_battle` | Overlay | Completes on: `battle_entered` | Enter the Battle | Tap the Battle button to enter tactical combat. |
| 9 | `tactical_overview` | Modal | Button click | Tactical Combat | Each unit has movement points and attack power. Position your troops wisely!<br><br>Units attack adjacent enemies. Flanking (attacking from multiple sides) deals bonus damage. |
| 10 | `move_unit` | Overlay | Completes on: `battle_action` | Move Your Units | Tap one of your units to select it, then tap a highlighted hex to move there.<br><br>Blue hexes show where you can move. |
| 11 | `attack_enemy` | Overlay | Completes on: `battle_action` | Attack! | Move next to an enemy unit, then tap the enemy to attack.<br><br>Red highlights show valid attack targets. |
| 12 | `end_turn` | Overlay | Completes on: `turn_ended` | End Your Turn | When you've moved all units or want to pass, tap End Turn.<br><br>The enemy will then take their turn. |
| 13 | `complete` | Modal | Button click (no skip) | You're Ready! | You now know the basics of Eagle0!<br><br>Explore diplomacy, recruit powerful heroes, and conquer the realm. Good luck, commander! |
### Notes on Onboarding Flow
- Steps 1-5 cover strategic gameplay
- Step 6 is invisible - just waits for a battle
- Steps 7-12 cover tactical combat
- Step 13 celebrates completion
**Questions to consider:**
- Should we skip tactical tutorial if player skips to first battle themselves?
- Should there be a "skip all" option visible from step 1?
- Is the step order correct for typical first-game flow?
---
## Strategic Contextual Tutorials
Triggered when players encounter features for the first time.
| Description | You can negotiate with other factions!<br><br>Offer alliances, declare war, or propose tribute. Your diplomatic choices shape the realm. |
| Description | Free heroes wander the realm seeking a lord to serve.<br><br>Recruit them to lead your armies! Heroes have unique abilities and grow stronger with experience. |
| Description | You've captured enemy soldiers!<br><br>You can ransom them for gold, recruit them into your army, or execute them as a warning. |
---
## Tactical Contextual Tutorials
Triggered during battles when players encounter spells, terrain, or abilities.
### Lightning Bolt Spell
| Field | Value |
|-------|-------|
| ID | `spell_lightning` |
| Trigger | `spell_lightning_available` |
| Display | Tooltip |
| Title | Lightning Bolt |
| Description | Your mage can cast Lightning Bolt!<br><br>This spell strikes a single target for heavy damage. Great for eliminating key enemy units. |
### Meteor Strike Spell
| Field | Value |
|-------|-------|
| ID | `spell_meteor` |
| Trigger | `spell_meteor_available` |
| Display | Modal |
| Title | Meteor Strike |
| Description | Meteor is a devastating area spell!<br><br>It takes a turn to cast: first select target, then it lands next turn. Plan ahead! |
### Holy Wave Spell
| Field | Value |
|-------|-------|
| ID | `spell_holywave` |
| Trigger | `spell_holywave_available` |
| Display | Tooltip |
| Title | Holy Wave |
| Description | Holy Wave heals your units and damages undead!<br><br>Position your troops carefully to maximize its effect. |
### Raise Dead Spell
| Field | Value |
|-------|-------|
| ID | `spell_raisedead` |
| Trigger | `spell_raisedead_available` |
| Display | Modal |
| Title | Raise Dead |
| Description | Dark magic can raise fallen soldiers as undead!<br><br>They fight for you, but beware - they may crumble if your necromancer falls. |
| Description | Units can cross shallow water, but it's risky.<br><br>Crossing takes extra movement and may fail. Some units swim better than others. |
### Cavalry Charge
| Field | Value |
|-------|-------|
| ID | `ability_charge` |
| Trigger | `ability_charge_available` |
| Display | Overlay |
| Title | Cavalry Charge |
| Description | Your cavalry can Charge!<br><br>Charging deals bonus damage based on distance traveled. Use open terrain for maximum impact. |
---
## Display Modes
| Mode | Description | Use For |
|------|-------------|---------|
| **Modal** | Full popup with dimmed background, blocks interaction | Important concepts, multi-paragraph explanations |
| **Overlay** | Semi-transparent overlay, can highlight UI elements | Guiding player to interact with specific UI |
| **Tooltip** | Small popup near target element | Quick tips, less important info |
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.