Skip to content

Merge dev → main (automated)#601

Open
SorraTheOrc wants to merge 49 commits into
mainfrom
release/dev-to-main-20260714140001
Open

Merge dev → main (automated)#601
SorraTheOrc wants to merge 49 commits into
mainfrom
release/dev-to-main-20260714140001

Conversation

@SorraTheOrc

Copy link
Copy Markdown
Member

Automated release created by ship skill.\n\nIncludes CHANGELOG.md with work-item summaries from this release.

Sorra the Orc 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.
… 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
Sorra the Orc and others added 19 commits July 12, 2026 13:00
…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
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