Skip to content

Merge dev → main (automated v0.1.2)#598

Merged
SorraTheOrc merged 20 commits into
mainfrom
release/dev-to-main-20260620135506
Jun 20, 2026
Merged

Merge dev → main (automated v0.1.2)#598
SorraTheOrc merged 20 commits into
mainfrom
release/dev-to-main-20260620135506

Conversation

@SorraTheOrc

Copy link
Copy Markdown
Member

Automated release created by ship skill. Includes CheckpointManager, save/load for Feudalism/Main Street, audio resilience, and bug fixes.

Sorra the Orc added 20 commits June 19, 2026 11:15
The Patron was being removed from the patron column display before its
fly animation started. This happened because executeTurn() removes the
patron from session.patrons and adds it to player.patrons, then
onRefreshAll() rebuilds the patron display from the (now empty) patron
list — but the fly animation hadn't started yet.

Fix: Add a patron animation cache to FeudalismRenderer that keeps the
patron visible in the patron column during card purchase/reserve
animation. The cache is set before the initial refresh, and cleared
after the patron fly animation completes (onRefreshPatronsAndPlayer),
at which point the patron is properly shown in the player area.

Changes:
- FeudalismRenderer: Added patronAnimationCache field and
  cachePatronForAnimation() method. refreshPatrons() renders cached
  patron alongside session patrons.
- FeudalismTurnController: Added onSetPatronAnimationCache callback.
  Set cache before initial refresh in executeAction/executeAiTurn,
  clear after patron fly completes (onRefreshPatronsAndPlayer).
- FeudalismScene: Wired onSetPatronAnimationCache to renderer method.
…alismOverlays.ts

Wrap all 3 direct scene.sound.play calls in FeudalismOverlays.ts with
try/catch blocks following the established pattern from LostCitiesOverlays.ts:
  try { this.scene.sound.play?.(SFX_KEYS.XXX); } catch { /* ignore */ }

These calls were the only unprotected scene.sound.play calls across all
example-games/ and src/ after audit. LostCitiesOverlays.ts had all 7 calls
already protected. All other games use soundManager.play() correctly.

Build: ✅
Tests: 198 files, 3565 passed, 5 skipped ✅
Add a drop-in safePlaySound(scene, key) utility function that wraps
scene.sound.play?.(key) in try/catch, preventing game-loop crashes
from missing audio keys in overlay helpers.

- Export safePlaySound from SoundManager.ts (Phaser-agnostic interface)
- Export from src/core-engine/index.ts barrel
- 9 unit tests covering: valid scene, throwing play, null sound, null
  scene, undefined play, optional-chaining preservation, no console noise
- Updated SFX_CONVENTION.md documenting the safe playback pattern

Build: ✅
Tests: 198 files, 3574 passed, 5 skipped ✅
…vulnerable calls

Add custom ESLint rule 'local/no-direct-sound-play' that flags direct
scene.sound.play() calls that are NOT inside a try/catch block.

The rule correctly:
- Flags scene.sound.play() outside try/catch
- Allows try { scene.sound.play() } catch { }
- Does not flag typeof checks (GymAudioFeedbackScene style)
- Does not flag soundManager.play() calls (correct namespace usage)

Also fixes 22 newly discovered vulnerable calls in src/ui/ animation
helpers that use scene.sound?.play() fallback pattern (vulnerable to
missing audio key crashes despite optional chaining on sound):

- dealCard.ts (5 calls)
- discardCard.ts (4 calls)
- flipCard.ts (5 calls)
- moveGameObject.ts (4 calls)
- placeCard.ts (4 calls)

Each is now wrapped in try/catch matching the established pattern.

Build: ✅ | Tests: 198 files, 3574 passed, 5 skipped ✅
Add Playwright-based browser test (FeudalismAudioResilience.browser.test.ts)
that verifies:
- safePlaySound does not throw for missing audio keys
- safePlaySound handles null scene gracefully
- Scene remains active after safePlaySound calls with missing keys
- Direct scene.sound.play() with missing key DOES throw (proving why
  protection is necessary)
- Game-over overlay renders with scene staying active after sound calls
- Null scene and missing sound manager are handled

Build: ✅ | Tests: 198 files, 3574 passed, 5 skipped ✅
…en texture-key sprite lookup with card-index-based lookup

Root cause: animatePhase1() used getLcTextureKey(templateId, CARD_W, CARD_H)
to find hand sprites, but hand sprites use HAND_CARD_W/HAND_CARD_H, so
the DPR-aware texture key never matched (e.g. 95x130 vs 100x137).

