Merge dev → main (automated) - #770
Merged
Merged
Conversation
added 30 commits
July 23, 2026 23:13
…r & Blend Spike gym scene - Removed shaderAttempted and shaderResult private fields - Removed [ Attempt Shader ] button from the button row in create() - Removed attemptShader() method entirely - Updated Controls help text to remove [ Attempt Shader ] line - Updated Test Plan to remove step referencing Attempt Shader - Remaining controls (Next Tint, Next Blend, Reset Tint) are untouched - All 4388 unit tests and 393 browser tests pass
…rd Data PRD Creates a comprehensive PRD at docs/main-street/prd-balance-process-and-tooling.md defining: - Structured balance review process (trigger events, review workflow, guardrail thresholds, decision gates) - 7 micro-level per-card metrics (pick rate, win-rate delta, cost-to-income ratio, synergy utilization, upgrade adoption, event impact, survival rate) with formulas, data sources, and interpretation guidance - 8 macro-level global metrics (win rate matrix, score distribution, economy health, synergy diversity, loss mode decomposition, card usage diversity, turn-by-turn snapshots, trap card prevalence) - CLI tool architecture for 5 scripts (balance-report, balance-cards, balance-global, balance-capture-baseline, balance-check) - Baseline management strategy for regression comparison - Integration with existing Monte Carlo harness, playtest scenarios, CI, difficulty presets, AI strategies, and EconomyLedger - Implementation roadmap (4 phases) - Metric feasibility assessment with harness extension priorities Updated cross-references in: - README.md (added balance documentation links) - docs/main-street/balancing-methodology.md - docs/main-street/playtest-scenarios.md - docs/main-street/monte-carlo-sample-results.md - docs/main-street/card-catalog.md - docs/main-street/prd-milestone-3.md
This reverts commit b121b21.
Commit b121b21 ('Sync work items and comments') deleted all 1195 project files from the dev branch. Reverted via 49c8763 using --strategy-option=theirs, then re-applied the cross-reference documentation updates from the Balance PRD that were overwritten by the revert merge conflict resolution. Restored files (1196 total): - src/ (96 files): engine modules - example-games/ (all 7 games) - tests/ (all test files) - public/assets/ (SVG cards, audio, thumbnails) - scripts/, schemas/, tools/, config files - .github/workflows/, .githooks/ Re-applied cross-references to prd-balance-process-and-tooling.md PRD in: - README.md (2 references) - docs/main-street/balancing-methodology.md - docs/main-street/playtest-scenarios.md - docs/main-street/monte-carlo-sample-results.md - docs/main-street/card-catalog.md - docs/main-street/prd-milestone-3.md
…date investment card timing guidance - Add T8 step (buy Bookshop + auto-place) to unified tutorial flow - Update scenario: replace biz-hardware with biz-bookshop ( Culture business) - Updated coin budget: Laundromat (), Bookshop (), Local Festival () = 0 - Add auto-place logic in MainStreetTurnController for combined buy+place - Update i18n strings: new T8 culture business step, updated T7 festival timing hint - Renumber T8→T9 through T13→T14 - Update all tests for 14-step structure and new card references - Fix browser overlay tests for renumbered step IDs Work committed to dev
…valuation Add estimatePositiveScoreProbability() function that evaluates whether placing a card in a tableau column will lead to a positive final score. The function considers: - Remaining cards of that color in the draw pile - Visible cards on both player and opponent expeditions - How many follow-up cards can legally be placed in ascending order - The current point deficit needed to overcome the -20 base cost Integrate this into the GreedyStrategy scorePhase1Action() to award bonuses for high-confidence plays and penalties for low-confidence ones. New tests verify: - Avoiding plays with low probability (opponent holds blocking cards) - Playing low cards when many follow-ups are available - Avoiding high cards with no follow-up potential - Dynamic risk tolerance based on draw pile size - Direct unit tests for the probability function All 267 existing tests continue to pass.
…ll be used in a swap
Two changes prevent GreedyStrategy from wasting discard draws:
1. now evaluates ALL legal moves (including column
bonus) and only returns 'discard' when the best-scored move is a SWAP
that is strictly better than discard-and-flip. Previously, a swap with
a negative column bonus could trigger 'discard' even when the raw
swap+bonus was still worse than doing nothing (DAF).
2. now passes to
so column-building feasibility weighting is
applied consistently between Phase 1 (draw source) and Phase 2 (move
selection). Previously, Phase 2 had no column bonus, so the move
chosen often differed from the Phase 1 rationale.
New edge-case tests verify:
- 'discard' is NOT chosen when the best legal move with the discard
card would be a discard-and-flip
- 'discard' is NOT chosen when the column bonus exists but is
insufficient to make a swap strictly better than discard-and-flip
- Column bonus scenarios are updated to use cases where the bonus
actually makes a demonstrable difference
- E-1: Add cardsOwned tracking to MonteCarloRunSummary - E-2: Add strategy×difficulty batch runner (runAllCombinations) - E-3: Add marketOffers tracking to MonteCarloRunSummary - E-4: Add getHistory() to EconomyLedger - Add economyHistory field to MonteCarloRunSummary (E-4 integration) - Add --sweep flag to CLI for batch runs - Add ALL_STRATEGIES, ALL_DIFFICULTIES constants - Add CombinationResult, RunAllCombinationsOptions interfaces - Add monte-carlo-sweep npm script - Update docs with sweep mode and extension fields All 89 tests pass across 7 test files.
Modified takeScreenshot() to exclude HUD elements (help panel, header chrome, nav buttons, event log) from the screenshot RenderTexture using a Set-based blacklist by identity reference. Added unit tests for the HUD exclusion logic in isolation. - 4 new tests in tests/gym/GymSaveLoadScreenshotFilter.test.ts - All 252 unit test files pass (4438 tests) - npm run build succeeds
- Inject version at build time via Vite define (__APP_VERSION__ from package.json) - Add createVersionLabel() factory in src/ui/versionDisplay.ts with shared constants - Display version (v0.1.7) in GameSelectorScene bottom-left corner - Display version on game canvas when SettingsPanel opens (hidden otherwise) - Add type declaration for __APP_VERSION__ in vite-env.d.ts - Update DEVELOPER.md with Build-Time Version Injection documentation - Add unit tests for GameSelectorScene version label - Add browser tests for SettingsPanel version label display Closes CG-0MR89YKN0000FK5W
Creates a reusable GymButtonBar class that provides: - Left/center/right zone alignment - Even spacing within zones - Automatic row wrapping when buttons exceed zone width - Support for 1..n rows - Preserves existing button styling (colors, hover effects, font) - Returns button references for setVisible(), setText(), etc. - Supports multiple bars at different Y positions - refresh() and destroy() lifecycle methods Includes 19 unit tests verifying zone positioning, spacing, wrapping, styling, button references, and lifecycle management. Part of CG-0MRDGW0DH001AA58 (Gym button bar)
Adds initButtonBar() method and buttonBar property to GymSceneBase so Gym scenes can easily create auto-laying-out button bars. - initButtonBar(y, opts?) creates a GymButtonBar at the given Y position - buttonBar property exposes the bar for addButton() calls - addButton() marked as @deprecated in favor of buttonBar.addButton() - addButtonAtAnchor() preserved for migration - 8 integration tests verifying source code structure - Imports GymButtonBar from src/ui/GymButtonBar Part of CG-0MRDGW0DH001AA58 (Gym button bar)
Migrates simple Gym scenes (single-row button groups) from manual this.addButton(x, y, ...) to this.buttonBar.addButton(label, callback, opts). Scenes migrated: - GymOverlayUiScene (4 buttons) - GymTranscriptScene (6 buttons, preserving btnHit/btnStick/btnNewHand refs) - GymUndoRedoScene (5 buttons, undo/redo buttons unaffected) - GymSaveLoadScene (8 buttons across 2 rows) - GymTooltipScene (4 buttons with dynamic toggle label) - GymMarketOfferEngineScene (4 buttons) - GymParameterizedOverlayScene (3 buttons) - GymGraphicsLightingSpikeScene (2 buttons) - GymGraphicsShaderSpikeScene (3 buttons) - GymSllScene (3 buttons with custom colors at 2 Y positions) All button styling (colors, hover effects, font) preserved via opts. Button references preserved for dynamic show/hide (btnHit), setText (toggleBtnRef). Part of CG-0MRDGW0DH001AA58 (Gym button bar)
Migrates multi-row and mixed-layout Gym scenes from manual this.addButton(x, y, ...) to this.buttonBar.addButton(label, callback, opts). Scenes migrated: - GymHandPileScene (17 buttons across 4 rows with labels) - GymRuleEngineScene (13 buttons in 2 sections with wrapping) - GymSpatialRulesScene (14 buttons across 3 rows with labels) - GymHudComponentsScene (6 buttons across 2 rows) - GymDeckRngScene (4 buttons, design buttons left manual) - GymI18nScene (5 buttons across 2 rows) - GymAiStrategyScene (8 buttons across 3 sections) - GymAudioFeedbackScene (4 buttons, dynamic buttons left manual) - GymSvgHelpersScene (6 buttons across 2 rows) All button styling preserved. Button references maintained for dynamic show/hide and text changes (dragButton, enBtn, etc.). Part of CG-0MRDGW0DH001AA58 (Gym button bar)
- Updated EVENT_TEMPLATE_COUNT from 36 to 37 in game-state.test.ts - Updated expanded-card-pool.test.ts event template count (36→37) and deck size (108→111) - Updated market.test.ts unique template count (36→37) - Added evt-recession to expanded-card-manifest.json
- Restore evt-recession card in card-data.csv (was reverted in d0a6f4a) - Add missing evt-recession.svg card asset - Update csv-checksum.json to match new CSV content - Fix meta-progression.test.ts card counts (83->84, 81->82) - Fix turnflow.test.ts coin assertion to be seed-resilient
…uring deck-back fly-in Adds pendingRefillSlots infrastructure to FeudalismRenderer (following the same pattern as patronAnimationCache) so that market slots being refilled render as empty during the deck-back fly-in animation. After the animation completes, a fresh refreshAll() call re-renders the actual card. Changes: - FeudalismRenderer: add pendingRefillSlots set, addPendingRefillSlots(), clearPendingRefillSlots(), isPendingRefillSlot(). refreshMarket() checks the set before rendering cards, showing empty rectangles for flagged slots. - FeudalismTurnController: add onSetPendingRefillSlots and onClearPendingRefillSlots callbacks. executeAction() and executeAiTurn() set pending refill slots before onRefreshAll(), clear them in the onRefreshMarket callback (called after deck-back lands). - FeudalismScene: wire up new callbacks in both create() and restoreFromCheckpoint(). - Tests: FeudalismRefillAnimation.browser.test.ts with 4 tests verifying the pending refill infrastructure. ACs covered: 1. Slot appears visually empty during refill animation 2. Deck-back flies to empty slot, card appears after landing 3. Existing animations unchanged 4. Works for all tiers, purchase and reserve-from-market 5. Reduced motion mode correctly skips animation, shows card immediately 6. Code comments updated 7. Full test suite passes (205 tests, 16 feudalism test files)
- Removed redundant [ Toggle HelpPanel ] and [ Toggle Settings ] buttons - Repositioned status text lines on separate Y offsets to avoid overlap - Updated help content (Controls, Test Plan) to reflect toggle button removal - Updated browser tests: toggle assertions replaced with Open/Close assertions All 20 GymHudComponentsScene browser tests pass.
- Updated setMaxRotationDegrees() clamp from >= 0 to [0, 359] range - Updated JSDoc to document the 0-359 range - Updated Gym slider maxValue from 45 to 359 - Added 11 boundary tests: 0, 359, negative, NaN, Infinity, and range invariant
…used imports/variables)
…Animation test Fix 5 TS6133 errors (unused imports/variables) in the refill animation browser test file that were causing build failures. - Removed unused 'beforeEach' import - Removed unused GAME_W, GAME_H constants - Restored needed 'renderer' variable in tier test - Removed unused 'renderer' variable in callback test
CG-0MRDL6LSS001LPCG Moves the celebration sound (SFX_KEYS.PATRON_VISIT) and toast messages from immediately after executeTurn() to just before playCardAnimation(), for both human (executeAction) and AI (executeAiTurn) paths. Also removes the premature onRefreshAll() that rendered the market slot empty before the flying animation began. Changes: - executeAction(): remove early sound+toast and refreshAll, defer them to the animation block - executeAiTurn(): same reordering for AI turn consistency - New test file (11 tests) verifying source code call ordering
…ing legacy addButton calls, remove deprecated methods - Migrate GymAudioFeedbackScene dynamic buttons from this.addButton() to this.buttonBar.addButton() with left zone for both sound event and visual feedback auto-generated buttons - Migrate GymTokenPileViewScene 9 buttons (8 grid + 1 Reset All) from this.addButton() to this.buttonBar.addButton() with center zone - Remove deprecated addButton() method from GymSceneBase (migration complete) - Remove deprecated addButtonAtAnchor() method from GymSceneBase (migration complete) - Update GymSceneBaseButtonBar tests to verify removal instead of presence - All legacy this.addButton() calls eliminated from all Gym scenes Part of CG-0MRDGW0DH001AA58 (Gym button bar)
- Add GymButtonBar section to docs/DEVELOPER.md under Shared HUD Components with API reference, zone documentation, per-button overrides, and GymSceneBase integration details - Update README.md to mention GymButtonBar in the Shared HUD Components list Documentation covers: - Constructor configuration (y, zone, padding, buttonGap, rowSpacing, width) - Left/center/right zone behavior and wrapping - Per-button overrides (zone, fontSize, color, hoverColor) - Instance methods (addButton, refresh, destroy) - GymSceneBase integration via initButtonBar() Relates to CG-0MRDGW0DH001AA58 (Gym button bar)
Two bugs fixed: 1. Identity references: The exclusion set used this.helpPanel and this.helpButton (wrapper class instances), not the actual Phaser scene children. Added getSceneChildren() accessors to HelpPanel (returns the container) and HelpButton (returns circle, label, hitArea) so scenes can exclude them by identity. 2. Position override: rt.draw(drawables, 0, 0) temporarily set every object's position to (0,0) — Phaser 4's DynamicTexture x/y parameters replace object positions, not add as offsets. Only containers rendered children at correct relative positions, making non-container objects (cards, text, buttons) all stack at the origin while the HelpPanel container appeared normally. Fixed by calling rt.draw(drawables) without x/y. Test: 18/18 SaveLoad tests pass, 441/441 gym tests pass, build clean.
added 28 commits
July 27, 2026 10:27
The scroll mask was created at scene origin (0,0), but the settings panel slides in from the right at (canvasWidth - panelWidth, 0). The mask covered (0, 0, panelWidth, canvasHeight) in scene space, which is the wrong area, clipping all scroll content away. Fix: add the mask graphics to 'this.container' so its coordinates are in container-local space, where the panel occupies (0, 0, panelWidth, canvasHeight). The mask now correctly clips the scrollable content to the visible panel area.
Root cause: the mask graphics was added to the container, rendering as a visible filled rectangle on top of everything, obscuring all content. Fix: match the HelpPanel pattern — create the mask at scene root, set visible=false, position it to cover the panel area when slid in from the right (canvasWidth - panelWidth, 0, panelWidth, canvasHeight). Use 'new Phaser.Display.Masks.GeometryMask()' instead of createGeometryMask(). Also ensures mask stays correctly positioned regardless of container position, even during slide-in animation.
When the expanded tree exceeds the dialog content area, the tree now scrolls via mouse wheel instead of clipping at ~20 extra lines. Uses the same pattern as HelpPanel: invisible geometry mask in scene space covering the content area, with scrollable inner container. Scroll position is persisted across re-renders (filter changes, node expand/collapse) via state.scrollY.
- Expand tree content area: removed spurious REFRESH_BTN_HEIGHT (36px) subtraction (refresh button sits at the top with the filter, not the bottom) and reduced BOTTOM_PADDING from 60 to 10, reclaiming ~86px of vertical tree space. - State Inspector wheel handler now checks pointer is within dialog bounds before scrolling, preventing scroll events from leaking to other overlays. - Settings Panel wheel handler also checks pointer is within the panel bounds, preventing dual-scrolling when the inspector is open on top.
Create src/ui/Dialog.ts — reusable modal dialog with: - Backdrop + centered overlay box - Title with close button (✕) - Scrollable content area with invisible geometry mask - Bounds-checked wheel handler (pointer-in-box) - Proper cleanup (event listener removal, object destruction) - onClose callback for caller cleanup - Auto-calculates content height for scroll clamping Refactor all three debug overlays to use it: - StateInspectorOverlay: ~80 lines removed (backdrop, title, close, mask, wheel handler, hudContainer parenting all handled by Dialog) - GameEventLogOverlay: ~70 lines removed, now scrollable - AiDecisionOverlay: ~70 lines removed, now scrollable All dialogs now share identical scrolling behaviour (mask, wheel, bounds checking) via the single Dialog component.
The separate Dialog.ts component duplicated the existing overlay infrastructure. This commit removes it and integrates the scrollable dialog functionality directly into Overlay.ts as createScrollableOverlay(), keeping everything in one place. Changes: - Overlay.ts: Add createScrollableOverlay() factory + types. Updated module doc to highlight overlays are suitable for dialogs. - ParameterizedOverlay.ts: Add module doc noting that scrollable overlays are the right choice for dialog-style content. - Dialog.ts: Deleted (functionality merged into Overlay.ts). - Debug overlays (StateInspector, GameEventLog, AiDecision): Import from Overlay.ts instead of Dialog.ts. - ui/index.ts: Remove Dialog exports, add ScrollableOverlay exports.
ScrollableOverlay → OverlayDialog: - createScrollableOverlay → createOverlayDialog - ScrollableOverlayOptions → OverlayDialogOptions - ScrollableOverlayHandle → OverlayDialogHandle The key differentiator is that this is a managed dialog overlay (lifecycle, content container, close/refresh) versus a static config-driven overlay (ParameterizedOverlay). The scrolling is an implementation detail.
The scroll container was created at (contentX, contentY) but refresh() called scrollContainer.setY(-scrollY), which overrode the container's Y position. With scrollY = 0, setY(0) moved the container to y=0 — above the mask area at contentY — hiding all content. Fix: scrollContainer.y = contentY - scrollY (preserves contentY, only offsets by scroll amount).
… counting, add port-based orphan cleanup Replaced the fragile reference-counting pattern in dev-server-utils.ts with a straightforward start-stop-per-call lifecycle: - ensureDevServer() now kills any existing process on port 3000 before starting a fresh server (belt-and-suspenders via fuser/lsof). - killDevServer() unconditionally kills the child process and cleans up any remaining process on port 3000. - Lock file simplified to store only PID (no refCount). - Added killProcessOnPort() helper for cross-platform port-based process termination (fuser on Linux, lsof on macOS). - Signal handlers use the same port-based cleanup as fallback. - Updated tests for simplified API. - Updated docs/DEVELOPER.md troubleshooting sections.
…esolve cold-start timeout The e2e replay test (replay-main-street.e2e.test.ts) failed when run alongside the parallel unit test pool because Vite's cold compilation under CPU contention exceeded the 30s (and later 60s/120s) waitForGameBoot timeout. Root cause (two issues): 1. Signal handler overreach in dev-server-utils.ts: installDevServerCleanupHandlers() called killProcessOnPort(3000) in its SIGTERM/SIGINT/exit handlers. When other test workers finished (or timed out), their exit handlers killed ANY process on port 3000 — including the dev server spawned by the replay script's child process. 2. CPU contention: the replay e2e test shared the 'unit' Vitest project which runs all test files in parallel. The fresh Vite dev server's initial lazy compilation had to compete for CPU with hundreds of parallel unit tests. Fixes: - dev-server-utils.ts: Remove killProcessOnPort(3000) from signal handlers. Port-based cleanup is still performed by ensureDevServer() (before starting fresh) and killDevServer() (when our own child finishes) — these are the correct, scoped locations. - vite.config.ts: Move replay e2e tests to a dedicated 'replay-e2e' Vitest project with pool: 'forks' + singleFork: true. This provides clean OS process isolation with no leftover signal handlers or stale lock files, and uncontested CPU for Vite's cold compilation. - replay.ts: Add page.setDefaultTimeout + explicit page.goto timeout as belt-and-suspenders. Revert SCENE_READY_TIMEOUT to 30_000 (no longer needed). - test config: Increase runReplay() timeout to 180s for headroom. - docs: Add replay-e2e project to test project documentation. Refs: CG-0MS35J1FX005QYDV (AC6)
Implement five strategic improvements to the Lost Cities AI: 1. Opponent Card Denial (Block Play) - Bonus for playing numbered cards the opponent could use to continue their expedition, denying them the card. Applies when extending existing expeditions. 2. Optimal Investment Timing - Prefer playing investments before numbered cards in the same color. Bonus for early investments with numbered cards to follow. Strong penalty for late investments (4+ cards). 3. Opponent Expedition Blocking - New scoreBlockingPlay() function rewards playing cards that fill gaps in opponent expeditions, preventing them from scoring highly. 4. Endgame / Deck-Count Awareness - When draw pile < 10: avoid starting new expeditions without enough cards, reduce probability penalties, increase discard penalties for opponent-wanted cards. 5. Score-Aware Multi-Column Strategy - Prioritize completing existing columns when none are scored. More willing to start new columns when at least one column is already positive. All improvements maintain backward compatibility with existing GreedyStrategy interface. Tests: 57 tests pass (52 existing + 17 new)
…onEventCards CG-0MS0Z9XDF001TAD4: Tests for generalized duration-mitigation - Remove hardcoded dEvent.id === 'evt-flu-outbreak' guard so computeDurationWithClinicReduction applies to ALL DurationEventCards - Add non-flu DurationEventCard tests in turnflow.test.ts: - Clinic reduces duration by 2 - Medical Center reduces duration by 3 - No clinic leaves duration unchanged - All existing flu-outbreak integration tests still pass with identical behaviour Build: npm run build ✓ Tests: 260 test files, 4537 passed, 10 skipped ✓
…Gini, HHI, CI) - Implement median(), iqr(), gini(), hhi(), confidenceInterval() in scripts/balance/engine/statistics.ts - 41 unit tests covering edge cases (empty arrays, single-element, negative values, floating point) - All functions have typed signatures and JSDoc - IQR uses Moore & McCabe exclusive method - Gini uses standard formula returning 0 (equal) to (n-1)/n (max inequality) - HHI accepts raw counts (fromCounts=true) or proportional shares - confidenceInterval uses population variance with configurable z-score
… baseline module - Implement guardrail thresholds (PRD §3.3) with evaluateGuardrails() in scripts/balance/guards/thresholds.ts - Implement baseline capture/validation/loading in scripts/balance/engine/baseline.ts - Create barrel files: scripts/balance/engine/index.ts, scripts/balance/index.ts, scripts/balance/utils/statistics.ts - 21 unit tests for thresholds and baseline modules
- M1 (Pick Rate): computes purchases/market appearances, null when marketOffers absent - M2 (Win-Rate Delta): computes winRate(owned) - winRate(not owned), null when cardsOwned absent - M3 (Cost-to-Income Ratio): static computation from card CSV data, handles zero income (Infinity) - M4 (Synergy Utilization): actual/max synergy bonuses, null when incomeBreakdown absent - M5 (Upgrade Adoption): upgrades/parent purchases, null when cardsOwned absent - M6 (Event Impact Score): avg(coinDelta + repDelta*5), fallback to CSV static deltas - M7 (Survival Rate): wins(owned)/runs(owned), null when cardsOwned absent - 29 unit tests across all 7 metrics - Updated barrel file
- G1 (Win Rate × Difficulty): strategy/difficulty matrix entry - G2 (Score Distribution): median, mean, Q1, Q3, IQR, stdDev, min, max - G3 (Economy Health): avg coins/turn, bankruptcy rate, tightness index - G4 (Synergy Diversity): HHI of synergy type shares, null when finalGrid absent - G5 (Loss Mode Decomposition): bankruptcy/reputation/timeout shares - G6 (Card Usage Diversity): Gini coefficient from won-run appearances - G7 (Turn-by-Turn Snapshots): avg economy trajectory per turn - G8 (Trap Card Prevalence): cards with winRateDelta < -10% AND pickRate > 20% - 27 unit tests across all 8 metrics - Phase 1 dependent metrics (G3, G4, G6, G7, G8) degrade to null - Updated barrel file
…evaluation - compareMetrics(current, baseline, thresholds?) returns structured ComparisonReport - Per-metric comparison includes: current, baseline, delta, deltaPct, status - Guardrail evaluation using thresholds.ts severity levels (pass/flag/fail) - Zero baseline returns ±Infinity deltaPct - Empty inputs produce structured empty report - Threshold overrides supported via optional parameter - Output matches PRD §6.5 format: meta, summary, comparisons - 15 unit tests covering all edge cases - Updated barrel file
- Integration tests (11 tests) covering: statistics→global-metrics pipeline, statistics→card-metrics pipeline, card-metrics+global-metrics→comparison, full pipeline with synthetic Phase 1 data, Phase 1 data absent (graceful degradation), non-overlapping comparison, static metrics, CIs - API reference docs at docs/main-street/balance-analysis-api.md (all modules) - PRD updated with actual file paths, implementation status, deviations notes - 144 total balance tests passing
Rename checkPatronVisit to collectQualifyingPatrons, returning PatronTile[] instead of a single PatronTile | null. All qualifying patrons are now moved to the player's collection in the same turn. Changes: - TurnResult.patronVisit → patronVisits: PatronTile[] (array, possibly empty) - checkPatronVisit() → collectQualifyingPatrons() returns all qualifying patrons - finishTurn() accepts PatronTile[] parameter - discardTokens() passes empty array for patron visits - GameTranscript: FeudalismTurnRecord.patronVisit → patronVisits - Turn controller: handles array, animates first patron, shows combined toast - Animator: playCardAnimation accepts PatronTile[] array - New tests: 0, 1, 2, 3+ patron scenarios, partial qualification, dedup - All 4687 tests pass, build succeeds
Adds a '[ GitHub ]' link in the bottom-right corner of the game home page that opens https://github.com/TheWizardsCode/Tableau-Card-Engine in a new browser tab when clicked. - Clickable text label styled consistently with the existing UI (monospace font, muted green color #88ff88, alpha 0.6) - Positioned at bottom-right (GAME_W - 8, GAME_H - 12) with bottom-right anchor to avoid overlap with version label - Uses Phaser text.on('pointerdown') with window.open() to open URL Build: success Tests: 33/33 passing in GameSelectorScene.test.ts
…at logo Fixes two issues from review feedback: 1. Added setInteractive() to enable pointer events (missing before) 2. Replaced '[ GitHub ]' text label with the GitHub Octocat SVG icon loaded from an inline data URI in preload() 3. Added hidden alt text 'GitHub repository' for accessibility Build: success Tests: 35/35 passing in GameSelectorScene.test.ts
- Moved from bottom-right to top-right corner with 10px margin - SVG fill changed from #88ff88 to #ffffff (white octocat) - Icon size increased to 28x28 for better visibility at top - Removed alpha transparency (full opacity white on dark bg) - Changed origin from (0.5,0.5) to (1,0) for top-right anchoring - Removed unused VERSION_ALPHA import Tests: 36/36 passing
… multi-patron visibility) When multiple patrons qualify in a single turn: - Previously: only the first patron was animated; others were collected silently - Fixed: all qualifying patrons now get individual fly-in animations chained sequentially via new chainAllPatronAnimations() method Tests added: - Purchase qualifying two patrons simultaneously - Take-different with two pre-qualified patrons - Patron collection surviving token-over-limit discard flow - Confirms collectQualifyingPatrons returns all qualifying patrons
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.