Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
fb9e861
CG-0MQMEH2IX0050WNR: Fix duplicate UI on Feudalism resume
Jun 20, 2026
196fb94
CG-0MQMA71Z70070KA7: Audit and fix test/development process and resou…
Jun 21, 2026
228008d
CG-0MQNUA5B4003OUOA: Preserve dev server on port 3000 when killing te…
Jun 21, 2026
7645022
CG-0MP415DUV0032SKK: Externalize Main Street tutorial copy into i18n …
Jun 21, 2026
16c91e8
CG-0MQK10H46004OVCC: Fix draw pile count in Lost Cities misaligned wi…
Jun 21, 2026
53896da
CG-0MQK10H46004OVCC: Adjust draw pile count offset - reduce by 50%
Jun 21, 2026
a5c95c6
CG-0MQ14RE9X006WIRK: Fix timing in Beleaguered Castle help/settings o…
Jun 21, 2026
1f5c54e
CG-0MQK1M0Y1009MY2D: Replace text-only overlay buttons with styled ac…
Jun 21, 2026
f83bb12
CG-0MQK18MBO0023YIG: Fix score table column alignment in Lost Cities …
Jun 22, 2026
f3d22bf
CG-0MQLEMHAU005D3JS: Add tests for core reduced motion utility and UI…
Jun 22, 2026
170b751
CG-0MQLESCC7009L7PO: Implement reduced motion support in all core UI …
Jun 22, 2026
a9a00aa
CG-0MQLEMKJ1001RK7W: Add Golf reduced motion tests (test-first contract)
Jun 22, 2026
59551d7
CG-0MQLESCD3002N5RT: Implement Golf reduced motion support
Jun 22, 2026
5def33a
CG-0MQLESCC5009PLXF, CG-0MQLESCC3005I9IP, CG-0MQLESCCS004J9V4, CG-0MQ…
Jun 22, 2026
b0a0b8f
CG-0MQLESCE5006JENM: Update documentation for reduced motion support
Jun 22, 2026
1c19e73
CG-0MQK3FL6F005DO2Y: Fix reversed buttons in tutorial offer modal
Jun 22, 2026
9e9157b
CG-0MQK3IYDT0024SWG: Review and improve tutorial text for plain-langu…
Jun 22, 2026
8c6f8da
CG-0MQK46XSL005F7MR: Skip tutorial permanently — never re-offer after…
Jun 23, 2026
647f828
CG-0MQK3SVEQ0035IMZ: Clinic rework - Health synergy, reputation per t…
Jun 23, 2026
2c90aaf
CG-0MQPT6ES00065MOI: Fix Beleaguered Castle crash when reduced motion…
Jun 23, 2026
af20fa6
CG-0MQN0TW7A0022IGH: Implement ActiveEffect system with core types, f…
Jun 23, 2026
462ab7f
CG-0MQN0TW5M000ADLL: Add DurationEventCard type, isDurationEventCard …
Jun 23, 2026
ca2c82f
CG-0MQN1BLHH0086LU8: Add activeEffects field to MainStreetState with …
Jun 23, 2026
888de28
CG-0MQN1BLH4002AH7R: Add Flu outbreak event card to card pool and Tie…
Jun 23, 2026
5b20b4c
CG-0MQN1BLHG004I83H: Implement duration event resolution in MainStree…
Jun 23, 2026
f732ec0
CG-0MQN0TWQU008MHFL: Write income modifier and decay tests (test-first)
Jun 23, 2026
74b4e8c
CG-0MQN1BLH2006J5AI: Implement income integration with active effects…
Jun 23, 2026
e7ca133
CG-0MQN1BLIP001FXOA: Add integration and Monte Carlo tests for Flu ev…
Jun 23, 2026
1e8594b
CG-0MQN1BLHV001JHU1: Update documentation for Flu event cards and Act…
Jun 23, 2026
ff9e14b
Update test expectations and manifest for new evt-flu-outbreak event …
Jun 23, 2026
bd5facf
CG-0MQK4150U0024TKR: Replace standalone menu buttons with Settings pa…
Jun 23, 2026
0e3d8da
CG-0MQK4150U0024TKR: Restyle header Menu button as proper action butt…
Jun 23, 2026
95b06c8
CG-0MQK4150U0024TKR: Auto-create common infrastructure in CardGameSce…
Jun 23, 2026
3114535
CG-0MQK45ACM0042YMG: Pawn Shop should be more negative - exclude from…
Jun 23, 2026
b38af96
CG-0MQK45ACM0042YMG: Pawn Shop fully excluded from synergy (receive +…
Jun 23, 2026
4c0085f
CG-0MQQTN4PT000SB3E: Remove upgrade choice modal, apply upgrades dire…
Jun 24, 2026
31d2124
CG-0MQK3SVEQ0035IMZ: Address audit gaps - fix test race and update docs
Jun 24, 2026
05fd5bb
CG-0MQK3SVEQ0035IMZ: Fix code quality - rename export-sushi-icons.js …
Jun 24, 2026
6da52d4
CG-0MQK49GXI000CFUH: Display win target score (x / y) in HUD and tooltip
Jun 24, 2026
9de501b
CG-0MQK4ADJ2007EV1S: Fix empty tooltip for Park (community-space) cards
Jun 24, 2026
5592cef
CG-0MQK4EGJ0001JN6N: Add neutral sound for zero-income turns
Jun 24, 2026
52715d7
CG-0MQK4H9180049050: Fix Activity Log content overflowing container
Jun 24, 2026
9dec22b
CG-0MQK4018M002SEWJ: Move stats panel button to lower-left to fix ove…
Jun 24, 2026
994adcf
CG-0MQSDK7OH006JWHC: Add UI indicator for active effects (Flu Outbreak)
Jun 24, 2026
f42a873
CG-0MQS4XMZV0099LZL: Fix pre-existing tutorial e2e test failures
Jun 24, 2026
6c12c62
CG-0MQK4TCLT0022T2A: Fix event deck exhaustion on turn 13
Jun 24, 2026
713a0b0
CG-0MQK4TCLT0022T2A: Fix event deck exhaustion - double unique events…
Jun 25, 2026
7f4e55b
CG-0MQSF0385003SXHV: Fix tutorial E2E resource exhaustion by splittin…
Jun 25, 2026
49c4227
CG-0MQK4K8D0004QQAW: Rename Discover button to Research for investmen…
Jun 25, 2026
02e1ab0
CG-0MQK4QB4A008MN40: Main Street layout adjustments
Jun 25, 2026
0e2964d
CG-0MQSF0385003SXHV: Fix audit gaps — add tutorial project config and…
Jun 25, 2026
5e00fcd
CG-0MQU3AREL0099R1I: Main Street refined layout - column-based right …
Jun 26, 2026
0ba4c3b
CG-0MQU3AREL0099R1I: Layout tweaks — right column at screen edge, sma…
Jun 26, 2026
039d7bf
CG-0MQSR0H7Z0049BAD: Replace seed-based tutorial setup with explicit …
Jun 26, 2026
b7a3e5e
CG-0MQK4V0Z2006P2EP: Fix Development row Discover button refreshing i…
Jun 26, 2026
758e6d3
CG-0MQSR0H7Z0049BAD: Update E2E test to expect scenario-based card ID…
Jun 27, 2026
7df965b
CG-0MQVJ4DIC009K8D8: Add scenario validation test for requiredCardId …
Jun 27, 2026
d7ecc99
CG-0MQVJ4H0I002WL01: Add tutorial setup path integration tests
Jun 27, 2026
4877062
CG-0MQWVAHJ8008P6XB: Remove 5 unused eslint-disable directives
Jun 27, 2026
c76eb70
CG-0MQK4X80F004MQA6: Fix tooltips on disabled buttons
Jun 27, 2026
e6c14c6
CG-0MQK52UBR006L9F5: Add per-turn income and reputation info to all M…
Jun 28, 2026
db9ec01
CG-0MQK55JM0000KU1T: Add synergy visual lines, synergy bonus in toolt…
Jun 28, 2026
f8fb7fb
CG-0MQK58K2F007P5P0: Rename Bookshop upgrade from Library to Reader's…
Jun 28, 2026
0e994c3
CG-0MQYB41P3007MVQO: Fix pre-existing browser test failures across Ma…
Jun 28, 2026
b5d965e
CG-0MQK5RSU5006KIH5: Fix Gym click-to-play discard interaction
Jun 28, 2026
6eca118
CG-0MQYG6MBK000SM4M: Fix empty discard pile not clickable in Gym Hand…
Jun 29, 2026
10dcb82
CG-0MQK603OE003Y8TJ: Fix Cancel Move to return card to original hand …
Jun 29, 2026
c1dcb15
CG-0MQYB41P3007MVQO: Fix remaining 3 pre-existing test failures (Help…
Jun 29, 2026
35ac995
CG-0MQYL2HJI008W4XD: Fix SettingsPanel defaulting to Easy when no loc…
Jun 29, 2026
e89f835
CG-0MQYL2HJI008W4XD: Restart game when difficulty changes in Settings…
Jun 29, 2026
21b249a
CG-0MQK6OWPR000K0LO: Hand positioning - move centering logic into Han…
Jun 30, 2026
dfc77ee
CG-0MQSFA6OC008EK7Z: Write tests for Player Hand Management & State
Jun 30, 2026
bbc7574
CG-0MQSFG2U3008P93M: Write tests for Hand Card Synergy Bonus System
Jun 30, 2026
4ed01ea
CG-0MQSFGA60005RNGZ: Write tests for Market Cycling System
Jun 30, 2026
652259a
CG-0MQSFGE540037N6S: Write tests for Staff Cards & Hand Capacity
Jun 30, 2026
0a9915a
CG-0MQRXNAUB009VFQP: Implement Player Hand Management & State
Jun 30, 2026
32e9779
CG-0MQRXO41A0023MFG: Implement Hand Card Synergy Bonus System
Jun 30, 2026
fac93a6
CG-0MQSFGOU30078033: Implement Market Cycling System
Jun 30, 2026
0ee6a64
CG-0MQRXO41D009DH26: Implement Staff Cards & Hand Capacity System
Jun 30, 2026
63a63aa
CG-0MQRXO42D0050ISQ: Write Integration Tests for Multi-Use Card Economy
Jun 30, 2026
7533f8e
CG-0MQRXRSCP004ANI6: Update documentation for Multi-Use Card Economy
Jun 30, 2026
de87841
CG-0MQSFG6US006H0XK: Write tests for Card Placement & Sell System
Jun 30, 2026
6b3f87f
CG-0MQSFGJXH000FE08: Implement Card Placement & Sell System
Jun 30, 2026
f2c1500
CG-0MQK61TRP007QRZO: Fix vertical hand layout positioning to remain c…
Jun 30, 2026
c196531
CG-0MQK6X48C005VOGG: Improve audio visual feedback — show music note …
Jun 30, 2026
1794075
CG-0MQZYDCMY007DHTI: Fix unconditional 'player-turn' phase in restore…
Jun 30, 2026
d252371
CG-0MQQHBHIF004E2BN: Add distinct income sounds for positive, negativ…
Jul 1, 2026
1252807
CG-0MQK71RB9009VSZI: Fix status line not updating in Shader and Blend…
Jul 1, 2026
050d4a5
CG-0MQK7IQE9003LH7X: Fix HelpPanel and SettingsPanel status line upda…
Jul 1, 2026
3469743
CG-0MQK7CSBZ004BAPS: Fix SLL Scene layout issues
Jul 2, 2026
cb9aef6
CG-0MQK73QQP005O4OQ: Fix Lighting Spike in Gym to produce visible eff…
Jul 2, 2026
86ef9f0
Fix TS2366 exhaustiveness errors in main-street files
Jul 2, 2026
e667f45
SA-0MQW8461Q001IHU9: Fix tutorial highlight zone coordinates to match…
Jul 2, 2026
36858f4
CG-0MR2PHIEZ007TNDC: Fix GymHudComponentsScene browser test timing is…
Jul 2, 2026
b15d91c
CG-0MR3RV3L20009FO7: Fix streetGrid highlight zone to match street gr…
Jul 2, 2026
f80b73b
CG-0MR47ZKRO004Z5DX + CG-0MR4841OF0055F51: Fix GolfReplay and TheMind…
Jul 3, 2026
594cdb1
CG-0MR481920006RAO5: Replace Grand Opening Sale with Local Festival i…
Jul 3, 2026
40c2553
CG-0MQS46G5U001CYUG: Add challenge celebration VFX and sound
Jul 3, 2026
9c048f7
CG-0MQK73QQP005O4OQ: Fix toggleLight to use setIntensity instead of l…
Jul 3, 2026
2b159e0
CG-0MR4XUJM9008GK6Z: Fix Activity Log scroll bounds to use actual con…
Jul 4, 2026
211602f
CG-0MQIOPOZH006ILUB: Add visibility-aware column weighting to Golf Gr…
Jul 4, 2026
0b642a6
CG-0MR4XUJM9008GK6Z: Fix initial logAutoScroll to false to prevent co…
Jul 4, 2026
e749829
CG-0MR4XUJM9008GK6Z: Fix remaining auto-scroll bug and add regression…
Jul 4, 2026
c089198
CG-0MR4XUJM9008GK6Z: Fix mask clipping - remove setVisible(false) fro…
Jul 5, 2026
e5ac05c
CG-0MR4XUJM9008GK6Z: Fix log entries appearing above scroll area with…
Jul 5, 2026
0b846ac
Merge origin/dev into main (automated)
Jul 5, 2026
c37ef49
Bump version to v0.1.3
Jul 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,4 @@ Worklog Specific Ignores
.opencode/tmp/

### End of Worklog Specific Ignores
.pi/
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ tableau-card-engine/
| Sushi Go! | `example-games/sushi-go/` | Card drafting game (human vs. AI). Pick and pass hands over 3 rounds, collect sets of sushi dishes, and score the most points |
| Feudalism | `example-games/feudalism/` | Engine-building card game (human vs. AI). Collect gem tokens, purchase development cards for bonuses, attract nobles, and reach 15 prestige to win. Checkpoint autosaves after each turn (human + AI) with startup recovery |
| Lost Cities | `example-games/lost-cities/` | Two-player expedition card game (human vs. AI). Bet on up to 5 colored expeditions across a 3-round match with investment multipliers, ascending-play rules, and cumulative scoring |
| Main Street | `example-games/main-street/` | Single-player tableau builder. Buy businesses/upgrades/events, place businesses on a 10-slot street rendered as a responsive 2x5 grid, and optimize score over 20 turns. Tutorial overlay zones are defined in a separate SLL layout file (`main-street-tutorial.layout.json`) composed with the base layout. |
| Main Street | `example-games/main-street/` | Single-player tableau builder. Buy businesses/upgrades/events, place businesses on a 10-slot street rendered as a responsive 2x5 grid, and optimize score over 20 turns. **Multi-Use Card Economy**: cards can be held in hand for synergy bonuses; staff cards expand hand capacity with ongoing costs. Market cycles each turn. Tutorial overlay zones are defined in a separate SLL layout file (`main-street-tutorial.layout.json`) composed with the base layout. |
| Scenario: Tutorial | `example-games/main-street/scenes/MainStreetTutorialScene.ts` | Guided introduction to Main Street. Non-interactive tutorial overlays walk through the market, street placement, synergies, events, and scoring. Easy difficulty, 25 turns. Accessible from the Game Selector. |

More games are planned: Coloretto.
Expand All @@ -96,7 +96,7 @@ When a Business card is upgraded (level > 0), the renderer applies visual overla
|---------|----------|-------------|
| **Level badge** | Top-right | Gold bold text showing "Lvl N" (e.g., "Lvl 2") |
| **Income display** | Bottom-center | Green bold text showing combined income (baseIncome + incomeBonus), e.g., "+8" |
| **Name overlay** | Top-center | White bold text with dark semi-transparent background showing the upgraded card name (e.g., "Library" instead of "Bookshop") |
| **Name overlay** | Top-center | White bold text with dark semi-transparent background showing the upgraded card name (e.g., "Reader's Café" instead of "Bookshop") |
| **Upgrade border** | Card perimeter | 3px golden stroke (`#ffaa22`) for visual distinction from base cards |

### Architecture
Expand Down
60 changes: 55 additions & 5 deletions docs/DEVELOPER.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,17 @@ MONTE_SEEDS=50 npm run monte-carlo
MONTE_SEEDS=200 MONTE_MIN_WIN_RATE=0.20 MONTE_MAX_WIN_RATE=0.80 npm test
```

Tests use [Vitest](https://vitest.dev/) configured inline in `vite.config.ts` with two test projects:
Tests use [Vitest](https://vitest.dev/) configured inline in `vite.config.ts` with three test projects:

| Project | Environment | File Pattern | Purpose |
|---------|-------------|-------------|---------|
| `unit` | Node.js | `tests/**/*.test.ts` | Logic, data, and integration tests |
| `browser` | Chromium (Playwright) | `tests/**/*.browser.test.ts` | Phaser UI and rendering tests |
| `browser` | Chromium (Playwright) | `tests/**/*.browser.test.ts` (excludes tutorial E2E) | Phaser UI and rendering tests |
| `tutorial` | Chromium (Playwright) | `tests/e2e/main-street-tutorial-e2e-*.browser.test.ts` | Main Street tutorial E2E tests (run separately to avoid GPU context exhaustion) |

Both projects run together via `npm test`. The browser project runs in headless Chromium using `@vitest/browser` with the Playwright provider.
All three projects run together via `npm test`. The browser and tutorial projects run in headless Chromium using `@vitest/browser` with the Playwright provider.

The tutorial E2E tests are split into 6 part files (1-6 tests per file) and run via `scripts/run-tutorial-tests.sh`, which spawns a separate Chromium instance for each part to avoid the Phaser 4 RC GPU/Canvas context exhaustion that occurs after ~8 game create/destroy cycles in a single browser process. The helper module at `tests/helpers/main-street-tutorial-e2e.ts` contains shared game lifecycle utilities (`bootGameWithTutorial`, `destroyGame` with CanvasPool drain) and click helpers for tutorial step advancement.

During Vitest runs, the dev-only transcript persistence middleware (`POST /api/transcripts`) is intentionally disabled even though Vitest browser mode uses an internal Vite server. This prevents file-system side effects and reduces harness noise/flakiness during test execution.

Expand Down Expand Up @@ -212,6 +215,7 @@ src/
│ ├── GameState.ts GameState<T>, createGameState (deprecated for setup — use SetupOptions)
│ ├── SetupOptions.ts BaseSetupOptions, MultiplayerSetupOptions, resolveSetupOptions
│ ├── SeededRng.ts createSeededRng — deterministic PRNG (LCG) for shuffles and AI
│ ├── ActiveEffect.ts Duration-based modifier system (create, decay, apply, query)
│ ├── CheckpointManager.ts Checkpoint save-and-resume abstraction (save, load, clear, checkAndResume)
│ ├── CheckpointResumeOverlay.ts Built-in default resume overlay component
│ ├── TranscriptRecorder.ts BaseTranscript interface, TranscriptRecorderBase<T> abstract base class
Expand Down Expand Up @@ -615,6 +619,48 @@ The `CheckpointManager` delegates all storage to `SaveLoadStore` (IndexedDB
with localStorage fallback). See `src/core-engine/CheckpointManager.ts` for
full API documentation.

## ActiveEffect System

The `ActiveEffect` module (`src/core-engine/ActiveEffect.ts`) provides a
duration-based modifier system that tracks ongoing effects over multiple turns.

### Core Types

- **`ActiveEffect`** – interface with `effectType`, `multiplier`, `turnsRemaining`,
`sourceEventId`, and `description`.
- **`DecayResult`** – result of a decay operation with `active`, `expired`, and
`effects` arrays.

### API Functions

All functions are exported from `@core-engine/index`:

| Function | Purpose |
|----------|---------|
| `createActiveEffect(type, mult, turns, sourceId, desc)` | Create a new effect |
| `decayActiveEffects(effects)` | Decrement all effects, return active/expired sets |
| `applyActiveEffectMultiplier(effects, type, baseValue)` | Apply matching multipliers (rounded) |
| `hasActiveEffectOfType(effects, type)` | Check if any effect of given type exists |

### Usage Pattern

Duration-based Event cards (e.g. `evt-flu-outbreak`) extend `EventCard` with
`duration`, `effectType`, and `multiplier` fields. The engine's `resolveEvent()`
function detects `DurationEventCard` instances via the `isDurationEventCard()`
type guard and creates an `ActiveEffect` instead of applying one-shot deltas.

Income-modifier effects are applied per-slot during `applyIncome()` _before_
the reputation coin multiplier. Effects decay at the end of each turn during
`EndCheck` in `processEndOfTurn()`.

### Main Street Integration

- `MainStreetState.activeEffects` stores the active effects array
- Serialization/deserialization includes `activeEffects` with migration for
old saves (missing field defaults to `[]`)
- Duration computation for `evt-flu-outbreak` scans the street grid for
Clinic/Medical Center cards

## Replay Tool

The replay tool (`scripts/replay.ts`) replays a fixture transcript through the game's Phaser scene in a headless browser, capturing per-turn screenshots. It is the foundation for thumbnail generation and visual regression testing.
Expand Down Expand Up @@ -1106,7 +1152,7 @@ this.applyUpgradeOverlays(cardContainer, biz, renderW, renderH);
```
┌─────────────────────────────────────────────────────────────────┐
│ Game State Update │
│ Player upgrades Bookshop → Library (level 1→2, income +3→+8) │
│ Player upgrades Bookshop → Reader's Café (level 1→2, income +3→+8) │
└──────────────────────────┬──────────────────────────────────────┘
Expand Down Expand Up @@ -1610,7 +1656,7 @@ The tutorial layout defines these zones (all use normalized coordinates with opt
|---------|-------------|-----------------|
| `hud` | HUD strip (top bar with coins, reputation, score) | Yes (full-width bounding box) |
| `marketBusinessRow` | Business card row in the market area | Yes |
| `streetGrid` | The 2×5 street grid for placing businesses | Yes (full-width) |
| `streetGrid` | The 2×5 street grid for placing businesses | Yes (stops before right column) |
| `endTurnButton` | End Turn action button area | Yes |
| `incidentQueue` | Scrollable incident cards queue | Yes |
| `investmentsRow` | Investment/upgrade card row | Yes |
Expand Down Expand Up @@ -1917,6 +1963,8 @@ wl close <id> --reason "..." --json # close when done
**Vite dev server won't start:**
- Check port 3000 is not already in use: `lsof -i :3000`
- Try `npm run dev -- --port 3001` for an alternate port
- **Stale lock file:** If port 3000 appears free but the dev server fails, remove any stale lock file: `rm -f tmp/dev-server-lock.json`
- **Orphaned Vite process:** If `lsof -i :3000` shows a Node.js process, kill it: `kill -9 $(lsof -t -i :3000)`

**TypeScript errors on build:**
- Run `npx tsc --noEmit` to see detailed errors
Expand All @@ -1932,6 +1980,7 @@ wl close <id> --reason "..." --json # close when done
- Check that `@vitest/browser` version matches `vitest` version
- Browser tests boot a real Phaser game and may take 8-10 seconds each
- If tests hang, check for unresolved game instances (ensure `afterEach` destroys the game)
- **Process/resource leak cleanup:** All browser tests should clean up Phaser.Game instances in `afterEach` using `game.destroy(true, false)` and remove the game container div. The dev server utilities (`scripts/dev-server-utils.ts`) include SIGTERM/SIGINT handlers to clean up orphaned Vite processes and stale lock files on forced exit.

**Large bundle warning:**
- The Phaser library is ~1.4 MB minified -- this is expected
Expand All @@ -1941,6 +1990,7 @@ wl close <id> --reason "..." --json # close when done
- The replay tool (`npm run replay`) and transcript export (`npm run transcripts:export`) auto-start the dev server if `localhost:3000` is not responding
- If auto-start fails, start the dev server manually: `npm run dev`
- Check port 3000 availability: `lsof -i :3000`
- **Port conflict detection:** The `ensureDevServer()` helper now checks for existing processes on port 3000 before starting, logs warnings for potential conflicts, and cleans up stale lock files automatically.

**Replay tool: Unsupported transcript version error:**
- The transcript schema includes a `version` field; the replay tool validates this and exits with a clear error if the version is unsupported
Expand Down
12 changes: 6 additions & 6 deletions docs/main-street/card-catalog-baseline.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{
"source": "Tier 1 baseline from example-games/main-street/MainStreetTiers.ts",
"capturedAt": "2026-05-12T00:15:46.437Z",
"capturedAt": "2026-06-23T09:24:24.962Z",
"perTier": {
"tier1": {
"business": 7,
"business": 6,
"event": 6,
"upgrade": 5,
"total": 18
"upgrade": 8,
"total": 20
}
},
"totals": {
"baselineTotal": 18,
"targetAtLeast": 36
"baselineTotal": 20,
"targetAtLeast": 40
}
}
37 changes: 25 additions & 12 deletions docs/main-street/card-catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,22 @@ This document lists every card template in the Main Street card pool, organised

| Family | Templates | Copies each | Total cards |
|----------|-----------|-------------|-------------|
| Business | 17 | 3 | 51 |
| Event | 17 | 3 | 51 |
| Upgrade | 25 | 2 | 50 |
| Business | 18 | 3 | 54 |
| Event | 18 | 3 | 54 |
| Upgrade | 27 | 2 | 54 |

**Synergy types:** Food, Culture, Commerce, Service (M2), Entertainment (M2)
**Synergy types:** Food, Culture, Commerce, Service (M2), Entertainment (M2), Health (M2)

## Expansion summary (baseline vs current)

| Snapshot | Business | Event | Upgrade | Total templates |
|---|---:|---:|---:|---:|
| Tier 1 baseline (`docs/main-street/card-catalog-baseline.json`) | 7 | 6 | 5 | 18 |
| Current catalog (`MainStreetCards.ts`) | 17 | 17 | 25 | 59 |
| Net increase | +10 | +11 | +20 | +41 |
| Current catalog (`MainStreetCards.ts`) | 18 | 18 | 27 | 63 |
| Net increase | +11 | +12 | +22 | +45 |

- 2x target from baseline: `>= 36` templates
- Current total: `59` templates (`3.28x` baseline)
- Current total: `63` templates (`3.50x` baseline)
- Non-baseline card IDs are tracked in `docs/main-street/expanded-card-manifest.json`

### Guidance: adding more cards safely
Expand Down Expand Up @@ -71,7 +71,14 @@ Park has been reclassified as a **Community Space** card (see below).
|----|------|------|--------|---------|--------------|-------------|-----------|
| `biz-laundromat` | Laundromat | 3 | 2 | Service | Laundromat | Self-serve laundry. +1/adj Service. | Budget Service entry point. |
| `biz-barbershop` | Barbershop | 3 | 2 | Service | Barbershop | Classic cuts. +1/adj Service. | Pairs with Laundromat for early Service cluster. |
| `biz-clinic` | Clinic | 5 | 3 | Service | Clinic | Walk-in medical care. +1/adj Service. | Premium Service; high cost/income mirrors Hardware Store. |

#### Health (new synergy type)

| ID | Name | Cost | Income | Synergy | Upgrade Path | Description | Rationale |
|----|------|------|--------|---------|--------------|-------------|-----------|
| `biz-clinic` | Clinic | 10 | 0 (rep +0.2/turn) | Health | Clinic | Walk-in medical care. Provides +0.2 rep/turn. | Non-profit community health provider; reputation instead of income. |
| `biz-private-clinic` | Private Clinic | 8 | 2 | Health | Private Clinic | Private medical practice. Gains +1 coin per adjacent Health business. | For-profit counterpart to Clinic; income-focused. |
| `biz-pharmacy` | Pharmacy | 6 | 1 | Health | — | Provides essential medications. Gains +1 coin per adjacent Health business. | Standalone Health card (no upgrade). |

#### Entertainment (new synergy type)

Expand Down Expand Up @@ -153,6 +160,7 @@ Events fall into two categories:
| `evt-construction` | Road Construction | Incident | 0 | All | -- | -1 | 0 | -1 coin to all biz. | Mild universal disruption. |
| `evt-viral-review` | Viral Review | Incident | 0 | All | -- | +2 | +1 | +2 coins, +1 rep from online fame. | Positive windfall; universal. |
| `evt-vandalism` | Vandalism | Incident | 0 | All | -- | -1 | -1 | -1 coin, -1 rep. | Dual-penalty universal disruption. |
| `evt-flu-outbreak` | Flu Outbreak | Incident | 0 | All | -- | 0 | 0 | 80% income for 5 turns. Duration reduced by Clinic/Medical Center. | Duration-based modifier (see ActiveEffect system). |

### Event Balance Summary

Expand All @@ -164,6 +172,9 @@ Events fall into two categories:
| M1 Incident (positive) | 1 | 0.0 | +2.0 |
| M2 Incident (negative) | 5 | -1.4 | -0.4 |
| M2 Incident (positive) | 3 | +1.33 | +1.0 |
| M2 Incident (duration) | 1 | 0.0 | 0.0 |

> Duration-based incidents (e.g. `evt-flu-outbreak`) apply an ActiveEffect instead of a one-shot delta. Their impact is listed as 0 coin/rep delta because the effect is applied over multiple turns via an income multiplier.

The M2 incident pool is more balanced than M1: 5 negative vs. 3 positive incidents (compared to M1's 3 negative vs. 1 positive). This reduces the punishing feel while maintaining strategic tension.

Expand All @@ -179,7 +190,7 @@ Each Upgrade targets a specific Business by name. Applying an upgrade increments
|----|------|--------|------|---------|--------|-------------|-----------|
| `upg-patisserie` | Upgrade to Patisserie | Bakery | 4 | +1 | +1 | Bakery -> Patisserie. | Classic upgrade; income + range. |
| `upg-bistro` | Upgrade to Bistro | Diner | 4 | +1 | +1 | Diner -> Bistro. | Matches Patisserie in cost/power. |
| `upg-library` | Upgrade to Library | Bookshop | 3 | +1 | +0 | Bookshop -> Library. | Cheaper; income only, no range. |
| `upg-readers-cafe` | Upgrade to Reader's Café | Bookshop | 3 | +1 | +0 | Bookshop -> Reader's Café (+0.1 rep/turn). | Cheaper; income only, no range; reputation bonus. |

### M2 Standard Upgrade Templates (14)

Expand All @@ -199,7 +210,8 @@ Each Upgrade targets a specific Business by name. Applying an upgrade increments
| `upg-museum` | Upgrade to Museum | Art Gallery | 4 | +1 | +1 | Art Gallery -> Museum. | Premium bridge upgrade. |
| `upg-resort-spa` | Upgrade to Resort Spa | Day Spa | 5 | +2 | +1 | Day Spa -> Resort Spa. | Tied with IMAX for highest cost/power. |
| `upg-garden-center` | Upgrade to Garden Center | Florist | 3 | +1 | +1 | Florist -> Garden Center. | Budget bridge upgrade with range. |
| `upg-medical-center` | Upgrade to Medical Center | Clinic | 5 | +2 | +1 | Clinic -> Medical Center. | Premium Service upgrade. |
| `upg-medical-center` | Upgrade to Medical Center | Clinic | 5 | 0 (rep +0.1/turn) | +1 | Clinic -> Medical Center. Provides +0.1 rep/turn. | Reputation bonus upgrade; no income. |
| `upg-private-medical-center` | Upgrade to Private Medical Center | Private Clinic | 4 | +2 | 0 | Private Clinic -> Private Medical Center. | Income-focused upgrade; no range or reputation. |

### M2 Branching Upgrade Templates (4)

Expand Down Expand Up @@ -228,7 +240,7 @@ Multi-level upgrades require the business to already be at Level 1 (`requiredLev
| Cost | Count | Cards |
|------|-------|-------|
| 2 | 1 | Gourmet Truck |
| 3 | 9 | Library (Bookshop upgrade), Garden, Vintage Shop, Dry Cleaners, Salon, Roastery, Garden Center, Bread Factory, Fast Food |
| 3 | 9 | Reader's Café (Bookshop upgrade), Garden, Vintage Shop, Dry Cleaners, Salon, Roastery, Garden Center, Bread Factory, Fast Food |
| 4 | 8 | Patisserie, Bistro, Designer Store, Gaming Lounge, Museum, Drive-In Theater, Wellness Center, Community Hub |
| 5 | 6 | Home Improvement, IMAX Theater, Resort Spa, Medical Center, Grand Bakehouse, Restaurant |
| 6 | 2 | Multiplex, Luxury Retreat |
Expand All @@ -248,8 +260,9 @@ M2 introduces 5 bridge cards that belong to two synergy types simultaneously. Th
| Food | 2 (Bakery, Diner) | 2 (Cafe, Food Truck) | 4 |
| Culture | 1 (Bookshop) | 3 (Cafe, Art Gallery, Florist) | 4 (plus Park as Community Space) |
| Commerce | 3 (Hardware, Pawn Shop, Boutique) | 1 (Florist) | 4 |
| Service | 3 (Laundromat, Barbershop, Clinic) | 1 (Day Spa) | 4 |
| Service | 2 (Laundromat, Barbershop) | 1 (Day Spa) | 3 |
| Entertainment | 2 (Arcade, Cinema) | 3 (Food Truck, Art Gallery, Day Spa) | 5 |
| Health | 3 (Clinic, Private Clinic, Pharmacy) | 0 | 3 |

Culture and Entertainment have the most bridge-card representation, making them easiest to chain synergies with. Commerce and Service rely more on dedicated single-type cards.

Expand Down
Loading
Loading