dreamboard 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-CNNJAXXO.js → chunk-3I26YUTP.js} +4 -3
- package/dist/chunk-3I26YUTP.js.map +1 -0
- package/dist/{embedded-harness-I7VDED34.js → embedded-harness-TJZ5LB64.js} +2 -2
- package/dist/index.js +212 -110
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/dreamboard/SKILL.md +4 -29
- package/skills/dreamboard/references/adversarial-testing.md +1 -1
- package/skills/dreamboard/references/building-your-first-game.md +49 -0
- package/dist/chunk-CNNJAXXO.js.map +0 -1
- /package/dist/{embedded-harness-I7VDED34.js.map → embedded-harness-TJZ5LB64.js.map} +0 -0
|
@@ -1932,10 +1932,11 @@ import path from "path";
|
|
|
1932
1932
|
|
|
1933
1933
|
// src/generated/skill-content.generated.ts
|
|
1934
1934
|
var SKILL_ASSET_FILES = {
|
|
1935
|
-
"references/adversarial-testing.md": "# Adversarial Testing\n\nAdversarial testing uses scenario files to intentionally stress game rules, turn gating, and edge conditions.\n\nUse this guide with `dreamboard run --scenario <file>`.\n\nThis file is about adversarial coverage strategy, not JSON schema.\n\n- For the JSON step format itself, see [scenario-format.md](scenario-format.md).\n- For TypeScript regression tests, see [test-harness.md](test-harness.md).\n\n## Goals\n\n1. Validate that illegal or out-of-sequence actions are rejected.\n2. Validate that legal actions still work after edge-state pressure.\n3. Catch flaky behavior by running with deterministic seeds.\n\n## Core Workflow\n\n1. Start from a known state:\n\n```bash\ndreamboard run --new-session --seed 1337 --players <playerCount>\n```\n\n2. Inspect `.dreamboard/run/latest-your-turn.json` to identify:\n\n - Current controllable player(s)\n - Available actions and parameter shapes\n - Turn/phase context\n\n3. Write a focused scenario file that targets one adversarial behavior.\n4. Execute the scenario:\n\n```bash\ndreamboard run --players <playerCount> --scenario scenario-for-<case>.json\n```\n\n5. Validate outcomes using:\n - CLI exit result (`scenario_completed` vs `scenario_rejected`)\n - `.dreamboard/run/events.ndjson`\n - `.dreamboard/run/last-run-summary.json`\n\nFor deeper inspection, extract specific fields from events:\n\n```bash\nnode .agents/skills/dreamboard/scripts/events-extract.mjs --type ACTION_REJECTED --field message.reason\nnode .agents/skills/dreamboard/scripts/events-extract.mjs --type YOUR_TURN --field message.availableActions --limit 1\n```\n\n## Progression Strategy\n\n1. Start simple first:\n - Begin with 1-step or 2-step scenarios.\n - Use a single player (`player-1`) and one expected behavior per file.\n - Confirm your action names and parameter shapes are correct before scaling up.\n2. Then move to complex simulations:\n - Create multi-step scenarios that chain several turns.\n - Include multiple players across steps (for example `player-1`, `player-2`, `player-3`).\n - Combine normal and adversarial actions in the same flow to test resilience after state transitions.\n3. Create scenario that fully tests a round (e.g. all players played their hand, or all players placed their workers)\n4. Ensure AUTO phase transitions are correct.\n5. Keep deterministic inputs (`--new-session --seed 1337`) while increasing complexity so failures stay reproducible.\n\nBuild scenario files using the contract in [scenario-format.md](scenario-format.md), then use the patterns below to decide what to test.\n\n## Adversarial Patterns\n\nCreate one scenario per pattern so failures are easy to triage.\n\n1. Invalid parameters: missing required input, invalid enum, or out-of-range values.\n2. Wrong player: action submitted by a non-active/non-controllable player.\n3. Order violation: execute actions in a sequence that should be illegal.\n4. Resource exhaustion: repeat actions until resources/cards/slots are depleted.\n5. Duplicate or replay behavior: attempt the same step repeatedly in one turn.\n6. Boundary transitions: move through end-of-round/end-of-game boundaries and attempt one extra action.\n\n## Practical Conventions\n\n1. Keep each scenario small and single-purpose.\n2. Name files by expected behavior, for example:\n - `scenario-reject-invalid-params.json`\n - `scenario-reject-wrong-player.json`\n - `scenario-endgame-boundary.json`\n3. Prefer `--new-session --seed 1337` while authoring/debugging to avoid random drift.\n4. If turn context is missing, `dreamboard run --scenario ...` will wait for the first `YOUR_TURN` automatically.\n5. API scenarios are strict: if a step gets `ACTION_REJECTED`, the run stops with `scenario_rejected`.\n6. Use `events-extract.
|
|
1935
|
+
"references/adversarial-testing.md": "# Adversarial Testing\n\nAdversarial testing uses scenario files to intentionally stress game rules, turn gating, and edge conditions.\n\nUse this guide with `dreamboard run --scenario <file>`.\n\nThis file is about adversarial coverage strategy, not JSON schema.\n\n- For the JSON step format itself, see [scenario-format.md](scenario-format.md).\n- For TypeScript regression tests, see [test-harness.md](test-harness.md).\n\n## Goals\n\n1. Validate that illegal or out-of-sequence actions are rejected.\n2. Validate that legal actions still work after edge-state pressure.\n3. Catch flaky behavior by running with deterministic seeds.\n\n## Core Workflow\n\n1. Start from a known state:\n\n```bash\ndreamboard run --new-session --seed 1337 --players <playerCount>\n```\n\n2. Inspect `.dreamboard/run/latest-your-turn.json` to identify:\n\n - Current controllable player(s)\n - Available actions and parameter shapes\n - Turn/phase context\n\n3. Write a focused scenario file that targets one adversarial behavior.\n4. Execute the scenario:\n\n```bash\ndreamboard run --players <playerCount> --scenario scenario-for-<case>.json\n```\n\n5. Validate outcomes using:\n - CLI exit result (`scenario_completed` vs `scenario_rejected`)\n - `.dreamboard/run/events.ndjson`\n - `.dreamboard/run/last-run-summary.json`\n\nFor deeper inspection, extract specific fields from events:\n\n```bash\nnode .agents/skills/dreamboard/scripts/events-extract.mjs --type ACTION_REJECTED --field message.reason\nnode .agents/skills/dreamboard/scripts/events-extract.mjs --type YOUR_TURN --field message.availableActions --limit 1\n```\n\n## Progression Strategy\n\n1. Start simple first:\n - Begin with 1-step or 2-step scenarios.\n - Use a single player (`player-1`) and one expected behavior per file.\n - Confirm your action names and parameter shapes are correct before scaling up.\n2. Then move to complex simulations:\n - Create multi-step scenarios that chain several turns.\n - Include multiple players across steps (for example `player-1`, `player-2`, `player-3`).\n - Combine normal and adversarial actions in the same flow to test resilience after state transitions.\n3. Create scenario that fully tests a round (e.g. all players played their hand, or all players placed their workers)\n4. Ensure AUTO phase transitions are correct.\n5. Keep deterministic inputs (`--new-session --seed 1337`) while increasing complexity so failures stay reproducible.\n\nBuild scenario files using the contract in [scenario-format.md](scenario-format.md), then use the patterns below to decide what to test.\n\n## Adversarial Patterns\n\nCreate one scenario per pattern so failures are easy to triage.\n\n1. Invalid parameters: missing required input, invalid enum, or out-of-range values.\n2. Wrong player: action submitted by a non-active/non-controllable player.\n3. Order violation: execute actions in a sequence that should be illegal.\n4. Resource exhaustion: repeat actions until resources/cards/slots are depleted.\n5. Duplicate or replay behavior: attempt the same step repeatedly in one turn.\n6. Boundary transitions: move through end-of-round/end-of-game boundaries and attempt one extra action.\n\n## Practical Conventions\n\n1. Keep each scenario small and single-purpose.\n2. Name files by expected behavior, for example:\n - `scenario-reject-invalid-params.json`\n - `scenario-reject-wrong-player.json`\n - `scenario-endgame-boundary.json`\n3. Prefer `--new-session --seed 1337` while authoring/debugging to avoid random drift.\n4. If turn context is missing, `dreamboard run --scenario ...` will wait for the first `YOUR_TURN` automatically.\n5. API scenarios are strict: if a step gets `ACTION_REJECTED`, the run stops with `scenario_rejected`.\n6. Use `events-extract.mjs` to quickly inspect rejection reasons, available actions, and player transitions before editing scenarios.\n\n### Error: `Card is not in your hand`\n\nWhen this rejection appears, verify the target player's hand state across turns before and after each scenario step.\n\n1. Extract rejection events first:\n\n```bash\nnode .agents/skills/dreamboard/scripts/events-extract.mjs --type ACTION_REJECTED --field message.reason\n```\n\n2. Inspect hand snapshots from turn events for the target player (example: `player-2`):\n\n```bash\nnode .agents/skills/dreamboard/scripts/events-extract.mjs --type YOUR_TURN --player player-2 --field message.gameState.hands\n```\n\n3. Compare entries by `index` in output to see how the player's hand changes between turns.\n4. Confirm the `cardId` in your scenario step exists in the target player's hand at the turn where the action is submitted.\n5. If the card disappears earlier than expected, inspect preceding steps for draws/discards/transfers that changed ownership.\n\n## Optional UI Driver\n\nUse `--scenario-driver ui` only for browser input-path coverage (click targets, interaction wiring).\nKeep most adversarial rule tests on the API driver for reliability and speed.\n",
|
|
1936
1936
|
"references/all-players-tracking.md": '# ALL_PLAYERS Phase - Tracking Who Has Acted\n\nThe engine automatically tracks action progress in `ALL_PLAYERS` (`MultiplePlayer`) phases through `ctx.phase`. Do not implement manual KV tracking for this.\n\n## Built-in phase tracking API\n\nUse `ctx.phase` in `validateAction` and `checkCompletion`:\n\n- `ctx.phase.hasPlayerActed(playerId)` - has this player already submitted an action?\n- `ctx.phase.haveAllExpectedPlayersActed()` - are all currently expected players done?\n- `ctx.phase.getPlayersStillWaiting()` - which players are still pending?\n- `ctx.phase.getExpectedPlayers()` - who is expected to act this phase?\n- `ctx.phase.getPlayersWhoActed()` - who has already acted?\n\n## Recommended pattern\n\n```typescript\nimport type { MultiplePlayerPhaseDefinition } from "../sdk/phaseHandlers";\nimport { createMultiplePlayerPhase } from "../sdk/phaseHandlers";\nimport { validationSuccess, validationError } from "../sdk/validation.js";\n\ntype PhaseAction = "revealCard";\n\nconst phaseDefinition = {\n onEnter(ctx) {\n const { state, apis } = ctx;\n apis.gameApi.setActivePlayers(state.player.getOrder());\n // No manual tracking setup required.\n },\n\n validateAction(ctx, playerId, actionType, parameters) {\n if (ctx.phase.hasPlayerActed(playerId)) {\n return validationError("already-acted", "You already acted this phase");\n }\n return validationSuccess();\n },\n\n onPlayerAction(ctx, playerId, actionType, parameters) {\n const { apis } = ctx;\n // ... process action ...\n // Engine tracking updates automatically after accepted actions.\n },\n\n checkCompletion(ctx) {\n if (ctx.phase.haveAllExpectedPlayersActed()) {\n return "nextPhase";\n }\n return null;\n },\n\n onComplete(ctx) {\n // Optional cleanup only. No action-tracking cleanup required.\n },\n\n getUIArgs(ctx, playerId) {\n return {\n waitingOn: ctx.phase.getPlayersStillWaiting(),\n };\n },\n} satisfies MultiplePlayerPhaseDefinition<"revealCards", PhaseAction>;\n\nexport const phase = createMultiplePlayerPhase(phaseDefinition);\n```\n\n## If UI needs progress data\n\n`ctx.phase` is available in app logic. For UI, pass derived values via `getUIArgs()` (for example `waitingOn`), or write explicit UI-facing values via global/player state APIs.\n\n## Checklist for ALL_PLAYERS phases\n\n1. Call `apis.gameApi.setActivePlayers(state.player.getOrder())` in `onEnter`.\n2. Block duplicate actions with `ctx.phase.hasPlayerActed(playerId)` in `validateAction`.\n3. Process each action in `onPlayerAction` without manual tracking writes.\n4. Transition from `checkCompletion` when `ctx.phase.haveAllExpectedPlayersActed()` is true.\n5. Use `ctx.phase.getPlayersStillWaiting()` / `getPlayersWhoActed()` for logs or UI args as needed.\n',
|
|
1937
1937
|
"references/api-reference.md": '# State & API Reference\n\nUse this page as a lookup while writing `app/phases/*.ts`.\n\nThis is a reference-first document, not a modeling guide. For schema design, use [manifest-authoring.md](manifest-authoring.md). For hand-vs-deck decisions, use [hands-vs-decks.md](hands-vs-decks.md).\n\n## Read APIs (`ctx.state.*`)\n\n### `state.player`\n\n| Method | Returns | Description |\n| -------------------------------------- | ------------- | ------------------------------------- |\n| `.getOrder()` | `PlayerId[]` | All players in turn order |\n| `.getCurrentIds()` | `PlayerId[]` | Currently active player IDs |\n| `.get(playerId)` | `Player` | Player data: `{ id, name, score }` |\n| `.getState(playerId)` | `PlayerState` | Player variables from manifest schema |\n| `.getHand(playerId, handId)` | `CardId[]` | Cards in a specific hand |\n| `.getAllHands(playerId)` | `CardId[]` | All cards across all hands |\n| `.isInHand(playerId, cardId, handId?)` | `boolean` | Check if card is in hand |\n| `.isActive(playerId)` | `boolean` | Is player currently active |\n\n### `state.deck`\n\n| Method | Returns | Description |\n| --------------------- | ---------------- | ---------------------- |\n| `.getCards(deckId)` | `CardId[]` | Cards in a shared deck |\n| `.getTopCard(deckId)` | `CardId \\| null` | Top card of deck |\n\n### `state.card`\n\n| Method | Returns | Description |\n| -------------------------------- | ------------------ | ---------------------------------------------------- |\n| `.get(cardId)` | `Card` | Full card data |\n| `.getProperties(cardId)` | Typed properties | Card-specific props (rank, suit, etc.) |\n| `.getLocation(cardId)` | `Location` | Where the card is (InHand, InDeck, InZone, Detached) |\n| `.getPlayedBy(cardId)` | `PlayerId \\| null` | Who played this card to a deck/zone |\n| `.getOwner(cardId)` | `PlayerId \\| null` | Card owner |\n| `.isVisibleTo(cardId, playerId)` | `boolean` | Visibility check |\n| `.getOwnedBy(playerId)` | `CardId[]` | All cards owned by player |\n\n### `state.game`\n\n| Method | Returns | Description |\n| -------------------- | ------------- | ------------------------------------- |\n| `.getGlobalState()` | `GlobalState` | Global variables from manifest schema |\n| `.getCurrentState()` | `StateName` | Current state machine state |\n\n## Mutation APIs (`ctx.apis.*`)\n\n### `apis.cardApi`\n\n| Method | Description |\n| --------------------------------------------------------------------------- | ------------------------------ |\n| `.moveCardsFromHandToDeck(playerId, handId, cardIds, deckId)` | Hand \u2192 Deck |\n| `.moveCardsFromHandToHand(fromPlayer, fromHand, toPlayer, toHand, cardIds)` | Hand \u2192 Hand |\n| `.moveCardsToPlayer(cardIds, toPlayerId, handId)` | Any \u2192 Hand |\n| `.flip(deckId, cardId)` | Flip card face up/down |\n| `.detachCard(cardId)` | Remove card from all locations |\n| `.transferOwnership(cardId, toPlayer)` | Change card owner |\n\n#### Card movement semantics\n\n- `moveCardsFromHandToHand(fromPlayer, fromHand, toPlayer, toHand, cardIds)` is **additive** at destination.\n- Destination cards are preserved; moved cards are added to the destination hand.\n- This API does **not** replace/overwrite the destination hand.\n- For pass/rotate mechanics where each player should receive exactly one other player\'s hand, avoid in-place cyclic moves. Snapshot source hands first, then move via a temporary location (or other two-phase transfer pattern).\n\n### `apis.deckApi`\n\n| Method | Description |\n| ------------------------------------------------------------- | ------------------------- |\n| `.moveCardsFromDeckToPlayer(deckId, playerId, handId, count)` | Deck \u2192 Hand (deal) |\n| `.moveCardsFromDeckToDeck(fromDeckId, toDeckId)` | Deck \u2192 Deck |\n| `.shuffle(deckId)` | Shuffle a deck |\n| `.addCards(deckId, cardIds)` | Add cards to a deck |\n| `.removeCard(deckId, cardId)` | Remove a card from a deck |\n\n### `apis.gameApi`\n\n| Method | Description |\n| ---------------------------------- | ---------------------------------------------- |\n| `.setActivePlayers(playerIds)` | Set who can act (use in ALL_PLAYERS `onEnter`) |\n| `.setNextPlayer(playerId)` | Set single active player |\n| `.advanceTurn()` | Move to next player in order |\n| `.declareWinner(playerId, reason)` | Declare game winner |\n| `.endGame()` | End the game |\n\n### `apis.globalStateApi`\n\n| Method | Description |\n| ------------------------------------- | ---------------------------- |\n| `.setGlobalState(newState)` | Replace all global variables |\n| `.setPlayerState(playerId, newState)` | Replace a player\'s variables |\n\n### `apis.kvApi`\n\nInternal-only key-value store. **UI cannot access these values.**\n\n| Method | Returns | Description |\n| ------------------ | ---------------- | ----------------------------------- |\n| `.set(key, value)` | `KvSetResult` | Store a JSON value |\n| `.get(key)` | `KvGetResult` | Read a value (`.success`, `.value`) |\n| `.delete(key)` | `KvDeleteResult` | Remove a key |\n| `.has(key)` | `boolean` | Check existence |\n| `.keys()` | `string[]` | List all keys |\n\n### `apis.resourceApi`\n\n| Method | Description |\n| ------------------------- | -------------------------- |\n| `.add(playerId, cost)` | Give resources to player |\n| `.deduct(playerId, cost)` | Take resources from player |\n\n### `apis.dieApi`\n\n| Method | Description |\n| -------------------------- | ------------------------- |\n| `.roll(dieId)` | Roll a die |\n| `.setValue(dieId, value?)` | Set die to specific value |\n\n## Typed KV Store\n\nUse `createTypedKv` from `sdk/stateApi.js` for type-safe KV access:\n\n```typescript\nimport { createTypedKv } from "../sdk/stateApi.js";\n\ninterface MyKv {\n playersActed: PlayerId[];\n roundData: { scores: number[] };\n}\n\nconst kv = createTypedKv<MyKv>(apis.kvApi);\nkv.set("playersActed", ["player-1"]); // Type-checked key and value\nconst acted = kv.get("playersActed"); // PlayerId[] | null\nkv.has("playersActed"); // boolean\nkv.delete("playersActed"); // boolean (existed)\n```\n\n## Card `playedBy` Tracking\n\nWhen cards move from a Hand to a Deck via `moveCardsFromHandToDeck()`, the engine automatically records `playedBy` on each card:\n\n```typescript\n// Move card from player\'s hand to a shared deck\napis.cardApi.moveCardsFromHandToDeck(\n playerId,\n "main-hand",\n [cardId],\n "play-area",\n);\n\n// Later, check who played a specific card\nconst whoPlayed = state.card.getPlayedBy(cardId); // PlayerId | null\n```\n\n## Location Type\n\nCards have a location discriminated union:\n\n```typescript\ntype Location =\n | { type: "Detached" }\n | {\n type: "InDeck";\n deckId: DeckId;\n playedBy: PlayerId | null;\n position: number | null;\n }\n | {\n type: "InHand";\n handId: HandId;\n playerId: PlayerId;\n position: number | null;\n }\n | {\n type: "InZone";\n zoneId: string;\n playedBy: PlayerId | null;\n position: number | null;\n };\n```\n\n## Validation Helpers\n\n```typescript\nimport { validationSuccess, validationError } from "../sdk/validation.js";\n\n// Valid action\nreturn validationSuccess();\n\n// Invalid action \u2014 errorCode should be kebab-case\nreturn validationError(\n "must-play-valid-combination",\n "You must play a valid card combination",\n);\n```\n',
|
|
1938
1938
|
"references/app-best-practices.md": '# App Best Practices\n\nUse this guide when implementing `app/phases/*.ts`.\n\nThis page is about phase logic and engine APIs. If you are still deciding the game model itself, fix `rule.md` or `manifest.json` first.\n\n## Before Writing Code\n\n1. Read `shared/manifest.ts` so you know the actual generated IDs and types.\n2. Read every file in `app/phases/` to understand the current flow.\n3. Review the generated SDK types in `app/sdk/`.\n\nIf the card model is still unclear, stop and revisit [hands-vs-decks.md](hands-vs-decks.md) and [manifest-authoring.md](manifest-authoring.md).\n\n## Core Principles\n\n1. **The app is authoritative**\n All gameplay mutations must go through `ctx.apis.*`. Do not assume the UI moved cards or tracked state correctly.\n2. **Prefer mutation results inside the current transaction**\n Values returned by mutation APIs are safer than immediately rereading `ctx.state.*`, which may still reflect the previous snapshot.\n3. **Use the right active-player API**\n Use `setActivePlayers()` for `ALL_PLAYERS` phases and `setNextPlayer()` for `SINGLE_PLAYER` phases.\n4. **All card movement goes through move APIs**\n Never hand-edit card locations in your own state.\n5. **Use generated IDs exactly**\n Deck IDs, hand IDs, resource IDs, board IDs, and action names should come from `shared/manifest.ts`.\n6. **KV is internal-only**\n `kvApi` is for app logic, not UI data. Pass UI-facing data through `getUIArgs()` or stored state.\n7. **Keep player-visible outcomes around long enough to render**\n If an `AUTO` phase resolves a trick, round, or reveal, preserve that result until the next player-facing phase can show it.\n\n## Validation\n\n- Implement `validateAction` for `SINGLE_PLAYER` and `ALL_PLAYERS` phases.\n- Return `validationSuccess()` for valid actions.\n- Return `validationError(code, message)` for invalid actions.\n- Keep error codes stable and kebab-case, for example `must-play-valid-combination`.\n\n## UI Args Contract\n\n- Define UI arg interfaces in `shared/ui-args.ts`.\n- Implement `getUIArgs(ctx, playerId)` for each player-facing phase.\n- Send only the data the UI actually needs.\n- Prefer computed values over forcing the UI to recompute them.\n\nExample:\n\n- better: `canPlayCard: boolean`\n- worse: raw state fragments that force the UI to rebuild game rules\n\n## Safety Habits\n\n- Check array length before indexing.\n- Use generated guards from `../generated/guards` for typed ID checks.\n- Avoid `any`.\n- Log compact payloads with `ctx.logger`, not giant arrays or full state dumps.\n\n## Common Patterns\n\n### Typed KV store\n\n```typescript\nimport { createTypedKv } from "../sdk/stateApi.js";\n\ninterface PhaseKv {\n playersActed: PlayerId[];\n roundNumber: number;\n}\n\nconst kv = createTypedKv<PhaseKv>(apis.kvApi);\nkv.set("playersActed", []);\nconst acted = kv.get("playersActed"); // PlayerId[] | null\n```\n\n### Card `playedBy` tracking\n\n```typescript\napis.cardApi.moveCardsFromHandToDeck(\n playerId,\n "main-hand",\n [cardId],\n "play-area",\n);\n\nconst whoPlayed = state.card.getPlayedBy(cardId); // PlayerId | null\n```\n\n### Turn advancement\n\n```typescript\nonAfterAction(ctx, playerId, actionType) {\n ctx.apis.gameApi.advanceTurn();\n},\n```\n\n### ALL_PLAYERS progress tracking\n\nUse the engine-managed helpers in [all-players-tracking.md](all-players-tracking.md) instead of building your own action-tracking store.\n',
|
|
1939
|
+
"references/building-your-first-game.md": '# Building Your First Game\n\nUse this as the default end-to-end workflow for a new Dreamboard game. The goal is not just "something renders"; the goal is a complete, playable game with rules, app logic, UI, and tests all aligned.\n\n## Core Workflow\n\n1. Create a game: `dreamboard new <slug> --description "<<short description>>"`\n2. Author `rule.md` and `manifest.json`\n Quality bar: define the full game loop up front, including setup, turn structure, win/loss conditions, scoring, tie behavior, hidden/public information, and every player action the app must support. `manifest.json` should reflect the real player counts, entities, zones, and controls needed by the rules, not a partial guess.\n Refer to\n - [references/rule-authoring.md](rule-authoring.md),\n - [references/manifest-authoring.md](manifest-authoring.md)\n3. Run `dreamboard update` after every manifest or rule change\n Quality bar: do this immediately after each rule or manifest edit so generated scaffold, types, and framework assumptions stay in sync. Do not keep coding against stale generated files.\n4. Implement rules in `app/phases/*.ts` and UI data in `shared/ui-args.ts`\n Quality bar: every gameplay phase must be implemented end to end. Cover all rule branches, all legal player actions, all transitions, all round-resolution logic, and all game-end paths. `shared/ui-args.ts` must provide enough data for the UI to render every playable state without guessing or reconstructing hidden game logic in React.\n Refer to\n - [references/phase-handlers.md](phase-handlers.md),\n - [references/api-reference.md](api-reference.md),\n - [references/hands-vs-decks.md](hands-vs-decks.md),\n - [references/all-players-tracking.md](all-players-tracking.md),\n - [references/app-best-practices.md](app-best-practices.md)\n5. Implement UI in `ui/App.tsx` and `ui/components/*`\n Quality bar: the entire game must be playable through the UI, including setup, core turns, confirmations, resolution states, errors and disabled states, and end-of-game feedback. Follow the styling requirements in [ui-style-guide.md](ui-style-guide.md). Avoid half-finished interfaces such as raw debug JSON, missing controls, placeholder labels, or flows that require reading logs to understand what to do next.\n Refer to\n - [references/ui-best-practices.md](ui-best-practices.md)\n6. Typecheck from the game root: `bun install && bun run typecheck`\n Quality bar: typecheck must pass cleanly from the game root with no ignored errors and no dead code left behind from iteration.\n7. Push compiled changes: `dreamboard push`\n Quality bar: the pushed build should reflect the same rules and UI you validated locally, with no pending local-only fixes or known desync between authored files and compiled output.\n8. Generate deterministic bases for testing: `dreamboard test generate`\n Quality bar: regenerate bases whenever initial setup, compiled behavior, or game identity changes. The generated bases should be stable enough that regressions are easy to spot.\n9. Run regression scenarios: `dreamboard test run`\n Quality bar: cover the happy path, invalid actions, boundary conditions, phase transitions, simultaneous or multi-player interactions where relevant, and every game-ending outcome. All scenario statuses should be green.\n Refer to\n - [references/test-harness.md](test-harness.md)\n10. Validate the runtime behavior and live UI with `dreamboard run --scenario <scenario_file>` and `dreamboard run --screenshot`\n Quality bar: if Playwright is installed locally, screenshot verification is required. Confirm the UI looks complete and usable in real runtime states, including the initial view, an active turn, resolution moments, and the end-game state. Fix any broken layout, missing affordances, or styling drift before considering the game done.\n Refer to\n - [references/adversarial-testing.md](adversarial-testing.md)\n\nDo not treat UI, rules, and tests as separate finish-later tracks. A game is only complete when the authored rules, app logic, UI flow, pushed build, and regression coverage all agree on the same behavior.\n\n## What "Done" Means\n\n- App and logic are complete: all phases are implemented, all rule branches are covered, and no required gameplay path is left as a TODO or inferred only in the UI.\n- UI is complete: the full game is playable without relying on logs, debug panels, or manual backend intervention.\n- Styling is complete: the game follows [ui-style-guide.md](ui-style-guide.md) rather than shipping a generic or half-finished interface.\n- Verification is complete: typecheck passes, regression scenarios are green, and runtime screenshot verification has been performed when Playwright is available.\n',
|
|
1939
1940
|
"references/hands-vs-decks.md": '# Hands vs Decks\n\nUse this guide when you are deciding how to model a card container in `manifest.json`.\n\nDreamboard separates three ideas that are easy to blur together in tabletop rules:\n\n- `cardSets`: what cards exist\n- `decks`: shared card containers\n- `playerHandDefinitions`: per-player card containers\n\nFor the full manifest schema, see [manifest-authoring.md](manifest-authoring.md).\n\n## Quick Decision Rule\n\n1. If you are defining the cards themselves, use a `cardSet`.\n2. If the whole table shares one container, use a `deck`.\n3. If each player gets their own copy of the container, use a `playerHandDefinition`.\n\n## Canonical Meanings\n\n| Concept | Manifest key | Runtime ID | Use it for |\n| -------- | ----------------------- | ------------- | --------------------------------- |\n| Card set | `cardSets` | none directly | Card content and card schema |\n| Hand | `playerHandDefinitions` | `HandId` | Cards each player owns separately |\n| Deck | `decks` | `DeckId` | Shared piles or shared card areas |\n\n## Naming Rule\n\nUse the Dreamboard meaning, not the tabletop nickname.\n\nIf a real-world game says each player has a "deck", but each player owns a separate pile, model it as a hand.\n\nExamples:\n\n- a player\'s draw pile in War is a hand\n- a player\'s scored pile is a hand\n- the shared draw pile in Poker is a deck\n- the shared discard pile is a deck\n- a shared trick area is a deck\n\n## Common Mistakes\n\n- Do not use a deck just because cards are face-down.\n- Do not use a deck for something every player owns separately.\n- Do not use a hand for a communal area just because cards stay there for a while.\n\nVisibility is separate from ownership:\n\n- if each player has their own container, it is still a hand\n- set `visibility: "public"` if other players should be able to see that hand\n\n## Example\n\n```json\n{\n "cardSets": [\n {\n "type": "preset",\n "id": "standard_52_deck",\n "name": "Standard 52-Card Deck"\n }\n ],\n "decks": [\n {\n "id": "main-deck",\n "name": "Main Deck",\n "cardSetId": "standard_52_deck"\n },\n {\n "id": "battle-zone",\n "name": "Battle Zone",\n "cardSetId": "standard_52_deck"\n }\n ],\n "playerHandDefinitions": [\n {\n "id": "player-deck",\n "displayName": "Your Deck",\n "visibility": "ownerOnly",\n "cardSetIds": ["standard_52_deck"]\n },\n {\n "id": "won-pile",\n "displayName": "Won Cards",\n "visibility": "public",\n "cardSetIds": ["standard_52_deck"]\n }\n ]\n}\n```\n\nIn that example:\n\n- `standard_52_deck` defines the cards\n- `main-deck` and `battle-zone` are shared decks\n- `player-deck` and `won-pile` are per-player hands\n\n## Related Move APIs\n\n```typescript\n// Hand -> Deck\napis.cardApi.moveCardsFromHandToDeck(playerId, handId, cardIds, deckId);\n\n// Deck -> Hand\napis.deckApi.moveCardsFromDeckToPlayer(deckId, playerId, handId, count);\n\n// Hand -> Hand\napis.cardApi.moveCardsFromHandToHand(\n fromPlayer,\n fromHand,\n toPlayer,\n toHand,\n cardIds,\n);\n\n// Deck -> Deck\napis.deckApi.moveCardsFromDeckToDeck(fromDeckId, toDeckId);\n```\n\n## Rule Of Thumb\n\n- shared container: `deck`\n- per-player container: `playerHandDefinition`\n- card blueprint: `cardSet`\n',
|
|
1940
1941
|
"references/manifest-authoring.md": '# Manifest Authoring Guide\n\nUse this guide when editing `manifest.json`.\n\n`manifest.json` describes the runtime shape of the game:\n\n- who can play\n- what cards, resources, dice, and boards exist\n- what actions players can submit\n- what phases and stored state the engine needs\n\nKeep the full game explanation in `rule.md`. Use the manifest to turn that explanation into typed runtime data.\n\nAfter editing the manifest, run `dreamboard update` to regenerate scaffolded files such as `shared/manifest.ts` and `app/phases/`.\n\nIf you are still deciding whether something is a hand, a deck, or a card set, read [hands-vs-decks.md](hands-vs-decks.md) alongside this guide.\n\n## Supported Top-Level Shape\n\n```json\n{\n "playerConfig": { ... },\n "cardSets": [ ... ],\n "playerHandDefinitions": [ ... ],\n "decks": [ ... ],\n "dice": [ ... ],\n "resources": [ ... ],\n "boardDefinitions": [ ... ],\n "availableActions": [ ... ],\n "stateMachine": { ... },\n "variableSchema": { ... }\n}\n```\n\n| Field | Required | Purpose |\n| ----------------------- | -------- | -------------------------------------------- |\n| `playerConfig` | \u2705 | Supported player counts |\n| `cardSets` | \u2705 | Card blueprints and card property schemas |\n| `playerHandDefinitions` | \u2705 | Per-player card containers |\n| `decks` | \u274C | Shared card containers |\n| `dice` | \u274C | Shared dice |\n| `resources` | \u274C | Typed resource economy |\n| `boardDefinitions` | \u274C | Structured boards such as hex, square, track |\n| `availableActions` | \u2705 | Player-submittable actions |\n| `stateMachine` | \u2705 | Game phases and transitions |\n| `variableSchema` | \u2705 | Global and per-player stored state |\n\n## Authoring Rules\n\n- Only use documented schema fields. If a key is not described in this guide, do not invent it in `manifest.json`.\n- Optional list fields should be omitted or set to `[]`. Do not use `null`.\n- Keep the manifest about runtime structure. Put presentation choices in UI code or `shared/ui-args.ts`.\n- There is no top-level `version` field in the manifest schema.\n- A deck entry is structural data only. Use `id`, `name`, and `cardSetId`.\n\n## Recommended Authoring Order\n\n1. Start from `rule.md`.\n2. Define the game\'s containers and content: players, card sets, hands, decks, resources, dice, and boards.\n3. Define the actions players can submit.\n4. Define the phase flow in `stateMachine`.\n5. Add only the stored state that cannot be derived from the rest of the game state.\n\n## 1. `playerConfig`\n\nUse `playerConfig` to declare the supported player counts.\n\n```json\n{\n "playerConfig": {\n "minPlayers": 2,\n "maxPlayers": 4,\n "optimalPlayers": 4\n }\n}\n```\n\n## 2. `cardSets`\n\n`cardSets` define the cards that exist in the game. They do not describe where those cards live during play.\n\nThere are two card set styles:\n\n- `preset` for built-in sets such as a standard 52-card deck\n- `manual` for authored card content and custom card schemas\n\n### Preset card set\n\n```json\n{\n "cardSets": [\n {\n "type": "preset",\n "id": "standard_52_deck",\n "name": "Standard 52-Card Deck"\n }\n ]\n}\n```\n\n### Manual card set\n\n```json\n{\n "type": "manual",\n "id": "resource-cards",\n "name": "Resource Cards",\n "cardSchema": {\n "properties": {\n "value": { "type": "integer", "description": "Point value" },\n "category": { "type": "string", "description": "Resource category" }\n }\n },\n "cards": [\n {\n "type": "lumber",\n "name": "Lumber",\n "count": 4,\n "properties": { "value": "1", "category": "wood" }\n }\n ]\n}\n```\n\nCard authoring reminders:\n\n- `type` is the manifest-level card type.\n- `count` is the number of copies to create.\n- runtime card IDs are derived from `type` and `count` (`lumber`, `lumber-2`, `lumber-3`, ...)\n- `properties` must match `cardSchema`\n- `name` and `text` are worth keeping because they help scaffolding, debugging, and agent reasoning\n\n## 3. `playerHandDefinitions`\n\nEach `playerHandDefinition` creates one container per player.\n\nUse a hand for anything each player owns separately, even if the tabletop rules call it a "deck", "reserve", or "scored pile".\n\n```json\n{\n "playerHandDefinitions": [\n {\n "id": "main-hand",\n "displayName": "Hand",\n "visibility": "ownerOnly",\n "maxCards": 7,\n "cardSetIds": ["standard_52_deck"]\n }\n ]\n}\n```\n\n| Field | Required | Purpose |\n| ------------- | -------- | ----------------------------------------------- |\n| `id` | \u2705 | Stable hand ID (`HandId`) |\n| `displayName` | \u2705 | Human-readable name |\n| `visibility` | \u274C | `ownerOnly`, `public`, or `hidden` |\n| `maxCards` | \u274C | Maximum allowed card count |\n| `minCards` | \u274C | Minimum required card count |\n| `cardSetIds` | \u274C | Allowed card sets; omit for any compatible card |\n| `description` | \u274C | Explanation for authors and tools |\n\n## 4. `decks`\n\n`decks` are shared card containers.\n\nUse a deck for any card location the whole table shares: a draw pile, discard pile, market row, trick area, or communal play area.\n\n```json\n{\n "decks": [\n {\n "id": "draw-pile",\n "name": "Draw Pile",\n "cardSetId": "standard_52_deck"\n },\n {\n "id": "discard-pile",\n "name": "Discard Pile",\n "cardSetId": "standard_52_deck"\n }\n ]\n}\n```\n\n| Field | Required | Purpose |\n| ----------- | -------- | -------------------------------------- |\n| `id` | \u2705 | Stable deck ID (`DeckId`) |\n| `name` | \u2705 | Human-readable name |\n| `cardSetId` | \u2705 | Card set used by this shared container |\n\n## 5. `dice`, `resources`, and `boardDefinitions`\n\nAdd these sections only if the game needs them.\n\n- `dice` define shared dice such as `d6` or custom dice.\n- `resources` define typed player economies such as coins, food, or energy.\n- `boardDefinitions` define structured boards such as hex maps, square grids, tracks, or networks.\n\nExample:\n\n```json\n{\n "dice": [{ "id": "d6", "name": "Six-Sided Die", "sides": 6 }],\n "resources": [{ "id": "coins", "displayName": "Coins" }]\n}\n```\n\n## 6. `availableActions`\n\nActions describe what players may submit to the engine.\n\nThink of actions as structured intent, not UI clicks. The UI gathers selections, then submits those values through action parameters.\n\n```json\n{\n "availableActions": [\n {\n "actionType": "playCard",\n "displayName": "Play Card",\n "description": "Play the selected card",\n "parameters": [\n {\n "name": "cardId",\n "type": "cardId",\n "required": true,\n "cardSetId": "standard_52_deck"\n }\n ],\n "errorCodes": ["NOT_YOUR_TURN", "INVALID_CARD"]\n }\n ]\n}\n```\n\nAction authoring reminders:\n\n- use stable, readable IDs\n- use `cardSetId` when a `cardId` or `cardType` parameter should be limited to one card set\n- use `array: true` with `minLength` and `maxLength` when an action takes multiple values\n\n## 7. `stateMachine`\n\n`stateMachine` defines the phase flow of the game.\n\nUse it to name phases, describe who can act, and define how the game moves from one phase to the next.\n\nState types:\n\n- `AUTO`: engine-driven phase\n- `SINGLE_PLAYER`: one active player acts\n- `ALL_PLAYERS`: multiple players may act in parallel\n\n```json\n{\n "stateMachine": {\n "initialState": "dealCards",\n "states": [\n {\n "name": "dealCards",\n "type": "AUTO",\n "description": "Shuffle and deal cards.",\n "transitions": [{ "targetState": "playTurn" }]\n },\n {\n "name": "playTurn",\n "type": "SINGLE_PLAYER",\n "description": "Active player plays a card.",\n "availableActions": ["playCard"],\n "transitions": [{ "targetState": "playTurn" }]\n }\n ]\n }\n}\n```\n\n## 8. `variableSchema`\n\n`variableSchema` is for state that must be stored explicitly.\n\nKeep it small. If something can be derived from card locations, resources, board state, or the current phase, it usually does not belong here.\n\nGood candidates:\n\n- running scores across rounds\n- round number, trump suit, current bid\n- flags that affect future legal moves\n\nAvoid storing:\n\n- counts that can be derived from hands or decks\n- phase flow already represented by `stateMachine`\n- duplicated resource totals\n\n```json\n{\n "variableSchema": {\n "globalVariableSchema": {\n "properties": {\n "roundNumber": { "type": "integer", "description": "Current round" }\n }\n },\n "playerVariableSchema": {\n "properties": {\n "score": {\n "type": "integer",\n "description": "Accumulated score"\n }\n }\n }\n }\n}\n```\n\n## Final Checklist\n\nBefore running `dreamboard update`, check that:\n\n- every referenced `cardSetId`, `handId`, `deckId`, resource ID, board ID, and action name exists\n- the manifest only uses documented schema fields\n- `variableSchema` stores only non-derivable state\n- optional list fields are omitted or `[]`, not `null`\n\nThen run:\n\n```bash\ndreamboard update\n```\n',
|
|
1941
1942
|
"references/phase-handlers.md": '# Phase Handlers\n\nPhase handlers are the core abstraction for implementing game logic. Each state in the manifest\'s `stateMachine` maps to a phase handler file in `app/phases/`.\n\n## Phase Types\n\n| Type | File Import | When to use |\n| --------------- | --------------------------- | ------------------------------------------------------------ |\n| `AUTO` | `createAutoPhase` | No player input needed (deal, resolve, score) |\n| `SINGLE_PLAYER` | `createSinglePlayerPhase` | One player acts at a time (play a card, roll dice) |\n| `ALL_PLAYERS` | `createMultiplePlayerPhase` | All players act simultaneously (reveal, pass cards, discard) |\n\n## Phase Lifecycle\n\n**AUTO**: `execute(ctx)` \u2192 returns next state name\n\n**SINGLE_PLAYER / ALL_PLAYERS**:\n\n1. `onEnter(ctx)` \u2014 initialize phase state, set active players\n2. `validateAction(ctx, playerId, actionType, parameters)` \u2014 validate before execution\n3. `onPlayerAction(ctx, playerId, actionType, parameters)` \u2014 process the action (mutate state)\n4. `onAfterAction(ctx, playerId, actionType)` \u2014 per-action side effects (e.g., advance turn)\n5. `checkCompletion(ctx)` \u2014 return `null` if not done, or the next state name if done\n6. `onComplete(ctx)` \u2014 cleanup after transition decision\n\n## PhaseContext (`ctx`)\n\n```typescript\ninterface PhaseContext {\n readonly state: StateApi; // Read game state\n readonly apis: GameApis; // Mutate game state\n readonly logger: Logger; // Log messages\n}\n```\n\n## AUTO Phase Example\n\n```typescript\nimport type { AutoPhaseDefinition } from "../sdk/phaseHandlers";\nimport { createAutoPhase } from "../sdk/phaseHandlers";\n\nconst phaseDefinition = {\n execute(ctx) {\n const { state, apis, logger } = ctx;\n apis.deckApi.shuffle("main-deck");\n for (const playerId of state.player.getOrder()) {\n apis.deckApi.moveCardsFromDeckToPlayer(\n "main-deck",\n playerId,\n "main-hand",\n 5,\n );\n }\n return "playPhase";\n },\n} satisfies AutoPhaseDefinition<"dealCards">;\n\nexport const phase = createAutoPhase(phaseDefinition);\n```\n\n## SINGLE_PLAYER Phase Example\n\n```typescript\nimport type { SinglePlayerPhaseDefinition } from "../sdk/phaseHandlers";\nimport { createSinglePlayerPhase } from "../sdk/phaseHandlers";\nimport { validationSuccess, validationError } from "../sdk/validation.js";\n\nconst phaseDefinition = {\n onEnter(ctx) {\n const { state, apis } = ctx;\n const firstPlayer = state.player.getOrder()[0];\n apis.gameApi.setNextPlayer(firstPlayer);\n },\n\n validateAction(ctx, playerId, actionType, parameters) {\n return validationSuccess();\n },\n\n onPlayerAction(ctx, playerId, actionType, parameters) {\n const { apis } = ctx;\n // Process action...\n },\n\n onAfterAction(ctx, playerId, actionType) {\n const { apis } = ctx;\n apis.gameApi.advanceTurn(); // Move to next player\n },\n\n checkCompletion(ctx) {\n // Return null to keep playing, or next state name to transition\n return null;\n },\n\n getUIArgs(ctx, playerId) {\n return {};\n },\n} satisfies SinglePlayerPhaseDefinition<"playPhase", "playCard">;\n\nexport const phase = createSinglePlayerPhase(phaseDefinition);\n```\n\n## ALL_PLAYERS Phase Example\n\nSee [all-players-tracking.md](all-players-tracking.md) for the recommended tracking pattern.\n\n## Terminal Phase (Game End)\n\nFor states with no transitions (game end), use `TerminalPhaseDefinition`:\n\n```typescript\nimport type { TerminalPhaseDefinition } from "../sdk/phaseHandlers";\nimport { createTerminalPhase } from "../sdk/phaseHandlers";\n\nconst phaseDefinition = {\n execute(ctx) {\n const { state, apis } = ctx;\n // Optionally declare winner\n apis.gameApi.declareWinner("player-1", "Most points");\n apis.gameApi.endGame();\n return "endGame";\n },\n} satisfies TerminalPhaseDefinition<"endGame">;\n\nexport const phase = createTerminalPhase(phaseDefinition);\n```\n\n## Type Safety\n\nPhase definitions use `satisfies` for type checking:\n\n- `AutoPhaseDefinition<\'stateName\'>` \u2014 state name from manifest\n- `SinglePlayerPhaseDefinition<\'stateName\', \'actionName\'>` \u2014 with action type union\n- `MultiplePlayerPhaseDefinition<\'stateName\', \'actionName\'>` \u2014 for ALL_PLAYERS\n- Union multiple actions: `\'playCard\' | \'drawCard\'`\n',
|
|
@@ -1948,7 +1949,7 @@ var SKILL_ASSET_FILES = {
|
|
|
1948
1949
|
"references/ui-genre-worker-placement.md": '# UI Guide: Worker Placement Games\n\nPatterns for Agricola, Viticulture, Lords of Waterdeep, Caverna, and similar euro-style worker placement games.\n\n## Game characteristics\n\n- Players place **worker tokens** on shared **action slots** to gain resources or take actions.\n- Slots may be exclusive (one player per round) or shared.\n- Phases typically include: placement, resolution/harvest, and cleanup.\n- Key UI signals: available slots, slot costs/rewards, number of workers remaining, resource counts.\n\n## Recommended SDK components\n\n### Primary\n\n| Component | Usage |\n| --------------------- | ----------------------------------------------------------------------------------------------------- |\n| `SlotSystem` | Core board \u2014 renders all action slots with occupants. Use `layout="grouped"` to organize by category. |\n| `DefaultSlotItem` | Pre-built slot renderer showing name, description, capacity, cost/reward labels. |\n| `DefaultSlotOccupant` | Pre-built worker token display with player color. |\n| `ResourceCounter` | Display each player\'s resource pool (wood, stone, food, etc.). |\n| `ActionButton` | "Place Worker" button with integrated cost display and affordability check. |\n\n### Supporting\n\n| Component | Usage |\n| ----------------------------- | ------------------------------------------------------------ |\n| `ActionPanel` / `ActionGroup` | Group available actions (e.g., "Place Worker", "End Turn"). |\n| `CostDisplay` | Show resource costs on slot details. |\n| `PlayerInfo` | Show each player\'s name, workers remaining, and turn status. |\n| `PhaseIndicator` | Distinguish placement phase from harvest/scoring phases. |\n| `GameEndDisplay` | Final scoring with detailed breakdown. |\n| `Drawer` | Mobile drawer for detailed slot inspection. |\n\n### Hooks\n\n| Hook | Usage |\n| --------------------------------- | ---------------------------------------------------------------------------------------------- |\n| `useSlotSystem(slots, occupants)` | Utility for slot lookups: `getSlot`, `isFull`, `getRemainingCapacity`, `getOccupantsByPlayer`. |\n| `useMyResources()` | Current player\'s resource pool. |\n| `useGameState` | Get `isMyTurn`, `currentState`, `currentPlayerIds`. |\n| `useAction` | Submit place-worker or end-turn actions. |\n| `useUIArgs` | Get phase-specific data (available slots, worker count, etc.). |\n| `usePlayerInfo` | Map player IDs to names and colors. |\n\n## Key patterns\n\n### Slot board with placement\n\n```tsx\nconst { placementPhase } = useUIArgs();\nconst slots = placementPhase?.slots ?? [];\nconst occupants = placementPhase?.occupants ?? [];\nconst availableSlotIds = new Set(placementPhase?.availableSlotIds ?? []);\nconst slotApi = useSlotSystem(slots, occupants);\n\n<SlotSystem\n slots={slots}\n occupants={occupants}\n layout="grouped"\n renderSlot={(slot, slotOccupants) => (\n <DefaultSlotItem\n name={slot.name}\n description={slot.description}\n capacity={slot.capacity}\n occupantCount={slotOccupants.length}\n isExclusive={slot.exclusive}\n isAvailable={availableSlotIds.has(slot.id)}\n costLabel={formatCost(slot.cost)}\n rewardLabel={formatReward(slot.reward)}\n onClick={() => handlePlaceWorker(slot.id)}\n renderOccupants={() => (\n <div className="flex gap-1">\n {slotOccupants.map((o) => (\n <DefaultSlotOccupant\n key={o.pieceId}\n color={playerColors[o.playerId]}\n label={playerInfo[o.playerId]?.name}\n />\n ))}\n </div>\n )}\n />\n )}\n/>;\n```\n\n### Resource display\n\n```tsx\nconst resources = useMyResources();\n\n<ResourceCounter\n resources={[\n {\n type: "wood",\n label: "Wood",\n icon: TreePine,\n iconColor: "text-amber-700",\n },\n {\n type: "stone",\n label: "Stone",\n icon: Mountain,\n iconColor: "text-slate-400",\n },\n { type: "food", label: "Food", icon: Apple, iconColor: "text-red-400" },\n { type: "gold", label: "Gold", icon: Coins, iconColor: "text-yellow-400" },\n ]}\n counts={resources}\n layout="row"\n/>;\n```\n\n### Action buttons with costs\n\n```tsx\n<ActionButton\n label="Build Farm"\n cost={{ wood: 2, stone: 1 }}\n currentResources={resources}\n resourceDefs={resourceDefinitions}\n icon={Home}\n onClick={() => actions.buildActions.build({ buildingType: "farm" })}\n/>\n```\n\n## UIArgs recommendations\n\n```typescript\n// shared/ui-args.ts\nexport interface PlacementPhaseUIArgs {\n slots: SlotDefinition[];\n occupants: SlotOccupant[];\n availableSlotIds: string[]; // Slots the current player can legally place on\n workersRemaining: number;\n}\n\nexport interface HarvestPhaseUIArgs {\n feedingRequired: number; // Food needed\n currentFood: number;\n}\n```\n',
|
|
1949
1950
|
"references/ui-style-guide.md": '# UI Style Guide\n\nThis style guide details the design philosophy, visual tokens, and core components for the Dreamboard.games UI SDK. The framework uses a **Hand-Drawn** aesthetic that emphasizes human creativity over corporate polish.\n\n## Design Philosophy\n\nThe Hand-Drawn design style celebrates authentic imperfection and human touch in a digital world. It rejects clinical precision in favor of organic, playful irregularity that evokes sketches on paper, sticky notes on a wall, and napkin diagrams from a brainstorming session.\n\n### Core Principles\n\n- **No Straight Lines**: Every border, shape, and container uses irregular border-radius values to create wobbly, hand-drawn edges that reject geometric perfection.\n- **Authentic Texture**: The design layer paper grain, dot patterns, and subtle background textures to simulate physical media (notebook paper, post-its, sketch pads).\n- **Playful Rotation**: Elements are deliberately tilted using small rotation transforms (`-rotate-1`, `rotate-2`) to break rigid grid alignment and create casual energy.\n- **Hard Offset Shadows**: Reject soft blur shadows entirely. Use solid, offset box-shadows (like `4px 4px 0px`) to create a cut-paper, layered collage aesthetic.\n- **Handwritten Typography**: Use exclusively handwritten or marker-style fonts (like Kalam for headings and Patrick Hand for body text) that feel human and approachable.\n- **Limited Color Palette**: Stick to pencil blacks (`#2d2d2d`), paper whites (`#fdfbf7`), correction marker red (`#ff4d4d`), post-it yellow (`#fff9c4`), and ballpoint blue (`#2d5da1`).\n- **Intentional Messiness**: Embrace overlap, asymmetry, and visual "mistakes" that make the design feel spontaneous.\n\n## CSS Tokens & Utilities\n\nThe platform provides several global CSS utilities designed for this aesthetic:\n\n- **Borders**: `.wobbly-border`, `.wobbly-border-md`, `.wobbly-border-lg` apply the irregular `border-radius` shapes. Used with `border-2`, `border-[3px]`, or `border-[4px]`.\n- **Shadows**: `.hard-shadow`, `.hard-shadow-md`, `.hard-shadow-lg` apply the solid offset shadows. Use `.hard-shadow-sm` for active states (buttons pressing flat).\n\n## Implementing in Components\n\nWhen writing or modifying UI components in `ui/` or `ui-sdk/`:\n\n### Containers & Cards\n\n- Add `wobbly-border-md` or `wobbly-border-lg`.\n- Use `border-[3px] border-border` to give it a thick pencil-like stroke.\n- Apply `bg-[#fdfbf7]` (warm paper) or `bg-white`.\n- Add a solid shadow with `hard-shadow` or `hard-shadow-lg`.\n- Slightly rotate the element with `rotate-1` or `-rotate-1`.\n\n### Buttons\n\n- Use `wobbly-border`.\n- Normal state: `border-[3px] border-border bg-[#fdfbf7] text-foreground hard-shadow`.\n- Hover state: `hover:bg-primary hover:text-white hover:hard-shadow-sm hover:-translate-y-1`.\n- Active state: `active:shadow-none active:translate-y-1 active:translate-x-1`.\n- Secondary variants can use `bg-[#e5e0d8]` or `bg-[#fff9c4]`.\n\n### Typography\n\n- Add `font-sans` for standard handwritten text or `font-display` for bold marker headings.\n- Make things bold! `font-bold` works well with handwritten fonts to make them legible.\n\n### Decoration\n\n- For emphasis, wrap text in a post-it styling: `bg-[#fff9c4] px-2 py-1 border-2 border-border wobbly-border rotate-2 inline-block`.\n- Add "tape" to tops of containers: `<div className="absolute top-2 left-1/2 -translate-x-1/2 w-24 h-6 bg-[#e5e0d8] border border-border opacity-80 backdrop-blur-sm -rotate-2 z-10" />`.\n',
|
|
1950
1951
|
"scripts/events-extract.mjs": '#!/usr/bin/env node\n\nimport { access, readFile } from "node:fs/promises";\n\nconst DEFAULT_EVENTS_PATH = ".dreamboard/run/events.ndjson";\n\nfunction printHelp() {\n console.log(`Extract fields from dreamboard run artifacts.\n\nUsage:\n node .agents/skills/dreamboard/scripts/events-extract.mjs [options]\n\nOptions:\n --file, -f <path> NDJSON file path (default: ${DEFAULT_EVENTS_PATH})\n --type <eventType> Filter by event type (repeatable)\n --field <path> Dot path to extract (e.g. message.reason)\n --player <playerId> Filter by player ID across common message fields\n --limit <number> Limit output records\n --no-index Omit line index in output\n --help, -h Show this help\n\nExamples:\n node .agents/skills/dreamboard/scripts/events-extract.mjs --type YOUR_TURN\n node .agents/skills/dreamboard/scripts/events-extract.mjs --type ACTION_REJECTED --field message.reason\n node .agents/skills/dreamboard/scripts/events-extract.mjs --player player-2 --field message.availableActions\n`);\n}\n\nfunction parseCliArgs(argv) {\n const options = {\n filePath: DEFAULT_EVENTS_PATH,\n types: new Set(),\n includeIndex: true,\n };\n\n for (let index = 0; index < argv.length; index += 1) {\n const token = argv[index];\n const nextToken = argv[index + 1];\n\n if (token === "--help" || token === "-h") {\n printHelp();\n process.exit(0);\n }\n if (token === "--no-index") {\n options.includeIndex = false;\n continue;\n }\n if (token === "--file" || token === "-f") {\n if (!nextToken) {\n throw new Error("--file requires a value");\n }\n options.filePath = nextToken;\n index += 1;\n continue;\n }\n if (token === "--type") {\n if (!nextToken) {\n throw new Error("--type requires a value");\n }\n options.types.add(nextToken);\n index += 1;\n continue;\n }\n if (token === "--field") {\n if (!nextToken) {\n throw new Error("--field requires a value");\n }\n options.fieldPath = nextToken;\n index += 1;\n continue;\n }\n if (token === "--player") {\n if (!nextToken) {\n throw new Error("--player requires a value");\n }\n options.playerId = nextToken;\n index += 1;\n continue;\n }\n if (token === "--limit") {\n if (!nextToken) {\n throw new Error("--limit requires a value");\n }\n const parsed = Number.parseInt(nextToken, 10);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n throw new Error("--limit must be a positive integer");\n }\n options.limit = parsed;\n index += 1;\n continue;\n }\n\n throw new Error(`Unknown argument: ${token}`);\n }\n\n return options;\n}\n\nfunction getByPath(value, path) {\n const segments = path.split(".").filter(Boolean);\n let current = value;\n\n for (const segment of segments) {\n if (current === null || current === undefined) {\n return undefined;\n }\n if (typeof current !== "object") {\n return undefined;\n }\n current = current[segment];\n }\n\n return current;\n}\n\nfunction extractPlayerCandidates(record) {\n const message = record.message ?? {};\n const players = new Set();\n\n const singleCandidates = [\n message.playerId,\n message.targetPlayer,\n message.toUser,\n ];\n for (const candidate of singleCandidates) {\n if (typeof candidate === "string") {\n players.add(candidate);\n }\n }\n\n const listCandidates = [\n message.activePlayers,\n message.previousPlayers,\n message.currentPlayers,\n message.controllablePlayerIds,\n message.eligiblePlayerIds,\n ];\n for (const candidate of listCandidates) {\n if (!Array.isArray(candidate)) {\n continue;\n }\n for (const item of candidate) {\n if (typeof item === "string") {\n players.add(item);\n }\n }\n }\n\n return [...players];\n}\n\nfunction toOutputRecord(record, lineIndex, options) {\n const output = {};\n\n if (options.includeIndex) {\n output.index = lineIndex;\n }\n output.observedAt = record.observedAt ?? null;\n output.sessionId = record.sessionId ?? null;\n output.eventId = record.eventId ?? null;\n output.type = record.type ?? null;\n\n if (options.fieldPath) {\n output.field = options.fieldPath;\n output.value = getByPath(record, options.fieldPath);\n } else {\n output.message = record.message ?? null;\n }\n\n return output;\n}\n\nasync function main() {\n const options = parseCliArgs(process.argv.slice(2));\n await access(options.filePath);\n\n const text = await readFile(options.filePath, "utf8");\n const lines = text\n .split(/\\r?\\n/u)\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n\n let emitted = 0;\n for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {\n const line = lines[lineIndex];\n let record;\n try {\n record = JSON.parse(line);\n } catch {\n continue;\n }\n\n if (options.types.size > 0 && !options.types.has(record.type ?? "")) {\n continue;\n }\n\n if (options.playerId) {\n const players = extractPlayerCandidates(record);\n if (!players.includes(options.playerId)) {\n continue;\n }\n }\n\n const output = toOutputRecord(record, lineIndex + 1, options);\n console.log(JSON.stringify(output));\n emitted += 1;\n\n if (options.limit !== undefined && emitted >= options.limit) {\n break;\n }\n }\n}\n\nmain().catch((error) => {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`events-extract failed: ${message}`);\n process.exit(1);\n});\n',
|
|
1951
|
-
"SKILL.md":
|
|
1952
|
+
"SKILL.md": "---\nname: dreamboard\ndescription: Create multiplayer, rule-enforced, turn-based board game on Dreamboard.games platform.\nmetadata:\n short-description: Dreamboard Game Development Workflow\n tags: [dreamboard, cli, game-dev, board-game, turn-based, multiplayer]\n---\n\n# Dreamboard\n\n## Goal\n\nCreate and iterate on a Dreamboard game locally, then sync rules, scaffold, UI, and tests back to the platform.\n\n## Why Dreamboard\n\nDreamboard exists to turn a board-game idea into a playable digital prototype quickly.\n\n- Describe the game instead of hand-building every primitive first.\n- Generate rules scaffolding, components, and UI structure faster.\n- Playtest with real players without repeated print-and-play friction.\n- Iterate live while Dreamboard handles turns, hands, and multiplayer plumbing.\n\n## Prereqs\n\n- Dreamboard CLI installed and available as `dreamboard`\n Install with `npm install -g dreamboard`\n- Authenticated via `dreamboard login`\n\n## Building Your First Game\n\nRead [references/building-your-first-game.md](references/building-your-first-game.md)\n\n## References\n\n- Rules and manifest:\n [references/rule-authoring.md](references/rule-authoring.md),\n [references/manifest-authoring.md](references/manifest-authoring.md)\n- App logic and engine concepts:\n [references/phase-handlers.md](references/phase-handlers.md),\n [references/api-reference.md](references/api-reference.md),\n [references/hands-vs-decks.md](references/hands-vs-decks.md),\n [references/all-players-tracking.md](references/all-players-tracking.md),\n [references/app-best-practices.md](references/app-best-practices.md)\n- UI and UX:\n [references/ui-best-practices.md](references/ui-best-practices.md),\n [references/ui-style-guide.md](references/ui-style-guide.md),\n genre references under `references/ui-genre-*.md`\n- TypeScript regression harness (`dreamboard test generate/run`):\n [references/test-harness.md](references/test-harness.md)\n- JSON runtime scenarios (`dreamboard run --scenario ...`):\n [references/scenario-format.md](references/scenario-format.md)\n- Rejection and edge-case pressure using JSON runtime scenarios:\n [references/adversarial-testing.md](references/adversarial-testing.md)\n\n## Testing Modes\n\nThere are two different scenario systems:\n\n- `dreamboard test generate` / `dreamboard test run`\n TypeScript scenario files in `test/scenarios/*.scenario.ts` against deterministic generated base snapshots. Use this for regression coverage.\n- `dreamboard run --scenario path/to/file.json`\n JSON action scripts that drive a live session. Use this for debugging flows, reproductions, and adversarial experiments.\n\n## Guardrails\n\n- `manifest.json` and `rule.md` are the source of truth for scaffolding.\n- Run `dreamboard update` after manifest changes to keep generated files in sync.\n- `dreamboard update` is local-first and fails when remote drift is detected.\n- Use `dreamboard update --pull` only when you explicitly need to reconcile unexpected remote changes into the workspace.\n- `dreamboard test generate` should be re-run after base changes, compiled-result changes, or game identity changes.\n- Add at least one zero-step `initial-state` scenario so setup invariants fail before action tests begin.\n- Keep `scripts/events-extract.mjs` as a debugging helper for `.dreamboard/run/events.ndjson`; assertions belong in test scenarios.\n\n## Editable Surface\n\nEdit:\n\n- `app/phases/*.ts`\n- `shared/ui-args.ts`\n- `ui/App.tsx`\n- `ui/components/*`\n- `ui/sdk/components/*`\n\nDo not edit framework-owned files such as:\n\n- `app/index.ts`\n- `app/sdk/*`\n- `app/generated/*`\n- `ui/index.tsx`\n- `ui/sdk/context/*`\n- `ui/sdk/hooks/*`\n- `ui/sdk/types/*`\n\n## Framework Feedback\n\nUse `feedback.md` in the game project root to record framework issues, missing features, or workflow friction. Include reproduction steps, expected behavior, and actual behavior when possible.\n"
|
|
1952
1953
|
};
|
|
1953
1954
|
var SKILL_MD_CONTENT = SKILL_ASSET_FILES["SKILL.md"];
|
|
1954
1955
|
|
|
@@ -2284,4 +2285,4 @@ export {
|
|
|
2284
2285
|
writeSnapshotFromFiles,
|
|
2285
2286
|
getLocalDiff
|
|
2286
2287
|
};
|
|
2287
|
-
//# sourceMappingURL=chunk-
|
|
2288
|
+
//# sourceMappingURL=chunk-3I26YUTP.js.map
|