Merge dev → main (automated)#601
Open
SorraTheOrc wants to merge 49 commits into
Open
Conversation
added 30 commits
July 10, 2026 23:02
…oss all games Changes: - Added CURRENCY_SYMBOL_KEY, getCurrencySymbol(), and formatCurrency() to I18n module (defaults to €) for locale-aware currency formatting - Main Street: Replaced $ with € in activity log strings (5 files) - Main Street: Updated tutorial text (€6 instead of ) - Main Street: Updated cardLabel() and tooltip descriptions - Lost Cities: Replaced $ with € in all 30 multiplier card SVG assets - Tests: Updated activity log test expectations to use € - Exported new I18n functions from core-engine barrel file
- Added HintBar component (src/ui/HintBar.ts) with positioned text at bottom-center - Supports dynamic text updates (setText), visibility toggle (show/hide/toggle/setVisible) - Configurable position, styling, and initial text via HintBarOptions - Exported from src/ui/index.ts - Added tests (tests/ui/HintBar.test.ts) covering construction, text updates, visibility, destroy
- MainStreetRenderer.createInstructions() now creates a shared HintBar component instead of a raw Phaser text object for the hint/instruction area - instructionText is preserved as a reference to HintBar.textObject for backward compatibility (tests, getSectionRectsForTest) - MainStreetInputManager.onHintClick() now uses hintBar.setText() for hint display, using the shared component across all hint scenarios - Added hintBar property to MainStreetScene - Only Main Street had a hint system; no other games needed migration
- Add randomizeHand() method that replaces the current hand with 5 random cards - Add '[ Randomize Hand ]' button to the controls row - Update controls help text to document the new button - Add tests for randomize hand logic (hand size, replacement, face-up cards, deck replenishment)
- Generated visually representative 120x68 PNG thumbnails for all 6 games: - Golf: Green felt with card grid layout - Beleaguered Castle: Castle columns with card stacks - Sushi Go!: Colorful sushi rolls with hand area - Feudalism: Medieval castle tower with crown and card - Lost Cities: Compass/exploration theme with expedition cards - Main Street: Building skyline along a street - Created missing main-street thumbnail (was missing) - All thumbnails match the 120x68 format expected by GameSelectorScene
…Scene - Add destX/destY/flipOnArrivalTexture options to discardCard() for animated discard-to-pile behavior with optional flip on arrival - Add discard mode toggle (animate vs shrink) to GymHandPileScene with a [ Toggle Discard Mode ] button - Default mode is 'animate': card smoothly moves from hand position to discard pile position, flipping face-down on arrival - 'shrink' mode preserves original fade+shrink-in-place behavior - Toggle state persists during scene session (not reset on Reset) - Update help text and test plan to document the new feature
…ip texture - discardCard.ts: destination animation now smoothly rotates the card to 0 degrees (pile orientation) during the move phase, so the card rotates from its arc-layout rotation to match the discard pile - GymHandPileScene: use getCardTexture(card) instead of hardcoded 'card_back' for flipOnArrivalTexture, so the flip correctly matches the face-up/face-down status of the card in the discard pile - Tests: add rotation: 0 assertions to all destination animation tests
…nagement to discardCard The discard pile sprite is at depth 1, but card sprites are at default depth 0. When the card arrived at the pile position during the destination animation, it rendered behind the pile sprite, hiding the flip animation. Fix: Add depth option to DiscardCardOptions. When provided: - The target's depth is set to the given value before animation starts - On completion (and if destroyAfter is false), the original depth is restored - In GymHandPileScene, pass depth: 2 when discarding in animate mode
… right edge instead of center Root cause: Commit 0c90e50 reverted the container-local coordinate fix from b1165fa. The UpgradeOverlaySpec coordinates used absolute card-pixel positions (x: width/2, y: height*0.38) but the renderer adds these to a Phaser Container whose local origin (0,0) is at the card centre. So x: width/2 placed text at the RIGHT edge of the card, causing income/reputation labels to overflow off the right side. Fix: Convert coordinates to container-local space: - incomeText: x=0, y=-height*0.06 (centred, slightly above middle) - reputationText: x=0, y=+height*0.1 (centred, below income) - Update test expectations to match container-local coordinates
…hots via replay pipeline - Ran replay pipeline for all 6 games producing per-turn screenshots - Generated thumbnails from midpoint frames resized to 120x68 - Games: golf, beleaguered-castle, sushi-go, feudalism, lost-cities, main-street Bug fixes discovered: - FeudalismScene: moved replayController before replay-mode early return - SushiGoScene: moved replayController before replay-mode early return - FeudalismScene: guarded turnController access in refreshAll for replay mode Previously thumbnails were SVG/generated via sharp; now they're actual in-game screenshots captured via headless Playwright replay.
…y of sounds and feedback types - Replace hardcoded DEMO_SFX_KEYS with comprehensive DEFAULT_SFX_KEYS (8 sound keys) - Replace hardcoded DEMO_EVENT_MAPPING with comprehensive DEFAULT_EVENT_MAPPING (8 event mappings) - Add FEEDBACK_TYPES array for auto-discovered visual feedback types (popTextOrIcon, Celebrate) - Dynamically generate buttons from soundManager.keys() and FEEDBACK_TYPES - Add GymAudioFeedbackAutoDiscover.test.ts with 11 tests verifying auto-discovery, dynamic buttons, and sound registration - Keep existing controls: mute, volume, invalid key, pop text, celebration - Update preload to load all 8 default audio assets from public/assets/audio/default/ - Update help text to reflect auto-discovery behavior - Add dynamicButtons tracking for proper cleanup
Instead of starting at cx - 240 (x=400), dynamic sound and feedback buttons now start at LEFT_MARGIN=40px and are evenly spaced across the available (GAME_W - 80) pixels, making full use of the horizontal space.
- Expanded Market section to fill space between Patrons and
Supply with equal small margins
- Removed section titles (Patrons, Market, Supply) from section
boxes and moved top border down to avoid settings/help buttons
- Deck spaces now show full tier name ('Smallholding', 'Farm',
'Estate') on the deck cards instead of abbreviation labels
- Removed second heading ('Supply') from above token column
- Removed abbreviation labels next to each supply token
- Removed Mead from supply token rendering
- Removed '(none)' label from Player and AI token areas
- Right-aligned Supply section with small margin
- Moved action buttons down (ACTION_Y: 652 -> 670)
- Fixed tutorial i18n test: '' -> '€6' (pre-existing failure)
…ymOverlayUiScene
- Fix GeometryMask positioning: mask shape is now at same position as
masked container so clip region actually contains the content.
- Add 70 lines of descriptive scrollable content (1120px total height
vs 200px mask area) so scrolling is clearly necessary.
- Implement wheel-based scrolling via scene.input.on('wheel', handler).
Content scrolls at 0.5x the raw wheel delta for smooth motion.
- Add visible scrollbar: dark track bar and green thumb indicator on
the right edge of the masked area. Thumb position updates in real
time as the user scrolls.
- Scroll state clamped to [0, maxScrollY]; resets on overlay reopen.
- Add 5 browser tests covering: content overflow, wheel scrolling,
clamping, position reset, and scrollbar visibility.
- Wheel handler properly removed in closeOverlay to prevent leaks.
This reverts commit 66b2e6c.
… clipping in GymOverlayUiScene GeometryMask is only supported in the Canvas renderer. The project defaulted to Phaser.AUTO which picks WebGL on modern browsers, causing setMask() to silently return early (printing a WebGL warning) without applying the mask. The result was scrollable content rendered fully visible with no clipping. Fix: explicitly pass type: Phaser.CANVAS to createCardGame() in main.ts, matching what the browser test suite already does. The existing GeometryMask positioning is correct: Graphics at (areaX, areaY) with rect at local (0,0)-(300,200) clips the container at the same world position in Canvas mode. Tests confirm: all 14 GymOverlayUiScene tests pass (Canvas | Web Audio).
…display - Added setFaceUp()/getFaceUp() to PileView for independent face-up display control (does not modify card model's faceUp property) - PileView.update() respects _faceUp state: shows card backs when false, face textures when true; uses cardTextureKey directly for standard cards - Added '[ Toggle Face Up ]' button and 'Face: up/down' label to GymHandPileScene controls row 3, positioned after the discard mode toggle - toggleDiscardFaceUp() handler flips discardView display state and logs - Reset() resets face-up state to default (face-up) - Added 11 unit tests in pileView.faceUp.test.ts covering: default state, toggle, empty pile, custom cardTextureFn interaction, card order preservation, card model isolation, chaining, and getFaceUp() accessor - Updated help text and test plan in GymHandPileScene
…ith auto-save and multi-hand support Replaces the generic event-demo transcript scene with a full Blackjack simulation: - Deals 2 cards each to player and dealer (first event) - Player can Hit (draw a card) or Stick (end turn) (second event) - Dealer auto-plays with 800ms delays when player sticks - Winner determination with win/loss/push - Auto-save transcript on hand end using autoSaveTranscript() - 'New Hand' button replaces 'Finalize' for multiple hands per session - Comprehensive tests for hand totals, Ace flexibility, edge cases - Fixed pre-existing tutorial test currency symbol mismatch ( -> €6) Tests: 4031 passing, 9 skipped
- PileView: add _faceUp state, setFaceUp()/getFaceUp() methods, and face-up/face-down display switching - GymHandPileScene: add Toggle Face Up button and label - Tests for PileView face-up state management - Generate-all-thumbnails utility script
…tion
Add an illegal move sound that plays automatically whenever the
shakeIllegalMove animation is triggered.
Changes:
- Add ILLEGAL_MOVE key ('sfx-illegal-move') to COMMON_SFX_KEYS in
SoundManager.ts
- Generate illegal-move.wav in public/assets/audio/default/
- Modify shakeIllegalMove to accept optional soundKey (defaults to
COMMON_SFX_KEYS.ILLEGAL_MOVE) and play via safePlaySound at shake start
- Update GymHandPileScene to load the illegal-move audio asset
- Update LostCitiesScene to load un-namespaced key for safePlaySound
- Update tests for shakeIllegalMove sound integration
- Add COMMON_SFX_KEYS.ILLEGAL_MOVE test to SoundManager.test.ts
- Document new key in docs/SFX_CONVENTION.md
The sound plays in all games that use the shakeIllegalMove animation
(Gym, Lost Cities). Callers can suppress the sound by passing
soundKey: ''. Existing callers are unaffected - they get the default
sound automatically.
…s tooltips Adds a hasTooltips config option to SettingsPanel that controls whether the Tooltips toggle UI is shown. Games without tooltips (Golf, Beleaguered Castle, Feudalism) set this to false, hiding the toggle. Changes: - SettingsPanelConfig: new hasTooltips? field (default: true) - SettingsPanel constructor: conditionally creates tooltip toggle UI - CardGameScene.initSettingsPanel: new hasTooltips parameter - GolfScene: passes hasTooltips=false (no tooltip system) - BeleagueredCastleScene: passes hasTooltips=false (no tooltip system) - FeudalismScene: passes hasTooltips=false (no tooltip system) - New browser test: SettingsPanelTooltips.browser.test.ts (6 tests)
…card ordering logic Acceptance Criteria: 1. AI does not place investment cards unless it has enough cards (3+) of that color - Added countNumberedCardsInHandOfColor() helper - Modified scorePhase1Action() to penalize investments without enough support 2. AI does not play a higher card when a lower card of same color exists in hand - Added hasLowerNumberedCardInHand() helper - Modified scorePhase1Action() to penalize playing higher cards before lower ones 3. Overall AI improved: 4 new tests pass, all 29 tests pass, build succeeds Files changed: - example-games/lost-cities/AiStrategy.ts: Added helpers and improved scoring logic - tests/lost-cities/lost-cities-ai.test.ts: Added 4 new tests for improved behaviors
…bilistic recall Add CardMemoryTracker class that records cards seen on the discard pile and returns probabilistically recalled rank counts based on skill rating. - New src/ai/CardMemoryTracker.ts with recordCard() and getVisibleRanks() methods - Updated src/ai/index.ts barrel file to export CardMemoryTracker - Added tests/ai/CardMemoryTracker.test.ts with 21 tests covering: - Basic recording (single/multiple/different ranks) - Perfect recall at skill=100 - Statistical accuracy at skill=50, 1, 80, 25, 75 - Misremembered count bounds (0-4) - Edge cases (skill=0, empty records, rank independence) - Constructor defaults, clamping, and custom values
Add optional rng and CardMemoryTracker parameters to countVisibleRanks() so that the function can merge probabilistic historical discard card counts with perfect current visible card counts. - Updated countVisibleRanks() signature to accept optional (rng, memoryTracker) - When tracker is provided: merge grid + discard top (perfect) with historical discard counts (memory-augmented) - The current discard top card is always counted perfectly (tracker count for discard top rank is decremented by 1 to avoid double-counting it) - Fully backward compatible when no tracker/rng is provided - Added 5 new tests covering: empty tracker, skill=100 historical merge, discard top perfect when tracker misremembers, combined counts, and backward compatibility
…racking Integrate CardMemoryTracker into AiPlayer for probabilistic recall of discard pile cards based on configurable skill rating. - AiPlayer constructor accepts optional skillRating (default: 80) and creates a CardMemoryTracker instance - Added recordCard() method on AiPlayer for external callers to record observed discard cards - AiPlayer.chooseAction() and AiPlayer.chooseDrawSource() now thread the memory tracker through to countVisibleRanks for memory-augmented rank counts - Updated AiStrategy interface and GreedyStrategy.chooseAction to accept optional memoryTracker parameter - Updated chooseDrawSource signature to accept optional memoryTracker - Added 6 new tests for skill rating passthrough, recordCard, memory persistence, and skill rating comparison
…ration Add CSV checksum computation, persistence, and detection mechanism to keep card SVGs in sync when card-data.csv changes. Changes: - CsvChecksum.ts: DJB2 hash function for CSV checksum computation - MainStreetCards.ts: export CSV_CHECKSUM and CSV_ROWS - MainStreetState.ts: csvChecksum field in serialized state - MainStreetCardSvgGenerator.ts: add generateCardSvgFromCsvRow() and per-type SVG generators (event, upgrade, staff, business) - MainStreetSvgTextureManager.ts: add regenerateSvgSourcesFromCsv() - MainStreetLifecycleManager.ts: add checkForCsvMismatchAndRegenerate() wired into new-game and checkpoint-resume flows - generate-main-street-card-svgs.mjs: refactored to export regenerateCardSvgs() + computeCsvChecksum() with csv-checksum.json output - csv-checksum.json: build artifact for runtime change detection - tests/main-street/csv-checksum.test.ts: 14 tests Closes CG-0MRF343AJ004N6ZW
Add tests verifying that chooseDrawSource uses memory-augmented counts from CardMemoryTracker when deciding draw source. The implementation was already wired through in Features 2 and 3 - this commit adds the tests. - Added test: prefers discard when memory indicates unseen copies remain (skill=100 with recorded historical discard cards) - Added test: backward compatibility without memory tracker
Add aiSkillRating property (default: 80) to GolfScene, read from GolfSetupOptions, and pass to AiPlayer constructor for configuring probabilistic card memory. - Added skillRating field to GolfSetupOptions in GolfGame.ts - Added aiSkillRating public property on GolfScene (default: 80) - AiPlayer constructed with scene's aiSkillRating value - All existing tests continue to pass
Add boundary skill level tests verifying that skill=25 produces lower rank accuracy than skill=75 when using memory tracker with the same random seed. Both CardMemoryTracker and AiStrategy already had coverage for skill=0,1,50,80,100 - this adds coverage for 25 and 75. - Added test: skill rating 25 produces lower rank accuracy than skill 75 (same RNG seed, 10 recorded King cards, skill=75 recalls more) - All 331 unit tests pass across 19 test files
…stle Addresses audit gap: illegal move sound was only integrated in Gym and Lost Cities games but not in Beleaguered Castle. - Added ILLEGAL_MOVE to SFX_KEYS in BeleagueredCastleConstants.ts - Imported shakeIllegalMove and loaded illegal-move.wav in preload() - shakeIllegalMove triggers in all 5 illegal move paths: 1. Drag-end when not dropped on a zone 2. handleDrop when move is invalid 3. handleCardClick when tableau move is illegal 4. setupClickToMove (foundation) when move is illegal 5. setupClickToMove (tableau) when move is illegal - Added test for ILLEGAL_MOVE constant
…with texture cache clearing
Fix a race condition where card data edits to card-data.csv were not
reflected in-game because SVG texture prewarming rasterized stale
static SVG files before the CSV mismatch check could run.
Changes:
- MainStreetLifecycleManager.ts (loadCampaignAndSetup):
- Call regenerateSvgSourcesFromCsv() synchronously after
setupMainStreetGame(), before async campaign load
- Chain re-apply onto cardSvgLoadPromise to handle race condition
where late-resolving SVG fetches overwrite regenerated SVGs
- MainStreetSvgTextureManager.ts (regenerateSvgSourcesFromCsv):
- Add texture cache clearing via clearCachedTexturesForIds() after
regeneration, ensuring stale Phaser textures are invalidated
- tests/main-street/svg-texture-cache-invalidation.test.ts:
- Add 4 new tests verifying texture clearing, graceful degradation,
SVG source replacement, and error resilience
…ing in early SVG regeneration The synchronous regeneration call in loadCampaignAndSetup() was clearing all ms_card_* Phaser textures immediately, but the scene's create() method creates sprites (via createContainers, startDayPhase) AFTER this point and BEFORE prewarm runs. Those sprites referenced the now-cleared textures, causing 'Cannot read properties of null (reading drawImage)' on the next render frame. Fix: Add clearTextures parameter to regenerateSvgSourcesFromCsv(). - Early regeneration in loadCampaignAndSetup() calls with false (no texture clearing) so existing sprites can still reference textures - Re-apply chain on cardSvgLoadPromise keeps default true so textures are cleared just-in-time, right before prewarmVisibleCardTextures() - checkForCsvMismatchAndRegenerate() also uses false since it runs long after scene setup when sprites already exist
…e crash Root cause: The re-apply chain cleared all ms_card_ textures, then prewarmVisibleCardTextures() started async SVG rasterization with await Promise.all(). Between the yield point and when rasterization completed, a render frame could fire and hit missing textures. Fix: Move texture cache invalidation inside prewarmVisibleCardTextures(). For each texture key, remove the existing texture and start rasterization in the same synchronous loop iteration, before any await. This ensures textures are never missing when the render loop fires. Consequences: - regenerateSvgSourcesFromCsv() no longer clears textures (only updates SVG sources) - clearTextures parameter removed - clearCachedTexturesForIds() private method retained for syncDisplayMetrics() pattern - The re-apply chain continues to work: regenerate SVG sources -> prewarm removes stale textures per-key -> rasterizes fresh ones -> refreshAll
Adds a browser test that boots a full Main Street game, captures all console.error and console.warn calls during startup (including SVG regeneration, prewarming, and render loop initialisation), and asserts that no errors occur — particularly the drawImage(null) crash that previously happened when textures were cleared with a yield point before rasterisation could replace them.
…ure in prewarm Root cause: prewarmVisibleCardTextures() is called TWICE (from the create() promise chain and from startDayPhase() promise chain). Our per-key remove+recreate caused the second call to find existing promises in rasteriseSvgToTexture's textureCache, await them, and yield to a render frame with a missing texture → drawImage(null). Fix: restore the 'if (s.textures.exists(key)) continue;' guard that was removed in the previous commit. Since regenerateSvgSourcesFromCsv() runs BEFORE any prewarm call (both the early synchronous call and the re-apply chain on cardSvgLoadPromise), textures created by the first prewarm call already use the correct CSV-fresh SVGs. The second prewarm call correctly skips them — no removal, no recreation, no race. Also removes the now-unused clearCachedTexturesForIds() method.
…uered Castle startup Adds boot-and-check test coverage for Golf and Beleaguered Castle. Known benign console.error patterns (audio decoding, favicon 404) are filtered out so only unexpected errors cause test failures. All three games pass: MainStreet (0 errors), Golf (0 errors), BeleagueredCastle (only pre-existing audio decode errors filtered).
…d/border/label
Root cause: SushiGoScene.removeTableauHighlights() was destroying ALL
Rectangle grandchildren in player tableau card containers, including the
essential card background rectangle (bg) created by createCardRect(). This
left only the SVG icon visible, since the background Rect, border stroke,
and the bg's interactive hit area for hover/tooltip events were destroyed.
Fix: Tag chopsticks highlight Rectangles with setData('chopsticksHighlight',
true) when creating them, and only destroy tagged Rectangles in
removeTableauHighlights(). This preserves non-highlight Rectangles such as
the card background bg.
Changes:
- example-games/sushi-go/scenes/SushiGoScene.ts:
- highlight.setData('chopsticksHighlight', true) in
refreshChopsticksTableauHighlight()
- gc.getData('chopsticksHighlight') guard in removeTableauHighlights()
- tests/sushi-go/SushiGoTableauRendering.browser.test.ts (new):
- 8 browser tests verifying bg Rectangle presence, tooltip hover,
highlight effects, AI tableau no-regression, and chopsticks
highlight tagging
Make MAX_RANK_COPIES configurable via a configuration object while maintaining full backward compatibility with the positional number parameter. Changes: - Add CardMemoryTrackerConfig interface with optional skill and maxCopies - Constructor now accepts number | CardMemoryTrackerConfig (backward compatible) - Replace hardcoded MAX_RANK_COPIES with instance property this.maxCopies - Export CardMemoryTrackerConfig type from src/ai/index.ts barrel - Add 10 new tests: configurable maxCopies at skill=0 with values 1,4,8,52 - Create src/ai/README.md documenting shared core engine AI components - All 22 original tests pass unchanged Closes CG-0MRH2FERF001OGWB
…changes - Reverted Bakery Test/Diner Test to Bakery/Diner - Regenerated SVG assets and csv-checksum.json - Updated test expectations for Clinic, Library, tier counts - Monte-Carlo baseline recalculated Also includes: - CardMemoryTracker skill rating updates (setSkill) - SettingsPanel skill rating slider - GolfScene skill rating wiring - CardGameScene skill rating config
- Added initHUDContainer() call to BeleagueredCastleScene - Added shared findGameObjectByText helper for Golf overlay tests - Fixed Export Transcript button text search (remove brackets) - Updated hud-layer-contract test to verify panel existence (not parenting to avoid RangeError)
…ve dead clickAtGameCoords - Pass OVERLAY_DEPTH (2000) to createGameOverOverlay for consistent overlay depth - Update win overlay test to expect buttons at depth 11 with correct labels - Remove unused clickAtGameCoords helper function
…er, update Golf docs in DEVELOPER.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated release created by ship skill.\n\nIncludes CHANGELOG.md with work-item summaries from this release.