Merge dev → main (automated)#599
Merged
Merged
Conversation
added 30 commits
June 20, 2026 17:20
Bug: restoreFromCheckpoint() created new FeudalismRenderer and
FeudalismAnimator instances (with new containers / Phaser game objects)
without destroying the originals, resulting in two complete sets of UI.
Fix: Follow the Beleaguered Castle pattern — mutate the existing session
in-place (Object.assign) and reuse the existing renderer and animator.
This avoids creating duplicate containers. refreshAll() already calls
removeAll(true) on every container before re-rendering, so existing
children are properly destroyed before new ones are created.
Changes:
- Replace with
- Remove creation of new FeudalismRenderer and FeudalismAnimator instances
- Remove calls to createContainers / createInstructions / createInfluenceDisplay
- Add clearMarketSelection() before refreshAll() to reset selection state
- Remove redundant second refreshAll() call (refreshAll is already called
by setPhase('player-turn') downstream)
- Add comprehensive JSDoc explaining the in-place mutation pattern
…rce leaks
Improvements:
1. Added port conflict detection (isPortInUse) to check if port 3000 is occupied before starting dev server
2. Added checkAndCleanupStaleDevServer() to detect and remove stale lock files from orphaned PIDs
3. Added installDevServerCleanupHandlers() - SIGTERM/SIGINT handlers and process.on('exit') to clean up tracked child processes and lock files on forced exit
4. Added trackChildProcess() to manage child process lifecycle for signal-based cleanup
5. Integrated isPortInUse check into ensureDevServer() with informative warnings about port conflicts
6. Updated killDevServer() to untrack children from the cleanup list after exit
7. Added dev-server-cleanup.test.ts with comprehensive tests for stale lock detection, port conflict handling, and tmp directory recovery
8. Updated docs/DEVELOPER.md troubleshooting section with process leak info
…st processes In killDevServer(), when refCount reaches 0 and child is null (server was already running, e.g. user started it with npm run dev), the code was killing the dev server by PID. This terminated the user's dev server along with test processes. Changed to only kill the dev server when child is non-null (i.e., we started it). When child is null, clean up the lock file but leave the existing server running on port 3000.
…localization system Replace hardcoded title/body string literals in UNIFIED_TUTORIAL_STEPS with i18n key references (titleKey/bodyKey). Create English locale bundle (tutorial-en.ts) containing all 13 steps' text. Add resolveTutorialStepText() helper for runtime resolution via t(). Changes: - TutorialFlow.ts: title/body -> titleKey/bodyKey in UnifiedTutorialStepDef - TutorialFlow.ts: add resolveTutorialStepText() and re-export tutorialKey - MainStreetTutorialHints.ts: register English locale at module load time - MainStreetTutorialHints.ts: use t(step.titleKey/bodyKey) for display - New: example-games/main-street/i18n/tutorial-en.ts (English locale bundle) - New: tests/main-street/tutorial-i18n.test.ts (16 i18n-specific tests) - Updated: tutorial-flow.test.ts (use titleKey/bodyKey + i18n text resolution) - Updated: tutorial-text-updates.test.ts (resolve via i18n t() function) - New: docs/main-street/tutorial-localization.md (how to edit/add translations) Acceptance criteria: 1. Tutorial step copy is no longer hardcoded strings in TutorialFlow.ts 2. Default English locale bundle registered at runtime 3. Tutorial behavior unchanged (same 13 steps, same controls) 4. Tests verify i18n resolution and key coverage 5. Documentation covers editing and adding translations
…th card artwork Root cause: DrawPileView called super() without passing countOffsetY, so it defaulted to 60px in PileView. The card is CARD_H=130px tall, centered at y + CARD_H/2, so the count text appeared at y + 125, only 5px from the card bottom edge at y + 130, causing overlap. Fix: Pass explicit countOffsetY: cardH/2 + 16 (=81px for CARD_H=130) to position the count text 16px below the card bottom edge. Also export DrawPileView for unit testing. Tests: Added lost-cities-draw-pile-view.test.ts with 3 tests verifying: - countOffsetY > CARD_H/2 - DrawPileView extends PileView - Count text positioned correctly in empty state
Moved countOffsetY from CARD_H/2 + 16 (81px) to CARD_H/2 + 5.5 (70.5px), reducing the downward offset by 50% (10.5px) as requested.
…verlay tests The help panel and settings panel close() methods use a 300ms tween animation, and removeInputBlocker() is called in the onComplete callback. The test was waiting only waitFrames(10) (~166ms at 60fps), which is too short for the animation to finish, causing a false assertion failure. When the assertion fails, vitest pretty-format crashes with "RangeError: Invalid string length" trying to format the massive Phaser Scene object in the error context, causing entire suites to fail as crashed suites. Fix: increased waitFrames after close() from 10 to 20 (~333ms) to ensure the 300ms tween completes before checking inputBlocker is null.
…tion buttons in Lost Cities Converts [ Next Round ] and [ New Match ] buttons from createOverlayButton (text-only, green-on-transparent) to createActionButton (filled rectangle background with stroke border and scaling hover effect), matching the existing Menu button style. Changes: - Re-export createActionButton from LostCitiesAdapter - showRoundSummary(): [ Next Round ] uses createActionButton with depth: 11 - showMatchSummary(): [ New Match ] uses createActionButton with depth: 11 - Updated browser test to find container wrapping action button for click - Removed unused collectFromSceneAndHud helper All 243 Lost Cities tests pass (6 browser + 237 unit/other).
…overlays Replace single padded-string text objects with individually positioned Phaser.GameObjects.Text objects per column in both showRoundSummary() and showMatchSummary(). - Add COL_LABEL_X_OFFSET, COL_P0_X_OFFSET, COL_P1_X_OFFSET constants - Header rows: separate 'Color'/'Round', 'You', 'AI' texts - Data rows: separate label (left-aligned, originX=0) + scores (right-aligned, originX=1) - Total/summary rows use same column layout - Match-end breakdown section similarly refactored Fixes misalignment caused by proportional font (Arial, sans-serif) with string padding (padEnd/padStart). Tests added: LostCitiesOverlayAlignment.browser.test.ts (3 new)
… helpers
- Create src/ui/ReducedMotion.ts with getEffectiveReducedMotion() utility
that checks SettingsStore preference (primary) and CSS media query (fallback)
- Add tests/ui/ReducedMotion.test.ts with 23 tests covering:
- getEffectiveReducedMotion() with settings panel preference, CSS media query,
both set (precedence), and neither set
- Placeholder tests for UI helpers accepting reducedMotion param
(placeCard, dealCard, discardCard, flipCard, moveGameObject)
- Placeholder tests for popTextOrIcon and sceneTransition SettingsStore checks
- Type contract verification for all UI helper option types
- Export getEffectiveReducedMotion from src/ui/index.ts barrel file
…helpers - Add reducedMotion?: boolean parameter to MoveGameObjectOptions, FlipCardOptions, PlaceCardOptions, DealCardOptions, DiscardCardOptions - When reducedMotion=true: skip tweens, snap to final state, fire callbacks synchronously - Update flipCard: instant texture swap + position when reducedMotion=true - Update moveGameObject: snap target to destX/destY when reducedMotion=true - Update placeCard, dealCard, discardCard: explicit param overrides CSS media query - Update popTextOrIcon and sceneTransition: use getEffectiveReducedMotion() to check both SettingsStore preference and CSS media query (removed private prefersReducedMotion) - All changes are backward-compatible (optional parameter)
- Create tests/golf/GolfAnimator.reducedMotion.test.ts with placeholder tests for reducedMotion-aware GolfAnimator (defines expected API contract) - Type contract verification for GolfAnimator reduced motion property
- Add reducedMotion property to GolfAnimator (defaults to false) - When reducedMotion=true: animateTurn skips all animations and calls onComplete immediately; showDrawnCard creates sprite at destination without tweens; animateDrawnCardToDiscard hides sprite and calls onComplete synchronously - GolfScene reads this.settingsPanel.reducedMotion and passes to animator - Add placeholder tests documenting the reduced motion contract
…LESCE4009A494: Implement reduced motion support in remaining games - The Mind: MindAnimator.reducedMotion skips animateCardTowardsPile, showPenaltyCards, showLevelCompleteText; scene passes settingsPanel value - Lost Cities: LostCitiesAnimator.reducedMotion skips phase1/phase2 animations; moveGameObject calls pass reducedMotion; scene passes settingsPanel value - Beleaguered Castle: BeleagueredCastleRenderer.reducedMotion skips dealTableauAnimated and snapBack; scene passes settingsPanel value - Feudalism: FeudalismAnimator.reducedMotion passed to all moveGameObject calls; scene passes settingsPanel value
- Add reducedMotion parameter to dealCard, placeCard, discardCard, flipCard, moveGameObject, popTextOrIcon, runSceneTransition option tables - Update accessibility section to describe 3-tier priority (explicit param, SettingsStore, CSS media query) - Document getEffectiveReducedMotion() utility - Add missing flipCard and moveGameObject to animations overview - Add new option tables for flipCard, moveGameObject, popTextOrIcon, runSceneTransition
Swapped the positions of 'Skip for Now' (now on the left) and 'Start Tutorial' (now on the right) in TutorialOfferModal.ts to match the consistent convention used in all other tutorial overlays (MainStreetTutorialHints.ts): left = dismiss/exit, right = proceed/continue. Also renamed variable names (startX/endX -> leftX/rightX) and updated comments for clarity. - Only TutorialOfferModal.ts modified - Button text, colors, hover colors, and callbacks unchanged
…age compliance - Updated all 13 tutorial step texts (T1-T13) in tutorial-en.ts for ~10-year-old reading level, under 50 words per body, 1-2 concepts per step, and plain-language principles (short sentences, common words, active voice) - Externalized TutorialOfferModal.ts (title, body, button labels) to i18n - Externalized MainStreetTutorialHints.ts overlay button labels to i18n - Added modalKey() and overlayKey() helpers in tutorial-en.ts - Updated docs/main-street/tutorial-localization.md with new keys and plain-language editorial guidelines - All existing tests pass without modification
… skipping - Update shouldShowTutorialOffer() to return false for 'skipped' status - Update tests to expect false for skipped state - Three tests updated: skipped-state offer, skip-action persistence, and legacy-bridge precedence
…urn, new cards Implement health-category clinic rework: - Add 'Health' synergy type to SynergyType union - Add reputationPerTurn (optional) to BusinessCard/CommunitySpaceCard - Add reputationBonus (optional, readonly) to UpgradeCard - Add reputationBonus (mutable accumulator) to BusinessCard/CommunitySpaceCard - Rework Clinic: Health synergy, baseIncome=0, reputationPerTurn=0.2 - Rework Medical Center upgrade: incomeBonus=0, reputationBonus=0.1 - New Private Clinic (cost 8, income 2, Health, upgradeable) - New Private Medical Center upgrade (cost 4, incomeBonus 2) - New Pharmacy (cost 6, income 1, Health, standalone) - Update synergyColor function for Health (teal/cyan 0x1ABC9C) - Add computeReputationPerTurn() to Adjacency module - Apply reputation per turn during income phase in applyIncome() - Accumulate reputationBonus in purchaseUpgrade - Register all Health cards in Tier 4 - Update SVG generator for Health synergy color - Regenerate card SVGs, manifests, and Monte Carlo baselines - Update all test assertions for new template counts/decks
… is enabled - Added syncTableauHandViews() call in dealTableauAnimated() reduced-motion branch, ensuring card sprites are created and interactivity is wired up before onDealComplete fires. - Also fixed a test race condition in dev-server-cleanup.test.ts that was deleting the entire tmp/ directory in beforeEach/afterEach, which raced with parallel-running replay e2e tests. The fix is a single-line addition to BeleagueredCastleRenderer.ts. The reduced-motion branch previously skipped sprite setup entirely, leaving the game board with no visible cards and no draggable interactivity.
…actory, decay logic, multiplier application, and barrel export - Add ActiveEffect interface with effectType, multiplier, turnsRemaining, sourceEventId, description - Implement createActiveEffect, decayActiveEffects, applyActiveEffectMultiplier, hasActiveEffectOfType - Add DecayResult type for decay operation output - Export ActiveEffect types and functions from core-engine barrel - Write comprehensive unit tests (18 tests) covering creation, decay, multiplier composition, type checking, and full lifecycle
…guard, and unit tests - Add DurationEventCard interface extending EventCard with duration, effectType, multiplier - Add isDurationEventCard type guard function - Update AnyCard union to include DurationEventCard - Write 7 unit tests covering type discrimination, guard behavior with null/undefined, and field structure - All 7 tests pass and TypeScript compilation succeeds
…serialization and migration - Add activeEffects: ActiveEffect[] to MainStreetState interface (defaults to []) - Add activeEffects to MainStreetSerializedState - Add activeEffects to setupMainStreetGame initial state - Add activeEffects to serializeMainStreetState and deserializeMainStreetState - Add migration rule for missing activeEffects field (defaults to []) - Import ActiveEffect type from core-engine
…r 4 unlock list - Add evt-flu-outbreak DurationEventCard to EVENT_TEMPLATES in MainStreetCards.ts - Add evt-flu-outbreak to TIER_4_NEW_CARD_IDS in MainStreetTiers.ts - Generated SVG card art (65 SVGs generated) - All tests pass (tier catalog coverage, card SVG coverage, card catalog baseline)
…tEngine - Modify resolveEvent() to detect DurationEventCard and create ActiveEffect instead of one-shot deltas - Add computeDurationWithClinicReduction() helper for Flu event duration reduction (Medical Center -3, Clinic -2, floor at 1) - Record active-effect onset in activity log and transcript - Add active-effect transcript event type to MainStreetTranscript - Write 9 unit tests covering DurationEventCard resolution, clinic scanning, duration reduction, and regular EventCard unaffected path - All tests pass
- Write 7 tests covering per-slot income multiplier, multiple effect composition, and turn-end decay - Tests are expected to fail until F6 implements the income integration and decay logic - Tests cover: single 0.8× effect, per-slot before-reputation application, no-active-effects baseline, two-effect composition (0.64×), decay at turn end, removal when expired, effect still applies for current turn before expiry
… and turn decay - Modify applyIncome() in MainStreetAdjacency.ts to apply active effect income-multiplier per-slot before reputation multiplier - Add decayActiveEffects call in processEndOfTurn() EndCheck phase - Log effect expiration to activity log - All 7 income-decay tests now pass - All existing tests pass
…ent lifecycle Integration tests: - Full flu lifecycle: draw → income reduction → decay → expiration → normal - Clinic reduces duration from 5 to 3 turns - Medical Center reduces duration from 5 to 2 turns - Activity log records flu onset and expiration Save/load tests: - Serialize/deserialize round-trip preserves activeEffects - Missing activeEffects in old saves defaults to [] Monte Carlo: - Basic sanity test verifying flu event doesn't break win rates - All 7 tests pass
…iveEffect system - docs/main-street/card-catalog.md: Added evt-flu-outbreak entry and duration event summary - docs/main-street/core-rules-and-mechanics.md: Added ActiveEffect system section (Section 9) covering data structure, turn flow, and flu example - docs/DEVELOPER.md: Added ActiveEffect system overview with API table and Main Street integration notes
…template - Fix meta-progression test counts: 64→65 cards, 62→63 unique IDs - Fix expanded-card-pool test counts: 17→18 templates, 51→54 deck cards - Fix expanded-card-pool positive incident ratio: exclude neutral duration-based events from calculation - Fix game-state test: EVENT_TEMPLATE_COUNT 17→18 - Update expanded-card-manifest.json with evt-flu-outbreak entry
added 28 commits
June 30, 2026 16:36
Add MainStreetIntegration.test.ts with 12 integration test cases: - Full game loop with purchase-to-hand, tableau placement, and income - IncomePhase net income calculation (base + synergy - staff costs) - Market cycling over multiple turns with mixed purchases - Staff card lifecycle (purchase → ongoing cost → layoff) - Layoff with fewer hand cards than slots to remove - Save/load migration round-trip for all multi-use fields - Old-format save compatibility (missing fields default to empty) - Deterministic RNG preservation after migration round-trip - Combined multi-system gameplay over 3 turns All 1504 tests pass, 2 skipped (needing I3 selling feature).
- Create docs/main-street/prd-multi-use-card-economy.md with complete technical specification covering hand management, synergy, placement/ sell, market cycling, staff cards, architecture, and test coverage - Update the-build-gdd.md with Appendix D documenting all new mechanics and removed physical-game mechanics - Update README.md to mention Multi-Use Card Economy features
Add MainStreetPlaceSell.test.ts with 26 test cases covering all 6 acceptance criteria: AC1: Place from hand to tableau at 80% cost (coins deducted) AC2: Sell from hand for 75% value (coins credited, card to discard) AC3: Sell from tableau for 75% value (coins credited, slot emptied) AC4: Insufficient coins blocks placement with error state AC5: Sold cards accumulate in discard pile AC6: Place/sell actions recorded in game transcript Tests follow the TDD specification pattern from the existing hand state tests (MainStreetHandState.test.ts): - Use feature detection flags (HAND_FEATURE_AVAILABLE, PLACE_SELL_API_AVAILABLE, PLACE_SELL_LEGALITY_AVAILABLE, CONSTANTS_AVAILABLE) so they compile and skip gracefully before the implementation items (I1, I3) are complete. - Access new state fields via (state as any) for type safety. - Guard all implementation-dependent tests with it.runIf().
Add place/sell functionality to MainStreet: - PLACE_COST_RATIO (0.8) and SELL_VALUE_RATIO (0.75) constants - placeFromHand(state, handIndex, slotIndex) — places card from hand to tableau at 80% cost, validates slot and coins - sellFromHand(state, handIndex) — sells card from hand for 75% value, credits coins, adds to discardPile - sellFromTableau(state, slotIndex) — sells tableau card for 75% value, frees slot, adds to discardPile - All operations log to activity log - Fix place/sell test: add missing slot occupation before test All 1528 tests pass, 4 skipped. Every child item of the Multi-Use Card Economy epic is now completed!
…entered Changes CASCADE_X from hardcoded 120 (far left) to GAME_W / 2 (640, matching the horizontal layout's HAND_CENTER_X). This ensures the vertical cascade stays anchored at the same central screen position as the horizontal layout, preventing the hand from jumping to the left when toggling layouts. Added GymHandPileLayoutToggle.test.ts with 7 tests verifying: - CASCADE_X is set to GAME_W / 2 - Vertical toggle uses setBaseX with CASCADE_X - Horizontal toggle uses setCenterX with HAND_CENTER_X - Layout direction is set correctly for both modes - Spacing slider is synced to CASCADE_SPACING on toggle
…icon with event name, increase duration to 1800ms, font size to 20px Changes: - GymAudioFeedbackScene: showPopText now displays '"'♪ eventName'"' instead of '"'eventName: sfxKey'"' (hides internal sound key debug info, shows user-friendly music note + event) - Duration increased from 400ms to 1800ms (normal) and from 100ms to 500ms (reduced motion) - Font size increased from 14px to 20px for readability - Added tests: popTextOrIcon longer duration test, GymAudioFeedback structural tests verifying music note, readable duration, and font size in showPopText
…FromCheckpoint
Bug: After resuming a checkpoint saved when it's the AI's turn,
restoreFromCheckpoint() unconditionally set the phase to 'player-turn',
allowing the human to interact (e.g., click reserved cards) even though
currentPlayerIndex pointed to the AI. This caused 'Card X not found in
market or reserved cards' errors.
Fix: Replace unconditional this.turnController.setPhase('player-turn')
with a check of session.players[session.currentPlayerIndex].isAI:
- If AI -> setPhase('ai-turn') + schedule executeAiTurn() with delay
- If human -> setPhase('player-turn') (unchanged behavior)
Also added ANIM_DURATION import from FeudalismConstants to use the
same timing (ANIM_DURATION + 200) as afterTurnComplete.
Tests: Added resume-turn-phase.test.ts with 13 tests verifying:
- Fresh game starts with currentPlayerIndex=0 (human turn)
- After human turn, checkpoint preserves AI as current player
- After AI turn, checkpoint preserves human as current player
- Multiple turn cycles correctly alternate player index
- Serialize/deserialize round-trips preserve currentPlayerIndex
- CheckpointManager round-trips preserve both turn states
…e, and zero income Extends the existing neutral income sound (added in 5592cef) with dedicated INCOME_POSITIVE and INCOME_NEGATIVE SFX keys, completing all three income states. Changes: - Add INCOME_POSITIVE ('sfx-income-positive') and INCOME_NEGATIVE ('sfx-income-negative') constants to SFX_KEYS in MainStreetConstants.ts - Add ToneForge factory mappings for all three income sounds in sfx-tf-mapping.ts - Update MainStreetAnimator to play INCOME_NEGATIVE for delta < 0 and keep INCOME_NEUTRAL for delta === 0 (positive delta already emits income-gained event) - Change income-gained event mapping from COIN_POP to INCOME_POSITIVE in MainStreetLifecycleManager (dedicated positive income sound, not generic coin-pop) - Add preload entries for INCOME_POSITIVE (coin-pop.wav) and INCOME_NEGATIVE (discard.wav) as WAV fallbacks, consistent with existing INCOME_NEUTRAL pattern - Expand tests: verify all three SFX keys are defined, mapped to distinct tf keys, and route correctly by income sign (positive/negative/zero)
… Spike scene The status line text was created with createHudText but the reference was not stored, so the text never updated when tint/blend modes changed. Changes: - Store statusLineText reference as a class property - Add updateStatusLine() method to refresh the displayed text - Call updateStatusLine() from cycleTint(), cycleBlendMode(), and resetTint() - Add browser integration tests for the shader spike scene All 6 new tests pass, verifying: - Initial status line shows correct defaults - Status line updates on tint cycle, blend cycle, and reset tint - Event log continues to work without regression - Combined tint+blend state is reflected correctly
…tes in Gym HUD scene Fixes two issues with panel status lines in GymHudComponentsScene: 1. SettingsPanel status line was not updating when panel toggled 2. HelpPanel status line was behind the open panel (x=60, panel covers 448px) Changes: - Store status line text object references as class properties - Add updateStatusLines() method to refresh text on panel state changes - Move HelpPanel status line from x=60 to x=460 (visible when panel is open) - Call updateStatusLines() in all button handlers (Open, Close, Toggle) - Add 8 new tests verifying status line initial display, updates on open/close/toggle for both panels, and HelpPanel visibility positioning
- Fixed action anchor Y position (0.175 → 0.27, pixelOverride 126 → 195) to prevent [ Toggle Fill ] overlapping the SLL Title Anchor. - Set Shell overlay to be ON by default (shellVisible = true). - Removed the Layout button and its associated logic. - Made the Profile button cycle through layout options (changes visual layout of elements on screen, not just description text). - Fixed overlay visualization: shows colour-coded dots at actual element positions (Title, Help, Action, Content) with correlated legend, instead of meaningless zone rect coordinates. - Updated tests to match new behaviour.
…ects
Bugs fixed:
1. Replaced setPipeline('Light2D') (Phaser 3 API) with setLighting(true) (Phaser 4 API) so sprites are affected by lights
2. Stored light reference from addLight() for proper toggle/move control
3. Fixed toggleLight() to actually enable/disable the LightsManager instead of just calling enable() repeatedly
4. Fixed moveLight() to use .x/.y properties directly instead of non-existent setPosition() method
5. Updated log messages to accurately reflect actual lighting state
6. Added comprehensive browser tests covering Canvas fallback, WebGL operation, toggle, and move functionality
- Added missing 'buy-business-to-hand' case to scoreAction() in MainStreetAiStrategy.ts (return 0 as fallback score) - Added missing 'staff' case to cardLabel() in MainStreetCards.ts (format: name + cost, matching upgrade pattern) - Added missing 'buy-business-to-hand' case to buildRationale() in MainStreetHint.ts (return descriptive string) These were the last 3 TypeScript errors blocking a clean 'npm run build'.
… updated Main Street layout Updated all 7 non-null tutorial highlight zones in main-street-tutorial.layout.json to match the current base layout and renderer positions: - hud: narrowed from 100% to 50% screen width (centered) - marketBusinessRow: extended width to match market bg box (to x=940) - streetGrid: moved up to y=337px (from 433px) matching new streetTop - endTurnButton: updated to match createActionButton position - incidentQueue: moved from left side to right column (x=960) - investmentsRow: extended width to match market bg box - helpButton: updated to match hint button position (left of end turn) Also updated test expectations and added layout sync warning comment.
…sue causing RangeError crash Root cause: Tests 4-20 used advanceFrames(5) and advanceFrames(10) waits (~83ms and ~167ms at 60fps) which are insufficient for the 300ms panel open/close animations in HelpPanel and SettingsPanel. When assertions on post-close input blocker state failed, Vitest's pretty-format tried to serialize the deeply-nested Phaser object graph with maxDepth: Infinity, causing a RangeError: Invalid string length. Fix: Increased all advanceFrames calls from 5 or 10 to 20 frames (~333ms), which exceeds the 300ms animation duration and ensures: - Input blockers are removed before assertions - Toggle animations complete before the next toggle - Open animations complete before close operations Changed file: tests/gym/GymHudComponentsScene.browser.test.ts
…id area instead of spanning full width The streetGrid highlight zone for the T4 'Place a Business' tutorial step was incorrectly spanning the full game width (w=1) and positioned too low on the y-axis (y=0.468056). Changes: - Set y=0.444444 to match the base street anchor, including the grid label area - Set w=0.73 so the highlight stops before the right sidebar column at x=0.75 - Updated expected zone bounds in test to compute values from proportional layout coordinates (320px y, 934px w at 1280x720) - Updated DEVELOPER.md streetGrid zone description from 'full-width' to 'stops before right column' All 34 tutorial layout resolution tests pass.
…Migration browser test failures Fixes: 1. GolfReplay.browser.test.ts: Updated stockSprite.visible assertion to match PileView behavior — when the pile is empty, the sprite remains visible but ghosted (reduced alpha), not hidden. Changed assertion to check visible=true and alpha <= 0.3 for both stock and discard sprites. 2. MindRenderer.disableGameInteraction: Added safety check for sprite.scene before calling disableInteractive(). When the game reaches game-over during AI-only play, some sprites may have been destroyed (e.g., during level transitions when renderHumanHand destroys old sprites and creates new ones). Without this check, disableInteractive() crashes with 'TypeError: Cannot read properties of undefined (reading 'sys')' because Phaser sets scene=null on destroyed game objects. These were pre-existing failures discovered-from CG-0MR2PHIEZ007TNDC.
…n tutorial T7 Root cause: processEndOfTurn() calls cycleMarketCards(), which discards all scenario-placed market cards and refills from the random deck. By T7, the explicit card placement from the TutorialScenario system is lost. Changes: - TutorialScenario.ts: Replace evt-grand-opening with evt-festival in STANDARD_TUTORIAL_SCENARIO investments row. Update coin budget comments. - tutorial-en.ts: T7 body text now references Local Festival. - TutorialFlow.ts: Update coin budget table and comments for cost. - MainStreetState.ts: Add skipMarketCycleOnEndTurn flag to prevent market cycling during the tutorial T6->T7 transition. - MainStreetEngine.ts: Guard cycleMarketCards() behind the flag. - MainStreetTurnController.ts: Set the flag when tutorial is active and current step is action 'end-turn' (T6). Reset after processing. - Test files: Update E2E and unit tests to reference Local Festival () instead of Grand Opening Sale ().
- Add CELEBRATE SFX key (sfx-challenge-complete) to MainStreetConstants - Add sfx-challenge-complete mapping to sfx-tf-mapping.ts (success-fanfare) - Update TurnResult to include newlyCompletedChallenges array - processEndOfTurn now captures and returns newly completed challenge IDs - Add animateCelebration() to MainStreetAnimator with particle burst, pop-text fallback, and reduced-motion support - Wire celebration triggers in MainStreetTurnController.endTurn() with staggered timing for multiple challenges - Preload celebration audio in MainStreetLifecycleManager - Add tests (challenge-celebration.test.ts) for newlyCompletedChallenges - Fix MainStreetOverlay.browser.test.ts mock to include new field Files changed: example-games/main-street/MainStreetEngine.ts example-games/main-street/scenes/MainStreetAnimator.ts example-games/main-street/scenes/MainStreetConstants.ts example-games/main-street/scenes/MainStreetLifecycleManager.ts example-games/main-street/scenes/MainStreetTurnController.ts example-games/main-street/sfx-tf-mapping.ts tests/main-street/MainStreetOverlay.browser.test.ts tests/main-street/challenge-celebration.test.ts
…ights.enable/disable
…tent heights instead of fixed LOG_LINE_H Root cause: logMaxScroll was computed as hiddenCount * LOG_LINE_H (18px), but word-wrapped entries can be taller than 18px. This caused the scroll max to be smaller than the actual content height, and entries at the bottom of the scroll window extended past the mask boundary. Fixes: 1. refreshLog(): Render all entries to compute logTotalContentH from actual text heights, then compute logMaxScroll = logTotalContentH - visibleH. Scroll is now applied by shifting the content container (setY) instead of re-rendering with a different startIdx. 2. applyLogScroll(): Updated to use the same container shift logic for consistency. 3. Added browser tests verifying scroll bounds use actual content heights, auto-scroll snaps to bottom, overflow entries are properly clamped on content height decrease, and word-wrap entries have larger scroll bounds than the fixed line-height estimate.
…eedyStrategy Adds countVisibleRanks() and computeColumnBonus() functions that enable the GreedyStrategy to consider visible instances of each card rank when evaluating column-building moves. When all 4 copies of a target rank are visible, the column bonus is reduced to zero (pursuit is futile). When fewer are visible, the bonus scales proportionally. - countVisibleRanks(): counts face-up grid cards + discard top, preserving the AI information boundary - computeColumnBonus(): returns a negative score adjustment for swaps that build toward a column match (2 matching + 1 unknown), scaled by remaining unknown copies of the target rank - chooseDrawSource(): enhanced to prefer discard when it helps build a column and unknown copies of the target rank remain - chooseMoveForCard(): accepts optional visibleRanks parameter to apply column weighting in move selection - 12 new tests covering: countVisibleRanks, computeColumnBonus (proportionality, all-visible edge case, discard-and-flip, non-matching), chooseMoveForCard integration, chooseDrawSource integration - All 168 existing golf tests continue to pass
…ntent scrolling off the top The initial logAutoScroll = true caused the first refreshLog() render to snap to the bottom (logScrollOffset = logMaxScroll), pushing the first entries above the mask. Changing to false shows the earliest entries first, and auto-scroll kicks in naturally when the user scrolls to the bottom.
… test Root cause: the 'all entries fit' branch in refreshLog() explicitly set logAutoScroll=true. After entry count grew to overflow, this re-enabled auto-scroll which snapped to bottom and pushed content above the mask. Fix: remove s.logAutoScroll = true from the 'all entries fit' branch. logAutoScroll is now only updated in the overflow branch via atBottom computation, matching the original (pre-container-shift) behavior. New test 'does not position content above the log display area when entries first overflow' validates: 1. Initial render (entries fit) keeps logScrollOffset=0 and logAutoScroll=false 2. After entries overflow, logScrollOffset stays 0, container is NOT shifted up 3. All text objects have non-negative local Y (no entries above the container origin), proving content starts at the correct position below the title bar
…m mask graphics Root cause analysis: In Phaser 4 RC7, GeometryMask.preRenderCanvas calls graphics.renderCanvas() directly to draw the clip path onto the canvas context. While GraphicsCanvasRenderer does not itself check the property, there may be edge cases in the scene graph traversal where an invisible graphics object is skipped during mask setup. The safest fix is to keep the mask graphics visible but use a transparent fill (alpha=0), so the clip path is reliably created by the mask pipeline while the normal render pipeline draws nothing visible. Changes: - MainStreetRenderer.ts: Removed setVisible(false) on logMaskGraphics; use fillStyle(0xffffff, 0) (transparent) instead of opaque white - MainStreetInputManager.ts: Updated updateLogMask() to use fillStyle(0xffffff, 0) for consistency - activity-log-rendering.browser.test.ts: Added tests verifying mask is applied to content container, coordinates are correct at scroll position 0, and container shifts up when scrolled to bottom All 10 activity-log-rendering browser tests pass.
… per-entry visibility Root cause: Phaser 4 RC7 GeometryMask does not reliably clip content outside the mask area, causing log entries shifted above the visible window (via container.setY shift) to render over the title bar and beyond the log panel. Fix: 1. Per-entry visibility safety net in refreshLog() and applyLogScroll(): After shifting the content container, iterate through all children and set visible=true only for those whose local Y falls within [scrollOffset, scrollOffset + visibleH). Entries outside this window have visible=false and are not rendered at all. 2. Fixed barBg (turn header background) rendering: use setPosition(0, yOff) instead of hardcoded fillRect(0, yOff, ...) so that Graphics.y correctly reflects the entry position for visibility checks. 3. Mask graphics: removed setVisible(false), use transparent fill (alpha=0) to avoid interfering with mask rendering. 4. Reverted logAutoScroll initialization to false (no auto-scroll on first render). 5. Removed spurious logAutoScroll=true from all-entries-fit branch. 12 browser tests pass.
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.