Fix: Save selectedCardIndex before clearing in executePlayerPhase1() and
pass it to animatePhase1() as handIndex. The animator now directly indexes
into handSprites[handIndex], bypassing texture key matching entirely.

Test update: Browser test wait times increased from 150-200ms to 400ms
to account for the now-functional 300ms ANIM_DURATION tween.

Files:
- LostCitiesAnimator.ts: accept handIndex param, use direct index lookup
- LostCitiesTurnController.ts: save selectedCardIndex before clearing
- LostCitiesRoundEnd.browser.test.ts: increase wait times for animation
…ast card

Bug: When the Golf discard pile has exactly one card and the player draws it,
the discard pile sprite continued to show the drawn card's face instead of
updating to an empty-pile ghosted card-back placeholder.

Root cause: GolfAnimator.updateDiscardPileAfterDraw() called the deprecated
no-op showDiscardPlaceholder() for the size <= 1 case, so no visual update
occurred.

Fix: Replace the no-op with direct sprite manipulation that sets the discard
sprite to the ghosted card-back texture ('card_back' at alpha 0.25, matching
PileView's emptyAlpha configuration). Direct manipulation is required here
rather than calling PileView.update() because the card has only been peeked
from the pile at this stage — popOrThrow() happens later in executeTurn().

Test: Enhanced the existing 'Draw from discard pile' browser test to verify
that after drawing the only discard-pile card, the discard sprite texture
is 'card_back' with ghosted alpha, while the model still correctly reports
1 card (since the pop happens on the subsequent move).
…ile before flight starts

Split the onRefreshPatronsAndPlayer callback in executeAction() and
executeAiTurn() into two distinct callbacks:
  - onBeforePatronAnimation: clears patron animation cache before the
    flying patron is created, ensuring the static tile disappears first
  - (new third callback): full refresh after patron fly animation completes

This eliminates the duplicate-patron visual artifact during flight.

discovered-from:CG-0MQK0SKBA005QZFR
…-discard animation

Root cause: animatePhase2() used cardAssetKey() (full-size template ID like
lc-blue-2) + getLcTextureKey() (key-only construction, no validation or
fallback) when creating the temporary animation sprite for draw-from-discard.
The correct compact textures (e.g., lc-blue-2-sm) were already prewarmed
by prewarmTextures(), but the lookup used the wrong template ID and a
non-fallback-aware function.

Fix:
- Import compactAssetKey from LostCitiesCards
- Use compactAssetKey(drawnCard) instead of cardAssetKey(drawnCard) to
  match the -sm suffixed template IDs used by discard pile textures
- Use getLcFaceKey(this.scene, templateId, ...) instead of
  getLcTextureKey(templateId, ...) to get synchronous SVG rasterisation
  with card-back fallback if rasterisation fails

Verified:
- Build succeeds (tsc --noEmit + vite build)
- All 198 test files pass (3574 tests, 5 skipped)
- Created tests/core-engine/checkpoint-manager.test.ts with 23 tests covering:
  - save/load/clear round-trip
  - checkAndResume workflow (no checkpoint, checkpoint exists, overlay)
  - Error handling (storage unavailable, corrupt data)
  - Both built-in and callback-based overlay approaches
  - Game type isolation
  - Fire-and-forget save behaviour
- Created src/core-engine/CheckpointManager.ts with full implementation:
  - CheckpointManager class with save(), load(), clear(), checkAndResume()
  - Supports both built-in default and callback-based overlay
  - Defensive error handling falling through to freshStart on failure
- Exported CheckpointManager types and class from @core-engine barrel

Test-first: tests define the API contract for Feature 3 (CG-0MQL8CPZS009R74Q).
All 3597 tests pass, build succeeds.
…n default resume overlay and complete CheckpointManager

- Added CheckpointResumeOverlay.ts with createDefaultResumeOverlay function
  providing [Resume] and [New Game] buttons (visual Phaser overlay component)
- Updated CheckpointManager.ts JSDoc to reference the built-in default overlay
- Updated @core-engine barrel (index.ts) to export createDefaultResumeOverlay
  and ResumeOverlayScene types
- All 3597 tests pass, build succeeds
… module

- Created FeudalismSaveLoad.ts with serialization/deserialization,
  SaveSerializer, and checkpoint helpers
- Created tests/feudalism/save-load.test.ts with 18 integration tests
  covering: serialize/deserialize round-trip, checkpoint save/load,
  human+AI turn saves, RNG seed serialization, market deck contents,
  checkpoint clear lifecycle
- All 3615 tests pass, build succeeds
- Modified FeudalismSession to include seed field for RNG serialization
- Modified setupFeudalismGame to accept seed option and track it
- Added onSaveCheckpoint callback to TurnControllerCallbacks interface
- Turn controller calls onSaveCheckpoint after every completed turn
- FeudalismScene: integrated SaveLoadStore, CheckpointManager,
  checkpoint check on startup with resume overlay, checkpoint
  cleared on game end, auto-save after each turn
- FeudalismSaveLoad already created in previous feature (CG-0MQL8BEXS003ZNNN)
- All 3615 tests pass, build succeeds
…checkpoint logic with CheckpointManager

Replaces inline checkpoint methods in BeleagueredCastleScene.ts with calls
to the new core engine CheckpointManager abstraction:

- checkForSavedCheckpoint() -> checkpointManager.checkAndResume()
- saveCheckpoint() -> checkpointManager.save()
- clearCheckpointAndStartFresh() -> checkpointManager.clear() + scene restart
- showResumeOverlay() refactored to accept callbacks from CheckpointManager
- Removed imports: saveBCSnapshot, loadBCSnapshot, clearBCSnapshot
- Added imports: CheckpointManager, bcStateSerializer, BCSerializedState

All 3615 unit tests pass, build succeeds.
…Manager docs, update game descriptions

- Added CheckpointManager.ts and CheckpointResumeOverlay.ts to core-engine
  file listing in DEVELOPER.md
- Added 'Checkpoint Save and Resume' section documenting the CheckpointManager
  API, resume overlay options, and games using checkpoints
- Updated Beleaguered Castle game description to mention checkpoint autosave
- Updated Feudalism game description to mention checkpoint autosave
- Updated README.md descriptions for BC and Feudalism to mention autosave
- Noted Main Street (CG-0MQKYJ6J2004G94U) as unblocked via work item comment
Integrate the shared CheckpointManager from @core-engine into Main Street
for automatic checkpoint save after each turn with resume-or-fresh-game
startup recovery, following the Feudalism integration pattern.

## Changes

### MainStreetSaveLoad.ts
- Added createMainStreetCheckpointManager() factory for creating a
  canonical CheckpointManager bound to Main Street's game type, slot,
  and serializer
- Added clearTurnStartCheckpoint() convenience function delegating to
  CheckpointManager.clear()
- Refactored saveTurnStartCheckpoint()/loadTurnStartCheckpoint() to
  delegate to CheckpointManager internally (public API unchanged)

### MainStreetTurnController.ts
- Added onSaveCheckpoint callback property (invoked after each
  completed turn when game is still playing)
- Added onGameEnd callback property (invoked on game end to clear
  checkpoint)

### MainStreetLifecycleManager.ts
- Creates CheckpointManager after SaveLoadStore initialization
- Wires onSaveCheckpoint/onGameEnd callbacks to turn controller
- Added checkForSavedCheckpoint() method using CheckpointManager's
  checkAndResume() with the built-in default resume overlay
- Checkpoint check runs after async campaign load; resume overlay
  takes priority over tutorial offer modal
- On resume: state restored from checkpoint, tutorial marked as seen
- On new game: checkpoint cleared, fresh game continues

### MainStreetScene.ts
- Added checkpointManager property declaration
- Added checkForSavedCheckpoint() method delegating to lifecycle manager

### Tests
- New tests/main-street/checkpoint-manager.test.ts with 12 tests
  covering CheckpointManager factory, save/load/clear round-trips,
  public API compatibility, and clearTurnStartCheckpoint
- Updated existing save-load.test.ts to match CheckpointManager's
  error-handling (incompatible version returns null instead of rejecting)

All 17 save-load + checkpoint tests pass. Full test suite passes
(1144/1148, 4 pre-existing tutorial highlight failures).
…efault resume overlay

The createDefaultResumeOverlay() function now creates a full-screen
semi-transparent dark background (0x000000, alpha 0.75) before rendering
text and buttons, making the overlay readable against the game board.

Changes:
- Added OverlayRect interface (headless-compatible, no Phaser import)
- Extended ResumeOverlayScene.add with rectangle() method
- Added BACKGROUND_DEPTH constant (2000, below TEXT_DEPTH 2001)
- Background created at center, full-screen (1280x720), interactive
  to block pointer events
- Background destroyed on both Resume and New Game dismissal
- Added comprehensive unit tests (17 tests) covering background
  creation, input blocking, destruction, hover effects, and callbacks

Acceptance criteria: all 7 criteria satisfied. Full test suite (475+ tests)
passes, build completes without errors.
@SorraTheOrc SorraTheOrc merged commit b0d1c24 into main Jun 20, 2026
1 check failed
@SorraTheOrc SorraTheOrc deleted the release/dev-to-main-20260620135506 branch June 20, 2026 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant