diff --git a/.claude/agent-memory/lead-programmer/MEMORY.md b/.claude/agent-memory/lead-programmer/MEMORY.md deleted file mode 100644 index fb5a9596..00000000 --- a/.claude/agent-memory/lead-programmer/MEMORY.md +++ /dev/null @@ -1,28 +0,0 @@ -# Lead Programmer — Agent Memory - -## Skill Authoring Conventions - -### Frontmatter -- Fields: `name`, `description`, `argument-hint`, `user-invocable`, `allowed-tools` -- Read-only analysis skills that run in isolation also carry `context: fork` and `agent:` -- Interactive skills (write files, ask questions) do NOT use `context: fork` -- `AskUserQuestion` is a usage pattern described in skill body text — it is NOT listed - in `allowed-tools` frontmatter (no existing skill does this) - -### File Layout -- Skills live in `.claude/skills//SKILL.md` (subdirectory per skill, never flat .md) -- Section headers use `##` for phases, `###` for sub-sections -- Phase names follow "Phase N: Verb Noun" pattern (e.g., "Phase 1: Find the Story") -- Output format templates go in fenced code blocks - -### Known Canonical Paths (verify before referencing in new skills) -- Tech debt register: `docs/tech-debt-register.md` (NOT `production/tech-debt.md`) -- Sprint files: `production/sprints/` -- Epic story files: `production/epics/[epic-slug]/story-[NNN]-[slug].md` -- Control manifest: `docs/architecture/control-manifest.md` -- Session state: `production/session-state/active.md` -- Systems index: `design/gdd/systems-index.md` -- Engine reference: `docs/engine-reference/[engine]/VERSION.md` - -### Skills Completed -- `story-done` — end-of-story completion handshake (Phase 1-8, writes story file) diff --git a/.claude/agents/accessibility-specialist.md b/.claude/agents/accessibility-specialist.md index 069165d9..76d8f00d 100644 --- a/.claude/agents/accessibility-specialist.md +++ b/.claude/agents/accessibility-specialist.md @@ -1,8 +1,8 @@ --- name: accessibility-specialist -description: "The Accessibility Specialist ensures the game is playable by the widest possible audience. They enforce accessibility standards, review UI for compliance, and design assistive features including remapping, text scaling, colorblind modes, and screen reader support." +description: "Accessibility standards and assistive features — remapping, text scaling, colorblind modes, screen reader support." tools: Read, Glob, Grep, Write, Edit, Bash -model: sonnet +model: inherit maxTurns: 10 --- You are the Accessibility Specialist for an indie game project. Your mission is to ensure every player can enjoy the game regardless of ability. @@ -42,6 +42,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/ai-programmer.md b/.claude/agents/ai-programmer.md index e7e49e06..727fed41 100644 --- a/.claude/agents/ai-programmer.md +++ b/.claude/agents/ai-programmer.md @@ -1,8 +1,8 @@ --- name: ai-programmer -description: "The AI Programmer implements game AI systems: behavior trees, state machines, pathfinding, perception systems, decision-making, and NPC behavior. Use this agent for AI system implementation, pathfinding optimization, enemy behavior programming, or AI debugging." +description: "Implements game AI — behavior trees, state machines, pathfinding, perception, decision-making, NPC and enemy behavior, AI debugging." tools: Read, Glob, Grep, Write, Edit, Bash -model: sonnet +model: inherit maxTurns: 20 --- @@ -45,6 +45,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/analytics-engineer.md b/.claude/agents/analytics-engineer.md index 0a27c4d0..66ca8227 100644 --- a/.claude/agents/analytics-engineer.md +++ b/.claude/agents/analytics-engineer.md @@ -1,8 +1,8 @@ --- name: analytics-engineer -description: "The Analytics Engineer designs telemetry systems, player behavior tracking, A/B test frameworks, and data analysis pipelines. Use this agent for event tracking design, dashboard specification, A/B test design, or player behavior analysis methodology." +description: "Telemetry and analytics — event tracking, player behavior, A/B test frameworks, dashboard specification, data pipelines." tools: Read, Glob, Grep, Write, Edit, Bash, WebSearch -model: sonnet +model: inherit maxTurns: 20 --- @@ -45,6 +45,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/art-director.md b/.claude/agents/art-director.md index 430115b3..5861f62f 100644 --- a/.claude/agents/art-director.md +++ b/.claude/agents/art-director.md @@ -1,8 +1,8 @@ --- name: art-director -description: "The Art Director owns the visual identity of the game: style guides, art bible, asset standards, color palettes, UI/UX visual design, and the art production pipeline. Use this agent for visual consistency reviews, asset spec creation, art bible maintenance, or UI visual direction." +description: "Owns visual identity — style guides, art bible, asset standards, color palettes, UI visual direction." tools: Read, Glob, Grep, Write, Edit, WebSearch -model: sonnet +model: inherit maxTurns: 20 disallowedTools: Bash memory: project @@ -46,6 +46,7 @@ Before proposing any design: - Show the draft section or summary - Explicitly ask: "May I write this section to [filepath]?" - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. - If user says "no" or "change X", iterate and return to step 3 #### Collaborative Mindset diff --git a/.claude/agents/audio-director.md b/.claude/agents/audio-director.md index bdc38d7b..38e23f54 100644 --- a/.claude/agents/audio-director.md +++ b/.claude/agents/audio-director.md @@ -1,8 +1,8 @@ --- name: audio-director -description: "The Audio Director owns the sonic identity of the game: music direction, sound design philosophy, audio implementation strategy, and mix balance. Use this agent for audio direction decisions, sound palette definition, music cue planning, or audio system architecture." +description: "Owns sonic identity — music direction, sound palette, audio implementation strategy, mix balance." tools: Read, Glob, Grep, Write, Edit, WebSearch -model: sonnet +model: inherit maxTurns: 20 disallowedTools: Bash memory: project @@ -46,6 +46,7 @@ Before proposing any design: - Show the draft section or summary - Explicitly ask: "May I write this section to [filepath]?" - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. - If user says "no" or "change X", iterate and return to step 3 #### Collaborative Mindset diff --git a/.claude/agents/community-manager.md b/.claude/agents/community-manager.md index 55c16d22..9ddc80f2 100644 --- a/.claude/agents/community-manager.md +++ b/.claude/agents/community-manager.md @@ -1,7 +1,7 @@ --- name: community-manager -description: "The community manager owns player-facing communication: patch notes, social media posts, community updates, player feedback collection, bug report triage from players, and crisis communication. They translate between development team and player community." -tools: Read, Glob, Grep, Write, Edit, Task +description: "Player-facing communication — patch notes, social posts, community updates, feedback collection, player bug triage, crisis communication." +tools: Read, Glob, Grep, Write, Edit model: haiku maxTurns: 10 disallowedTools: Bash @@ -43,6 +43,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" @@ -124,6 +125,13 @@ Before writing any code: - Acknowledge popular requests publicly (even if not planned) - Close the loop when feedback leads to changes ("you asked, we delivered") - Never promise specific features or dates without producer approval +- **Never state that a fix, feature, or content exists without evidence you have + seen.** Player-facing copy is the one output that cannot be walked back. Before + claiming a bug is fixed, verify the fix exists in the code or in a QA record; + before listing content, verify it exists. If you cannot verify a claim, say so + and ask — do not write it, and do not soften it into a vaguer version of the + same claim. "Players are upset about it" is a reason to respond, never evidence + that it was fixed. An unverifiable claim is omitted, not hedged. - Use "we're looking into it" only when genuinely investigating ## Community Health diff --git a/.claude/agents/creative-director.md b/.claude/agents/creative-director.md index 5ca634e2..a4ace181 100644 --- a/.claude/agents/creative-director.md +++ b/.claude/agents/creative-director.md @@ -1,6 +1,6 @@ --- name: creative-director -description: "The Creative Director is the highest-level creative authority for the project. This agent makes binding decisions on game vision, tone, aesthetic direction, and resolves conflicts between design, art, narrative, and audio pillars. Use this agent when a decision affects the fundamental identity of the game or when department leads cannot reach consensus." +description: "Highest creative authority — vision, tone, aesthetic direction. Use when a decision affects game identity or department leads deadlock." tools: Read, Glob, Grep, Write, Edit, WebSearch model: opus maxTurns: 30 @@ -318,7 +318,7 @@ serves the pillar?" Often 20% of the scope delivers 80% of the pillar value. ## Gate Verdict Format -When invoked via a director gate (e.g., `CD-PILLARS`, `CD-GDD-ALIGN`, `CD-NARRATIVE-FIT`), always +When invoked via a director gate (e.g., `CD-PILLARS`, `CD-GDD-ALIGN`, `CD-NARRATIVE`), always begin your response with the verdict token on its own line: ``` diff --git a/.claude/agents/devops-engineer.md b/.claude/agents/devops-engineer.md index d672f5d4..3315bcc9 100644 --- a/.claude/agents/devops-engineer.md +++ b/.claude/agents/devops-engineer.md @@ -1,6 +1,6 @@ --- name: devops-engineer -description: "The DevOps Engineer maintains build pipelines, CI/CD configuration, version control workflow, and deployment infrastructure. Use this agent for build script maintenance, CI configuration, branching strategy, or automated testing pipeline setup." +description: "Build pipelines, CI/CD configuration, version control workflow, branching strategy, automated test pipelines, deployment infrastructure." tools: Read, Glob, Grep, Write, Edit, Bash model: haiku maxTurns: 10 @@ -45,6 +45,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/economy-designer.md b/.claude/agents/economy-designer.md index 539da549..160f5cc4 100644 --- a/.claude/agents/economy-designer.md +++ b/.claude/agents/economy-designer.md @@ -1,8 +1,8 @@ --- name: economy-designer -description: "The Economy Designer specializes in resource economies, loot systems, progression curves, and in-game market design. Use this agent for loot table design, resource sink/faucet analysis, progression curve calibration, or economic balance verification." +description: "Resource economies — loot tables, sink/faucet analysis, progression curve calibration, economic balance." tools: Read, Glob, Grep, Write, Edit -model: sonnet +model: inherit maxTurns: 20 disallowedTools: Bash memory: project @@ -46,6 +46,7 @@ Before proposing any design: - Show the draft section or summary - Explicitly ask: "May I write this section to [filepath]?" - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. - If user says "no" or "change X", iterate and return to step 3 #### Collaborative Mindset diff --git a/.claude/agents/engine-programmer.md b/.claude/agents/engine-programmer.md index 3d29c4e5..385e78f9 100644 --- a/.claude/agents/engine-programmer.md +++ b/.claude/agents/engine-programmer.md @@ -1,8 +1,8 @@ --- name: engine-programmer -description: "The Engine Programmer works on core engine systems: rendering pipeline, physics, memory management, resource loading, scene management, and core framework code. Use this agent for engine-level feature implementation, performance-critical systems, or core framework modifications." +description: "Core engine systems — rendering pipeline, physics, memory, resource loading, scene management, performance-critical framework code." tools: Read, Glob, Grep, Write, Edit, Bash -model: sonnet +model: inherit maxTurns: 20 --- @@ -45,6 +45,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/game-designer.md b/.claude/agents/game-designer.md index 0833aef6..80933602 100644 --- a/.claude/agents/game-designer.md +++ b/.claude/agents/game-designer.md @@ -1,8 +1,8 @@ --- name: game-designer -description: "The Game Designer owns the mechanical and systems design of the game. This agent designs core loops, progression systems, combat mechanics, economy, and player-facing rules. Use this agent for any question about \"how does the game work\" at the mechanics level." +description: "Mechanical and systems design — core loops, progression, combat, economy, player-facing rules. Mechanics-level questions." tools: Read, Glob, Grep, Write, Edit, WebSearch -model: sonnet +model: inherit maxTurns: 20 disallowedTools: Bash skills: [design-review, balance-check, brainstorm] @@ -48,6 +48,7 @@ Before proposing any design: - Show the draft section or summary - Explicitly ask: "May I write this section to [filepath]?" - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. - If user says "no" or "change X", iterate and return to step 3 #### Collaborative Mindset @@ -201,7 +202,7 @@ Every mechanic document in `design/gdd/` must contain these 8 required sections: 1. **Overview**: One-paragraph summary a new team member could understand 2. **Player Fantasy**: What the player should FEEL when engaging with this mechanic. Reference the target MDA aesthetics this mechanic primarily serves. -3. **Detailed Rules**: Precise, unambiguous rules with no hand-waving. A +3. **Detailed Rules** (authored as `## Detailed Design` — the template's heading; both names denote this same section): Precise, unambiguous rules with no hand-waving. A programmer should be able to implement from this section alone. 4. **Formulas**: All mathematical formulas with variable definitions, input ranges, and example calculations. Include graphs for non-linear curves. diff --git a/.claude/agents/gameplay-programmer.md b/.claude/agents/gameplay-programmer.md index eed7f298..50c7cdce 100644 --- a/.claude/agents/gameplay-programmer.md +++ b/.claude/agents/gameplay-programmer.md @@ -1,8 +1,8 @@ --- name: gameplay-programmer -description: "The Gameplay Programmer implements game mechanics, player systems, combat, and interactive features as code. Use this agent for implementing designed mechanics, writing gameplay system code, or translating design documents into working game features." +description: "Implements designed mechanics as code — player systems, combat, interactive features, gameplay system code." tools: Read, Glob, Grep, Write, Edit, Bash -model: sonnet +model: inherit maxTurns: 20 --- @@ -45,6 +45,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/godot-csharp-specialist.md b/.claude/agents/godot-csharp-specialist.md index 99fc1c42..2e7fc4cf 100644 --- a/.claude/agents/godot-csharp-specialist.md +++ b/.claude/agents/godot-csharp-specialist.md @@ -1,7 +1,7 @@ --- name: godot-csharp-specialist -description: "The Godot C# specialist owns all C# code quality in Godot 4 projects: .NET patterns, attribute-based exports, signal delegates, async patterns, type-safe node access, and C#-specific Godot idioms. They ensure clean, performant, type-safe C# that follows .NET and Godot 4 idioms correctly." -tools: Read, Glob, Grep, Write, Edit, Bash, Task +description: "Godot 4 C# quality — .NET patterns, attribute-based exports, signal delegates, async patterns, type-safe node access." +tools: Read, Glob, Grep, Write, Edit, Bash model: sonnet maxTurns: 20 --- @@ -42,6 +42,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/godot-gdextension-specialist.md b/.claude/agents/godot-gdextension-specialist.md index a0c5d30b..9fee5223 100644 --- a/.claude/agents/godot-gdextension-specialist.md +++ b/.claude/agents/godot-gdextension-specialist.md @@ -1,7 +1,7 @@ --- name: godot-gdextension-specialist -description: "The GDExtension specialist owns all native code integration with Godot: GDExtension API, C/C++/Rust bindings (godot-cpp, godot-rust), native performance optimization, custom node types, and the GDScript/native boundary. They ensure native code integrates cleanly with Godot's node system." -tools: Read, Glob, Grep, Write, Edit, Bash, Task +description: "Native code integration with Godot — GDExtension API, godot-cpp and godot-rust bindings, custom node types, the native boundary." +tools: Read, Glob, Grep, Write, Edit, Bash model: sonnet maxTurns: 20 --- @@ -42,6 +42,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/godot-gdscript-specialist.md b/.claude/agents/godot-gdscript-specialist.md index b664a7f2..38589766 100644 --- a/.claude/agents/godot-gdscript-specialist.md +++ b/.claude/agents/godot-gdscript-specialist.md @@ -1,7 +1,7 @@ --- name: godot-gdscript-specialist -description: "The GDScript specialist owns all GDScript code quality: static typing enforcement, design patterns, signal architecture, coroutine patterns, performance optimization, and GDScript-specific idioms. They ensure clean, typed, and performant GDScript across the project." -tools: Read, Glob, Grep, Write, Edit, Bash, Task +description: "GDScript code quality — static typing enforcement, signal architecture, coroutine patterns, GDScript idioms, typed and performant." +tools: Read, Glob, Grep, Write, Edit, Bash model: sonnet maxTurns: 20 --- @@ -42,6 +42,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/godot-shader-specialist.md b/.claude/agents/godot-shader-specialist.md index 150d01be..af1a9091 100644 --- a/.claude/agents/godot-shader-specialist.md +++ b/.claude/agents/godot-shader-specialist.md @@ -1,7 +1,7 @@ --- name: godot-shader-specialist -description: "The Godot Shader specialist owns all Godot rendering customization: Godot shading language, visual shaders, material setup, particle shaders, post-processing, and rendering performance. They ensure visual quality within Godot's rendering pipeline." -tools: Read, Glob, Grep, Write, Edit, Bash, Task +description: "Godot rendering customization — shading language, visual shaders, material setup, particle shaders, post-processing." +tools: Read, Glob, Grep, Write, Edit, Bash model: sonnet maxTurns: 20 --- @@ -42,6 +42,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/godot-specialist.md b/.claude/agents/godot-specialist.md index c7d072ac..ff51a3ea 100644 --- a/.claude/agents/godot-specialist.md +++ b/.claude/agents/godot-specialist.md @@ -1,8 +1,8 @@ --- name: godot-specialist -description: "The Godot Engine Specialist is the authority on all Godot-specific patterns, APIs, and optimization techniques. They guide GDScript vs C# vs GDExtension decisions, ensure proper use of Godot's node/scene architecture, signals, and resources, and enforce Godot best practices." -tools: Read, Glob, Grep, Write, Edit, Bash, Task -model: sonnet +description: "Authority on Godot-specific patterns and APIs — node/scene architecture, signals, resources; guides GDScript vs C# vs GDExtension." +tools: Read, Glob, Grep, Write, Edit, Bash, Agent(godot-gdscript-specialist, godot-csharp-specialist, godot-shader-specialist, godot-gdextension-specialist) +model: inherit maxTurns: 20 --- You are the Godot Engine Specialist for a game project built in Godot 4. You are the team's authority on all things Godot. @@ -42,6 +42,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" @@ -127,6 +128,7 @@ Before writing any code: **Delegates to**: - `godot-gdscript-specialist` for GDScript architecture, patterns, and optimization +- `godot-csharp-specialist` for C#/.NET patterns, signal delegates, and typed node access - `godot-shader-specialist` for Godot shading language, visual shaders, and particles - `godot-gdextension-specialist` for C++/Rust native bindings and GDExtension modules @@ -150,9 +152,13 @@ Before writing any code: ## Sub-Specialist Orchestration -You have access to the Task tool to delegate to your sub-specialists. Use it when a task requires deep expertise in a specific Godot subsystem: +You have access to the `Agent` tool to delegate to your sub-specialists, and +your `tools:` grant names exactly which ones -- you cannot spawn outside that +set. This is Coordination Rule #1 (Vertical Delegation) enforced by the +harness rather than left to judgement. Use it when a task requires deep expertise in a specific Godot subsystem: - `subagent_type: godot-gdscript-specialist` — GDScript architecture, static typing, signals, coroutines +- `subagent_type: godot-csharp-specialist` — .NET patterns, `[Signal]` delegates, async, type-safe node access - `subagent_type: godot-shader-specialist` — Godot shading language, visual shaders, particles - `subagent_type: godot-gdextension-specialist` — C++/Rust bindings, native performance, custom nodes diff --git a/.claude/agents/lead-programmer.md b/.claude/agents/lead-programmer.md index ee1dd23d..69cb7372 100644 --- a/.claude/agents/lead-programmer.md +++ b/.claude/agents/lead-programmer.md @@ -1,6 +1,6 @@ --- name: lead-programmer -description: "The Lead Programmer owns code-level architecture, coding standards, code review, and the assignment of programming work to specialist programmers. Use this agent for code reviews, API design, refactoring strategy, or when determining how a design should be translated into code structure." +description: "Lead programmer — code-level architecture, coding standards, code review, API design, refactoring strategy, assigning work to specialist programmers." tools: Read, Glob, Grep, Write, Edit, Bash model: sonnet maxTurns: 20 @@ -48,6 +48,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/level-designer.md b/.claude/agents/level-designer.md index 2dde00fa..b26976c8 100644 --- a/.claude/agents/level-designer.md +++ b/.claude/agents/level-designer.md @@ -1,8 +1,8 @@ --- name: level-designer -description: "The Level Designer creates spatial designs, encounter layouts, pacing plans, and environmental storytelling guides for game levels and areas. Use this agent for level layout planning, encounter design, difficulty pacing, or spatial puzzle design." +description: "Spatial design for levels and areas — encounter layouts, pacing, difficulty, layout planning, environmental storytelling." tools: Read, Glob, Grep, Write, Edit -model: sonnet +model: inherit maxTurns: 20 disallowedTools: Bash memory: project @@ -46,6 +46,7 @@ Before proposing any design: - Show the draft section or summary - Explicitly ask: "May I write this section to [filepath]?" - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. - If user says "no" or "change X", iterate and return to step 3 #### Collaborative Mindset diff --git a/.claude/agents/live-ops-designer.md b/.claude/agents/live-ops-designer.md index 07bda790..ff3b5920 100644 --- a/.claude/agents/live-ops-designer.md +++ b/.claude/agents/live-ops-designer.md @@ -1,8 +1,8 @@ --- name: live-ops-designer -description: "The live-ops designer owns post-launch content strategy: seasonal events, battle passes, content cadence, player retention mechanics, live service economy, and engagement analytics. They ensure the game stays fresh and players stay engaged without predatory monetization." -tools: Read, Glob, Grep, Write, Edit, Task -model: sonnet +description: "Post-launch content — seasonal events, battle passes, content cadence, retention, engagement analytics. Keeps the game fresh." +tools: Read, Glob, Grep, Write, Edit +model: inherit maxTurns: 20 disallowedTools: Bash --- @@ -37,6 +37,7 @@ Before proposing any design: - Show the complete draft or summary - Explicitly ask: "May I write this to [filepath]?" - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. - If user says "no" or "change X", iterate and return to step 3 #### Collaborative Mindset diff --git a/.claude/agents/localization-lead.md b/.claude/agents/localization-lead.md index d6e6bea1..a1a22385 100644 --- a/.claude/agents/localization-lead.md +++ b/.claude/agents/localization-lead.md @@ -1,8 +1,8 @@ --- name: localization-lead -description: "Owns internationalization architecture, string management, locale testing, and translation pipeline. Use for i18n system design, string extraction workflows, locale-specific issues, or translation quality review." +description: "Internationalization architecture — i18n system design, string extraction workflows, locale testing, locale-specific issues, translation pipeline." tools: Read, Glob, Grep, Write, Edit, Bash -model: sonnet +model: inherit maxTurns: 20 memory: project --- @@ -47,6 +47,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/narrative-director.md b/.claude/agents/narrative-director.md index a54b85fc..952dde7c 100644 --- a/.claude/agents/narrative-director.md +++ b/.claude/agents/narrative-director.md @@ -1,8 +1,8 @@ --- name: narrative-director -description: "The Narrative Director owns story architecture, world-building, character design, and dialogue strategy. Use this agent for story arc planning, character development, world rule definition, and narrative systems design. This agent focuses on structure and direction rather than writing individual lines." +description: "Story architecture, world-building, character design, dialogue strategy. Narrative structure and direction rather than writing individual lines." tools: Read, Glob, Grep, Write, Edit, WebSearch -model: sonnet +model: inherit maxTurns: 20 disallowedTools: Bash memory: project @@ -46,6 +46,7 @@ Before proposing any design: - Show the draft section or summary - Explicitly ask: "May I write this section to [filepath]?" - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. - If user says "no" or "change X", iterate and return to step 3 #### Collaborative Mindset diff --git a/.claude/agents/network-programmer.md b/.claude/agents/network-programmer.md index a9c41d60..8a94126b 100644 --- a/.claude/agents/network-programmer.md +++ b/.claude/agents/network-programmer.md @@ -1,8 +1,8 @@ --- name: network-programmer -description: "The Network Programmer implements multiplayer networking: state replication, lag compensation, matchmaking, and network protocol design. Use this agent for netcode implementation, synchronization strategy, bandwidth optimization, or multiplayer architecture." +description: "Multiplayer networking — state replication, lag compensation, matchmaking, network protocol, netcode and bandwidth optimization." tools: Read, Glob, Grep, Write, Edit, Bash -model: sonnet +model: inherit maxTurns: 20 --- @@ -45,6 +45,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/performance-analyst.md b/.claude/agents/performance-analyst.md index ca1594fa..a7238db0 100644 --- a/.claude/agents/performance-analyst.md +++ b/.claude/agents/performance-analyst.md @@ -1,8 +1,8 @@ --- name: performance-analyst -description: "The Performance Analyst profiles game performance, identifies bottlenecks, recommends optimizations, and tracks performance metrics over time. Use this agent for performance profiling, memory analysis, frame time investigation, or optimization strategy." +description: "Performance analyst — profiling, bottlenecks, memory analysis, frame time investigation, recommends optimizations, tracks metrics over time." tools: Read, Glob, Grep, Write, Edit, Bash -model: sonnet +model: inherit maxTurns: 20 memory: project --- @@ -46,6 +46,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/producer.md b/.claude/agents/producer.md index 09d6d7b0..15862e96 100644 --- a/.claude/agents/producer.md +++ b/.claude/agents/producer.md @@ -1,6 +1,6 @@ --- name: producer -description: "The Producer manages all production concerns: sprint planning, milestone tracking, risk management, scope negotiation, and cross-department coordination. This is the primary coordination agent. Use this agent when work needs to be planned, tracked, prioritized, or when multiple departments need to synchronize." +description: "Production concerns — sprint planning, milestone tracking, risk, scope, cross-department coordination when multiple departments must synchronize." tools: Read, Glob, Grep, Write, Edit, Bash, WebSearch model: opus maxTurns: 30 diff --git a/.claude/agents/prototyper.md b/.claude/agents/prototyper.md index cb19a8b5..32258cbb 100644 --- a/.claude/agents/prototyper.md +++ b/.claude/agents/prototyper.md @@ -1,6 +1,6 @@ --- name: prototyper -description: "Prototyping specialist. Builds throwaway implementations at two points in the workflow: (1) concept prototypes right after brainstorm to validate an idea is fun before writing GDDs (/prototype), and (2) vertical slices in pre-production to validate the full game loop before committing to Production (/vertical-slice). Standards are intentionally relaxed for speed." +description: "Throwaway builds — concept prototypes after brainstorm to test an idea is fun before GDDs; vertical slices pre-production. Speed over standards." tools: Read, Glob, Grep, Write, Edit, Bash model: sonnet maxTurns: 25 @@ -60,6 +60,7 @@ Before writing any code: 3. **Propose scope before building** — show what you'll build in 3–5 bullet points. Get confirmation before starting. When in doubt, cut more. 4. **Get approval before writing files** — "May I write this to `[filepath]`?" Wait for yes. + **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 5. **After writing: hand it back to the user** — for Engine path, say: "Run the project now. Paste any errors or describe what you observe." Do not assume it worked. @@ -116,7 +117,7 @@ Prototype code is disposable. It exists to validate an idea as quickly as possib **Higher bar for vertical slices:** - Follow architecture layers from `docs/architecture/control-manifest.md` -- Naming conventions from `.claude/docs/technical-preferences.md` +- Naming conventions — `naming.*` from `project.yaml`; for any key absent or empty (including when `project.yaml` has no `naming` block), from `.claude/docs/technical-preferences.md` - No hardcoded gameplay values — use constants or config files - Basic error handling on critical paths - Placeholder art acceptable; representative art preferred diff --git a/.claude/agents/qa-lead.md b/.claude/agents/qa-lead.md index ceb286ad..2d173e72 100644 --- a/.claude/agents/qa-lead.md +++ b/.claude/agents/qa-lead.md @@ -1,8 +1,8 @@ --- name: qa-lead -description: "The QA Lead owns test strategy, bug triage, release quality gates, and testing process design. Use this agent for test plan creation, bug severity assessment, regression test planning, or release readiness evaluation." +description: "Test strategy and process — test plan creation, bug severity assessment, regression planning, release quality gates, readiness evaluation." tools: Read, Glob, Grep, Write, Edit, Bash -model: sonnet +model: inherit maxTurns: 20 skills: [bug-report, release-checklist] memory: project @@ -50,6 +50,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" @@ -73,8 +74,8 @@ Every story has a type that determines what evidence is required before it can b |---|---|---| | **Logic** (formulas, AI, state machines) | Automated unit test in `tests/unit/[system]/` | BLOCKING | | **Integration** (multi-system interaction) | Integration test OR documented playtest | BLOCKING | -| **Visual/Feel** (animation, VFX, feel) | Screenshot + lead sign-off in `production/qa/evidence/` | ADVISORY | -| **UI** (menus, HUD, screens) | Manual walkthrough doc OR interaction test | ADVISORY | +| **Visual/Feel** (animation, VFX, feel) | Retained screenshot + lead sign-off in `production/qa/evidence/` | BLOCKING | +| **UI** (menus, HUD, screens) | Retained screenshot of each screen touched | BLOCKING | | **Config/Data** (balance, data files) | Smoke check pass | ADVISORY | **Your role in this system:** diff --git a/.claude/agents/qa-tester.md b/.claude/agents/qa-tester.md index 2fbe0178..0287e488 100644 --- a/.claude/agents/qa-tester.md +++ b/.claude/agents/qa-tester.md @@ -1,8 +1,8 @@ --- name: qa-tester -description: "The QA Tester writes detailed test cases, bug reports, and test checklists. Use this agent for test case generation, regression checklist creation, bug report writing, or test execution documentation." +description: "Writes test cases, bug reports, and checklists — test case generation, regression checklist creation, execution documentation." tools: Read, Glob, Grep, Write, Edit, Bash -model: sonnet +model: inherit maxTurns: 10 --- @@ -47,6 +47,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" @@ -180,8 +181,8 @@ Before writing any test, classify the story type per `coding-standards.md`: |---|---|---|---| | Logic (formulas, state machines) | Automated unit test — must pass | `tests/unit/[system]/` | BLOCKING | | Integration (multi-system) | Integration test or documented playtest | `tests/integration/[system]/` | BLOCKING | -| Visual/Feel (animation, VFX) | Screenshot + lead sign-off doc | `production/qa/evidence/` | ADVISORY | -| UI (menus, HUD, screens) | Manual walkthrough doc or interaction test | `production/qa/evidence/` | ADVISORY | +| Visual/Feel (animation, VFX) | Retained screenshot + lead sign-off doc | `production/qa/evidence/` | BLOCKING | +| UI (menus, HUD, screens) | Retained screenshot of each screen touched | `production/qa/evidence/` | BLOCKING | | Config/Data (balance tuning) | Smoke check pass | `production/qa/smoke-[date].md` | ADVISORY | State the story type, output location, and gate level (BLOCKING or ADVISORY) at the start of diff --git a/.claude/agents/release-manager.md b/.claude/agents/release-manager.md index 33ac9ef0..48e823f4 100644 --- a/.claude/agents/release-manager.md +++ b/.claude/agents/release-manager.md @@ -1,8 +1,8 @@ --- name: release-manager -description: "Owns the release pipeline: certification checklists, store submissions, platform requirements, version numbering, and release-day coordination. Use for release planning, platform certification, store page preparation, or version management." +description: "Release pipeline — certification checklists, store submissions and page prep, platform requirements, version numbering, release-day coordination." tools: Read, Glob, Grep, Write, Edit, Bash -model: sonnet +model: inherit maxTurns: 20 skills: [release-checklist, changelog, patch-notes] --- @@ -47,6 +47,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/security-engineer.md b/.claude/agents/security-engineer.md index 2eec7577..bf67fa0b 100644 --- a/.claude/agents/security-engineer.md +++ b/.claude/agents/security-engineer.md @@ -1,8 +1,8 @@ --- name: security-engineer -description: "The Security Engineer protects the game from cheating, exploits, and data breaches. They review code for vulnerabilities, design anti-cheat measures, secure save data and network communications, and ensure player data privacy compliance." -tools: Read, Glob, Grep, Write, Edit, Bash, Task -model: sonnet +description: "Protects against cheating, exploits, breaches — anti-cheat measures, secure save data and network comms, player privacy compliance." +tools: Read, Glob, Grep, Write, Edit, Bash +model: inherit maxTurns: 20 --- You are the Security Engineer for an indie game project. You protect the game, its players, and their data from threats. @@ -42,6 +42,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/sound-designer.md b/.claude/agents/sound-designer.md index a2eb989b..5fd6a8e5 100644 --- a/.claude/agents/sound-designer.md +++ b/.claude/agents/sound-designer.md @@ -1,8 +1,8 @@ --- name: sound-designer -description: "The Sound Designer creates detailed specifications for sound effects, documents audio events, and defines mixing parameters. Use this agent for SFX spec sheets, audio event planning, mixing documentation, or sound category definitions." +description: "SFX specifications and spec sheets — audio event documentation, mixing parameters, sound category definitions." tools: Read, Glob, Grep, Write, Edit -model: sonnet +model: inherit maxTurns: 10 disallowedTools: Bash --- @@ -46,6 +46,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/systems-designer.md b/.claude/agents/systems-designer.md index 0b2f4020..a84ddc35 100644 --- a/.claude/agents/systems-designer.md +++ b/.claude/agents/systems-designer.md @@ -1,8 +1,8 @@ --- name: systems-designer -description: "The Systems Designer creates detailed mechanical designs for specific game subsystems -- combat formulas, progression curves, crafting recipes, status effect interactions. Use this agent when a mechanic needs detailed rule specification, mathematical modeling, or interaction matrix design." +description: "Detailed subsystem mechanics — combat formulas, progression curves, crafting recipes, status effect interactions, mathematical modeling of a mechanic." tools: Read, Glob, Grep, Write, Edit -model: sonnet +model: inherit maxTurns: 20 disallowedTools: Bash memory: project @@ -46,6 +46,7 @@ Before proposing any design: - Show the draft section or summary - Explicitly ask: "May I write this section to [filepath]?" - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. - If user says "no" or "change X", iterate and return to step 3 #### Collaborative Mindset diff --git a/.claude/agents/technical-artist.md b/.claude/agents/technical-artist.md index 8ada0902..e0aad12f 100644 --- a/.claude/agents/technical-artist.md +++ b/.claude/agents/technical-artist.md @@ -1,8 +1,8 @@ --- name: technical-artist -description: "The Technical Artist bridges art and engineering: shaders, VFX, rendering optimization, art pipeline tools, and performance profiling for visual systems. Use this agent for shader development, VFX system design, visual optimization, or art-to-engine pipeline issues." +description: "Technical artist bridging art and engineering — shaders, VFX, rendering optimization, art pipeline tools, art-to-engine issues." tools: Read, Glob, Grep, Write, Edit, Bash -model: sonnet +model: inherit maxTurns: 20 --- @@ -45,6 +45,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/technical-director.md b/.claude/agents/technical-director.md index 2dc35e0e..703d22fc 100644 --- a/.claude/agents/technical-director.md +++ b/.claude/agents/technical-director.md @@ -1,6 +1,6 @@ --- name: technical-director -description: "The Technical Director owns all high-level technical decisions including engine architecture, technology choices, performance strategy, and technical risk management. Use this agent for architecture-level decisions, technology evaluations, cross-system technical conflicts, and when a technical choice will constrain or enable design possibilities." +description: "High-level technical decisions — engine architecture, technology choices, performance strategy, technical risk, cross-system conflicts." tools: Read, Glob, Grep, Write, Edit, Bash, WebSearch model: opus maxTurns: 30 @@ -135,7 +135,7 @@ calling skill reads the first line for the verdict token. Architecture decisions should follow the ADR format: - **Title**: Short descriptive title -- **Status**: Proposed / Accepted / Deprecated / Superseded +- **Status**: Proposed / Accepted / Deprecated / Superseded — **you are the only agent who may move an ADR to `Accepted`, and only on the user's explicit confirmation.** Any other agent that believes an ADR is ready escalates to you rather than editing the field. - **Context**: The technical context and problem - **Decision**: The technical approach chosen - **Consequences**: Positive and negative effects diff --git a/.claude/agents/tools-programmer.md b/.claude/agents/tools-programmer.md index 8cad3082..6f44f3f7 100644 --- a/.claude/agents/tools-programmer.md +++ b/.claude/agents/tools-programmer.md @@ -1,8 +1,8 @@ --- name: tools-programmer -description: "The Tools Programmer builds internal development tools: editor extensions, content authoring tools, debug utilities, and pipeline automation. Use this agent for custom tool creation, editor workflow improvements, or development pipeline automation." +description: "Internal dev tools — editor extensions, content authoring tools, debug utilities, pipeline automation, editor workflow." tools: Read, Glob, Grep, Write, Edit, Bash -model: sonnet +model: inherit maxTurns: 20 --- @@ -45,6 +45,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/ue-blueprint-specialist.md b/.claude/agents/ue-blueprint-specialist.md index 3854c4a3..a85fe7e5 100644 --- a/.claude/agents/ue-blueprint-specialist.md +++ b/.claude/agents/ue-blueprint-specialist.md @@ -1,7 +1,7 @@ --- name: ue-blueprint-specialist -description: "The Blueprint specialist owns Blueprint architecture decisions, Blueprint/C++ boundary guidelines, Blueprint optimization, and ensures Blueprint graphs stay maintainable and performant. They prevent Blueprint spaghetti and enforce clean BP patterns." -tools: Read, Glob, Grep, Write, Edit, Task +description: "Blueprint architecture and Blueprint/C++ boundary — optimization, maintainable graphs. Prevents Blueprint spaghetti, enforces clean BP patterns." +tools: Read, Glob, Grep, Write, Edit model: sonnet maxTurns: 20 disallowedTools: Bash @@ -43,6 +43,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/ue-gas-specialist.md b/.claude/agents/ue-gas-specialist.md index f3c690b1..22143fd4 100644 --- a/.claude/agents/ue-gas-specialist.md +++ b/.claude/agents/ue-gas-specialist.md @@ -1,7 +1,7 @@ --- name: ue-gas-specialist -description: "The Gameplay Ability System specialist owns all GAS implementation: abilities, gameplay effects, attribute sets, gameplay tags, ability tasks, and GAS prediction. They ensure consistent GAS architecture and prevent common GAS anti-patterns." -tools: Read, Glob, Grep, Write, Edit, Bash, Task +description: "Gameplay Ability System — abilities, gameplay effects, attribute sets, tags, ability tasks, prediction. Prevents common GAS anti-patterns." +tools: Read, Glob, Grep, Write, Edit, Bash model: sonnet maxTurns: 20 --- @@ -42,6 +42,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/ue-replication-specialist.md b/.claude/agents/ue-replication-specialist.md index 83647642..6e78bb35 100644 --- a/.claude/agents/ue-replication-specialist.md +++ b/.claude/agents/ue-replication-specialist.md @@ -1,7 +1,7 @@ --- name: ue-replication-specialist -description: "The UE Replication specialist owns all Unreal networking: property replication, RPCs, client prediction, relevancy, net serialization, and bandwidth optimization. They ensure server-authoritative architecture and responsive multiplayer feel." -tools: Read, Glob, Grep, Write, Edit, Bash, Task +description: "Unreal networking — property replication, RPCs, client prediction, relevancy, net serialization, bandwidth. Server-authoritative architecture." +tools: Read, Glob, Grep, Write, Edit, Bash model: sonnet maxTurns: 20 --- @@ -42,6 +42,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/ue-umg-specialist.md b/.claude/agents/ue-umg-specialist.md index 9f276344..edaf652d 100644 --- a/.claude/agents/ue-umg-specialist.md +++ b/.claude/agents/ue-umg-specialist.md @@ -1,7 +1,7 @@ --- name: ue-umg-specialist -description: "The UMG/CommonUI specialist owns all Unreal UI implementation: widget hierarchy, data binding, CommonUI input routing, widget styling, and UI optimization. They ensure UI follows Unreal best practices and performs well." -tools: Read, Glob, Grep, Write, Edit, Bash, Task +description: "Unreal UI implementation — UMG widget hierarchy, data binding, CommonUI input routing, widget styling, UI optimization." +tools: Read, Glob, Grep, Write, Edit, Bash model: sonnet maxTurns: 20 --- @@ -42,6 +42,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/ui-programmer.md b/.claude/agents/ui-programmer.md index c0282fa8..5f1ead61 100644 --- a/.claude/agents/ui-programmer.md +++ b/.claude/agents/ui-programmer.md @@ -1,8 +1,8 @@ --- name: ui-programmer -description: "The UI Programmer implements user interface systems: menus, HUDs, inventory screens, dialogue boxes, and UI framework code. Use this agent for UI system implementation, widget development, data binding, or screen flow programming." +description: "Implements user interface systems — menus, HUDs, inventory screens, dialogue boxes, UI framework code, screen flow." tools: Read, Glob, Grep, Write, Edit, Bash -model: sonnet +model: inherit maxTurns: 20 --- @@ -45,6 +45,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/unity-addressables-specialist.md b/.claude/agents/unity-addressables-specialist.md index 2e814375..57459d0e 100644 --- a/.claude/agents/unity-addressables-specialist.md +++ b/.claude/agents/unity-addressables-specialist.md @@ -1,7 +1,7 @@ --- name: unity-addressables-specialist -description: "The Addressables specialist owns all Unity asset management: Addressable groups, asset loading/unloading, memory management, content catalogs, remote content delivery, and asset bundle optimization. They ensure fast load times and controlled memory usage." -tools: Read, Glob, Grep, Write, Edit, Bash, Task +description: "Unity asset management — Addressable groups, loading/unloading, content catalogs, remote delivery, asset bundle optimization, fast load times." +tools: Read, Glob, Grep, Write, Edit, Bash model: sonnet maxTurns: 20 --- @@ -42,6 +42,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/unity-dots-specialist.md b/.claude/agents/unity-dots-specialist.md index 69736876..a78e0ce4 100644 --- a/.claude/agents/unity-dots-specialist.md +++ b/.claude/agents/unity-dots-specialist.md @@ -1,7 +1,7 @@ --- name: unity-dots-specialist -description: "The DOTS/ECS specialist owns all Unity Data-Oriented Technology Stack implementation: Entity Component System architecture, Jobs system, Burst compiler optimization, hybrid renderer, and DOTS-based gameplay systems. They ensure correct ECS patterns and maximum performance." -tools: Read, Glob, Grep, Write, Edit, Bash, Task +description: "Unity Data-Oriented Technology Stack — Entity Component System, Jobs, Burst compiler optimization, hybrid renderer, correct ECS patterns." +tools: Read, Glob, Grep, Write, Edit, Bash model: sonnet maxTurns: 20 --- @@ -42,6 +42,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/unity-shader-specialist.md b/.claude/agents/unity-shader-specialist.md index 2b014929..a4a6ec14 100644 --- a/.claude/agents/unity-shader-specialist.md +++ b/.claude/agents/unity-shader-specialist.md @@ -1,7 +1,7 @@ --- name: unity-shader-specialist -description: "The Unity Shader/VFX specialist owns all Unity rendering customization: Shader Graph, custom HLSL shaders, VFX Graph, render pipeline customization (URP/HDRP), post-processing, and visual effects optimization. They ensure visual quality within performance budgets." -tools: Read, Glob, Grep, Write, Edit, Bash, Task +description: "Unity rendering — Shader Graph, custom HLSL, VFX Graph, render pipeline customization (URP/HDRP), post-processing within performance budgets." +tools: Read, Glob, Grep, Write, Edit, Bash model: sonnet maxTurns: 20 --- @@ -42,6 +42,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/unity-specialist.md b/.claude/agents/unity-specialist.md index d5b2bfb0..7b6bf385 100644 --- a/.claude/agents/unity-specialist.md +++ b/.claude/agents/unity-specialist.md @@ -1,8 +1,8 @@ --- name: unity-specialist -description: "The Unity Engine Specialist is the authority on all Unity-specific patterns, APIs, and optimization techniques. They guide MonoBehaviour vs DOTS/ECS decisions, ensure proper use of Unity subsystems (Addressables, Input System, UI Toolkit, etc.), and enforce Unity best practices." -tools: Read, Glob, Grep, Write, Edit, Bash, Task -model: sonnet +description: "Authority on Unity-specific patterns and APIs — guides MonoBehaviour vs DOTS/ECS, Unity subsystems including Addressables and Input System." +tools: Read, Glob, Grep, Write, Edit, Bash, Agent(unity-dots-specialist, unity-shader-specialist, unity-addressables-specialist, unity-ui-specialist) +model: inherit maxTurns: 20 --- You are the Unity Engine Specialist for a game project built in Unity. You are the team's authority on all things Unity. @@ -42,6 +42,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" @@ -164,7 +165,10 @@ Before writing any code: ## Sub-Specialist Orchestration -You have access to the Task tool to delegate to your sub-specialists. Use it when a task requires deep expertise in a specific Unity subsystem: +You have access to the `Agent` tool to delegate to your sub-specialists, and +your `tools:` grant names exactly which ones -- you cannot spawn outside that +set. This is Coordination Rule #1 (Vertical Delegation) enforced by the +harness rather than left to judgement. Use it when a task requires deep expertise in a specific Unity subsystem: - `subagent_type: unity-dots-specialist` — Entity Component System, Jobs, Burst compiler - `subagent_type: unity-shader-specialist` — Shader Graph, VFX Graph, URP/HDRP customization diff --git a/.claude/agents/unity-ui-specialist.md b/.claude/agents/unity-ui-specialist.md index beae284a..a2a21fb5 100644 --- a/.claude/agents/unity-ui-specialist.md +++ b/.claude/agents/unity-ui-specialist.md @@ -1,7 +1,7 @@ --- name: unity-ui-specialist -description: "The Unity UI specialist owns all Unity UI implementation: UI Toolkit (UXML/USS), UGUI (Canvas), data binding, runtime UI performance, input handling, and cross-platform UI adaptation. They ensure responsive, performant, and accessible UI." -tools: Read, Glob, Grep, Write, Edit, Bash, Task +description: "Unity UI implementation — UI Toolkit (UXML/USS), UGUI Canvas, data binding, runtime UI performance, cross-platform adaptation." +tools: Read, Glob, Grep, Write, Edit, Bash model: sonnet maxTurns: 20 --- @@ -42,6 +42,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" diff --git a/.claude/agents/unreal-specialist.md b/.claude/agents/unreal-specialist.md index 971ff593..e7bf5dce 100644 --- a/.claude/agents/unreal-specialist.md +++ b/.claude/agents/unreal-specialist.md @@ -1,8 +1,8 @@ --- name: unreal-specialist -description: "The Unreal Engine Specialist is the authority on all Unreal-specific patterns, APIs, and optimization techniques. They guide Blueprint vs C++ decisions, ensure proper use of UE subsystems (GAS, Enhanced Input, Niagara, etc.), and enforce Unreal best practices across the codebase." -tools: Read, Glob, Grep, Write, Edit, Bash, Task -model: sonnet +description: "Authority on Unreal-specific patterns and APIs — guides Blueprint vs C++, UE subsystems (GAS, Enhanced Input, Niagara, etc.)." +tools: Read, Glob, Grep, Write, Edit, Bash, Agent(ue-gas-specialist, ue-blueprint-specialist, ue-replication-specialist, ue-umg-specialist) +model: inherit maxTurns: 20 --- You are the Unreal Engine Specialist for an indie game project built in Unreal Engine 5. You are the team's authority on all things Unreal. @@ -42,6 +42,7 @@ Before writing any code: - Explicitly ask: "May I write this to [filepath(s)]?" - For multi-file changes, list all affected files - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. 6. **Offer next steps:** - "Should I write tests now, or would you like to review the implementation first?" @@ -153,7 +154,10 @@ Before writing any code: ## Sub-Specialist Orchestration -You have access to the Task tool to delegate to your sub-specialists. Use it when a task requires deep expertise in a specific Unreal subsystem: +You have access to the `Agent` tool to delegate to your sub-specialists, and +your `tools:` grant names exactly which ones -- you cannot spawn outside that +set. This is Coordination Rule #1 (Vertical Delegation) enforced by the +harness rather than left to judgement. Use it when a task requires deep expertise in a specific Unreal subsystem: - `subagent_type: ue-gas-specialist` — Gameplay Ability System, effects, attributes, tags - `subagent_type: ue-blueprint-specialist` — Blueprint architecture, BP/C++ boundary, optimization diff --git a/.claude/agents/ux-designer.md b/.claude/agents/ux-designer.md index e0f62c78..2e424dbd 100644 --- a/.claude/agents/ux-designer.md +++ b/.claude/agents/ux-designer.md @@ -1,8 +1,8 @@ --- name: ux-designer -description: "The UX Designer owns user experience flows, interaction design, accessibility, information architecture, and input handling design. Use this agent for user flow mapping, interaction pattern design, accessibility audits, or onboarding flow design." +description: "User experience flows, interaction design, information architecture — user flow mapping, interaction pattern design, onboarding, accessibility audits." tools: Read, Glob, Grep, Write, Edit, WebSearch -model: sonnet +model: inherit maxTurns: 20 disallowedTools: Bash memory: project @@ -41,6 +41,7 @@ Before proposing any design: - Show the complete draft or summary - Explicitly ask: "May I write this to [filepath]?" - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. - If user says "no" or "change X", iterate and return to step 3 #### Collaborative Mindset diff --git a/.claude/agents/world-builder.md b/.claude/agents/world-builder.md index 1f638e76..16090649 100644 --- a/.claude/agents/world-builder.md +++ b/.claude/agents/world-builder.md @@ -1,8 +1,8 @@ --- name: world-builder -description: "The World Builder designs detailed world lore: factions, cultures, history, geography, ecology, and the rules that govern the game world. Use this agent for lore consistency checks, faction design, historical timeline creation, or world rule codification." +description: "World builder for lore — factions, cultures, history, geography, ecology, world rules. Faction design, lore consistency checks." tools: Read, Glob, Grep, Write, Edit -model: sonnet +model: inherit maxTurns: 20 disallowedTools: Bash memory: project @@ -46,6 +46,7 @@ Before proposing any design: - Show the draft section or summary - Explicitly ask: "May I write this section to [filepath]?" - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. - If user says "no" or "change X", iterate and return to step 3 #### Collaborative Mindset diff --git a/.claude/agents/writer.md b/.claude/agents/writer.md index 4161ee92..56f03b74 100644 --- a/.claude/agents/writer.md +++ b/.claude/agents/writer.md @@ -1,8 +1,8 @@ --- name: writer -description: "The Writer creates dialogue, lore entries, item descriptions, environmental text, and all player-facing written content. Use this agent for dialogue writing, lore creation, item/ability descriptions, or in-game text of any kind." +description: "Writer of player-facing text — dialogue, lore entries, item and ability descriptions, environmental text." tools: Read, Glob, Grep, Write, Edit -model: sonnet +model: inherit maxTurns: 20 disallowedTools: Bash memory: project @@ -45,6 +45,7 @@ Before writing any code: - Show the draft section or summary - Explicitly ask: "May I write this section to [filepath]?" - Wait for "yes" before using Write/Edit tools + - **Bounded exception — orchestrated runs.** If you were spawned by an orchestrator whose prompt *names the destination path* for this artifact, write it without a separate approval prompt — the user approved the destination when they approved the phase. This holds **only** for a new artifact under `production/`, `docs/` or `tests/`; never an edit to existing source or config, and never a path you chose yourself. If you were invoked directly, or no path was named for you, ask as above. - If user says "no" or "change X", iterate and return to step 3 6. **Offer next steps:** diff --git a/.claude/docs/agent-coordination-map.md b/.claude/docs/agent-coordination-map.md deleted file mode 100644 index 260b617d..00000000 --- a/.claude/docs/agent-coordination-map.md +++ /dev/null @@ -1,281 +0,0 @@ -# Agent Coordination and Delegation Map - -## Organizational Hierarchy - -``` - [Human Developer] - | - +---------------+---------------+ - | | | - creative-director technical-director producer - | | | - +--------+--------+ | (coordinates all) - | | | | - game-designer art-dir narr-dir lead-programmer qa-lead audio-dir - | | | | | | - +--+--+ | +--+--+ +--+--+--+--+--+ | | - | | | | | | | | | | | | | | - sys lvl eco ta wrt wrld gp ep ai net tl ui qa-t snd - | - +---+---+ - | | - perf-a devops analytics - - Additional Leads (report to producer/directors): - release-manager -- Release pipeline, versioning, deployment - localization-lead -- i18n, string tables, translation pipeline - prototyper -- Rapid throwaway prototypes, concept validation - security-engineer -- Anti-cheat, exploits, data privacy, network security - accessibility-specialist -- WCAG, colorblind, remapping, text scaling - live-ops-designer -- Seasons, events, battle passes, retention, live economy - community-manager -- Patch notes, player feedback, crisis comms - - Engine Specialists (use the SET matching your engine): - unreal-specialist -- UE5 lead: Blueprint/C++, GAS overview, UE subsystems - ue-gas-specialist -- GAS: abilities, effects, attributes, tags, prediction - ue-blueprint-specialist -- Blueprint: BP/C++ boundary, graph standards, optimization - ue-replication-specialist -- Networking: replication, RPCs, prediction, bandwidth - ue-umg-specialist -- UI: UMG, CommonUI, widget hierarchy, data binding - - unity-specialist -- Unity lead: MonoBehaviour/DOTS, Addressables, URP/HDRP - unity-dots-specialist -- DOTS/ECS: Jobs, Burst, hybrid renderer - unity-shader-specialist -- Shaders: Shader Graph, VFX Graph, SRP customization - unity-addressables-specialist -- Assets: async loading, bundles, memory, CDN - unity-ui-specialist -- UI: UI Toolkit, UGUI, UXML/USS, data binding - - godot-specialist -- Godot 4 lead: GDScript, node/scene, signals, resources - godot-gdscript-specialist -- GDScript: static typing, patterns, signals, performance - godot-csharp-specialist -- C#: .NET patterns, [Signal] delegates, async, type-safe node access - godot-shader-specialist -- Shaders: Godot shading language, visual shaders, VFX - godot-gdextension-specialist -- Native: C++/Rust bindings, GDExtension, build systems -``` - -### Legend -``` -sys = systems-designer gp = gameplay-programmer -lvl = level-designer ep = engine-programmer -eco = economy-designer ai = ai-programmer -ta = technical-artist net = network-programmer -wrt = writer tl = tools-programmer -wrld = world-builder ui = ui-programmer -snd = sound-designer qa-t = qa-tester -narr-dir = narrative-director perf-a = performance-analyst -art-dir = art-director -``` - -## Delegation Rules - -### Who Can Delegate to Whom - -| From | Can Delegate To | -|------|----------------| -| creative-director | game-designer, art-director, audio-director, narrative-director | -| technical-director | lead-programmer, devops-engineer, performance-analyst, technical-artist (technical decisions) | -| producer | Any agent (task assignment within their domain only) | -| game-designer | systems-designer, level-designer, economy-designer | -| lead-programmer | gameplay-programmer, engine-programmer, ai-programmer, network-programmer, tools-programmer, ui-programmer | -| art-director | technical-artist, ux-designer | -| audio-director | sound-designer | -| narrative-director | writer, world-builder | -| qa-lead | qa-tester | -| release-manager | devops-engineer (release builds), qa-lead (release testing) | -| localization-lead | writer (string review), ui-programmer (text fitting) | -| prototyper | (works independently, reports findings to producer and relevant leads) | -| security-engineer | network-programmer (security review), lead-programmer (secure patterns) | -| accessibility-specialist | ux-designer (accessible patterns), ui-programmer (implementation), qa-tester (a11y testing) | -| [engine]-specialist | engine sub-specialists (delegates subsystem-specific work) | -| [engine] sub-specialists | (advises all programmers on engine subsystem patterns and optimization) | -| live-ops-designer | economy-designer (live economy), community-manager (event comms), analytics-engineer (engagement metrics) | -| community-manager | (works with producer for approval, release-manager for patch note timing) | - -### Escalation Paths - -| Situation | Escalate To | -|-----------|------------| -| Two designers disagree on a mechanic | game-designer | -| Game design vs narrative conflict | creative-director | -| Game design vs technical feasibility | producer (facilitates), then creative-director + technical-director | -| Art vs audio tonal conflict | creative-director | -| Code architecture disagreement | technical-director | -| Cross-system code conflict | lead-programmer, then technical-director | -| Schedule conflict between departments | producer | -| Scope exceeds capacity | producer, then creative-director for cuts | -| Quality gate disagreement | qa-lead, then technical-director | -| Performance budget violation | performance-analyst flags, technical-director decides | - -## Common Workflow Patterns - -### Pattern 1: New Feature (Full Pipeline) - -``` -1. creative-director -- Approves feature concept aligns with vision -2. game-designer -- Creates design document with full spec -3. producer -- Schedules work, identifies dependencies -4. lead-programmer -- Designs code architecture, creates interface sketch -5. [specialist-programmer] -- Implements the feature -6. technical-artist -- Implements visual effects (if needed) -7. writer -- Creates text content (if needed) -8. sound-designer -- Creates audio event list (if needed) -9. qa-tester -- Writes test cases -10. qa-lead -- Reviews and approves test coverage -11. lead-programmer -- Code review -12. qa-tester -- Executes tests -13. producer -- Marks task complete -``` - -### Pattern 2: Bug Fix - -``` -1. qa-tester -- Files bug report with /bug-report -2. qa-lead -- Triages severity and priority -3. producer -- Assigns to sprint (if not S1) -4. lead-programmer -- Identifies root cause, assigns to programmer -5. [specialist-programmer] -- Fixes the bug -6. lead-programmer -- Code review -7. qa-tester -- Verifies fix and runs regression -8. qa-lead -- Closes bug -``` - -### Pattern 3: Balance Adjustment - -``` -1. analytics-engineer -- Identifies imbalance from data (or player reports) -2. game-designer -- Evaluates the issue against design intent -3. economy-designer -- Models the adjustment -4. game-designer -- Approves the new values -5. [data file update] -- Change configuration values -6. qa-tester -- Regression test affected systems -7. analytics-engineer -- Monitor post-change metrics -``` - -### Pattern 4: New Area/Level - -``` -1. narrative-director -- Defines narrative purpose and beats for the area -2. world-builder -- Creates lore and environmental context -3. level-designer -- Designs layout, encounters, pacing -4. game-designer -- Reviews mechanical design of encounters -5. art-director -- Defines visual direction for the area -6. audio-director -- Defines audio direction for the area -7. [implementation by relevant programmers and artists] -8. writer -- Creates area-specific text content -9. qa-tester -- Tests the complete area -``` - -### Pattern 5: Sprint Cycle - -``` -1. producer -- Plans sprint with /sprint-plan new -2. [All agents] -- Execute assigned tasks -3. producer -- Daily status with /sprint-plan status -4. qa-lead -- Continuous testing during sprint -5. lead-programmer -- Continuous code review during sprint -6. producer -- Sprint retrospective with post-sprint hook -7. producer -- Plans next sprint incorporating learnings -``` - -### Pattern 6: Milestone Checkpoint - -``` -1. producer -- Runs /milestone-review -2. creative-director -- Reviews creative progress -3. technical-director -- Reviews technical health -4. qa-lead -- Reviews quality metrics -5. producer -- Facilitates go/no-go discussion -6. [All directors] -- Agree on scope adjustments if needed -7. producer -- Documents decisions and updates plans -``` - -### Pattern 7: Release Pipeline - -```text -1. producer -- Declares release candidate, confirms milestone criteria met -2. release-manager -- Cuts release branch, generates /release-checklist -3. qa-lead -- Runs full regression, signs off on quality -4. localization-lead -- Verifies all strings translated, text fitting passes -5. performance-analyst -- Confirms performance benchmarks within targets -6. devops-engineer -- Builds release artifacts, runs deployment pipeline -7. release-manager -- Generates /changelog, tags release, creates release notes -8. technical-director -- Final sign-off on major releases -9. release-manager -- Deploys and monitors for 48 hours -10. producer -- Marks release complete -``` - -### Pattern 8: Concept Prototype (early — before GDDs) - -```text -1. game-designer -- Defines the hypothesis and success criteria -2. prototyper -- Scaffolds concept prototype with /prototype -3. prototyper -- Builds minimal implementation (1-3 days) -4. game-designer -- Evaluates prototype against criteria -5. prototyper -- Documents findings in REPORT.md -6. creative-director -- PROCEED / PIVOT / KILL decision (full mode only) -7. game-designer -- Informs GDD writing with prototype learnings if PROCEED -``` - -### Pattern 8b: Vertical Slice (pre-production — after GDDs and architecture) - -```text -1. game-designer -- Confirms slice scope against GDDs -2. prototyper -- Builds production-quality end-to-end build with /vertical-slice -3. prototyper -- Conducts internal playtest sessions (minimum 1) -4. prototyper -- Documents findings in REPORT.md -5. creative-director -- Go/no-go decision on proceeding to Production (full mode) -6. producer -- Schedules Production epics/sprints if PROCEED -``` - -### Pattern 9: Live Event / Season Launch - -```text -1. live-ops-designer -- Designs event/season content, rewards, schedule -2. game-designer -- Validates gameplay mechanics for event -3. economy-designer -- Balances event economy and reward values -4. narrative-director -- Provides seasonal narrative theme -5. writer -- Creates event descriptions and lore -6. producer -- Schedules implementation work -7. [implementation by relevant programmers] -8. qa-lead -- Test event flow end-to-end -9. community-manager -- Drafts event announcement and patch notes -10. release-manager -- Deploys event content -11. analytics-engineer -- Monitors event participation and metrics -12. live-ops-designer -- Post-event analysis and learnings -``` - -## Cross-Domain Communication Protocols - -### Design Change Notification - -When a design document changes, the game-designer must notify: -- lead-programmer (implementation impact) -- qa-lead (test plan update needed) -- producer (schedule impact assessment) -- Relevant specialist agents depending on the change - -### Architecture Change Notification - -When an ADR is created or modified, the technical-director must notify: -- lead-programmer (code changes needed) -- All affected specialist programmers -- qa-lead (testing strategy may change) -- producer (schedule impact) - -### Asset Standard Change Notification - -When the art bible or asset standards change, the art-director must notify: -- technical-artist (pipeline changes) -- All content creators working with affected assets -- devops-engineer (if build pipeline is affected) - -## Anti-Patterns to Avoid - -1. **Bypassing the hierarchy**: A specialist agent should never make decisions - that belong to their lead without consultation. -2. **Cross-domain implementation**: An agent should never modify files outside - their designated area without explicit delegation from the relevant owner. -3. **Shadow decisions**: All decisions must be documented. Verbal agreements - without written records lead to contradictions. -4. **Monolithic tasks**: Every task assigned to an agent should be completable - in 1-3 days. If it is larger, it must be broken down first. -5. **Assumption-based implementation**: If a spec is ambiguous, the implementer - must ask the specifier rather than guessing. Wrong guesses are more expensive - than a question. diff --git a/.claude/docs/agent-teams.md b/.claude/docs/agent-teams.md new file mode 100644 index 00000000..b24f2837 --- /dev/null +++ b/.claude/docs/agent-teams.md @@ -0,0 +1,27 @@ +# Agent Teams (experimental — opt-in) + +> On-demand reference. Demoted out of `coordination-rules.md` (an always-loaded +> CLAUDE.md import) in v1.1 — read this when actually considering a team, not +> on every turn. + +Multiple independent Claude Code *sessions* running simultaneously, coordinated +via a shared task list. Each session has its own context window and token budget. +Requires the `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` environment variable. + +## Use agent teams when + +- Work spans multiple subsystems that will not touch the same files +- Each workstream would take >30 minutes and benefits from true parallelism +- A senior agent (technical-director, producer) needs to coordinate 3+ specialist + sessions working on different epics simultaneously + +## Do not use agent teams when + +- One session's output is required as input for another (use sequential subagents) +- The task fits in a single session's context (use subagents instead) +- Cost is a concern — each team member burns tokens independently + +## Current status + +Opt-in via `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`. Document first usage here +when adopted. diff --git a/.claude/docs/automation-modes.md b/.claude/docs/automation-modes.md new file mode 100644 index 00000000..ecb80f7e --- /dev/null +++ b/.claude/docs/automation-modes.md @@ -0,0 +1,261 @@ +# Automation Modes — Shared Skill Pattern + +This document defines the standard `modes.automation` pattern for every skill +that uses `AskUserQuestion` or writes files. Skills reference this document +instead of embedding the full mode-handling rules inline — eliminating drift +when the pattern needs updating. + +**Scope**: ~44 skills across authoring, review, team orchestration, +implementation, and utility categories. Any skill that gates a decision +through `AskUserQuestion` or writes files should follow this pattern. + +**Companion spec**: `.claude/docs/effects-map.md` +section `modes.automation` is the source of truth for behavior. This document +is the implementation pattern. + +> **Do not read `effects-map.md` during a skill run** — it is ~110 KB, a +> reference for authoring the spec rather than a runtime input, and **this +> document is self-sufficient** for deciding what to ask and what to proceed on. +> If a case genuinely is not covered here, read only its `modes.automation` +> section, never the file. + +--- + +## How to Use This Document + +In any skill, add a single resolution step at startup and reference the +mode-aware AskUserQuestion pattern below at each gated site. + +**Startup prelude** (add near Phase 0 / Phase 1): + +``` +Resolve automation mode (once, store for the run): +1. Read `modes.automation` from `project.local.yaml` (if present) → use that +2. Else read `modes.automation` from `project.yaml` → use that value +3. Else → default to `collaborative` + +Pattern reference: `.claude/docs/automation-modes.md`. +``` + +This resolution is automatic via `get_effective_yaml_key modes.automation` — +the helper deep-merges project.local.yaml on top of project.yaml. + +**At each AskUserQuestion site**, follow the pattern table in §Universal +Rules below. The skill author labels each decision as **major** or **minor** +(per the classification matrix in §Major vs Minor) so the pattern can apply +the right rule. + +--- + +## The Three Modes + +| Mode | Speed | Control | What changes | +|------|-------|---------|--------------| +| `collaborative` | Slowest | Full — every decision is the user's | Default. Q→O→D→Draft→Approval strictly followed. | +| `guided` | Balanced | High — major decisions are the user's, minor ones proceed automatically | AI states recommendation and proceeds for minor decisions. AskUserQuestion reserved for major decisions. | +| `autonomous` | Fastest | Low — AI decides, logs, and proceeds | No AskUserQuestion (except `automation_always_ask` categories). All decisions logged. | + +**Default**: `collaborative`. New projects start here. + +**Set by**: `/start`, `/settings`. + +--- + +## Universal Rules per Mode + +### Collaborative (current v1.0 behavior — no change) + +- `AskUserQuestion` called for every multi-option decision +- 2–4 options presented with pros/cons for every design choice +- Full draft shown and approved before every file write +- "May I write this to [filepath]?" asked before every write +- Section-by-section approval in multi-section authoring skills +- Multi-file changes require explicit approval + +### Guided + +- `AskUserQuestion` called for **major decisions only** (see classification below) +- Minor decisions: AI states recommendation inline and proceeds — e.g. + > *"Going with a static utility pattern here — it fits the existing architecture. Continuing unless you want to change direction."* +- Draft shown briefly before writing — proceeds after a short summary, does not wait for explicit "yes" +- "May I write?" asked for **new files only** — updates to existing files proceed directly +- Still presents options for major decisions but caps at 2 choices with a clear recommendation +- Multi-section authoring: writes each approved section immediately, no per-section confirmation + +### Autonomous + +- No `AskUserQuestion` calls **except** for categories listed in `modes.automation_always_ask` +- No draft review +- No "May I write?" prompts — writes directly +- Picks the recommended option for every decision without presenting alternatives +- All decisions logged via `log_decision` (helper in yaml-helper.sh) to + `production/session-logs/decision-log.md` +- User reviews decision log post-session to audit choices made + +--- + +## Major vs Minor Decision Classification + +Skills label each gated decision as **major** or **minor** before applying +the mode rules. + +### Major — always use AskUserQuestion in guided mode + +| Decision type | Example | +|---------------|---------| +| Choosing a system name or document path | "What should we call this system?" | +| Mutually exclusive design directions | "Real-time or turn-based?" | +| Any choice that gates downstream work | Engine choice, architecture approach | +| Scope changes | "Cut this feature or slip the deadline?" | +| Any decision that can't be changed without significant rework | Core loop mechanic | + +### Minor — AI recommends and proceeds in guided mode + +| Decision type | Example | +|---------------|---------| +| Which section to work on next | "Moving to Edge Cases next" | +| Optional section inclusion | "Adding a Visual Notes section — fits the system" | +| Formatting and structure choices | Heading levels, table vs prose | +| Adding detail to an already-decided direction | Sub-options within an approved approach | +| Next-step routing after a phase completes | "Running design-review now" | + +--- + +## `automation_always_ask` Categories + +Even in `autonomous` mode, certain decision categories ALWAYS trigger +`AskUserQuestion`. The configured list lives at +`modes.automation_always_ask` in `project.yaml`. + +**Default** (when unset): `[scope_changes, file_deletions, schema_changes]`. + +### Recognized categories + +**This table is the set you may configure, NOT the set that always asks.** Only +the categories actually listed in `modes.automation_always_ask` — the three +defaults above, unless the project overrides them — interrupt an `autonomous` +run. The other rows do nothing until a project opts into them. Read the resolved +list with `resolve_config` rather than assuming a row here is active; a real run +logged two `architecture_decisions` as rule violations on a project where that +category was never configured. + +| Category | Examples of decisions in this category | +|----------|----------------------------------------| +| `scope_changes` | Cutting a feature, slipping a deadline, splitting/merging an epic, removing acceptance criteria | +| `file_deletions` | Removing a story, deleting a GDD, removing a system, removing a test file | +| `schema_changes` | Changes to project.yaml, story template, control manifest, ADR template, GDD template | +| `architecture_decisions` | New ADR creation, ADR replacement, system boundary changes | +| `version_bumps` | Engine version change, framework version bump, dependency major version change | +| `external_calls` | Invoking external APIs (asset gen, AI services) when in autonomous mode | + +### Helper + +```bash +is_always_ask_category +``` + +Returns 0 if the named category is in `modes.automation_always_ask`, +1 otherwise. Skills use this at decision points that fall into any +of the six categories above. + +--- + +## Exemptions — Skills That Ignore the Automation Setting + +These skills always behave as `collaborative` regardless of the setting: + +| Skill | Why always collaborative | +|-------|--------------------------| +| `hotfix` | Emergency decisions — user must approve scope and risk before any action | +| `gate-check` | Results must be reviewed — a gate verdict without acknowledgement defeats the purpose | +| `day-one-patch` | Release-critical — every action needs explicit sign-off | +| `setup-engine` | One-time irreversible choice that affects the entire project | + +These skills do NOT add the automation prelude described above. They keep +the v1.0 Q→O→D→Draft→Approval protocol exactly. + +--- + +## Decision Log Format (Autonomous Mode) + +When `autonomous` mode skips an `AskUserQuestion`, the skill calls +`log_decision` to write a structured entry. Format per entry: + +```markdown +## [timestamp] — [skill-name] + +**Decision point:** [what was being decided] +**Options considered:** [list of options that would have been presented] +**Chosen:** [what was picked] +**Reason:** [one-line rationale] +**Category:** [scope_changes | file_deletions | schema_changes | … or "minor"] +``` + +Written to `production/session-logs/decision-log.md` (append-only, never +truncated, created if absent). The user reviews the log post-session to +audit choices made. + +### Helper + +```bash +log_decision "" "" "" "" "" "" +``` + +Wraps the format above. Skills call this at every decision point in +autonomous mode, including ones where the chosen value differs from +the recommended default (so the audit trail is complete). + +### What to log, and at what granularity + +These rules keep autonomous logs consistent across runs: + +- **Log every decision that would have shown an `AskUserQuestion` widget** — + including option/framing choices, not only final approvals. The + framing/options decisions (which approach, which formula, which edge + cases) are usually the most informative entries; do not log only the + rote "approve this section" steps. +- **Collapse a single multi-tab widget into ONE entry.** List each tab's + choice under **Chosen** rather than emitting one entry per tab. +- **Do NOT log conversational answers the model synthesizes for itself** + (e.g. internal Q&A while drafting a section) — only log what would have + been an explicit user decision. +- **Do NOT emit a separate entry for routine "may I write?" file gates** — + fold the write into the decision it implements (autonomous mode writes + directly anyway). + +### Categorizing data-file writes + +The most common categorization mistake is treating a data-file append as a +`schema_changes` decision: + +- **Appending facts to a registry or index** — e.g. + `design/registry/entities.yaml`, `design/gdd/systems-index.md` — is + **`minor`**. It adds data without altering a *template*. +- **`schema_changes`** means editing the *structure* of `project.yaml` or a + template (story / ADR / GDD / control-manifest template). These are + always-ask. +- **When genuinely unsure** between `minor` and an always-ask category, + prefer the always-ask category and prompt — failing safe. + +--- + +## The Pattern Skills Apply at Each AskUserQuestion Site + +``` +At each decision point in this skill: + +1. Classify the decision: major or minor (see §Major vs Minor above). +2. Check whether the decision falls into an `automation_always_ask` + category: scope_changes, file_deletions, schema_changes, + architecture_decisions, version_bumps, external_calls. +3. Apply the mode rule: + + collaborative: AskUserQuestion always. + guided + major: AskUserQuestion with capped options + recommendation. + guided + minor: State recommendation inline, proceed. + autonomous + always_ask: AskUserQuestion (regardless of mode). + autonomous + other: Pick recommended, log via log_decision, proceed. +``` + +This is the contract. Skills that follow this pattern produce +consistent behavior across all three automation modes. diff --git a/.claude/docs/code-root-resolution.md b/.claude/docs/code-root-resolution.md new file mode 100644 index 00000000..4ab8096f --- /dev/null +++ b/.claude/docs/code-root-resolution.md @@ -0,0 +1,67 @@ +# Code Root Resolution + +Shared procedure for any skill that reads, scans, counts or writes game source +files. Referenced from the point of use in each such skill. Those skills keep +the one load-bearing imperative inline — **an unresolved code root means the +check did not run** — and cite this file for the resolution order. + +`.claude/docs/directory-structure.md` is the authority for which directory holds +code. This file is the procedure for applying it. + +## The roots + +| `engine.name` | Code root | Why it cannot be `src/` | +|---|---|---| +| Godot | `src/` | No constraint; Godot loads from `res://` anywhere under the project root | +| Unity | `Assets/` | Unity compiles **only** `Assets/` and `Packages/` | +| Unreal | `Source//` | UnrealBuildTool discovers modules under `Source/`; content lives in `Content/` | + +`src/` is the **Godot row of that table, not a universal path.** + +## Resolution order + +1. `engine.name` from `project.local.yaml`, then `project.yaml`. +2. The legacy mirror: an `Engine:` line in + `.claude/docs/technical-preferences.md`. A value of `[TO BE CONFIGURED]` + does not count. +3. Derivation from the tree, but **only when it is unambiguous**. Exactly one of + `src/`, `Assets/` or `Source/` present means that is the root. Two or more + present means the project is genuinely undecidable — do not guess. +4. Otherwise: **unresolved.** + +The hooks already implement exactly this as `resolve_code_root()` in +`.claude/hooks/yaml-helper.sh`. Skills must match its semantics so that a hook +and a skill never disagree about where the code lives. + +## Unresolved does not default to `src/` + +This is the rule the whole file exists for, and it is obligation 2 of +`.claude/rules/skill-authoring.md`: an absent value may not default to the +permissive one. + +**For a read or scan:** report `NOT ASSESSED — code root unresolved` and say +which check did not run. Never report zero hits. + +A scan anchored to a root the project does not use matches nothing, and +**nothing is indistinguishable from a clean result.** A localization check finds +no hardcoded strings; a security scan finds no secrets; a content audit counts +no implemented content; a stage detector counts no source files and calls a +finished project greenfield. Every one of those reads as a pass. This has been +found repeatedly across skills and hooks, which is why the resolution lives in +one place now. + +**For a write:** do not write. Report the unresolved root and stop. + +Writing game code to the wrong root is worse than a false pass, because it +produces a project that cannot build: on Unity, source placed outside `Assets/` +is never compiled and never appears in the editor, so the work is silently +inert. Resolving the engine in order to pick a specialist agent is **not** the +same as resolving the code root — a skill can do the first correctly and still +write to the wrong directory. + +## Applying it + +Where a skill says `src/`, read it as *the code root* and substitute the table +above. Where a skill greps, glob the resolved root. Where a skill writes, +write under the resolved root and use the engine's own conventions beneath it +(`Assets/Scripts//` on Unity, `Source///` on Unreal). diff --git a/.claude/docs/coding-standards.md b/.claude/docs/coding-standards.md index d86347d8..c59dfbc6 100644 --- a/.claude/docs/coding-standards.md +++ b/.claude/docs/coding-standards.md @@ -1,7 +1,10 @@ # Coding Standards - All game code must include doc comments on public APIs -- Every system must have a corresponding architecture decision record in `docs/architecture/` +- Architecture decisions are recorded as ADRs in `docs/architecture/`. **How many is + set by `modes.workflow`** — all ADRs at `full`, *critical* ADRs only at `standard`, + and none required at `minimal`, where the decision log and `design/game-brief.md` + carry the rationale instead. See `.claude/docs/workflow-modes.md`. - Gameplay values must be data-driven (external config), never hardcoded - All public methods must be unit-testable (dependency injection over singletons) - Commits must reference the relevant design document or task ID @@ -9,12 +12,22 @@ - **Verification-driven development**: Write tests first when adding gameplay systems. For UI changes, verify with screenshots. Compare expected output to actual output before marking work complete. Every implementation should have a way to prove it works. +- **A parse check is not a run.** Every story that changes something + player-observable is launched and observed before it closes, and the + observation is a retained screenshot in `production/qa/evidence/`. Procedure + per engine: `.claude/docs/run-and-observe.md`. Not waived at + `qa.level: minimal` — tests are, the look is not. # Design Document Standards - All design docs use Markdown - Each mechanic has a dedicated document in `design/gdd/` -- Documents must include these 8 required sections: +- **How many of the 8 sections are required depends on `modes.workflow`** — + all 8 at `full`, 5 (+ Formulas when the system defines numeric rules — rates, + curves, thresholds, costs; the system's `Category` is a hint, not the test) at + `standard`, and no + GDD at all at `minimal`, where `design/game-brief.md` is the design record. + See `.claude/docs/workflow-modes.md`. The 8 sections: 1. **Overview** -- one-paragraph summary 2. **Player Fantasy** -- intended feeling and experience 3. **Detailed Rules** -- unambiguous mechanics @@ -31,14 +44,34 @@ All stories must have appropriate test evidence before they can be marked Done: -| Story Type | Required Evidence | Location | Gate Level | +| Story Type | Required Evidence | Location | Default Gate Level | |---|---|---|---| | **Logic** (formulas, AI, state machines) | Automated unit test — must pass | `tests/unit/[system]/` | BLOCKING | | **Integration** (multi-system) | Integration test OR documented playtest | `tests/integration/[system]/` | BLOCKING | -| **Visual/Feel** (animation, VFX, feel) | Screenshot + lead sign-off | `production/qa/evidence/` | ADVISORY | -| **UI** (menus, HUD, screens) | Manual walkthrough doc OR interaction test | `production/qa/evidence/` | ADVISORY | +| **Visual/Feel** (animation, VFX, feel) | Retained screenshot + lead sign-off | `production/qa/evidence/` | BLOCKING | +| **UI** (menus, HUD, screens) | Retained screenshot of each screen touched | `production/qa/evidence/` | BLOCKING | | **Config/Data** (balance tuning) | Smoke check pass | `production/qa/smoke-[date].md` | ADVISORY | +The **Default Gate Level** applies when `testing.strict` is not set in +`project.yaml`. A project may override it per test type: `testing.strict.logic`, +`.integration`, `.visual`, `.ui`, and `.config` each take `true` (BLOCKING) or +`false` (ADVISORY). `/story-done`, `/story-readiness`, `/dev-story`, +`/gate-check`, and `/smoke-check` resolve the effective level from that setting, +falling back to the defaults above when it is absent. + +> **Why Visual and UI block.** "Retained" is load-bearing — the screenshot must +> still be on disk in `production/qa/evidence/` when the story is closed. One +> that was captured and discarded is an assertion, not evidence. These two rows +> block because for a game the way it looks *is* the product: an advisory visual +> gate gets deferred in favour of whatever does block, and the result is a +> well-tested game that looks wrong. + +> **Exception — `/smoke-check`.** The ADVISORY default for **Config/Data** above +> applies to *per-story evidence* gates. `/smoke-check` is a build-health gate, +> not a per-story evidence gate, so its own unset default for +> `testing.strict.config` is **BLOCKING**. This divergence is intentional and is +> documented at both sites; do not reconcile one to the other. + ## Automated Test Rules - **Naming**: `[system]_[feature]_test.[ext]` for files; `test_[scenario]_[expected]` for functions diff --git a/.claude/docs/config-resolution.md b/.claude/docs/config-resolution.md new file mode 100644 index 00000000..1dbb2e5a --- /dev/null +++ b/.claude/docs/config-resolution.md @@ -0,0 +1,262 @@ +# Config Resolution + +How a skill learns its effective settings. **One command, no prose chain.** + +## The rule + +A skill that needs config puts this near the top of its **body** (not frontmatter): + +````markdown +!`source .claude/hooks/yaml-helper.sh 2>/dev/null && resolve_config` +```` + +The command runs as preprocessing *before the model sees the skill*, and its +output replaces the placeholder inline. The skill then reads the resolved values +and **does not re-derive them**. + +Skills that resolve a per-system tier pass the system name: `resolve_config combat`. + +## Why the body, not `context:` + +`` !`cmd` `` in the skill body is the documented mechanism. Frontmatter +`context:` accepts only `context: fork` — it does **not** run shell. A +`context: |` block containing `!` commands **never executes**; put the command in +the skill body instead. + +Two consequences: + +- `allowed-tools` does **not** gate this. It governs tools the *model* calls + during the turn; preprocessing happens before that. A skill needs no `Bash` + grant to use `resolve_config`. +- The repo-wide setting `disableSkillShellExecution: true` disables it. If a + skill sees no config block, it falls back to the defaults below. + +## The block + +``` +=== CCGS Resolved Config === +review_mode: lean (production/review-mode.txt) +automation: guided (project.local.yaml) +workflow: standard (project.yaml) +docs.density: balanced (default) +story_granularity: balanced (default) +qa.level: standard (default) +team.size: individual (rigor:standard) +project.stage: Systems Design (production/stage.txt) +automation_always_ask: scope_changes, file_deletions, schema_changes (default) +engine: Godot 4.6 (project.yaml) +testing.strict: logic=true integration=false visual=unset ui=unset config=unset (unset = each skill applies its own default) +system_overrides: combat=full inventory=minimal +notes: none +Values above are fully resolved (local -> yaml -> legacy -> default). Use as-is. +An inline --review flag, if passed, overrides review_mode. +=== end CCGS config === +``` + +Every knob line ends with its **provenance** in parentheses. That is what makes a +wrong value diagnosable — `review_mode: solo (default)` when `project.yaml` says +`full` points straight at the problem. + +## Resolution chain + +| Step | Source | Applies to | +|---|---|---| +| 1 | `project.local.yaml` | any setting on the `/settings --local` whitelist (12 keys — `modes.review_mode`, `modes.automation`, `modes.automation_always_ask`, `team.size`, all five `testing.strict.*`, `performance.enforce`, `features.*`) | +| 2 | `project.yaml` | all keys | +| 3 | legacy plain-text mirror | `modes.review_mode` → `production/review-mode.txt`; `project.stage` → `production/stage.txt` | +| 4 | **`modes.rigor` expansion** | the six knobs `rigor` fronts — `modes.workflow`, `docs.density`, `qa.level`, `modes.story_granularity`, `modes.review_mode`, `team.size` | +| 5 | documented default | the table below | + +An **enum-invalid value does not win** — the chain continues past it and the +rejected value is named on the `notes:` line. A typo degrades to the documented +default rather than propagating a nonsense mode into every skill. + +> **Step 4 sits below every explicit source and above the terminal default, and +> that ordering is the whole back-compat guarantee.** A `project.yaml` that +> already sets `docs.density: terse` resolves exactly as it did before `rigor` +> existed, because step 2 answers first. Invert steps 4 and 5 and `rigor` becomes +> a no-op; hoist step 4 above step 2 and it silently overrides settings people +> already have. Tests **X.2** and **X.3** assert both directions. +> +> Derived values report their provenance as **`rigor:`**, so the source +> vocabulary is `{project.local.yaml, project.yaml, , rigor:, +> default, unset}`. `docs.density: balanced (rigor:standard)` says the value was +> never written down anywhere — it follows the project's rigor level and will +> move if that level moves. + +> **Read scope == write whitelist.** Anything `/settings --local` may *write* +> is also *read* during resolution. If the read scope were ever narrower, +> `/settings --local modes.review_mode=solo` would write a value +> that `/settings` accepted and displayed and every skill then ignored. The +> test suite asserts the two lists stay in step — adding a setting to one +> without the other reproduces that bug. +> +> This matches the spec: `effects-map.md` § "Local Override Pattern" has always +> stated the chain applies to *every* setting. The narrow read scope was an +> implementation gap, not a design. + +> **A key OUTSIDE the whitelist, hand-written into `project.local.yaml`, is +> reported rather than swallowed.** `/settings --local` refuses to +> write one, but the file exists to be edited by hand, so that path needs its own +> guard. `modes.rigor: full` written there is a real key with a legal value, so +> `validate_yaml_enum` passes it — and then resolution never consults the local +> file for that path, so the setting vanishes and the user sees the default they +> were trying to override, with no error anywhere. Every check in the chain +> validated the **value**; nothing validated the **location**. +> +> `validate_local_scope` now names such keys on `notes:`. It **warns and never +> reconciles**, the same shape as the stage-mirror check: it does not edit the +> file, does not begin honouring the key, and does not change resolution. A +> locked setting is locked because it decides which artifacts exist on disk, so +> a helper that quietly applied one would let two developers' checkouts diverge +> — the exact thing the whitelist prevents. The user moves the key to +> `project.yaml` or deletes it. +> +> `schema_version` and `framework.*` are exempt: `effects-map.md` classes them as +> file metadata rather than preferences, and a `project.local.yaml` carrying +> `schema_version: 1` is correct as written. + +## Defaults + +| Knob | Default | +|---|---| +| `modes.automation` | `collaborative` | +| `modes.rigor` | `minimal` | +| `modes.automation_always_ask` | `scope_changes`, `file_deletions`, `schema_changes` | + +**The six knobs `rigor` fronts have no terminal default at all.** `modes.workflow`, +`docs.density`, `qa.level`, `modes.story_granularity`, `modes.review_mode` and `team.size` are +deliberately absent from `_yaml_helper_defaults`: a default there would answer at step 5 before the +expansion at step 4 ever ran, making `rigor` a no-op for anyone who had not also +set the sub-knob. Their effective values come from the rigor level: + +| `modes.rigor` | `modes.workflow` | `docs.density` | `qa.level` | `modes.story_granularity` | `modes.review_mode` | `team.size` | +|---|---|---|---|---|---|---| +| `minimal` (default) | `minimal` | `terse` | `minimal` | `coarse` | `solo` | `individual` | +| `standard` | `standard` | `balanced` | `standard` | `balanced` | `lean` | `individual` | +| `full` | `full` | `thorough` | `full` | `fine` | `full` | `studio` | + +Because `rigor` itself defaults to `minimal`, an unconfigured project resolves +these six to the lean row. That default was `standard`; it changed because the +heavier tier measured several times more expensive to reach working code without +producing a better result. See the rationale block above `_yaml_helper_defaults` +in `.claude/hooks/yaml-helper.sh` for the reasoning and the ordering +constraint. Raising the tier is one question in `/start` or one `/settings` +call, and `settings-guidance.md § 4`'s upward triggers are written to fire from +this starting state. (`team.size` is the one knob the flip does not move: +`minimal` and `standard` both yield `individual`, and only `full` opts into the +`studio` roster.) Test **X.8** asserts the six stay defaultless. + +> **The flip DOES reach existing projects.** A `project.yaml` that sets some of +> the six explicitly but never sets `rigor` keeps its explicit values and takes +> the new `minimal` row for the rest -- so an unset `qa.level` that used to +> resolve `standard` now resolves `minimal`. Test **X.3** pins exactly this. +> Pin the old behaviour by setting `modes.rigor: standard` explicitly; see +> UPGRADING.md. + +`modes.workflow` is **fronted, not replaced**: it keeps +`workflow_overrides.system_overrides`, which still beats the derived value +(**X.6**). Setting any of the six explicitly overrides just that one and leaves +its siblings on the rigor level, which is how "comprehensive but compact" +(`rigor: full` + `docs.density: terse`) stays expressible. Two of the six — +`modes.review_mode` and `team.size` — are personal-experience knobs that also +remain overridable from `project.local.yaml` (that source sits above the +expansion), unlike the four on-disk knobs, which are locked. + +> **This table is asserted against the helper, not maintained by hand.** Test +> **X.10** reads `_yaml_helper_defaults` through `get_yaml_default` and checks +> both directions: every knob listed here must carry the value the helper +> actually returns, and none of the six fronted knobs may appear at all. It +> exists because a table can list knobs the helper has stopped defaulting and +> read as correct: the structural check pairs a knob with the word "default" on +> a single line, so a two-cell table row is invisible to it. Editing this table +> without editing the helper now fails the build. + +**`testing.strict.*` has no central default on purpose.** Its unset default +differs per skill by design: `/smoke-check` treats unset as **blocking** (it is a +build-health gate), while `/story-done` and `/dev-story` apply the per-story-type +table in `.claude/docs/coding-standards.md`. `resolve_config` therefore reports +only the *configured state* (`unset` where absent) and each skill applies its own +default. The script resolves **sources**; the skill owns **policy**. + +**`engine` is reported from `project.yaml` only.** No markdown reader is built. +Skills keep their existing per-field fallback to +`.claude/docs/technical-preferences.md`, which also remains the sole source for +forbidden patterns and allowed libraries — those never migrated. + +## Failure modes + +`resolve_config` **always exits 0 and always emits a complete block.** Each +condition below degrades to defaults and is named on `notes:`: + +| Condition | Behavior | +|---|---| +| `project.yaml` absent | legacy → defaults; `notes: project.yaml absent` | +| `project.yaml` **empty** | defaults; `notes: project.yaml is EMPTY`. Distinct from absent on purpose — a truncated write or an interrupted `/setup-engine` looks like this | +| Malformed YAML | unparseable keys resolve empty and fall through | +| Tab-indented lines | rejected, and the **line numbers are named** on `notes:` | +| CRLF line endings | parsed correctly (the Windows default) | +| UTF-8 BOM | parsed correctly; the BOM does not poison the first key | +| 3-space indent | parsed correctly; the parser is indent-tolerant | +| Key present, value empty | treated as **unset**; falls through to the next source, never to `""` | +| Enum-invalid value | chain continues; rejected value named in `notes:` | +| `project.local.yaml` without a base | `validate_local_yaml_base` message on `notes:` | +| Locked key in `project.local.yaml` | ignored as always, but now **named** on `notes:` — `local: not locally overridable, ignored — modes.rigor (move to project.yaml or delete)` | +| No Python interpreter | legacy files and defaults only; **explicitly noted** on `notes:` | +| No block at all | shell preprocessing disabled — use the defaults table above | + +A skill must never treat a missing block as "config is unset in an interesting +way". It means the block did not render; the defaults apply. + +### Two behaviours that are correct but SILENT + +In both cases the +resolved *value* is right — the chain falls through to the default — but the user +is told nothing, and their explicit configuration was discarded. + +| Input | What happens | Note emitted | +|---|---|---| +| **Duplicate key** (`rigor:` twice in one block) | **the last occurrence wins**, deterministically | **none** | +| **Wrong shape** — a list where a scalar belongs (`rigor:` followed by `- standard`) | key resolves empty, falls through to the default | **none** | + +**Why this is worth knowing rather than shrugging at.** An enum-*invalid* value is +announced (`modes.workflow: 'medium' is not a valid value … — ignored, chain +continued`). A structurally-invalid one is not. So a user who mistypes the value +gets told, and a user who mistypes the *structure* does not — and the second +mistake is the easier one to make by copy-paste. The framework has the mechanism +(`notes:`) and already uses it for absence, emptiness, tabs, locked keys, orphan +locals and bad enums. Shape is the gap. + +**Deliberately not fixed in v1.1.** Detecting "this key is present in the file but +resolved to nothing" means changing the parser every config read in the framework +goes through, and the empty-value fall-through above is a *legitimate* case of +exactly that signature. Distinguishing "empty on purpose" from "unparseable" is +real work, not a one-liner, and it lands in the most load-bearing file there is. +Recorded here as v1.2. + +**Duplicate-key precedence was undocumented before this entry.** Last-wins is +deterministic and reproducible, so it is not a bug — but nothing said so, which +meant nobody could rely on it either. + +## Why not inject at session start + +`resolve_config` is also safe to call from a hook, but session-start does **not** +call it. The block would cost tokens on every session whether or not any skill +needs config, and it would go stale mid-session as `/settings` writes land. The +per-skill body call is fresher and only loads where it is used. Session start +still prints the resolved review mode and any schema errors, which is the part a +human reads. + +## Testing + +The resolution chain, the legacy reader, enum fall-through, block shape and +every failure mode above are covered by the framework's own test suite under +`tests/`, along with the `modes.rigor` expansion and its precedence in both +directions. Two properties are asserted that are easy to lose: + +- The local **read** scope and the `/settings --local` **write** whitelist stay + in step. If they drift, `/settings` accepts and displays a value that every + skill then ignores. +- The expansion is *reachable* (`/start` asks about it) and *legible* + (`/settings` reports derived values rather than "(not set)"). A correct + expansion nobody is asked about and no view displays would be worth nothing. diff --git a/.claude/docs/context-management.md b/.claude/docs/context-management.md index 2734f3b3..cc76997b 100644 --- a/.claude/docs/context-management.md +++ b/.claude/docs/context-management.md @@ -17,13 +17,45 @@ after each significant milestone: - Implementation milestone reached - Test results obtained -The state file should contain: current task, progress checklist, key decisions -made, files being worked on, and open questions. +**Create it from `.claude/docs/templates/session-state.md`** — the file has a +schema, and hooks depend on it. + +Two regions, and the distinction is load-bearing: + +| Region | Who reads it | Rule | +|--------|--------------|------| +| `` … `` | `statusline.sh` (breadcrumb, Production+ only) | Keep the markers even when empty | +| `` … `` | `session-start.sh` and `pre-compact.sh` | **Overwrite, never append.** Under ~25 lines — hooks inject it verbatim | +| Everything after `` | humans, and an agent that wants detail | Free-form; no hook injects it | + +The checkpoint holds: current task, next step, what it is blocked on, files in +progress, open questions. Write it so a cold reader can resume without opening +anything else. + +> **Why the split.** One append-only file doing both jobs grows without bound — +> past 700 lines every consumer invents its own slice: a status line parsing a +> STATUS block nothing writes, `pre-compact` injecting `head -100` (12.7 KB, at +> the moment context is scarcest), `session-start` previewing `tail -20` — the +> opposite end. None was wrong, because nothing defined where +> the recoverable state lived. Now every consumer reads the named region, and +> the cost is fixed no matter how long the narrative grows. + +**Rotation.** When the narrative passes ~200 lines, move it into +`production/session-logs/`: + +```bash +bash .claude/scripts/rotate-session-state.sh # --dry-run to preview +``` + +The checkpoint stays, the narrative is appended to a dated log, nothing is +deleted. `session-start.sh` observes when rotation is due but never performs it. ### Status Line Block (Production+ only) When the project is in Production, Polish, or Release stage, include a structured -status block in `active.md` that the status line script can parse: +status block in `active.md` that the status line script can parse. (The project +stage is resolved from `project.stage` in `project.yaml`, falling back to +`production/stage.txt`.) ```markdown @@ -69,6 +101,37 @@ This keeps the context window holding only the *current* section's discussion - Medium (implement feature): ~8k tokens - Heavy (multi-system refactor): ~15k tokens +## Deterministic Helpers — `.claude/scripts/` + +The cheapest context saving is not summarising a document, it is **not reading it +at all**. When a question has a deterministic answer, a script should answer it +and the model should spend its budget on judgment instead. + +Two directories, two purposes — reach for the right one: + +| Directory | Purpose | Invoked by | +|---|---|---| +| `.claude/hooks/` | Event-driven — wired into `settings.json` and fired by Claude Code | the harness | +| `.claude/scripts/` | Manually invoked from a skill body | `bash .claude/scripts/.sh` | + +Current helpers: + +- **`gdd-structure-check.sh`** — which of the 8 standard sections a GDD contains. + Replaces a full-document read per GDD, and cannot hallucinate a missing + section. Reports **presence only**; the caller applies the workflow tier. +- **`review-scope.sh`** — which GDDs changed since the last cross-review, plus + their declared dependencies. Replaces reasoning through git history. + +Two rules when adding one: + +1. **A skill invoking a script needs `Bash` in `allowed-tools`**, or the step + silently no-ops. A skill that only needs *resolved config* does not — use the + documented `` !`cmd` `` body form, which is preprocessing and is not gated by + `allowed-tools` (see `.claude/docs/config-resolution.md`). +2. **Emit observations, not verdicts.** A script that scores or judges will + eventually contradict a tier, mode, or per-system override it does not know + about. Report what is on disk and let the caller decide what it means. + ## Subagent Delegation Use subagents for research and exploration to keep the main session clean. diff --git a/.claude/docs/coordination-rules.md b/.claude/docs/coordination-rules.md index 54dc1498..f1ab3c40 100644 --- a/.claude/docs/coordination-rules.md +++ b/.claude/docs/coordination-rules.md @@ -14,60 +14,43 @@ ## Model Tier Assignment -Skills and agents are assigned to model tiers based on task complexity: +Read `.claude/docs/model-tiers.md` on demand. It carries the tier table, the +per-skill assignments and the authoring rule. -| Tier | Model | When to use | -|------|-------|-------------| -| **Haiku** | `claude-haiku-4-5-20251001` | Read-only status checks, formatting, simple lookups — no creative judgment needed | -| **Sonnet** | `claude-sonnet-4-6` | Implementation, design authoring, analysis of individual systems — default for most work | -| **Opus** | `claude-opus-4-6` | Multi-document synthesis, high-stakes phase gate verdicts, cross-system holistic review | - -Skills with `model: haiku`: `/help`, `/sprint-status`, `/story-readiness`, `/scope-check`, -`/project-stage-detect`, `/changelog`, `/patch-notes`, `/onboard` - -Skills with `model: opus`: `/review-all-gdds`, `/architecture-review`, `/gate-check` - -All other skills default to Sonnet. When creating new skills, assign Haiku if the -skill only reads and formats; assign Opus if it must synthesize 5+ documents with -high-stakes output; otherwise leave unset (Sonnet). +**One line of it is load-bearing enough to restate here:** a skill's `model:` +frontmatter is **declared but not applied** — Claude Code reads it and serves the +skill from the session model regardless. Never tell a user that a skill's tier +is saving them money. The agent-side `model:` is a different mechanism and is +untested, not known-broken. ## Subagents vs Agent Teams This project uses two distinct multi-agent patterns: ### Subagents (current, always active) -Spawned via `Task` within a single Claude Code session. Used by all `team-*` skills -and orchestration skills. Subagents share the session's permission context, run -sequentially or in parallel within the session, and return results to the parent. +Spawned via the `Agent` tool within a single Claude Code session (renamed from +`Task` in Claude Code 2.1.63; `Task` still works as an alias). Used by all +`team-*` skills and orchestration skills. Subagents share the session's +permission context, run sequentially or in parallel within the session, and +return results to the parent. **When to spawn in parallel**: If two subagents' inputs are independent (neither -needs the other's output to begin), spawn both Task calls simultaneously rather -than waiting. Example: `/review-all-gdds` Phase 1 (consistency) and Phase 2 -(design theory) are independent — spawn both at the same time. +needs the other's output to begin), spawn both `Agent` calls simultaneously +rather than waiting. Example: `/review-all-gdds` **Phase 2** (consistency) and +**Phase 3** (design theory) are independent — spawn both at the same time. +Phase 1 loads the GDDs and both depend on it, so it is the one phase here that +must NOT be parallelised. ### Agent Teams (experimental — opt-in) -Multiple independent Claude Code *sessions* running simultaneously, coordinated -via a shared task list. Each session has its own context window and token budget. -Requires `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` environment variable. - -**Use agent teams when**: -- Work spans multiple subsystems that will not touch the same files -- Each workstream would take >30 minutes and benefits from true parallelism -- A senior agent (technical-director, producer) needs to coordinate 3+ specialist - sessions working on different epics simultaneously - -**Do not use agent teams when**: -- One session's output is required as input for another (use sequential subagents) -- The task fits in a single session's context (use subagents instead) -- Cost is a concern — each team member burns tokens independently - -**Current status**: Opt-in via `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`. Document first usage here when adopted. +Multiple independent Claude Code *sessions* coordinated via a shared task list. +Opt-in and never yet used here — read `.claude/docs/agent-teams.md` on demand +before proposing one. ## Parallel Task Protocol When an orchestration skill spawns multiple independent agents: -1. Issue all independent Task calls before waiting for any result +1. Issue all independent `Agent` calls before waiting for any result 2. Collect all results before proceeding to dependent phases 3. If any agent is BLOCKED, surface it immediately — do not silently skip 4. Always produce a partial report if some agents complete and others block diff --git a/.claude/docs/director-gates.md b/.claude/docs/director-gates.md index 7ca3bbdb..7c0b7cbf 100644 --- a/.claude/docs/director-gates.md +++ b/.claude/docs/director-gates.md @@ -1,96 +1,121 @@ # Director Gates — Shared Review Pattern -This document defines the standard gate prompts for all director and lead reviews -across every workflow stage. Skills reference gate IDs from this document instead -of embedding full prompts inline — eliminating drift when prompts need updating. +Index of all director and lead review gates. Skills reference gate IDs from this +index instead of embedding prompts inline — eliminating drift when prompts need +updating. Each gate's full definition (Trigger, Context to pass, Prompt, Verdicts) +lives in its own file under `.claude/docs/director-gates/`, named after the gate +ID in lowercase. **Scope**: All 7 production stages (Concept → Release), all 3 Tier 1 directors, all key Tier 2 leads. Any skill, team orchestrator, or workflow may invoke these gates. +**When a skill spawns a director for a gate, it must NOT read the gate definition +file itself. Include the gate definition file path in the `Agent` prompt and instruct +the director agent to read it first, then pass only the context fields that gate's +row requires. Gate definitions are read by the spawned agent, in the agent's own +context window.** + --- -## How to Use This Document +## Gate Index -In any skill, replace an inline director prompt with a reference: +Agent by prefix: `CD-` creative-director (Opus) · `TD-` technical-director (Opus) +· `PR-` producer (Opus) · `AD-` art-director (Sonnet) · `LP-` lead-programmer · +`QL-` qa-lead · `ND-` narrative-director (Tier 2 leads: Sonnet). +Definition files below are in `.claude/docs/director-gates/`. -``` -Spawn `creative-director` via Task using gate **CD-PILLARS** from -`.claude/docs/director-gates.md`. -``` - -Pass the context listed under that gate's **Context to pass** field, then handle -the verdict using the **Verdict handling** rules below. +| Gate ID | Purpose | Definition file | +|---------|---------|-----------------| +| CD-PILLARS | Pillar stress test after pillars are defined | cd-pillars.md | +| CD-GDD-ALIGN | GDD pillar alignment after a system GDD is authored | cd-gdd-align.md | +| CD-SYSTEMS | Systems decomposition vision check after `/map-systems` | cd-systems.md | +| CD-NARRATIVE | Narrative consistency of story/lore documents | cd-narrative.md | +| CD-PLAYTEST | Player experience validation of playtest reports | cd-playtest.md | +| CD-PHASE-GATE | Creative readiness at phase transition | cd-phase-gate.md | +| TD-SYSTEM-BOUNDARY | System boundary review before GDD authoring | td-system-boundary.md | +| TD-FEASIBILITY | Technical feasibility of early concept risks | td-feasibility.md | +| TD-ARCHITECTURE | Master architecture document sign-off | td-architecture.md | +| TD-ADR | Individual ADR review before Accepted | td-adr.md | +| TD-ENGINE-RISK | Engine version risk for post-cutoff APIs | td-engine-risk.md | +| TD-PHASE-GATE | Technical readiness at phase transition | td-phase-gate.md | +| TD-MANIFEST | Control manifest review before it is written | td-manifest.md | +| TD-CHANGE-IMPACT | Design-change impact assessment review | td-change-impact.md | +| PR-SCOPE | Scope and timeline validation | pr-scope.md | +| PR-SPRINT | Sprint plan feasibility review | pr-sprint.md | +| PR-MILESTONE | Milestone risk assessment | pr-milestone.md | +| PR-EPIC | Epic structure feasibility before story breakdown | pr-epic.md | +| PR-PHASE-GATE | Production readiness at phase transition | pr-phase-gate.md | +| AD-CONCEPT-VISUAL | Visual identity anchor after pillars lock | ad-concept-visual.md | +| AD-ART-BIBLE | Art bible sign-off before asset production | ad-art-bible.md | +| AD-PHASE-GATE | Visual readiness at phase transition | ad-phase-gate.md | +| LP-FEASIBILITY | Implementation feasibility of the architecture | lp-feasibility.md | +| LP-CODE-REVIEW | Code review of an implemented story | lp-code-review.md | +| QL-STORY-READY | Acceptance-criteria testability before sprint | ql-story-ready.md | +| QL-TEST-COVERAGE | Test coverage review before epic done / advance | ql-test-coverage.md | +| ND-CONSISTENCY | Narrative consistency of writer deliverables | nd-consistency.md | +| AD-VISUAL | Visual consistency of art/tech-art decisions | ad-visual.md | --- ## Review Modes -Review intensity controls whether director gates run. It can be set globally -(persists across sessions) or overridden per skill run. +Review intensity controls whether gates run. -**Global config**: `production/review-mode.txt` — one word: `full`, `lean`, or `solo`. -Set once during `/start`. Edit the file directly to change it at any time. +**Global config**: `modes.review_mode` in `project.yaml` — one word: `full`, +`lean`, or `solo`. Legacy fallback: `production/review-mode.txt` (single line, +same values); `project.yaml` wins when both are present. Set once during +`/start`, or change it any time with `/settings modes.review_mode=`. -**Per-run override**: any gate-using skill accepts `--review [full|lean|solo]` as an -argument. This overrides the global config for that run only. +**Per-run override**: any gate-using skill accepts `--review [full|lean|solo]`, +overriding the global config for that run only. -Examples: -``` -/brainstorm space horror → uses global mode -/brainstorm space horror --review full → forces full mode this run -/architecture-decision --review solo → skips all gates this run -``` - -| Mode | What runs | Best for | -|------|-----------|----------| -| `full` | All gates active — every workflow step reviewed | Teams, learning users, or when you want thorough director feedback at every step | -| `lean` | PHASE-GATEs only (`/gate-check`) — per-skill gates skipped | **Default** — solo devs and small teams; directors review at milestones only | -| `solo` | No director gates anywhere | Game jams, prototypes, maximum speed | - -**Check pattern — apply before every gate spawn:** - -``` -Before spawning gate [GATE-ID]: -1. If skill was called with --review [mode], use that -2. Else read production/review-mode.txt -3. Else default to lean - -Apply the resolved mode: -- solo → skip all gates. Note: "[GATE-ID] skipped — Solo mode" -- lean → skip unless this is a PHASE-GATE (CD-PHASE-GATE, TD-PHASE-GATE, PR-PHASE-GATE, AD-PHASE-GATE) - Note: "[GATE-ID] skipped — Lean mode" -- full → spawn as normal -``` +| Mode | What runs | +|------|-----------| +| `full` | All gates active — every workflow step reviewed | +| `lean` | PHASE-GATEs only (`/gate-check`) — per-skill gates skipped. **Default** | +| `solo` | No director gates anywhere (game jams, prototypes, max speed) | --- ## Invocation Pattern (copy into any skill) -**MANDATORY: Resolve review mode before every gate spawn.** Never spawn a gate without checking. The resolved mode is determined once per skill run: -1. If skill was called with `--review [mode]`, use that -2. Else read `production/review-mode.txt` -3. Else default to `lean` +**MANDATORY: apply the review mode before every gate spawn.** + +The value is already resolved — skills call +`` !`source .claude/hooks/yaml-helper.sh && resolve_config --keys review_mode` `` +and read `review_mode` from the emitted block. Do **not** re-derive it here. The +full chain (`project.local.yaml` → `project.yaml` → `production/review-mode.txt` +→ `modes.rigor` expansion, `standard`→`lean`), its defaults and its failure modes are specified in +`.claude/docs/config-resolution.md`, and covered by the config test suite. + +An inline `--review [mode]` argument, if the skill accepts one, overrides the +resolved value for that run. Apply the resolved mode: -- `solo` → **skip all gates**. Note in output: `[GATE-ID] skipped — Solo mode` +- `solo` → **skip all gates**. Note: `[GATE-ID] skipped — Solo mode` - `lean` → **skip unless this is a PHASE-GATE** (CD-PHASE-GATE, TD-PHASE-GATE, PR-PHASE-GATE, AD-PHASE-GATE). Note: `[GATE-ID] skipped — Lean mode` - `full` → spawn as normal +> **PHASE-GATE panel width is a second, independent axis.** `review_mode` decides +> whether the four PHASE-GATEs run at all; `modes.workflow` decides how many of +> them run — `minimal` → PR only, `standard` → TD + PR, `full` → all four. The +> rule and its rationale live in `/gate-check` Section 4b, the only skill that +> spawns PHASE-GATEs. Every other gate in this index is single-director and +> unaffected. Width never softens a verdict: the escalation rule below applies +> unchanged to whoever ran. + ``` # Apply mode check, then: -Spawn `[agent-name]` via Task: -- Gate: [GATE-ID] (see .claude/docs/director-gates.md) -- Context: [fields listed under that gate] +Spawn `[agent-name]` via `Agent`: +- Gate: [GATE-ID] — `Agent` prompt instructs the agent to read + `.claude/docs/director-gates/[gate-id].md` FIRST (parent must not read it) +- Context: [fields under that gate's **Context to pass**] - Await the verdict before proceeding. ``` -For parallel spawning (multiple directors at the same gate point): - -``` -# Apply mode check for each gate first, then spawn all that survive: -Spawn all [N] agents simultaneously via Task — issue all Task calls before -waiting for any result. Collect all verdicts before proceeding. -``` +For parallel spawning (multiple directors at one gate point): apply the mode +check per gate, then spawn all surviving agents simultaneously — issue all `Agent` +calls before waiting for any result; collect all verdicts before proceeding. --- @@ -122,655 +147,14 @@ For phase gates, record in `docs/architecture/architecture.md` or --- -## Tier 1 — Creative Director Gates - -Agent: `creative-director` | Model tier: Opus | Domain: Vision, pillars, player experience - ---- - -### CD-PILLARS — Pillar Stress Test - -**Trigger**: After game pillars and anti-pillars are defined (brainstorm Phase 4, -or any time pillars are revised) - -**Context to pass**: -- Full pillar set with names, definitions, and design tests -- Anti-pillars list -- Core fantasy statement -- Unique hook ("Like X, AND ALSO Y") - -**Prompt**: -> "Review these game pillars. Are they falsifiable — could a real design decision -> actually fail this pillar? Do they create meaningful tension with each other? Do -> they differentiate this game from its closest comparables? Would they help resolve -> a design disagreement in practice, or are they too vague to be useful? Return -> specific feedback for each pillar and an overall verdict: APPROVE (strong), CONCERNS -> [list] (needs sharpening), or REJECT (weak — pillars do not carry weight)." - -**Verdicts**: APPROVE / CONCERNS / REJECT - ---- - -### CD-GDD-ALIGN — GDD Pillar Alignment Check - -**Trigger**: After a system GDD is authored (design-system, quick-design, or any -workflow that produces a GDD) - -**Context to pass**: -- GDD file path -- Game pillars (from `design/gdd/game-concept.md` or `design/gdd/game-pillars.md`) -- MDA aesthetics target for this game -- System's stated Player Fantasy section - -**Prompt**: -> "Review this system GDD for pillar alignment. Does every section serve the stated -> pillars? Are there mechanics or rules that contradict or weaken a pillar? Does -> the Player Fantasy section match the game's core fantasy? Return APPROVE, CONCERNS -> [specific sections with issues], or REJECT [pillar violations that must be -> redesigned before this system is implementable]." - -**Verdicts**: APPROVE / CONCERNS / REJECT - ---- - -### CD-SYSTEMS — Systems Decomposition Vision Check - -**Trigger**: After the systems index is written by `/map-systems` — validates the -complete system set before GDD authoring begins - -**Context to pass**: -- Systems index path (`design/gdd/systems-index.md`) -- Game pillars and core fantasy (from `design/gdd/game-concept.md`) -- Priority tier assignments (MVP / Vertical Slice / Alpha / Full Vision) -- Any high-risk or bottleneck systems identified in the dependency map - -**Prompt**: -> "Review this systems decomposition against the game's design pillars. Does the -> full set of MVP-tier systems collectively deliver the core fantasy? Are there -> systems whose mechanics don't serve any stated pillar — indicating they may be -> scope creep? Are there pillar-critical player experiences that have no system -> assigned to deliver them? Are any systems missing that the core loop requires? -> Return APPROVE (systems serve the vision), CONCERNS [specific gaps or -> misalignments with their pillar implications], or REJECT [fundamental gaps — -> the decomposition misses critical design intent and must be revised before GDD -> authoring begins]." - -**Verdicts**: APPROVE / CONCERNS / REJECT - ---- - -### CD-NARRATIVE — Narrative Consistency Check - -**Trigger**: After narrative GDDs, lore documents, dialogue specs, or world-building -documents are authored (team-narrative, design-system for story systems, writer -deliverables) - -**Context to pass**: -- Document file path(s) -- Game pillars -- Narrative direction brief or tone guide (if exists at `design/narrative/`) -- Any existing lore that the new document references - -**Prompt**: -> "Review this narrative content for consistency with the game's pillars and -> established world rules. Does the tone match the game's established voice? Are -> there contradictions with existing lore or world-building? Does the content serve -> the player experience pillar? Return APPROVE, CONCERNS [specific inconsistencies], -> or REJECT [contradictions that break world coherence]." - -**Verdicts**: APPROVE / CONCERNS / REJECT - ---- - -### CD-PLAYTEST — Player Experience Validation - -**Trigger**: After playtest reports are generated (`/playtest-report`), or after -any session that produces player feedback - -**Context to pass**: -- Playtest report file path -- Game pillars and core fantasy statement -- The specific hypothesis being tested - -**Prompt**: -> "Review this playtest report against the game's design pillars and core fantasy. -> Is the player experience matching the intended fantasy? Are there systematic issues -> that represent pillar drift — mechanics that feel fine in isolation but undermine -> the intended experience? Return APPROVE (core fantasy is landing), CONCERNS [gaps -> between intended and actual experience], or REJECT [core fantasy is not present — -> redesign needed before further playtesting]." - -**Verdicts**: APPROVE / CONCERNS / REJECT - ---- - -### CD-PHASE-GATE — Creative Readiness at Phase Transition - -**Trigger**: Always at `/gate-check` — spawn in parallel with TD-PHASE-GATE and PR-PHASE-GATE - -**Context to pass**: -- Target phase name -- List of all artifacts present (file paths) -- Game pillars and core fantasy - -**Prompt**: -> "Review the current project state for [target phase] gate readiness from a -> creative direction perspective. Are the game pillars faithfully represented in -> all design artifacts? Does the current state preserve the core fantasy? Are there -> any design decisions across GDDs or architecture that compromise the intended -> player experience? Return READY, CONCERNS [list], or NOT READY [blockers]." - -**Verdicts**: READY / CONCERNS / NOT READY - ---- - -## Tier 1 — Technical Director Gates - -Agent: `technical-director` | Model tier: Opus | Domain: Architecture, engine risk, performance - ---- - -### TD-SYSTEM-BOUNDARY — System Boundary Architecture Review - -**Trigger**: After `/map-systems` Phase 3 dependency mapping is agreed but before -GDD authoring begins — validates that the system structure is architecturally -sound before teams invest in writing GDDs against it - -**Context to pass**: -- Systems index path (or the dependency map summary if index not yet written) -- Layer assignments (Foundation / Core / Feature / Presentation / Polish) -- The full dependency graph (what each system depends on) -- Any bottleneck systems flagged (many dependents) -- Any circular dependencies found and their proposed resolutions - -**Prompt**: -> "Review this systems decomposition from an architectural perspective before GDD -> authoring begins. Are the system boundaries clean — does each system own a -> distinct concern with minimal overlap? Are there God Object risks (systems doing -> too much)? Does the dependency ordering create implementation-sequencing problems? -> Are there implicit shared-state problems in the proposed boundaries that will -> cause tight coupling when implemented? Are any Foundation-layer systems actually -> dependent on Feature-layer systems (inverted dependency)? Return APPROVE -> (boundaries are architecturally sound — proceed to GDD authoring), CONCERNS -> [specific boundary issues to address in the GDDs themselves], or REJECT -> [fundamental boundary problems — the system structure will cause architectural -> issues and must be restructured before any GDD is written]." - -**Verdicts**: APPROVE / CONCERNS / REJECT - ---- - -### TD-FEASIBILITY — Technical Feasibility Assessment - -**Trigger**: After biggest technical risks are identified during scope/feasibility -(brainstorm Phase 6, quick-design, or any early-stage concept with technical unknowns) - -**Context to pass**: -- Concept's core loop description -- Platform target -- Engine choice (or "undecided") -- List of identified technical risks - -**Prompt**: -> "Review these technical risks for a [genre] game targeting [platform] using -> [engine or 'undecided engine']. Flag any HIGH risk items that could invalidate -> the concept as described, any risks that are engine-specific and should influence -> the engine choice, and any risks that are commonly underestimated by solo -> developers. Return VIABLE (risks are manageable), CONCERNS [list with mitigation -> suggestions], or HIGH RISK [blockers that require concept or scope revision]." - -**Verdicts**: VIABLE / CONCERNS / HIGH RISK - ---- - -### TD-ARCHITECTURE — Architecture Sign-Off - -**Trigger**: After the master architecture document is drafted (`/create-architecture` -Phase 7), and after any major architecture revision - -**Context to pass**: -- Architecture document path (`docs/architecture/architecture.md`) -- Technical requirements baseline (TR-IDs and count) -- ADR list with statuses -- Engine knowledge gap inventory - -**Prompt**: -> "Review this master architecture document for technical soundness. Check: (1) Is -> every technical requirement from the baseline covered by an architectural decision? -> (2) Are all HIGH risk engine domains explicitly addressed or flagged as open -> questions? (3) Are the API boundaries clean, minimal, and implementable? (4) Are -> Foundation layer ADR gaps resolved before implementation begins? Return APPROVE, -> CONCERNS [list], or REJECT [blockers that must be resolved before coding starts]." - -**Verdicts**: APPROVE / CONCERNS / REJECT - ---- - -### TD-ADR — Architecture Decision Review - -**Trigger**: After an individual ADR is authored (`/architecture-decision`), before -it is marked Accepted - -**Context to pass**: -- ADR file path -- Engine version and knowledge gap risk level for the domain -- Related ADRs (if any) - -**Prompt**: -> "Review this Architecture Decision Record. Does it have a clear problem statement -> and rationale? Are the rejected alternatives genuinely considered? Does the -> Consequences section acknowledge the trade-offs honestly? Is the engine version -> stamped? Are post-cutoff API risks flagged? Does it link to the GDD requirements -> it covers? Return APPROVE, CONCERNS [specific gaps], or REJECT [the decision is -> underspecified or makes unsound technical assumptions]." - -**Verdicts**: APPROVE / CONCERNS / REJECT - ---- - -### TD-ENGINE-RISK — Engine Version Risk Review - -**Trigger**: When making architecture decisions that touch post-cutoff engine APIs, -or before finalizing any engine-specific implementation approach - -**Context to pass**: -- The specific API or feature being used -- Engine version and LLM knowledge cutoff (from `docs/engine-reference/[engine]/VERSION.md`) -- Relevant excerpt from breaking-changes or deprecated-apis docs - -**Prompt**: -> "Review this engine API usage against the version reference. Is this API present -> in [engine version]? Has its signature, behaviour, or namespace changed since the -> LLM knowledge cutoff? Are there known deprecations or post-cutoff alternatives? -> Return APPROVE (safe to use as described), CONCERNS [verify before implementing], -> or REJECT [API has changed — provide corrected approach]." - -**Verdicts**: APPROVE / CONCERNS / REJECT - ---- - -### TD-PHASE-GATE — Technical Readiness at Phase Transition - -**Trigger**: Always at `/gate-check` — spawn in parallel with CD-PHASE-GATE and PR-PHASE-GATE - -**Context to pass**: -- Target phase name -- Architecture document path (if exists) -- Engine reference path -- ADR list - -**Prompt**: -> "Review the current project state for [target phase] gate readiness from a -> technical direction perspective. Is the architecture sound for this phase? Are -> all high-risk engine domains addressed? Are performance budgets realistic and -> documented? Are Foundation-layer decisions complete enough to begin implementation? -> Return READY, CONCERNS [list], or NOT READY [blockers]." - -**Verdicts**: READY / CONCERNS / NOT READY - ---- - -## Tier 1 — Producer Gates - -Agent: `producer` | Model tier: Opus | Domain: Scope, timeline, dependencies, production risk - ---- - -### PR-SCOPE — Scope and Timeline Validation - -**Trigger**: After scope tiers are defined (brainstorm Phase 6, quick-design, or -any workflow that produces an MVP definition and timeline estimate) - -**Context to pass**: -- Full vision scope description -- MVP definition -- Timeline estimate -- Team size (solo / small team / etc.) -- Scope tiers (what ships if time runs out) - -**Prompt**: -> "Review this scope estimate. Is the MVP achievable in the stated timeline for -> the stated team size? Are the scope tiers correctly ordered by risk — does each -> tier deliver a shippable product if work stops there? What is the most likely -> cut point under time pressure, and is it a graceful fallback or a broken product? -> Return REALISTIC (scope matches capacity), OPTIMISTIC [specific adjustments -> recommended], or UNREALISTIC [blockers — timeline or MVP must be revised]." - -**Verdicts**: REALISTIC / OPTIMISTIC / UNREALISTIC - ---- - -### PR-SPRINT — Sprint Feasibility Review - -**Trigger**: Before finalising a sprint plan (`/sprint-plan`), and after any -mid-sprint scope change - -**Context to pass**: -- Proposed sprint story list (titles, estimates, dependencies) -- Team capacity (hours available) -- Current sprint backlog debt (if any) -- Milestone constraints - -**Prompt**: -> "Review this sprint plan for feasibility. Is the story load realistic for the -> available capacity? Are stories correctly ordered by dependency? Are there hidden -> dependencies between stories that could block the sprint mid-way? Are any stories -> underestimated given their technical complexity? Return REALISTIC (plan is -> achievable), CONCERNS [specific risks], or UNREALISTIC [sprint must be -> descoped — identify which stories to defer]." - -**Verdicts**: REALISTIC / CONCERNS / UNREALISTIC - ---- - -### PR-MILESTONE — Milestone Risk Assessment - -**Trigger**: At milestone review (`/milestone-review`), at mid-sprint retrospectives, -or when a scope change is proposed that affects the milestone - -**Context to pass**: -- Milestone definition and target date -- Current completion percentage -- Blocked stories count -- Sprint velocity data (if available) - -**Prompt**: -> "Review this milestone status. Based on current velocity and blocked story count, -> will this milestone hit its target date? What are the top 3 production risks -> between now and the milestone? Are there scope items that should be cut to protect -> the milestone date vs. items that are non-negotiable? Return ON TRACK, AT RISK -> [specific mitigations], or OFF TRACK [date must slip or scope must cut — provide -> both options]." - -**Verdicts**: ON TRACK / AT RISK / OFF TRACK - ---- - -### PR-EPIC — Epic Structure Feasibility Review - -**Trigger**: After epics are defined by `/create-epics`, before stories are -broken out — validates the epic structure is producible before `/create-stories` -is invoked - -**Context to pass**: -- Epic definition file paths (all epics just created) -- Epic index path (`production/epics/index.md`) -- Milestone timeline and target dates -- Team capacity (solo / small team / size) -- Layer being epiced (Foundation / Core / Feature / etc.) - -**Prompt**: -> "Review this epic structure for production feasibility before story breakdown -> begins. Are the epic boundaries scoped appropriately — could each epic realistically -> complete before a milestone deadline? Are epics correctly ordered by system -> dependency — does any epic require another epic's output before it can start? -> Are any epics underscoped (too small, should merge) or overscoped (too large, -> should split into 2-3 focused epics)? Are the Foundation-layer epics scoped to -> allow Core-layer epics to begin at the start of the next sprint after Foundation -> completes? Return REALISTIC (epic structure is producible), CONCERNS [specific -> structural adjustments before stories are written], or UNREALISTIC [epics must -> be split, merged, or reordered — story breakdown cannot begin until resolved]." - -**Verdicts**: REALISTIC / CONCERNS / UNREALISTIC - ---- - -### PR-PHASE-GATE — Production Readiness at Phase Transition - -**Trigger**: Always at `/gate-check` — spawn in parallel with CD-PHASE-GATE and TD-PHASE-GATE - -**Context to pass**: -- Target phase name -- Sprint and milestone artifacts present -- Team size and capacity -- Current blocked story count - -**Prompt**: -> "Review the current project state for [target phase] gate readiness from a -> production perspective. Is the scope realistic for the stated timeline and team -> size? Are dependencies properly ordered so the team can actually execute in -> sequence? Are there milestone or sprint risks that could derail the phase within -> the first two sprints? Return READY, CONCERNS [list], or NOT READY [blockers]." - -**Verdicts**: READY / CONCERNS / NOT READY - ---- - -## Tier 1 — Art Director Gates - -Agent: `art-director` | Model tier: Sonnet | Domain: Visual identity, art bible, visual production readiness - ---- - -### AD-CONCEPT-VISUAL — Visual Identity Anchor - -**Trigger**: After game pillars are locked (brainstorm Phase 4), in parallel with CD-PILLARS - -**Context to pass**: -- Game concept (elevator pitch, core fantasy, unique hook) -- Full pillar set with names, definitions, and design tests -- Target platform (if known) -- Any reference games or visual touchstones mentioned by the user - -**Prompt**: -> "Based on these game pillars and core concept, propose 2-3 distinct visual identity -> directions. For each direction provide: (1) a one-line visual rule that could guide -> all visual decisions (e.g., 'everything must move', 'beauty is in the decay'), (2) -> mood and atmosphere targets, (3) shape language (sharp/rounded/organic/geometric -> emphasis), (4) color philosophy (palette direction, what colors mean in this world). -> Be specific — avoid generic descriptions. One direction should directly serve the -> primary design pillar. Name each direction. Recommend which best serves the stated -> pillars and explain why." - -**Verdicts**: CONCEPTS (multiple valid options — user selects) / STRONG (one direction clearly dominant) / CONCERNS (pillars don't provide enough direction to differentiate visual identity yet) - ---- - -### AD-ART-BIBLE — Art Bible Sign-Off - -**Trigger**: After the art bible is drafted (`/art-bible`), before asset production begins - -**Context to pass**: -- Art bible path (`design/art/art-bible.md`) -- Game pillars and core fantasy -- Platform and performance constraints (from `.claude/docs/technical-preferences.md` if configured) -- Visual identity anchor chosen during brainstorm (from `design/gdd/game-concept.md`) - -**Prompt**: -> "Review this art bible for completeness and internal consistency. Does the color -> system match the mood targets? Does the shape language follow from the visual -> identity statement? Are the asset standards achievable within the platform -> constraints? Does the character design direction give artists enough to work from -> without over-specifying? Are there contradictions between sections? Would an -> outsourcing team be able to produce assets from this document without additional -> briefing? Return APPROVE (art bible is production-ready), CONCERNS [specific -> sections needing clarification], or REJECT [fundamental inconsistencies that must -> be resolved before asset production begins]." - -**Verdicts**: APPROVE / CONCERNS / REJECT - ---- - -### AD-PHASE-GATE — Visual Readiness at Phase Transition - -**Trigger**: Always at `/gate-check` — spawn in parallel with CD-PHASE-GATE, TD-PHASE-GATE, and PR-PHASE-GATE - -**Context to pass**: -- Target phase name -- List of all art/visual artifacts present (file paths) -- Visual identity anchor from `design/gdd/game-concept.md` (if present) -- Art bible path if it exists (`design/art/art-bible.md`) - -**Prompt**: -> "Review the current project state for [target phase] gate readiness from a visual -> direction perspective. Is the visual identity established and documented at the -> level this phase requires? Are the right visual artifacts in place? Would visual -> teams be able to begin their work without visual direction gaps that cause costly -> rework later? Are there visual decisions that are being deferred past their latest -> responsible moment? Return READY, CONCERNS [specific visual direction gaps that -> could cause production rework], or NOT READY [visual blockers that must exist -> before this phase can succeed — specify what artifact is missing and why it -> matters at this stage]." - -**Verdicts**: READY / CONCERNS / NOT READY - ---- - -## Tier 2 — Lead Gates - -These gates are invoked by orchestration skills and senior skills when a domain -specialist's feasibility sign-off is needed. Tier 2 leads use Sonnet (default). - ---- - -### LP-FEASIBILITY — Lead Programmer Implementation Feasibility - -**Trigger**: After the master architecture document is written (`/create-architecture` -Phase 7b), or when a new architectural pattern is proposed - -**Context to pass**: -- Architecture document path -- Technical requirements baseline summary -- ADR list with statuses - -**Prompt**: -> "Review this architecture for implementation feasibility. Flag: (a) any decisions -> that would be difficult or impossible to implement with the stated engine and -> language, (b) any missing interface definitions that programmers would need to -> invent themselves, (c) any patterns that create avoidable technical debt or -> that contradict standard [engine] idioms. Return FEASIBLE, CONCERNS [list], or -> INFEASIBLE [blockers that make this architecture unimplementable as written]." - -**Verdicts**: FEASIBLE / CONCERNS / INFEASIBLE - ---- - -### LP-CODE-REVIEW — Lead Programmer Code Review - -**Trigger**: After a dev story is implemented (`/dev-story`, `/story-done`), or -as part of `/code-review` - -**Context to pass**: -- Implementation file paths -- Story file path (for acceptance criteria) -- Relevant GDD section -- ADR that governs this system - -**Prompt**: -> "Review this implementation against the story acceptance criteria and governing -> ADR. Does the code match the architecture boundary definitions? Are there -> violations of the coding standards or forbidden patterns? Is the public API -> testable and documented? Are there any correctness issues against the GDD rules? -> Return APPROVE, CONCERNS [specific issues], or REJECT [must be revised before merge]." - -**Verdicts**: APPROVE / CONCERNS / REJECT - ---- - -### QL-STORY-READY — QA Lead Story Readiness Check - -**Trigger**: Before a story is accepted into a sprint — invoked by `/create-stories`, -`/story-readiness`, and `/sprint-plan` during story selection - -**Context to pass**: -- Story file path -- Story type (Logic / Integration / Visual/Feel / UI / Config/Data) -- Acceptance criteria list (verbatim from the story) -- The GDD requirement (TR-ID and text) the story covers - -**Prompt**: -> "Review this story's acceptance criteria for testability before it enters the -> sprint. Are all criteria specific enough that a developer would know unambiguously -> when they are done? For Logic-type stories: can every criterion be verified with -> an automated test? For Integration stories: is each criterion observable in a -> controlled test environment? Flag criteria that are too vague to implement -> against, and flag criteria that require a full game build to test (mark these -> DEFERRED, not BLOCKED). Return ADEQUATE (criteria are implementable as written), -> GAPS [specific criteria needing refinement], or INADEQUATE [criteria are too -> vague — story must be revised before sprint inclusion]." - -**Verdicts**: ADEQUATE / GAPS / INADEQUATE - ---- - -### QL-TEST-COVERAGE — QA Lead Test Coverage Review - -**Trigger**: After implementation stories are complete, before marking an epic -done, or at `/gate-check` Production → Polish - -**Context to pass**: -- List of implemented stories with story types (Logic / Integration / Visual / UI / Config) -- Test file paths in `tests/` -- GDD acceptance criteria for the system - -**Prompt**: -> "Review the test coverage for these implementation stories. Are all Logic stories -> covered by passing unit tests? Are Integration stories covered by integration -> tests or documented playtests? Are the GDD acceptance criteria each mapped to at -> least one test? Are there untested edge cases from the GDD Edge Cases section? -> Return ADEQUATE (coverage meets standards), GAPS [specific missing tests], or -> INADEQUATE [critical logic is untested — do not advance]." - -**Verdicts**: ADEQUATE / GAPS / INADEQUATE - ---- - -### ND-CONSISTENCY — Narrative Director Consistency Check - -**Trigger**: After writer deliverables (dialogue, lore, item descriptions) are -authored, or when a design decision has narrative implications - -**Context to pass**: -- Document or content file path(s) -- Narrative bible or tone guide path (if exists) -- Relevant world-building rules -- Character or faction profiles affected - -**Prompt**: -> "Review this narrative content for internal consistency and adherence to -> established world rules. Are character voices consistent with their established -> profiles? Does the lore contradict any established facts? Is the tone consistent -> with the game's narrative direction? Return APPROVE, CONCERNS [specific -> inconsistencies to fix], or REJECT [contradictions that break the narrative -> foundation]." - -**Verdicts**: APPROVE / CONCERNS / REJECT - ---- - -### AD-VISUAL — Art Director Visual Consistency Review - -**Trigger**: After art direction decisions are made, when new asset types are -introduced, or when a tech art decision affects visual style - -**Context to pass**: -- Art bible path (if exists at `design/art/art-bible.md`) -- The specific asset type, style decision, or visual direction being reviewed -- Reference images or style descriptions -- Platform and performance constraints - -**Prompt**: -> "Review this visual direction decision for consistency with the established art -> style and production constraints. Does it match the art bible? Is it achievable -> within the platform's performance budget? Are there asset pipeline implications -> that create technical risk? Return APPROVE, CONCERNS [specific adjustments], or -> REJECT [style violation or production risk that must be resolved first]." - -**Verdicts**: APPROVE / CONCERNS / REJECT - ---- - ## Parallel Gate Protocol -When a workflow requires multiple directors at the same checkpoint (most common -at `/gate-check`), spawn all agents simultaneously: +At checkpoints needing multiple directors (most common at `/gate-check`): ``` -Spawn in parallel (issue all Task calls before waiting for any result): -1. creative-director → gate CD-PHASE-GATE -2. technical-director → gate TD-PHASE-GATE -3. producer → gate PR-PHASE-GATE -4. art-director → gate AD-PHASE-GATE +Spawn in parallel (issue all `Agent` calls before waiting for any result): +creative-director → CD-PHASE-GATE, technical-director → TD-PHASE-GATE, +producer → PR-PHASE-GATE, art-director → AD-PHASE-GATE Collect all four verdicts, then apply escalation rules: - Any NOT READY / REJECT → overall verdict minimum FAIL @@ -782,14 +166,14 @@ Collect all four verdicts, then apply escalation rules: ## Adding New Gates -When a new gate is needed for a new skill or workflow: - -1. Assign a gate ID: `[DIRECTOR-PREFIX]-[DESCRIPTIVE-SLUG]` - - Prefixes: `CD-` `TD-` `PR-` `LP-` `QL-` `ND-` `AD-` - - Add new prefixes for new agents: `audio-director` → `AU-`, `ux-designer` → `UX-` -2. Add the gate under the appropriate director section with all five fields: - Trigger, Context to pass, Prompt, Verdicts, and any special handling notes -3. Reference it in skills by ID only — never copy the prompt text into the skill +1. Assign a gate ID: `[DIRECTOR-PREFIX]-[DESCRIPTIVE-SLUG]`. Prefixes: `CD-` + `TD-` `PR-` `LP-` `QL-` `ND-` `AD-`; add new ones for new agents + (`audio-director` → `AU-`, `ux-designer` → `UX-`) +2. Create `.claude/docs/director-gates/[gate-id].md` (lowercase), starting with + the standard "> Gate definition..." header note, with all five fields: + Trigger, Context to pass, Prompt, Verdicts, special handling notes +3. Add its row to the Gate Index table above +4. Reference it in skills by ID only — never copy the prompt text into the skill --- @@ -798,9 +182,9 @@ When a new gate is needed for a new skill or workflow: | Stage | Required Gates | Optional Gates | |-------|---------------|----------------| | **Concept** | CD-PILLARS, AD-CONCEPT-VISUAL | TD-FEASIBILITY, PR-SCOPE | -| **Systems Design** | TD-SYSTEM-BOUNDARY, CD-SYSTEMS, PR-SCOPE, CD-GDD-ALIGN (per GDD) | ND-CONSISTENCY, AD-VISUAL | -| **Technical Setup** | TD-ARCHITECTURE, TD-ADR (per ADR), LP-FEASIBILITY, AD-ART-BIBLE | TD-ENGINE-RISK | +| **Systems Design** | TD-SYSTEM-BOUNDARY, CD-SYSTEMS, PR-SCOPE, CD-GDD-ALIGN (per GDD) | ND-CONSISTENCY, AD-VISUAL, TD-CHANGE-IMPACT (on GDD revision) | +| **Technical Setup** | TD-ARCHITECTURE, TD-ADR (per ADR), TD-MANIFEST, LP-FEASIBILITY, AD-ART-BIBLE | TD-ENGINE-RISK | | **Pre-Production** | PR-EPIC, QL-STORY-READY (per story), PR-SPRINT, all four PHASE-GATEs (via gate-check) | CD-PLAYTEST | -| **Production** | LP-CODE-REVIEW (per story), QL-STORY-READY, PR-SPRINT (per sprint), QL-TEST-COVERAGE (per sprint close-out) | PR-MILESTONE, AD-VISUAL | +| **Production** | LP-CODE-REVIEW (per story), QL-STORY-READY, PR-SPRINT (per sprint), QL-TEST-COVERAGE (per sprint close-out) | PR-MILESTONE, AD-VISUAL, TD-CHANGE-IMPACT (on GDD revision) | | **Polish** | QL-TEST-COVERAGE, CD-PLAYTEST, PR-MILESTONE | AD-VISUAL | | **Release** | All four PHASE-GATEs (via gate-check) | QL-TEST-COVERAGE | diff --git a/.claude/docs/director-gates/ad-art-bible.md b/.claude/docs/director-gates/ad-art-bible.md new file mode 100644 index 00000000..da50f8f3 --- /dev/null +++ b/.claude/docs/director-gates/ad-art-bible.md @@ -0,0 +1,26 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# AD-ART-BIBLE — Art Bible Sign-Off + +Agent: `art-director` | Model tier: Sonnet | Domain: Visual identity, art bible, visual production readiness + +**Trigger**: After the art bible is drafted (`/art-bible`), before asset production begins + +**Context to pass**: +- Art bible path (`design/art/art-bible.md`) +- Game pillars and core fantasy +- Platform and performance constraints (`platform.*` and `performance.*` from `project.yaml`, falling back to `.claude/docs/technical-preferences.md`) +- Visual identity anchor chosen during brainstorm (from `design/gdd/game-concept.md`) + +**Prompt**: +> "Review this art bible for completeness and internal consistency. Does the color +> system match the mood targets? Does the shape language follow from the visual +> identity statement? Are the asset standards achievable within the platform +> constraints? Does the character design direction give artists enough to work from +> without over-specifying? Are there contradictions between sections? Would an +> outsourcing team be able to produce assets from this document without additional +> briefing? Return APPROVE (art bible is production-ready), CONCERNS [specific +> sections needing clarification], or REJECT [fundamental inconsistencies that must +> be resolved before asset production begins]." + +**Verdicts**: APPROVE / CONCERNS / REJECT diff --git a/.claude/docs/director-gates/ad-concept-visual.md b/.claude/docs/director-gates/ad-concept-visual.md new file mode 100644 index 00000000..adeded97 --- /dev/null +++ b/.claude/docs/director-gates/ad-concept-visual.md @@ -0,0 +1,25 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# AD-CONCEPT-VISUAL — Visual Identity Anchor + +Agent: `art-director` | Model tier: Sonnet | Domain: Visual identity, art bible, visual production readiness + +**Trigger**: After game pillars are locked (brainstorm Phase 4), in parallel with CD-PILLARS + +**Context to pass**: +- Game concept (elevator pitch, core fantasy, unique hook) +- Full pillar set with names, definitions, and design tests +- Target platform (if known) +- Any reference games or visual touchstones mentioned by the user + +**Prompt**: +> "Based on these game pillars and core concept, propose 2-3 distinct visual identity +> directions. For each direction provide: (1) a one-line visual rule that could guide +> all visual decisions (e.g., 'everything must move', 'beauty is in the decay'), (2) +> mood and atmosphere targets, (3) shape language (sharp/rounded/organic/geometric +> emphasis), (4) color philosophy (palette direction, what colors mean in this world). +> Be specific — avoid generic descriptions. One direction should directly serve the +> primary design pillar. Name each direction. Recommend which best serves the stated +> pillars and explain why." + +**Verdicts**: CONCEPTS (multiple valid options — user selects) / STRONG (one direction clearly dominant) / CONCERNS (pillars don't provide enough direction to differentiate visual identity yet) diff --git a/.claude/docs/director-gates/ad-phase-gate.md b/.claude/docs/director-gates/ad-phase-gate.md new file mode 100644 index 00000000..2cbcaf09 --- /dev/null +++ b/.claude/docs/director-gates/ad-phase-gate.md @@ -0,0 +1,26 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# AD-PHASE-GATE — Visual Readiness at Phase Transition + +Agent: `art-director` | Model tier: Sonnet | Domain: Visual identity, art bible, visual production readiness + +**Trigger**: Always at `/gate-check` — spawn in parallel with CD-PHASE-GATE, TD-PHASE-GATE, and PR-PHASE-GATE + +**Context to pass**: +- Target phase name +- List of all art/visual artifacts present (file paths) +- Visual identity anchor from `design/gdd/game-concept.md` (if present) +- Art bible path if it exists (`design/art/art-bible.md`) + +**Prompt**: +> "Review the current project state for [target phase] gate readiness from a visual +> direction perspective. Is the visual identity established and documented at the +> level this phase requires? Are the right visual artifacts in place? Would visual +> teams be able to begin their work without visual direction gaps that cause costly +> rework later? Are there visual decisions that are being deferred past their latest +> responsible moment? Return READY, CONCERNS [specific visual direction gaps that +> could cause production rework], or NOT READY [visual blockers that must exist +> before this phase can succeed — specify what artifact is missing and why it +> matters at this stage]." + +**Verdicts**: READY / CONCERNS / NOT READY diff --git a/.claude/docs/director-gates/ad-visual.md b/.claude/docs/director-gates/ad-visual.md new file mode 100644 index 00000000..033103b2 --- /dev/null +++ b/.claude/docs/director-gates/ad-visual.md @@ -0,0 +1,23 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# AD-VISUAL — Art Director Visual Consistency Review + +Agent: `art-director` | Model tier: Sonnet (Tier 2 lead — invoked when a domain specialist's feasibility sign-off is needed) + +**Trigger**: After art direction decisions are made, when new asset types are +introduced, or when a tech art decision affects visual style + +**Context to pass**: +- Art bible path (if exists at `design/art/art-bible.md`) +- The specific asset type, style decision, or visual direction being reviewed +- Reference images or style descriptions +- Platform and performance constraints + +**Prompt**: +> "Review this visual direction decision for consistency with the established art +> style and production constraints. Does it match the art bible? Is it achievable +> within the platform's performance budget? Are there asset pipeline implications +> that create technical risk? Return APPROVE, CONCERNS [specific adjustments], or +> REJECT [style violation or production risk that must be resolved first]." + +**Verdicts**: APPROVE / CONCERNS / REJECT diff --git a/.claude/docs/director-gates/cd-gdd-align.md b/.claude/docs/director-gates/cd-gdd-align.md new file mode 100644 index 00000000..0ab51aca --- /dev/null +++ b/.claude/docs/director-gates/cd-gdd-align.md @@ -0,0 +1,23 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# CD-GDD-ALIGN — GDD Pillar Alignment Check + +Agent: `creative-director` | Model tier: Opus | Domain: Vision, pillars, player experience + +**Trigger**: After a system GDD is authored (design-system, quick-design, or any +workflow that produces a GDD) + +**Context to pass**: +- GDD file path +- Game pillars (from `design/gdd/game-concept.md` or `design/gdd/game-pillars.md`) +- MDA aesthetics target for this game +- System's stated Player Fantasy section + +**Prompt**: +> "Review this system GDD for pillar alignment. Does every section serve the stated +> pillars? Are there mechanics or rules that contradict or weaken a pillar? Does +> the Player Fantasy section match the game's core fantasy? Return APPROVE, CONCERNS +> [specific sections with issues], or REJECT [pillar violations that must be +> redesigned before this system is implementable]." + +**Verdicts**: APPROVE / CONCERNS / REJECT diff --git a/.claude/docs/director-gates/cd-narrative.md b/.claude/docs/director-gates/cd-narrative.md new file mode 100644 index 00000000..d6828ecf --- /dev/null +++ b/.claude/docs/director-gates/cd-narrative.md @@ -0,0 +1,24 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# CD-NARRATIVE — Narrative Consistency Check + +Agent: `creative-director` | Model tier: Opus | Domain: Vision, pillars, player experience + +**Trigger**: After narrative GDDs, lore documents, dialogue specs, or world-building +documents are authored (team-narrative, design-system for story systems, writer +deliverables) + +**Context to pass**: +- Document file path(s) +- Game pillars +- Narrative direction brief or tone guide (if exists at `design/narrative/`) +- Any existing lore that the new document references + +**Prompt**: +> "Review this narrative content for consistency with the game's pillars and +> established world rules. Does the tone match the game's established voice? Are +> there contradictions with existing lore or world-building? Does the content serve +> the player experience pillar? Return APPROVE, CONCERNS [specific inconsistencies], +> or REJECT [contradictions that break world coherence]." + +**Verdicts**: APPROVE / CONCERNS / REJECT diff --git a/.claude/docs/director-gates/cd-phase-gate.md b/.claude/docs/director-gates/cd-phase-gate.md new file mode 100644 index 00000000..6e9c8dfd --- /dev/null +++ b/.claude/docs/director-gates/cd-phase-gate.md @@ -0,0 +1,21 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# CD-PHASE-GATE — Creative Readiness at Phase Transition + +Agent: `creative-director` | Model tier: Opus | Domain: Vision, pillars, player experience + +**Trigger**: Always at `/gate-check` — spawn in parallel with TD-PHASE-GATE and PR-PHASE-GATE + +**Context to pass**: +- Target phase name +- List of all artifacts present (file paths) +- Game pillars and core fantasy + +**Prompt**: +> "Review the current project state for [target phase] gate readiness from a +> creative direction perspective. Are the game pillars faithfully represented in +> all design artifacts? Does the current state preserve the core fantasy? Are there +> any design decisions across GDDs or architecture that compromise the intended +> player experience? Return READY, CONCERNS [list], or NOT READY [blockers]." + +**Verdicts**: READY / CONCERNS / NOT READY diff --git a/.claude/docs/director-gates/cd-pillars.md b/.claude/docs/director-gates/cd-pillars.md new file mode 100644 index 00000000..86f2e6a6 --- /dev/null +++ b/.claude/docs/director-gates/cd-pillars.md @@ -0,0 +1,24 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# CD-PILLARS — Pillar Stress Test + +Agent: `creative-director` | Model tier: Opus | Domain: Vision, pillars, player experience + +**Trigger**: After game pillars and anti-pillars are defined (brainstorm Phase 4, +or any time pillars are revised) + +**Context to pass**: +- Full pillar set with names, definitions, and design tests +- Anti-pillars list +- Core fantasy statement +- Unique hook ("Like X, AND ALSO Y") + +**Prompt**: +> "Review these game pillars. Are they falsifiable — could a real design decision +> actually fail this pillar? Do they create meaningful tension with each other? Do +> they differentiate this game from its closest comparables? Would they help resolve +> a design disagreement in practice, or are they too vague to be useful? Return +> specific feedback for each pillar and an overall verdict: APPROVE (strong), CONCERNS +> [list] (needs sharpening), or REJECT (weak — pillars do not carry weight)." + +**Verdicts**: APPROVE / CONCERNS / REJECT diff --git a/.claude/docs/director-gates/cd-playtest.md b/.claude/docs/director-gates/cd-playtest.md new file mode 100644 index 00000000..55569146 --- /dev/null +++ b/.claude/docs/director-gates/cd-playtest.md @@ -0,0 +1,23 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# CD-PLAYTEST — Player Experience Validation + +Agent: `creative-director` | Model tier: Opus | Domain: Vision, pillars, player experience + +**Trigger**: After playtest reports are generated (`/playtest-report`), or after +any session that produces player feedback + +**Context to pass**: +- Playtest report file path +- Game pillars and core fantasy statement +- The specific hypothesis being tested + +**Prompt**: +> "Review this playtest report against the game's design pillars and core fantasy. +> Is the player experience matching the intended fantasy? Are there systematic issues +> that represent pillar drift — mechanics that feel fine in isolation but undermine +> the intended experience? Return APPROVE (core fantasy is landing), CONCERNS [gaps +> between intended and actual experience], or REJECT [core fantasy is not present — +> redesign needed before further playtesting]." + +**Verdicts**: APPROVE / CONCERNS / REJECT diff --git a/.claude/docs/director-gates/cd-systems.md b/.claude/docs/director-gates/cd-systems.md new file mode 100644 index 00000000..71dd176d --- /dev/null +++ b/.claude/docs/director-gates/cd-systems.md @@ -0,0 +1,27 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# CD-SYSTEMS — Systems Decomposition Vision Check + +Agent: `creative-director` | Model tier: Opus | Domain: Vision, pillars, player experience + +**Trigger**: After the systems index is written by `/map-systems` — validates the +complete system set before GDD authoring begins + +**Context to pass**: +- Systems index path (`design/gdd/systems-index.md`) +- Game pillars and core fantasy (from `design/gdd/game-concept.md`) +- Priority tier assignments (MVP / Vertical Slice / Alpha / Full Vision) +- Any high-risk or bottleneck systems identified in the dependency map + +**Prompt**: +> "Review this systems decomposition against the game's design pillars. Does the +> full set of MVP-tier systems collectively deliver the core fantasy? Are there +> systems whose mechanics don't serve any stated pillar — indicating they may be +> scope creep? Are there pillar-critical player experiences that have no system +> assigned to deliver them? Are any systems missing that the core loop requires? +> Return APPROVE (systems serve the vision), CONCERNS [specific gaps or +> misalignments with their pillar implications], or REJECT [fundamental gaps — +> the decomposition misses critical design intent and must be revised before GDD +> authoring begins]." + +**Verdicts**: APPROVE / CONCERNS / REJECT diff --git a/.claude/docs/director-gates/lp-code-review.md b/.claude/docs/director-gates/lp-code-review.md new file mode 100644 index 00000000..f6a56b41 --- /dev/null +++ b/.claude/docs/director-gates/lp-code-review.md @@ -0,0 +1,23 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# LP-CODE-REVIEW — Lead Programmer Code Review + +Agent: `lead-programmer` | Model tier: Sonnet (Tier 2 lead — invoked when a domain specialist's feasibility sign-off is needed) + +**Trigger**: After a dev story is implemented (`/dev-story`, `/story-done`), or +as part of `/code-review` + +**Context to pass**: +- Implementation file paths +- Story file path (for acceptance criteria) +- Relevant GDD section +- ADR that governs this system + +**Prompt**: +> "Review this implementation against the story acceptance criteria and governing +> ADR. Does the code match the architecture boundary definitions? Are there +> violations of the coding standards or forbidden patterns? Is the public API +> testable and documented? Are there any correctness issues against the GDD rules? +> Return APPROVE, CONCERNS [specific issues], or REJECT [must be revised before merge]." + +**Verdicts**: APPROVE / CONCERNS / REJECT diff --git a/.claude/docs/director-gates/lp-feasibility.md b/.claude/docs/director-gates/lp-feasibility.md new file mode 100644 index 00000000..1157a009 --- /dev/null +++ b/.claude/docs/director-gates/lp-feasibility.md @@ -0,0 +1,23 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# LP-FEASIBILITY — Lead Programmer Implementation Feasibility + +Agent: `lead-programmer` | Model tier: Sonnet (Tier 2 lead — invoked when a domain specialist's feasibility sign-off is needed) + +**Trigger**: After the master architecture document is written (`/create-architecture` +Phase 7b), or when a new architectural pattern is proposed + +**Context to pass**: +- Architecture document path +- Technical requirements baseline summary +- ADR list with statuses + +**Prompt**: +> "Review this architecture for implementation feasibility. Flag: (a) any decisions +> that would be difficult or impossible to implement with the stated engine and +> language, (b) any missing interface definitions that programmers would need to +> invent themselves, (c) any patterns that create avoidable technical debt or +> that contradict standard [engine] idioms. Return FEASIBLE, CONCERNS [list], or +> INFEASIBLE [blockers that make this architecture unimplementable as written]." + +**Verdicts**: FEASIBLE / CONCERNS / INFEASIBLE diff --git a/.claude/docs/director-gates/nd-consistency.md b/.claude/docs/director-gates/nd-consistency.md new file mode 100644 index 00000000..3e0ef349 --- /dev/null +++ b/.claude/docs/director-gates/nd-consistency.md @@ -0,0 +1,24 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# ND-CONSISTENCY — Narrative Director Consistency Check + +Agent: `narrative-director` | Model tier: Sonnet (Tier 2 lead — invoked when a domain specialist's feasibility sign-off is needed) + +**Trigger**: After writer deliverables (dialogue, lore, item descriptions) are +authored, or when a design decision has narrative implications + +**Context to pass**: +- Document or content file path(s) +- Narrative bible or tone guide path (if exists) +- Relevant world-building rules +- Character or faction profiles affected + +**Prompt**: +> "Review this narrative content for internal consistency and adherence to +> established world rules. Are character voices consistent with their established +> profiles? Does the lore contradict any established facts? Is the tone consistent +> with the game's narrative direction? Return APPROVE, CONCERNS [specific +> inconsistencies to fix], or REJECT [contradictions that break the narrative +> foundation]." + +**Verdicts**: APPROVE / CONCERNS / REJECT diff --git a/.claude/docs/director-gates/pr-epic.md b/.claude/docs/director-gates/pr-epic.md new file mode 100644 index 00000000..558b4f34 --- /dev/null +++ b/.claude/docs/director-gates/pr-epic.md @@ -0,0 +1,30 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# PR-EPIC — Epic Structure Feasibility Review + +Agent: `producer` | Model tier: Opus | Domain: Scope, timeline, dependencies, production risk + +**Trigger**: After epics are defined by `/create-epics`, before stories are +broken out — validates the epic structure is producible before `/create-stories` +is invoked + +**Context to pass**: +- Epic definition file paths (all epics just created) +- Epic index path (`production/epics/index.md`) +- Milestone timeline and target dates +- Team capacity (solo / small team / size) +- Layer being epiced (Foundation / Core / Feature / etc.) + +**Prompt**: +> "Review this epic structure for production feasibility before story breakdown +> begins. Are the epic boundaries scoped appropriately — could each epic realistically +> complete before a milestone deadline? Are epics correctly ordered by system +> dependency — does any epic require another epic's output before it can start? +> Are any epics underscoped (too small, should merge) or overscoped (too large, +> should split into 2-3 focused epics)? Are the Foundation-layer epics scoped to +> allow Core-layer epics to begin at the start of the next sprint after Foundation +> completes? Return REALISTIC (epic structure is producible), CONCERNS [specific +> structural adjustments before stories are written], or UNREALISTIC [epics must +> be split, merged, or reordered — story breakdown cannot begin until resolved]." + +**Verdicts**: REALISTIC / CONCERNS / UNREALISTIC diff --git a/.claude/docs/director-gates/pr-milestone.md b/.claude/docs/director-gates/pr-milestone.md new file mode 100644 index 00000000..575dcf9f --- /dev/null +++ b/.claude/docs/director-gates/pr-milestone.md @@ -0,0 +1,24 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# PR-MILESTONE — Milestone Risk Assessment + +Agent: `producer` | Model tier: Opus | Domain: Scope, timeline, dependencies, production risk + +**Trigger**: At milestone review (`/milestone-review`), at mid-sprint retrospectives, +or when a scope change is proposed that affects the milestone + +**Context to pass**: +- Milestone definition and target date +- Current completion percentage +- Blocked stories count +- Sprint velocity data (if available) + +**Prompt**: +> "Review this milestone status. Based on current velocity and blocked story count, +> will this milestone hit its target date? What are the top 3 production risks +> between now and the milestone? Are there scope items that should be cut to protect +> the milestone date vs. items that are non-negotiable? Return ON TRACK, AT RISK +> [specific mitigations], or OFF TRACK [date must slip or scope must cut — provide +> both options]." + +**Verdicts**: ON TRACK / AT RISK / OFF TRACK diff --git a/.claude/docs/director-gates/pr-phase-gate.md b/.claude/docs/director-gates/pr-phase-gate.md new file mode 100644 index 00000000..b96f1620 --- /dev/null +++ b/.claude/docs/director-gates/pr-phase-gate.md @@ -0,0 +1,22 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# PR-PHASE-GATE — Production Readiness at Phase Transition + +Agent: `producer` | Model tier: Opus | Domain: Scope, timeline, dependencies, production risk + +**Trigger**: Always at `/gate-check` — spawn in parallel with CD-PHASE-GATE and TD-PHASE-GATE + +**Context to pass**: +- Target phase name +- Sprint and milestone artifacts present +- Team size and capacity +- Current blocked story count + +**Prompt**: +> "Review the current project state for [target phase] gate readiness from a +> production perspective. Is the scope realistic for the stated timeline and team +> size? Are dependencies properly ordered so the team can actually execute in +> sequence? Are there milestone or sprint risks that could derail the phase within +> the first two sprints? Return READY, CONCERNS [list], or NOT READY [blockers]." + +**Verdicts**: READY / CONCERNS / NOT READY diff --git a/.claude/docs/director-gates/pr-scope.md b/.claude/docs/director-gates/pr-scope.md new file mode 100644 index 00000000..36016e90 --- /dev/null +++ b/.claude/docs/director-gates/pr-scope.md @@ -0,0 +1,25 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# PR-SCOPE — Scope and Timeline Validation + +Agent: `producer` | Model tier: Opus | Domain: Scope, timeline, dependencies, production risk + +**Trigger**: After scope tiers are defined (brainstorm Phase 6, quick-design, or +any workflow that produces an MVP definition and timeline estimate) + +**Context to pass**: +- Full vision scope description +- MVP definition +- Timeline estimate +- Team size (solo / small team / etc.) +- Scope tiers (what ships if time runs out) + +**Prompt**: +> "Review this scope estimate. Is the MVP achievable in the stated timeline for +> the stated team size? Are the scope tiers correctly ordered by risk — does each +> tier deliver a shippable product if work stops there? What is the most likely +> cut point under time pressure, and is it a graceful fallback or a broken product? +> Return REALISTIC (scope matches capacity), OPTIMISTIC [specific adjustments +> recommended], or UNREALISTIC [blockers — timeline or MVP must be revised]." + +**Verdicts**: REALISTIC / OPTIMISTIC / UNREALISTIC diff --git a/.claude/docs/director-gates/pr-sprint.md b/.claude/docs/director-gates/pr-sprint.md new file mode 100644 index 00000000..ca55c9d9 --- /dev/null +++ b/.claude/docs/director-gates/pr-sprint.md @@ -0,0 +1,24 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# PR-SPRINT — Sprint Feasibility Review + +Agent: `producer` | Model tier: Opus | Domain: Scope, timeline, dependencies, production risk + +**Trigger**: Before finalising a sprint plan (`/sprint-plan`), and after any +mid-sprint scope change + +**Context to pass**: +- Proposed sprint story list (titles, estimates, dependencies) +- Team capacity (hours available) +- Current sprint backlog debt (if any) +- Milestone constraints + +**Prompt**: +> "Review this sprint plan for feasibility. Is the story load realistic for the +> available capacity? Are stories correctly ordered by dependency? Are there hidden +> dependencies between stories that could block the sprint mid-way? Are any stories +> underestimated given their technical complexity? Return REALISTIC (plan is +> achievable), CONCERNS [specific risks], or UNREALISTIC [sprint must be +> descoped — identify which stories to defer]." + +**Verdicts**: REALISTIC / CONCERNS / UNREALISTIC diff --git a/.claude/docs/director-gates/ql-story-ready.md b/.claude/docs/director-gates/ql-story-ready.md new file mode 100644 index 00000000..4c6a9a17 --- /dev/null +++ b/.claude/docs/director-gates/ql-story-ready.md @@ -0,0 +1,27 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# QL-STORY-READY — QA Lead Story Readiness Check + +Agent: `qa-lead` | Model tier: Sonnet (Tier 2 lead — invoked when a domain specialist's feasibility sign-off is needed) + +**Trigger**: Before a story is accepted into a sprint — invoked by `/create-stories`, +`/story-readiness`, and `/sprint-plan` during story selection + +**Context to pass**: +- Story file path +- Story type (Logic / Integration / Visual/Feel / UI / Config/Data) +- Acceptance criteria list (verbatim from the story) +- The GDD requirement (TR-ID and text) the story covers + +**Prompt**: +> "Review this story's acceptance criteria for testability before it enters the +> sprint. Are all criteria specific enough that a developer would know unambiguously +> when they are done? For Logic-type stories: can every criterion be verified with +> an automated test? For Integration stories: is each criterion observable in a +> controlled test environment? Flag criteria that are too vague to implement +> against, and flag criteria that require a full game build to test (mark these +> DEFERRED, not BLOCKED). Return ADEQUATE (criteria are implementable as written), +> GAPS [specific criteria needing refinement], or INADEQUATE [criteria are too +> vague — story must be revised before sprint inclusion]." + +**Verdicts**: ADEQUATE / GAPS / INADEQUATE diff --git a/.claude/docs/director-gates/ql-test-coverage.md b/.claude/docs/director-gates/ql-test-coverage.md new file mode 100644 index 00000000..ac6ac7bc --- /dev/null +++ b/.claude/docs/director-gates/ql-test-coverage.md @@ -0,0 +1,23 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# QL-TEST-COVERAGE — QA Lead Test Coverage Review + +Agent: `qa-lead` | Model tier: Sonnet (Tier 2 lead — invoked when a domain specialist's feasibility sign-off is needed) + +**Trigger**: After implementation stories are complete, before marking an epic +done, or at `/gate-check` Production → Polish + +**Context to pass**: +- List of implemented stories with story types (Logic / Integration / Visual / UI / Config) +- Test file paths in `tests/` +- GDD acceptance criteria for the system + +**Prompt**: +> "Review the test coverage for these implementation stories. Are all Logic stories +> covered by passing unit tests? Are Integration stories covered by integration +> tests or documented playtests? Are the GDD acceptance criteria each mapped to at +> least one test? Are there untested edge cases from the GDD Edge Cases section? +> Return ADEQUATE (coverage meets standards), GAPS [specific missing tests], or +> INADEQUATE [critical logic is untested — do not advance]." + +**Verdicts**: ADEQUATE / GAPS / INADEQUATE diff --git a/.claude/docs/director-gates/td-adr.md b/.claude/docs/director-gates/td-adr.md new file mode 100644 index 00000000..76f79f42 --- /dev/null +++ b/.claude/docs/director-gates/td-adr.md @@ -0,0 +1,23 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# TD-ADR — Architecture Decision Review + +Agent: `technical-director` | Model tier: Opus | Domain: Architecture, engine risk, performance + +**Trigger**: After an individual ADR is authored (`/architecture-decision`), before +it is marked Accepted + +**Context to pass**: +- ADR file path +- Engine version and knowledge gap risk level for the domain +- Related ADRs (if any) + +**Prompt**: +> "Review this Architecture Decision Record. Does it have a clear problem statement +> and rationale? Are the rejected alternatives genuinely considered? Does the +> Consequences section acknowledge the trade-offs honestly? Is the engine version +> stamped? Are post-cutoff API risks flagged? Does it link to the GDD requirements +> it covers? Return APPROVE, CONCERNS [specific gaps], or REJECT [the decision is +> underspecified or makes unsound technical assumptions]." + +**Verdicts**: APPROVE / CONCERNS / REJECT diff --git a/.claude/docs/director-gates/td-architecture.md b/.claude/docs/director-gates/td-architecture.md new file mode 100644 index 00000000..6fbc0358 --- /dev/null +++ b/.claude/docs/director-gates/td-architecture.md @@ -0,0 +1,24 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# TD-ARCHITECTURE — Architecture Sign-Off + +Agent: `technical-director` | Model tier: Opus | Domain: Architecture, engine risk, performance + +**Trigger**: After the master architecture document is drafted (`/create-architecture` +Phase 7), and after any major architecture revision + +**Context to pass**: +- Architecture document path (`docs/architecture/architecture.md`) +- Technical requirements baseline (TR-IDs and count) +- ADR list with statuses +- Engine knowledge gap inventory + +**Prompt**: +> "Review this master architecture document for technical soundness. Check: (1) Is +> every technical requirement from the baseline covered by an architectural decision? +> (2) Are all HIGH risk engine domains explicitly addressed or flagged as open +> questions? (3) Are the API boundaries clean, minimal, and implementable? (4) Are +> Foundation layer ADR gaps resolved before implementation begins? Return APPROVE, +> CONCERNS [list], or REJECT [blockers that must be resolved before coding starts]." + +**Verdicts**: APPROVE / CONCERNS / REJECT diff --git a/.claude/docs/director-gates/td-change-impact.md b/.claude/docs/director-gates/td-change-impact.md new file mode 100644 index 00000000..b5f882cd --- /dev/null +++ b/.claude/docs/director-gates/td-change-impact.md @@ -0,0 +1,25 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# TD-CHANGE-IMPACT — Design Change Impact Review + +Agent: `technical-director` | Model tier: Opus | Domain: Architecture, engine risk, performance + +**Trigger**: After the Design Change Impact Report is produced +(`/propagate-design-change` Phase 6), before the resolution workflow begins + +**Context to pass**: +- The full Design Change Impact Report from Phase 6 — change summary, every + affected ADR with its Still Valid / Needs Review / Likely Superseded + classification, and the recommended actions + +**Prompt**: +> "Review this design-change impact assessment before any ADR is revised. Are the +> impact classifications correct — in particular, is any ADR under-classified +> (marked Still Valid when the change actually contradicts its assumptions)? Are +> the recommended actions architecturally sound? Were any cascading effects on +> other ADRs or systems missed — decisions that depend on the ones already +> flagged? Return APPROVE, CONCERNS [the specific ADRs or recommendations to +> revisit], or REJECT [the assessment must be re-analyzed before resolution +> begins — say what was missed]." + +**Verdicts**: APPROVE / CONCERNS / REJECT diff --git a/.claude/docs/director-gates/td-engine-risk.md b/.claude/docs/director-gates/td-engine-risk.md new file mode 100644 index 00000000..adeb123f --- /dev/null +++ b/.claude/docs/director-gates/td-engine-risk.md @@ -0,0 +1,22 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# TD-ENGINE-RISK — Engine Version Risk Review + +Agent: `technical-director` | Model tier: Opus | Domain: Architecture, engine risk, performance + +**Trigger**: When making architecture decisions that touch post-cutoff engine APIs, +or before finalizing any engine-specific implementation approach + +**Context to pass**: +- The specific API or feature being used +- Engine version and LLM knowledge cutoff (from `docs/engine-reference/[engine]/VERSION.md`) +- Relevant excerpt from breaking-changes or deprecated-apis docs + +**Prompt**: +> "Review this engine API usage against the version reference. Is this API present +> in [engine version]? Has its signature, behaviour, or namespace changed since the +> LLM knowledge cutoff? Are there known deprecations or post-cutoff alternatives? +> Return APPROVE (safe to use as described), CONCERNS [verify before implementing], +> or REJECT [API has changed — provide corrected approach]." + +**Verdicts**: APPROVE / CONCERNS / REJECT diff --git a/.claude/docs/director-gates/td-feasibility.md b/.claude/docs/director-gates/td-feasibility.md new file mode 100644 index 00000000..b06afb7c --- /dev/null +++ b/.claude/docs/director-gates/td-feasibility.md @@ -0,0 +1,24 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# TD-FEASIBILITY — Technical Feasibility Assessment + +Agent: `technical-director` | Model tier: Opus | Domain: Architecture, engine risk, performance + +**Trigger**: After biggest technical risks are identified during scope/feasibility +(brainstorm Phase 6, quick-design, or any early-stage concept with technical unknowns) + +**Context to pass**: +- Concept's core loop description +- Platform target +- Engine choice (or "undecided") +- List of identified technical risks + +**Prompt**: +> "Review these technical risks for a [genre] game targeting [platform] using +> [engine or 'undecided engine']. Flag any HIGH risk items that could invalidate +> the concept as described, any risks that are engine-specific and should influence +> the engine choice, and any risks that are commonly underestimated by solo +> developers. Return VIABLE (risks are manageable), CONCERNS [list with mitigation +> suggestions], or HIGH RISK [blockers that require concept or scope revision]." + +**Verdicts**: VIABLE / CONCERNS / HIGH RISK diff --git a/.claude/docs/director-gates/td-manifest.md b/.claude/docs/director-gates/td-manifest.md new file mode 100644 index 00000000..6f3c30db --- /dev/null +++ b/.claude/docs/director-gates/td-manifest.md @@ -0,0 +1,26 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# TD-MANIFEST — Control Manifest Review + +Agent: `technical-director` | Model tier: Opus | Domain: Architecture, engine risk, performance + +**Trigger**: After control-manifest rules are extracted and previewed +(`/create-control-manifest` Phase 4), before the manifest file is written + +**Context to pass**: +- The Control Manifest Preview from Phase 4 (rule counts per layer, full extracted rule list) +- The list of ADRs covered +- Engine version +- Any rules sourced from `technical-preferences.md` or engine reference docs + +**Prompt**: +> "Review this control manifest before it is written. Are all mandatory ADR +> patterns captured and accurately stated? Are the forbidden approaches complete +> and correctly attributed to their source ADR? Were any rules added that lack a +> source ADR or preference document — that is, invented rather than extracted? +> Are the performance guardrails consistent with the constraints the ADRs +> actually set? Return APPROVE, CONCERNS [list the specific rules to revise], or +> REJECT [rules that are wrong or unsourced and must be fixed before the manifest +> is written]." + +**Verdicts**: APPROVE / CONCERNS / REJECT diff --git a/.claude/docs/director-gates/td-phase-gate.md b/.claude/docs/director-gates/td-phase-gate.md new file mode 100644 index 00000000..e204448a --- /dev/null +++ b/.claude/docs/director-gates/td-phase-gate.md @@ -0,0 +1,22 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# TD-PHASE-GATE — Technical Readiness at Phase Transition + +Agent: `technical-director` | Model tier: Opus | Domain: Architecture, engine risk, performance + +**Trigger**: Always at `/gate-check` — spawn in parallel with CD-PHASE-GATE and PR-PHASE-GATE + +**Context to pass**: +- Target phase name +- Architecture document path (if exists) +- Engine reference path +- ADR list + +**Prompt**: +> "Review the current project state for [target phase] gate readiness from a +> technical direction perspective. Is the architecture sound for this phase? Are +> all high-risk engine domains addressed? Are performance budgets realistic and +> documented? Are Foundation-layer decisions complete enough to begin implementation? +> Return READY, CONCERNS [list], or NOT READY [blockers]." + +**Verdicts**: READY / CONCERNS / NOT READY diff --git a/.claude/docs/director-gates/td-system-boundary.md b/.claude/docs/director-gates/td-system-boundary.md new file mode 100644 index 00000000..34360085 --- /dev/null +++ b/.claude/docs/director-gates/td-system-boundary.md @@ -0,0 +1,31 @@ +> Gate definition. The spawning skill passes this file's path to the director agent; the AGENT reads it — the parent session should not. + +# TD-SYSTEM-BOUNDARY — System Boundary Architecture Review + +Agent: `technical-director` | Model tier: Opus | Domain: Architecture, engine risk, performance + +**Trigger**: After `/map-systems` Phase 3 dependency mapping is agreed but before +GDD authoring begins — validates that the system structure is architecturally +sound before teams invest in writing GDDs against it + +**Context to pass**: +- Systems index path (or the dependency map summary if index not yet written) +- Layer assignments (Foundation / Core / Feature / Presentation / Polish) +- The full dependency graph (what each system depends on) +- Any bottleneck systems flagged (many dependents) +- Any circular dependencies found and their proposed resolutions + +**Prompt**: +> "Review this systems decomposition from an architectural perspective before GDD +> authoring begins. Are the system boundaries clean — does each system own a +> distinct concern with minimal overlap? Are there God Object risks (systems doing +> too much)? Does the dependency ordering create implementation-sequencing problems? +> Are there implicit shared-state problems in the proposed boundaries that will +> cause tight coupling when implemented? Are any Foundation-layer systems actually +> dependent on Feature-layer systems (inverted dependency)? Return APPROVE +> (boundaries are architecturally sound — proceed to GDD authoring), CONCERNS +> [specific boundary issues to address in the GDDs themselves], or REJECT +> [fundamental boundary problems — the system structure will cause architectural +> issues and must be restructured before any GDD is written]." + +**Verdicts**: APPROVE / CONCERNS / REJECT diff --git a/.claude/docs/directory-structure.md b/.claude/docs/directory-structure.md index 7e859f28..268a01b6 100644 --- a/.claude/docs/directory-structure.md +++ b/.claude/docs/directory-structure.md @@ -3,16 +3,41 @@ ```text / ├── CLAUDE.md # Master configuration +├── project.yaml # Machine-readable project config — engine, modes, stage (source of truth) ├── .claude/ # Agent definitions, skills, hooks, rules, docs -├── src/ # Game source code (core, gameplay, ai, networking, ui, tools) +├── / # Game source code — THE PATH IS ENGINE-SPECIFIC, see below ├── assets/ # Game assets (art, audio, vfx, shaders, data) ├── design/ # Game design documents (gdd, narrative, levels, balance) ├── docs/ # Technical documentation (architecture, api, postmortems) │ └── engine-reference/ # Curated engine API snapshots (version-pinned) -├── tests/ # Test suites (unit, integration, performance, playtest) +├── tests/ # Game test suites (unit, integration, performance, playtest) ├── tools/ # Build and pipeline tools (ci, build, asset-pipeline) ├── prototypes/ # Throwaway prototypes (isolated from src/) -└── production/ # Production management (sprints, milestones, releases) - ├── session-state/ # Ephemeral session state (active.md — gitignored) - └── session-logs/ # Session audit trail (gitignored) +├── production/ # Production management (sprints, milestones, releases) +│ ├── session-state/ # Ephemeral session state (active.md — gitignored) +│ └── session-logs/ # Session audit trail (gitignored) +└── CCGS Skill Testing Framework/ # QA for the skills/agents themselves — /skill-test, /skill-improve ``` + +## The code root is engine-specific + +**Two of the three supported engines will not build a project whose code sits in +`src/`.** This is not a style preference — it is a hard constraint of the engine's +own toolchain: + +| `engine.name` | Code root | Why it cannot be `src/` | +|---|---|---| +| **Godot** | `src/` | No constraint — Godot loads from `res://` anywhere under the project root. | +| **Unity** | `Assets/` | Unity compiles **only** `Assets/` and `Packages/`. Code outside them is invisible to the compiler. | +| **Unreal** | `Source//` | UnrealBuildTool discovers modules under `Source/`; content lives in `Content/`. A module elsewhere is not built. | + +**Resolve the code root from `engine.name` before writing any source file.** Where +a skill, rule or template says `src/`, read it as *"the code root"* and substitute +the row above. `src/` is the table's Godot row, not a universal path. + +> **Known residual — do not read `src/` as settled.** Roughly 30 files under +> `.claude/` still name `src/` literally, because they were written when Godot was +> the only engine walked end to end. They are correct for Godot and wrong for the +> other two. This section is the authority; those files are the default. The +> breakage is real, not theoretical — a Unity tree laid out this way does not +> compile, and an Unreal one produces a module no `.uproject` can consume. diff --git a/.claude/docs/effects-map.md b/.claude/docs/effects-map.md new file mode 100644 index 00000000..bcdabb39 --- /dev/null +++ b/.claude/docs/effects-map.md @@ -0,0 +1,2421 @@ +# project.yaml — Effects Map + +> ## READ ONE SECTION, NEVER THIS WHOLE FILE +> +> This document is **~31,000 tokens** — roughly **four times an entire turn's +> context budget**, which sits near 8,300. It is organised as **36 sections of +> ~900 tokens each**, one per setting, headed `## `. +> +> **So opening it costs 36× what you need.** To look up a setting: +> +> ``` +> Grep pattern="^## modes.review_mode" path=".claude/docs/effects-map.md" -A 40 +> ``` +> +> Read the section for your key and stop. The same rule the skills already apply +> to their own reference files — `/gate-check` loads only the row for the target +> phase and says "never load the others" — applies here, and this is the largest +> file in the framework by a factor of seven. +> +> This banner exists because six skills cite this document and **none of them +> scoped the citation**. None instructed a full read either, so +> nothing was actually loading 31k — but "see effects-map.md for full schema" in +> front of a file this size is an invitation, and the cost of accepting it is +> silent. + +This document defines what each `project.yaml` setting actually does across +every skill that reads it. It is the tracked, authoritative reference. + +**Purpose:** When a skill reads a setting, this document is the authoritative +source for what each value means for that skill's behavior. Implementers update +this file when adding a new setting or changing how a skill responds to one. + +**Format per setting:** +- Priority chain — which source wins when multiple are present +- Per-value intent — what the value means in plain terms +- Skill behavior table — exactly what changes per skill per value + +**Coverage scope:** This document covers the skills most affected by each setting. +Skills not listed (e.g. `ux-review`, `balance-check`, `test-flakiness`, `test-helpers`, +`test-setup`, `soak-test`, `perf-profile`, `launch-checklist`, `release-checklist`, +`skill-test`, `skill-improve`, `vertical-slice`) are not meaningfully affected by +`project.yaml` settings and do not need entries here. Add a skill only when its +behavior genuinely branches on a setting value. + +**Local override pattern:** `project.yaml` is committed to git as the shared team +config. Per-developer overrides live in `project.local.yaml` (gitignored). Priority +chain across all settings: `project.local.yaml` → `project.yaml` → legacy mirror → +`modes.rigor` expansion → hardcoded default. The expansion step applies only to the +six knobs `rigor` fronts (`modes.workflow`, `docs.density`, `qa.level`, +`modes.story_granularity`, `modes.review_mode`, `team.size`), which for that reason have **no** hardcoded default — +see the `modes.rigor` section. +**Secrets do NOT belong in either file — use environment variables.** See the +"Local Override Pattern" section below for full design including the whitelist of +which settings can be locally overridden. + +--- + +## Local Override Pattern — `project.local.yaml` + +The `project.local.yaml` file lets each developer override specific settings for +their own use without affecting the team. The file is gitignored and never +committed. + +### How it works + +Priority chain (every setting): `project.local.yaml` → `project.yaml` → hardcoded default + +If a setting is present in `project.local.yaml`, that value wins. If absent, the +value from `project.yaml` is used. If absent there too, the hardcoded default +takes over. + +--- + +### Deep merge semantics + +When both files set a nested value, only the explicitly-set key is overridden. +Sibling keys in the same block are preserved. + +Example: + +`project.yaml`: +```yaml +testing: + strict: + logic: true + integration: true + visual: false +``` + +`project.local.yaml` (sparse override): +```yaml +testing: + strict: + logic: false +``` + +Effective config seen by skills: `logic: false, integration: true, visual: false`. +Only the explicitly-set `logic` key is overridden; sibling keys come through from +`project.yaml`. + +--- + +### Whitelist — which settings can be locally overridden + +Only personal-experience settings can be locally overridden. Project-wide facts +(engine, stage, naming conventions, what's required on disk) are locked to +`project.yaml` because divergence between developers would break team coordination. + +The rule for deciding which side a setting belongs on: + +> **If a setting affects what artifacts exist or what they look like on disk, +> it must be locked to `project.yaml`. If a setting only changes your personal +> experience (which agents spawn for you, whether you get prompts, your local +> CI bypass), it can be locally overridden.** + +**Locally overridable** (personal experience only — no effect on artifacts on disk): + +| Setting | Why local override makes sense | +|---------|-------------------------------| +| `modes.review_mode` | How much AI review you personally want | +| `modes.automation` | How often the AI prompts you | +| `modes.automation_always_ask` | Your personal safety categories | +| `team.size` | Which agents spawn for your reviews (review depth differs, artifacts don't) | +| `testing.strict.*` (each type) | Whether failures block your local work (CI still enforces project.yaml defaults) | +| `performance.enforce` | Whether budget violations block your local work (CI enforces project defaults) | +| `features.session_state` | Your session tracking preference | +| `features.token_budget_warn_at` | Your personal cost threshold | + +**Locked to `project.yaml`** (project-wide — would create artifact divergence if local): + +| Setting | Why it must be project-wide | +|---------|----------------------------| +| `schema_version`, `framework.*` | File metadata, not preferences — **exempt from the report below**, since a `project.local.yaml` carrying `schema_version: 1` is correct as written | +| `project.name`, `version`, `genre`, `kind`, `stage` | Project identity | +| `engine.*`, `specialists.*` | Whole team must use same engine | +| `naming.*` | Code conventions must be consistent across team | +| `platform.*` | Game facts (what platforms it ships on) | +| `performance.target_framerate`, `frame_budget_ms`, `draw_call_limit`, `memory_ceiling_mb` | Game budgets, shared targets | +| `accessibility.target` | Game commitment, project-wide | +| `cadence.*` | Sprint/milestone length affects team coordination | +| `modes.rigor` | Fronts the four below — locked for the same reason they are | +| `modes.workflow` and `workflow_overrides` | Affects which sections are required in authored docs | +| `modes.story_granularity` | Affects how big stories are in repo — team must be consistent | +| `docs.density` | Affects how deep authored docs are — team must produce consistent docs | +| `qa.level` | Affects what test evidence stories must include on disk | +| `qa.coverage_minimum` | Project quality bar | +| `strict_gate_checks` | Affects whether `project.stage` advances — a project-wide write | +| `commands.*` | Build/test commands are project facts | +| `testing.framework` | Whole project uses one framework | + +> **A locked key written into `project.local.yaml` by hand is reported, not +> swallowed.** `/settings --local` refuses to write one, but the +> file is meant to be hand-edited and that path had no guard: `modes.rigor: full` +> there is a real key with a legal value, so enum validation passes it, and then +> resolution never consults the local file for that path — the setting vanishes +> and the user sees the default they were trying to override, with no error +> anywhere. Every check validated the **value**; nothing validated the +> **location**. `validate_local_scope` in `yaml-helper.sh` now names such keys on +> `resolve_config`'s `notes:` line. It **warns and never reconciles**: it does not +> edit the file, does not begin honouring the key, and does not change +> resolution — a locked setting decides which artifacts exist on disk, so +> applying one quietly would produce exactly the divergence this table prevents. +> A clean file stays silent — you are only told when a key is actually in the +> wrong place. + +--- + +### `/settings --local` flag behavior + +``` +/settings workflow=minimal → writes to project.yaml (team-wide) +/settings --local automation=autonomous → writes to project.local.yaml (just you) +``` + +**Behavior rules:** + +- Default (no flag): writes to `project.yaml` +- `--local` flag: writes to `project.local.yaml` (creates it if absent) +- `--local` on a locked setting → error: + > *"`` cannot be locally overridden — it's a project-wide setting. + > Use `/settings =` (no `--local`) to change it for the whole team."* +- Writing to `project.yaml` when the same setting is currently locally overridden → warning: + > *"This setting is currently locally overridden in `project.local.yaml`. + > Writing to `project.yaml` will not affect your effective value."* + +--- + +### Schema validation on read + +When skills or hooks read either file: + +| Issue | Behavior | +|-------|----------| +| Unknown key (typo or stale setting) | Session-start warning: *"`project.local.yaml` has unknown key `modes.automaton` — typo? Ignored."* Does NOT block (forwards-compat with future settings). | +| Invalid value not in enum (e.g. `automation: chaotic`) | Hard error: skill exits with explanation of valid values. | +| Type mismatch (e.g. integer field set to a string) | Hard error: skill exits with type expected. | + +--- + +### Creation and error handling + +| Scenario | Behavior | +|----------|----------| +| `/start` on fresh project | Creates `project.yaml` only. Does NOT create `project.local.yaml`. | +| `/settings --local =` when local.yaml doesn't exist | Creates `project.local.yaml` containing just that setting. | +| `project.local.yaml` exists but `project.yaml` doesn't | Hard error: *"`project.yaml` missing — run `/start` to create one. Local overrides require a base."* | +| Both files exist | Read `project.yaml` first, deep-merge `project.local.yaml` on top. Normal operation. | +| Both files set the same key | `project.local.yaml` wins. No prompting. | + +--- + +### Two-developer clash scenarios — and why the whitelist prevents them + +**File-level conflict (`project.yaml`):** +Standard git merge conflict. Each developer's `project.local.yaml` is gitignored +and never collides with another developer's. Normal git workflow handles team-wide +changes. + +**Logical inconsistency (prevented by whitelist):** + +Example of what the whitelist prevents: + +> Sarah sets `modes.workflow: minimal` locally to skip GDD requirements. She +> doesn't write GDDs because her local mode doesn't require them. Mike runs +> `/gate-check` with the team default of `standard`; gate-check sees missing +> GDDs and fails. + +This can't happen because `modes.workflow` is locked to `project.yaml`. Same +for `qa.level`, `docs.density`, `strict_gate_checks`, `naming.*`, and every +other setting that affects what artifacts exist on disk. + +Personal experience settings (`automation`, `review_mode`, `team.size`, +`testing.strict.*`, `performance.enforce`) can diverge freely without affecting +anyone else. + +--- + +### Stricter reviewer reviewing a looser reviewer's work + +A common worry: "what if my teammate has stricter review settings than me? +Will their `/gate-check` or `/design-review` flag things in my work?" + +The answer depends on which kind of difference you're talking about: + +**Different review depth, same project requirements (HEALTHY):** + +- Teammate A: local `review_mode: full`, `automation: collaborative`, `testing.strict.logic: true` +- Teammate B: local `review_mode: solo`, `automation: autonomous`, `testing.strict.logic: false` +- Both bound by the same project.yaml-locked settings (e.g. `workflow: standard`, `qa.level: standard`) + +B writes a GDD that meets `workflow: standard` (all 5 required sections present). +B's `/design-review` runs with `review_mode: solo` — fast advisory check, no +specialists spawned. B moves on. + +Later A reviews the same GDD with `review_mode: full` — spawns 5–15 specialists, +finds polish opportunities and edge cases B's lighter review missed. A files +findings as PR comments or follow-up issues. B addresses the ones that matter. + +**This is normal team collaboration.** Different reviewers catching different +things is the value of having multiple reviewers. The artifacts on disk are the +same; only review depth differs. Nothing about this scenario creates project-state +divergence. + +**Different requirements (PREVENTED by the whitelist):** + +If `qa.level` were locally overridable: + +- B sets `qa.level: minimal` locally. B's stories don't include test evidence. +- B's `/story-done` accepts the story without evidence. Story marked Complete on disk. +- A pulls. A's `qa.level: standard` requires evidence. A's `/story-done` would have rejected it. +- A's next `/gate-check` fails because evidence is missing across multiple of B's "Complete" stories. + +This is the artifact divergence problem. To prevent it, `qa.level` is locked to +`project.yaml`. The team agrees together on what's required; individuals override +only their personal experience, never the team's standards. + +**The pattern to remember:** + +| Difference type | Outcome | +|----------------|---------| +| Different review depth (review_mode, team.size) | Stricter reviewer surfaces more findings — healthy. Artifacts unchanged. | +| Different local strictness on failures (testing.strict.*, performance.enforce) | Stricter dev's local /story-done blocks earlier — they fix it locally. CI uses project.yaml defaults so team-wide quality bar stays. | +| Different artifact requirements (workflow, qa.level, docs.density, etc.) | **Can't happen — these are locked.** Team agrees once in project.yaml. | + +--- + +### CI behavior + +CI environments have no `project.local.yaml` — the file is gitignored and never +present in a fresh clone. CI therefore always runs against `project.yaml` +defaults: strict project standards, no developer-specific overrides. + +This is intentional and important: + +| Setting | Local default for fast iteration | CI default (`project.yaml`) | +|---------|----------------------------------|------------------------------| +| `modes.review_mode` (locally overridable) | `solo` for one developer | `lean` (team default — phase gates always run) | +| `testing.strict.logic` (locally overridable) | `false` for fast WIP commits | `true` (failing tests block CI) | +| `modes.automation` (locally overridable) | `autonomous` for solo flow | `collaborative` (the team's collab protocol, but CI doesn't ask questions anyway — this is mostly a no-op on CI) | + +A developer can iterate locally with relaxed gates, but CI catches issues using +the strict project defaults. The team's quality bar is enforced at the CI level +regardless of any individual developer's local config. + +--- + +### Secrets — never in either file + +`project.yaml` and `project.local.yaml` are for **preferences and configuration**, +never for **secrets**. API keys, tokens, and credentials belong in: + +- **Environment variables** (preferred for production/CI) +- **`.env` file** (gitignored, local dev) +- **OS keychain** (when integrating with services that support it) + +When a skill needs an API key (e.g. `/asset-spec` calling Midjourney), it reads +from the environment, not from `project.yaml`. `project.yaml` only stores **which** +generator is used (`art.generator: midjourney`), not the API key for it. + +CI environments typically have no `project.local.yaml` (gitignored, not in the +clone) — so CI always runs against `project.yaml` defaults. This is correct +behavior: strict project defaults for CI, looser developer overrides for local work. + +--- + +## modes.review_mode + +**Controls:** How many specialist and director agents are spawned during skill execution +**Values:** `full` | `lean` | `solo` +**Default:** *none* — supplied by `modes.rigor` (`standard` yields `lean`) +**Set by:** `/start`, `/adopt`, `/settings` +**Read by:** See skill table below + +**Priority chain (all skills):** inline argument flag → `modes.review_mode` in `project.yaml` → `production/review-mode.txt` (legacy fallback) → `modes.rigor` expansion (no terminal default; `minimal`→`solo`, `standard`→`lean`, `full`→`full`) + +> **design-review follows the standard chain.** `modes.review_mode` controls +> design-review depth, its inline flag is `--review` for consistency with every +> other review-mode-aware skill, and the default is `lean` to match them. + +> **Hotfix / day-one-patch exception:** These skills are emergency or release-critical +> pipelines. They are never gated by `review_mode` — all agents always run. + +> **Known inertness — `full` and `lean` are identical in all nine `team-*` +> orchestrators.** Each of them documents a three-way split: +> `full` spawns every director and lead gate, `lean` skips director gates *unless* +> they are PHASE-GATE type, `solo` skips gate spawning entirely. But every +> `team-*` skill names exactly four gates — `CD-PHASE-GATE`, `TD-PHASE-GATE`, +> `PR-PHASE-GATE`, `AD-PHASE-GATE` — and all four are the type `lean` keeps. So +> `lean` has nothing to skip and behaves as `full`. +> +> `solo` genuinely differs, and outside the orchestrators (`/design-review`, +> `/architecture-review`) all three levels differ as documented. +> +> **This is recorded, not fixed.** Making `lean` mean something in the +> orchestrators requires deciding which non-phase-gate reviewers each of the nine +> should call at `full` — a design decision about review depth, not a wording fix, +> and one that changes how many agents a run spawns. + +--- + +### Value intent + +| Value | Intent | +|-------|--------| +| `full` | All director and specialist gates active. Best for teams, learning, or anyone who wants thorough AI feedback on every decision. | +| `lean` | Phase gates only — per-skill director sign-offs skipped. Default. Balances quality with token cost. | +| `solo` | No gates, no director spawns at all. Fastest. For confident solo iteration. | + +--- + +### Core skills (currently review-mode aware) + +| Skill | full | lean | solo | +|-------|------|------|------| +| **design-review** | 5–15 specialist agents spawned in Phase 3b | No specialist agents — single-session analysis only | Phases 1–4 only, no delegation, no next-steps prompt | +| **gate-check** | Director panel runs in parallel — **width set by `modes.workflow`**, not by this axis | Director panel runs in parallel — same width rule; phase gates always run | Artifact existence checks only, no directors spawned | +| **design-system** | All section specialists + CD-GDD-ALIGN sign-off gate | All section specialists, CD-GDD-ALIGN skipped | All section specialists, CD-GDD-ALIGN skipped | +| **art-bible** | All section specialists + AD-ART-BIBLE sign-off gate | All section specialists, AD-ART-BIBLE skipped | All section specialists, AD-ART-BIBLE skipped | +| **architecture-decision** | Engine specialist + TD-ADR gate | Both skipped | Both skipped | +| **create-architecture** | TD self-review + LP-FEASIBILITY agent | Both skipped | Both skipped | +| **brainstorm** | CD-PILLARS, AD-CONCEPT-VISUAL, TD-FEASIBILITY, PR-SCOPE | All skipped | All skipped | +| **map-systems** | TD-SYSTEM-BOUNDARY, PR-SCOPE, CD-SYSTEMS | All skipped | All skipped | +| **story-done** | QL-TEST-COVERAGE + LP-CODE-REVIEW | Both skipped | Both skipped | +| **sprint-plan** | PR-SPRINT feasibility check | Skipped | Skipped | +| **create-epics** | PR-EPIC scope check | Skipped | Skipped | +| **create-stories** | QL-STORY-READY + test case specs | Skipped | Skipped | +| **prototype** | CD-PLAYTEST director override | Skipped — prototyper verdict is final | Skipped — prototyper verdict is final | +| **milestone-review** | PR-MILESTONE feasibility check | Skipped — no producer verdict | Skipped — no producer verdict | +| **playtest-report** | CD-PLAYTEST creative assessment | Skipped | Skipped | +| **story-readiness** | QL-STORY-READY acceptance criteria check | Skipped | Skipped | + +--- + +### Skills to be made review-mode aware (currently ignore the setting) + +| Skill | full | lean | solo | +|-------|------|------|------| +| **review-all-gdds** | Phase 2 consistency pass + Phase 3 design theory pass (parallel) | Phase 2 consistency pass only, Phase 3 skipped | Phase 2 only, Phase 3 skipped | +| **architecture-review** | Engine specialist + security-engineer (if online features) | Both skipped | Both skipped | +| **dev-story** | Core programmer routing always runs + engine-specialist escalation on HIGH risk stories | Core programmer routing always runs, engine-specialist escalation skipped | Core programmer routing always runs, engine-specialist escalation skipped | +| **code-review** | Language, shader, UI specialists + qa-tester | All optional specialists skipped | All optional specialists skipped | +| **security-audit** | security-engineer spawned | Skipped | Skipped | +| **create-control-manifest** | technical-director validation step | Skipped | Skipped | +| **propagate-design-change** | technical-director validation step | Skipped | Skipped | + +--- + +### Team orchestration skills (pipeline behavior — different relationship) + +Team skills are execution pipelines, not review gates. `review_mode` controls +whether *optional* specialists within the pipeline are included — not whether +the pipeline runs. + +| Skill | full | lean | solo | +|-------|------|------|------| +| **team-combat** | Full pipeline including technical-artist, sound-designer, qa-tester | Core pipeline only (game-designer, gameplay-programmer, ai-programmer) — optional specialists skipped | Core pipeline only | +| **team-ui** | Full pipeline including accessibility-specialist, art-director review pass | Core pipeline (ux-designer, ui-programmer, engine UI specialist) — optional reviewers skipped | Core pipeline only | +| **team-audio** | Full pipeline including accessibility-specialist, engine-specialist | Core pipeline (audio-director, sound-designer, technical-artist) — optional specialists skipped | Core pipeline only | +| **team-qa** | Full QA cycle including per-story qa-tester spawns + qa-lead sign-off | Core qa-lead plan only, per-story tester spawns skipped | Core qa-lead plan only | +| **team-release** | Full pipeline including security-engineer, analytics-engineer, localization-lead | Core pipeline (producer, release-manager, devops-engineer, qa-lead) — optional reviewers skipped | Core pipeline only | +| **team-narrative** | Full pipeline including world-builder, writer, localization-lead | Core pipeline (narrative-director, art-director) — optional contributors skipped | Core pipeline only | +| **team-polish** | Full pipeline including engine-programmer, sound-designer, qa-tester | Core pipeline (performance-analyst, technical-artist) — optional specialists skipped | Core pipeline only | +| **team-level** | Full pipeline including narrative-director, world-builder, accessibility-specialist, qa-tester | Core pipeline (level-designer, systems-designer, art-director) — optional reviewers skipped | Core pipeline only | +| **team-live-ops** | Full pipeline including analytics-engineer, community-manager | Core pipeline (live-ops-designer, narrative-director, economy-designer) — optional contributors skipped | Core pipeline only | + +--- + +### Special cases and notes + +- **gate-check** is the only skill where `full` and `lean` behave identically — + phase gates always run. Only `solo` skips them. This is intentional: phase + gates are the minimum quality bar, not optional extras. **How many directors + run is a separate axis** — `modes.workflow` sets panel width (`minimal` → PR + only, `standard` → TD + PR, `full` → all four), because a fixed four-director + Opus panel cost a two-system jam exactly what it cost a thirty-system + commercial project. Width never softens a verdict: the strictest verdict from + whoever ran still wins, and `/gate-check` names the perspectives it skipped. +- **design-system** and **art-bible** always spawn section specialists regardless + of mode — only the final sign-off gate is controlled by `review_mode`. Specialist + delegation is mandatory for the authoring output to be useful, and that has not + changed. What sets `art-bible`'s **spawn count** is therefore not `review_mode` + but (a) `modes.workflow`, via how many sections get authored at all — `standard` + requires sections 1–4, not all 9, and Phase 1 recommends the tier's set — and + (b) batching consecutive sections that call the *same* agent with the *same* + input into one delegation (2–4, and 5–6). Unbatched, sections 5 and 6 spawn + `art-director` twice with the identical `sections 1–4` brief. Every section is + still specialist-authored; per-section approval and write-to-file are unchanged. +- **dev-story** core programmer routing (gameplay-programmer, ui-programmer, etc.) + always runs regardless of mode — only the optional engine-specialist escalation + for HIGH risk stories is gated. +- **hotfix** and **day-one-patch** are exempt — all agents always run. +- When a gate is skipped, skills emit: `"[GATE-ID] skipped — [mode] mode."` before + proceeding. This is consistent across all skills. + +--- + +## modes.rigor + +**Controls:** How much process the project carries overall — the single question +`/start` asks that sets the six knobs below +**Values:** `minimal` | `standard` | `full` +**Default:** `minimal` — see the rationale block above `_yaml_helper_defaults` +in `.claude/hooks/yaml-helper.sh`. Short version: the heavier tier cost several +times more to reach working code without producing a better result. +**Set by:** `/start` (Phase 3d), `/settings` +**Read by:** nothing directly — it is read *through* the six knobs it supplies + +**Priority chain:** `modes.rigor` in `project.yaml` → hardcoded default `minimal` +(**locked** — not overridable from `project.local.yaml`, like the four *on-disk* +knobs it fronts — `modes.workflow`, `docs.density`, `qa.level`, +`modes.story_granularity` — because those five change what artifacts exist on +disk. The two personal-experience knobs it also fronts, `modes.review_mode` and +`team.size`, stay locally overridable.) + +--- + +### Expansion table + +| `modes.rigor` | `modes.workflow` | `docs.density` | `qa.level` | `modes.story_granularity` | `modes.review_mode` | `team.size` | +|---|---|---|---|---|---|---| +| `minimal` | `minimal` | `terse` | `minimal` | `coarse` | `solo` | `individual` | +| `standard` *(default)* | `standard` | `balanced` | `standard` | `balanced` | `lean` | `individual` | +| `full` | `full` | `thorough` | `full` | `fine` | `full` | `studio` | + +### It fronts, it does not replace + +The expansion sits **below every explicit source and above the terminal +default**: + +``` +project.local.yaml → project.yaml → legacy file → rigor expansion → default +``` + +That ordering is the back-compat guarantee, and it is what makes the migration +free. Three consequences worth stating plainly: + +- **An existing `project.yaml` behaves identically.** If it already sets + `docs.density: terse`, step 2 answers before the expansion is consulted — so + the value and its reported source (`project.yaml`) are unchanged whether or not + `rigor` is present. No migration, no `schema_version` bump. +- **Overrides go both ways.** Unlike the `workflow_overrides` **boolean flags** — + which are additive and stricter-only — an explicit knob may be *looser* than + the rigor level implies. + (`workflow_overrides.system_overrides` is also two-directional — it replaces a tier; only the flags are stricter-only.) + Inheriting the stricter-only rule would make "comprehensive but compact" + inexpressible, which is the one off-diagonal the docs have always cited. +- **`modes.workflow` keeps `workflow_overrides.system_overrides`.** Per-system + tiers still beat the rigor-derived project tier. `workflow` was fronted rather + than folded away precisely because it drives ~5 distinct behaviors and owns the + only per-system override mechanism. + +The six fronted knobs therefore have **no entry in `_yaml_helper_defaults`**. A +terminal default there would answer before the expansion and make `rigor` a no-op +for anyone who had not also set the sub-knob. + +### Provenance + +Derived values report their source as `rigor:`, so the vocabulary is +`{project.local.yaml, project.yaml, , rigor:, default, +unset}`. `/settings` renders this as `(derived from rigor: standard)` and +force-appends the five to its view-all list — none of them is a leaf of either +YAML file, so enumerating file leaves alone would hide exactly the settings the +user just chose. + +--- + +## modes.workflow + +**Controls:** How much design documentation is required before code can begin — +the tradeoff between token/time cost of documentation and speed of reaching +working code +**Values:** `full` | `standard` | `minimal` +**Default:** *none* — supplied by `modes.rigor` (`standard` yields `standard`) +**Set by:** `/start`, `/settings` +**Read by:** See skill tables below + +**Priority chain:** `modes.workflow` in `project.yaml` → `modes.rigor` expansion → *(no hardcoded default)* + +> **Why the middle level is `standard`, not `indie`.** A developer-type label +> would conflate "type of developer" with "level of process". `standard` +> describes the *amount of process*, not the user, and pairs cleanly with +> `minimal` and `full`. + +> **Permissive, not restrictive:** `workflow` controls what is REQUIRED — +> not what is ALLOWED. Any skill can be run at any workflow level. The +> setting changes what gate-check enforces and what completeness checks +> validate, not what the user can invoke. + +> **`minimal` floor:** Even in minimal mode, engine choice and a filled +> **`design/game-brief.md`** are required before code starts — the brief's +> build-order field is the plan, so there is no separate `sprint-plan` step. +> Both take minutes and prevent real problems downstream. Everything else can +> be skipped. + +> **Escape hatches via `workflow_overrides`:** Users who want *almost* the +> standard set with one extra requirement (e.g. always force Edge Cases) use +> `workflow_overrides` rather than escalating to `full`. See next section. + +--- + +### Value intent + +| Value | Token/time cost | Required before code | Best for | +|-------|----------------|---------------------|----------| +| `full` | High | All 8 GDD sections per system, full architecture, all ADRs, art bible, UX specs per screen | Projects where design correctness matters more than speed — teams, commercial titles, learning the full pipeline | +| `standard` | Balanced | 5 GDD sections per system, one architecture doc, critical ADRs, game concept, systems index | Projects that have outgrown a brief — several interacting systems, or a design someone else has to implement | +| `minimal` | Low | One-page `design/game-brief.md` + engine choice (build order in the brief = the plan; no separate sprint plan) | **Default.** Get to code fast — small scope, jam projects, or the design already in your head. Raise it when the project grows | + +**standard required GDD sections:** Overview, Detailed Rules, Edge Cases, Dependencies, +Acceptance Criteria. +**standard conditional:** Formulas — required when the system **defines numeric +rules**: rates, curves, thresholds, costs, damage, drop weights, or any value a +balance pass would tune. Optional only when the system defines no such value. + +> **The test is what the system defines, not what it is called.** The `Category` +> column in `systems-index.md` is a *hint* — `Gameplay`, `Economy` and +> `Progression` systems almost always qualify — but it is not the test, and a +> `Core`, `UI` or `Persistence` system that defines a numeric rule qualifies too. +> +> Do not restate it as "required when system category is combat, economy, +> progression, or AI". Those four tokens are not the vocabulary `/map-systems` +> writes: `templates/systems-index.md` defines the categories as `Core · +> Gameplay · Progression · Economy · Persistence · UI · Audio · Narrative · +> Meta`, and lists **combat and AI as example systems under `Gameplay`**. A +> combat system categorised exactly as the template instructs therefore matched +> none of the four, and Formulas was silently dropped for the system most likely +> to need it. `coding-standards.md` and `rules/design-docs.md` already stated the +> "has math" test; the skills are reconciled to them rather than the other way +> round. + +> **Edge Cases is required at `standard`.** Skipping it consistently produces +> "wait, what happens if X is null?" debt during implementation. These 5 +> required sections are the minimum that produces implementable code. + +> **Art bible is conditional in `standard`.** It is required only if visual +> asset stories exist in the project. Code-focused projects can skip it +> entirely at `standard` level. + +--- + +### Authoring skills + +| Skill | full | standard | minimal | +|-------|------|----------|---------| +| **design-system** | All 8 sections required | 5 required sections + conditional Formulas; Player Fantasy, Tuning Knobs skipped | Not required — game brief replaces GDDs. Can still be run voluntarily. | +| **art-bible** | All 9 sections required | Required only if visual asset stories exist; sections 1–4 minimum when required | Not required. Can still be run voluntarily. | +| **create-architecture** | Full architecture — all layers, module ownership, data flow, API boundaries, full ADR audit | Simplified — system layer map + critical ADR list only | Not required. Can still be run voluntarily. | +| **ux-design** | UX spec required per screen | Core screens only (main menu, HUD, primary game loop) | Not required. Can still be run voluntarily. | +| **map-systems** | Required before design-system | Required before design-system | Not required | +| **architecture-decision** | All ADRs on required ADR list must be completed | Critical ADRs only — Foundation layer systems | Not required | + +--- + +### Review and validation skills + +| Skill | full | standard | minimal | +|-------|------|----------|---------| +| **design-review** | All 8 sections validated — any missing section blocks approval | 5 required sections validated; missing optional sections warned but do not block | Not applicable — no GDDs to review | +| **review-all-gdds** | Validates all 8 sections across all GDDs | Validates 5 required sections across all GDDs; optional sections surfaced as advisory only | Not applicable | +| **architecture-review** | Full traceability matrix — all GDDs, all ADRs | Reduced scope — architecture doc + critical ADRs only | Not applicable | +| **consistency-check** | Full entity registry cross-check against all GDD sections | Entity registry check against required sections only | Not meaningful — no GDDs to check | +| **content-audit** | Full content count audit against all GDD specs | Limited audit — only specs in required sections counted | Cannot run — no systems index exists | + +--- + +### Phase gate enforcement (gate-check) + +`gate-check` is where workflow has the most visible impact — the artifact +checklists differ per phase per mode. + +| Gate | full | standard | minimal | +|------|------|----------|---------| +| Concept → Systems Design | game-concept.md, pillars, Visual Identity Anchor | game-concept.md only | `design/game-brief.md` only (content check, not section check) | +| Systems Design → Technical Setup | systems-index, all MVP GDDs reviewed (8 sections), cross-GDD review | systems-index, 5-section GDDs reviewed, cross-GDD review optional | Not applicable — no gates between brief and code | +| Technical Setup → Pre-Production | engine, art bible sections 1–4+, 3+ ADRs, architecture, UX specs started | engine, art bible only if visual assets, critical ADRs, architecture | engine configured (required floor) | +| Pre-Production → Production | prototype, sprint plan, complete art bible, epics, 3+ playtests | prototype, sprint plan, epics, 1+ playtest | prototype (advisory); the brief's Build order is the plan — no separate sprint-plan floor | + + +| Production → Polish | core mechanics, tests, smoke check, QA sign-off | core mechanics, smoke check | smoke check | +| Polish → Release | all features, content complete, QA, smoke, no critical bugs | all features, smoke, no critical bugs | smoke, no critical bugs | + +**Director panel width** (`/gate-check` Section 4b) is workflow's second effect +here. Directors are Opus-tier, so a fixed four-director panel charged a +two-system jam exactly what it charged a thirty-system commercial project: + +| `workflow` | Panel | Directors | +|---|---|---| +| `full` | 4 | `creative-director`, `technical-director`, `producer`, `art-director` | +| `standard` | 2 | `technical-director`, `producer` | +| `minimal` | 1 | `producer` | + +`review_mode` remains the axis that decides *whether* the panel runs at all +(`solo` skips it entirely); workflow decides only how wide it is. Narrowing +never softens a verdict — the strictest verdict from whoever ran still wins, and +`/gate-check` names the perspectives that did not run. + +--- + +### Planning and implementation skills + +| Skill | full | standard | minimal | +|-------|------|----------|---------| +| **create-epics** | Requires all GDDs approved (8 sections) | Requires 5-section GDDs approved | **Skipped** (Option A) — no separate epic doc. `/create-stories` reads `design/game-brief.md` directly and synthesizes the implicit epic container itself. | +| **create-stories** | Requires all ADRs on required list present | Requires critical ADRs present | No ADR requirement | +| **dev-story** | Requires TR registry + governing ADR + control manifest — blocks without them | TR registry optional; critical ADR required where present | No TR registry, no ADR required — implements against `design/game-brief.md` | +| **story-readiness** | Full TR registry + ADR + control manifest validation | TR registry optional; critical ADR check only | Acceptance criteria check only | +| **story-done** | Full GDD traceability + ADR consistency check | 5-section GDD traceability check | Acceptance criteria check only | +| **qa-plan** | Full test plan derived from all 8 GDD sections | Test plan from 5 required sections | Test plan from acceptance criteria only | +| **regression-suite** | Critical paths mapped from all sections including Edge Cases | Critical paths from required sections (including Edge Cases) | Smoke test coverage only | + +--- + +### Support skills + +| Skill | full | standard | minimal | +|-------|------|----------|---------| +| **project-stage-detect** | Checks for all doc types; missing art bible or UX specs flagged as gaps | Adjusts expectations — art bible only flagged if visual-asset stories exist; non-core UX specs not flagged | `design/game-brief.md` + engine present = normal state; no gaps flagged for absent GDDs | +| **adopt** | Full format compliance audit across all doc types | Audit scope reduced to required docs only | `design/game-brief.md` format check only | +| **reverse-document** | Generates full 8-section GDD from code | Generates 5-section GDD from code | Generates `design/game-brief.md` from code | +| **propagate-design-change** | Full cascading impact analysis across all ADRs | Checks critical ADRs for references only | Not applicable | +| **help** | Full artifact detection — surfaces all missing docs as next steps | Adjusts "what's missing" to required docs only — optional docs not surfaced as gaps | `design/game-brief.md` + engine present = next step is code | + +--- + +### Workflow change mid-project + +When `workflow` changes via `/settings`, the skill emits an informational note: + +> "Workflow changed from [old] to [new]. Run `/content-audit` to see what's now +> required or extra. No automatic migration — existing files stay in place." + +No documents are auto-deleted, auto-generated, or blocked. The setting only affects +*new* enforcement going forward. + +--- + +## workflow_overrides + +**Controls:** Opt-in granular requirements that override the chosen workflow level +**Location:** **top level of `project.yaml`** — a sibling of `modes:`, *not* a child of it +**Values:** object with sub-flags (see below) +**Default:** all false / empty +**Set by:** `/settings` +**Read by:** `/design-system`, `/design-review`, `/art-bible`, `/gate-check`, `/review-all-gdds`, `/qa-plan` + +> **Why the location is called out.** `resolve_config` reads +> `workflow_overrides.system_overrides.*` from the document root +> (`yaml-helper.sh`), and all 14 consuming skill files grep the same top-level +> path. Nested under `modes:` it is still valid YAML, so nothing errors — the +> setting is simply never read. If you copied a `workflow_overrides` block from +> anywhere, check its indentation: it belongs at the document root, never under +> `modes:`. + +**Priority chain:** the three `workflow_overrides` **boolean flags** +(`edge_cases`, `tuning_knobs`, `art_bible_strict`) are *additive* on top of the +chosen `workflow` level — they make things stricter, never looser. None of them +has a value that opts out of a requirement the workflow level imposes. + +> **`workflow_overrides.system_overrides` is not a flag and is not additive — it replaces a tier in both directions.** +> This rule does not extend to it. On a +> `full` project, `system_overrides.inventory: minimal` really does drop that +> system's required sections to zero. Intended — see +> `.claude/docs/workflow-modes.md § workflow_overrides`. Stating the additive +> rule over the whole key is wrong: the guarantee never held for +> `system_overrides`, so do not rely on it as a safety net. + +--- + +### Fields + +```yaml +workflow_overrides: + edge_cases: false # force Edge Cases section required (no-op at standard since already required; relevant at minimal) + tuning_knobs: false # force Tuning Knobs section required (relevant at standard, no-op at full) + art_bible_strict: false # force all 9 art bible sections required regardless of asset stories + system_overrides: {} # per-system tier overrides — see below +``` + +### Per-system overrides + +```yaml +workflow_overrides: + system_overrides: + combat: full # combat-system GDD requires full (8 sections) regardless of project workflow + boss-encounters: full + inventory: minimal # inventory needs no GDD — the game brief covers it (minimal = no required sections) +``` + +`system_overrides` is a map of `system_name: workflow_level`. The named system uses the override; all other systems use the project-level `workflow`. + +--- + +### Affected skills + +| Skill | How workflow_overrides changes behavior | +|-------|----------------------------------------| +| **design-system** | Reads `system_overrides[system_name]` first; falls back to project `workflow`. Applies override-specific section requirements. | +| **design-review** | Same lookup — validates against the override tier, not the project default, for the named system. | +| **art-bible** | If `art_bible_strict: true`, requires all 9 sections regardless of `workflow` level. | +| **gate-check** | Considers `system_overrides` when validating that all required GDDs are complete. A system at `system_overrides.combat: full` blocks the gate until all 8 sections exist for combat. | +| **review-all-gdds** | Validates each GDD against its effective tier (project workflow + any system override). | + +--- + +## modes.automation + +**Controls:** Whether skills ask for decisions and approval before acting, or +recommend and proceed — the tradeoff between user control and speed +**Values:** `collaborative` | `guided` | `autonomous` +**Default:** `collaborative` +**Set by:** `/start`, `/settings` +**Read by:** All ~40 skills that use `AskUserQuestion` or write files + +**Priority chain:** `modes.automation` in `project.yaml` → hardcoded default + +> **Unlike review_mode and workflow, this setting is not a per-skill table.** +> It defines universal interaction rules that apply across all skills. A +> per-skill breakdown would repeat the same answer 40 times. + +> **Overrides COLLABORATIVE-DESIGN-PRINCIPLE.md:** `guided` and `autonomous` +> modes intentionally relax the Q→O→D→Draft→Approval protocol defined in that +> document. This is opt-in. `collaborative` preserves the protocol exactly. + +> **Safety net — `automation_always_ask`:** Even in `autonomous` mode, certain +> categories of decision can be configured to always prompt. See next section. + +--- + +### Value intent + +| Value | Speed | Control | What changes | +|-------|-------|---------|--------------| +| `collaborative` | Slowest | Full — every decision is the user's | Current behavior. Q→O→D→Draft→Approval strictly followed. | +| `guided` | Balanced | High — major decisions are the user's, minor ones proceed automatically | AI states recommendation and proceeds for minor decisions. AskUserQuestion reserved for major/irreversible decisions. | +| `autonomous` | Fastest | Low — AI decides, logs, and proceeds | No AskUserQuestion (except categories in `automation_always_ask`). No draft review. No write approval. All decisions logged to decision log. | + +--- + +### Universal rules per mode + +#### collaborative (current behavior — no change) + +- `AskUserQuestion` called for every multi-option decision +- 2–4 options presented with pros/cons for every design choice +- Full draft shown and approved before every file write +- "May I write this to [filepath]?" asked before every write +- "May I create [filepath] with skeleton?" asked before skeleton creation +- Section-by-section approval in multi-section authoring skills +- Multi-file changes require explicit approval of the full changeset + +#### guided + +- `AskUserQuestion` called for **major decisions only** (see classification below) +- Minor decisions: AI states recommendation inline and proceeds — e.g. + *"Going with a static utility pattern here — it fits the existing architecture. + Continuing unless you want to change direction."* +- Draft shown briefly before writing — proceeds after a short summary, does not + wait for explicit "yes" +- "May I write?" asked for **new files only** — updates to existing files proceed + directly +- Still presents options for major decisions but caps at 2 choices with a clear + recommendation +- Multi-section authoring: writes each approved section immediately, no per-section + confirmation prompt + +#### autonomous + +- No `AskUserQuestion` calls **except** for categories listed in `automation_always_ask` +- No draft review +- No "May I write?" prompts — writes directly +- Picks the recommended option for every decision without presenting alternatives +- All decisions logged immediately to `production/session-logs/decision-log.md` + with: timestamp, skill, decision point, option chosen, reasoning +- User reviews decision log post-session to audit choices made + +--- + +### Major vs minor decision classification (guided mode) + +**Major — always use AskUserQuestion in guided mode:** + +| Decision type | Example | +|---------------|---------| +| Choosing a system name or document path | "What should we call this system?" | +| Mutually exclusive design directions | "Real-time or turn-based?" | +| Any choice that gates downstream work | Engine choice, architecture approach | +| Scope changes | "Cut this feature or slip the deadline?" | +| Any decision that can't be changed without significant rework | Core loop mechanic | + +**Minor — AI recommends and proceeds in guided mode:** + +| Decision type | Example | +|---------------|---------| +| Which section to work on next | "Moving to Edge Cases next" | +| Optional section inclusion | "Adding a Visual Notes section — fits the system" | +| Formatting and structure choices | Heading levels, table vs prose | +| Adding detail to an already-decided direction | Sub-options within an approved approach | +| Next-step routing after a phase completes | "Running design-review now" | + +--- + +### Affected skills by category + +**Authoring skills — highest impact.** Each pauses 10–15 times per document in +`collaborative`. Drops to 2–3 in `guided`, zero in `autonomous`. + +`brainstorm`, `design-system`, `art-bible`, `create-architecture`, `ux-design`, +`map-systems`, `architecture-decision`, `create-epics`, `create-stories`, +`sprint-plan`, `prototype` + +**Review skills — medium impact.** Pause after presenting findings to ask what +to do next. In `guided` routing decisions proceed automatically. In `autonomous` +the recommended path is taken and logged. + +`design-review`, `architecture-review`, `gate-check`, `review-all-gdds`, +`story-readiness`, `story-done`, `milestone-review`, `playtest-report` + +**Team orchestration skills — medium impact.** Pause between phases for +check-in. In `guided` the pipeline advances automatically unless BLOCKED. In +`autonomous` the full pipeline runs end to end. + +`team-combat`, `team-ui`, `team-audio`, `team-qa`, `team-release`, +`team-narrative`, `team-polish`, `team-level`, `team-live-ops` + +**Implementation skills — lower impact.** Pause for architectural questions +before writing code. In `guided` only genuinely ambiguous decisions stop. In +`autonomous` the conventional approach is picked and logged. + +`dev-story`, `code-review` + +**Setup and utility skills — lowest impact.** Pause for initial configuration +or routing questions. In `guided` defaults are applied automatically. In +`autonomous` recommended defaults are taken without prompting. + +`start`, `adopt`, `setup-engine`, `retrospective`, `localize`, `propagate-design-change`, +`asset-spec`, `smoke-check`, `qa-plan`, `story-readiness` + +--- + +### Exemptions — skills that ignore the automation setting + +These skills always behave as `collaborative` regardless of the setting. +The reasons are noted. + +| Skill | Why always collaborative | +|-------|--------------------------| +| **hotfix** | Emergency decisions — user must approve scope and risk before any action | +| **gate-check** | Results must be reviewed — a gate verdict without user acknowledgement defeats the purpose | +| **day-one-patch** | Release-critical — every action needs explicit sign-off | +| **setup-engine** | One-time irreversible choice that affects the entire project | + +--- + +### Decision log format (autonomous mode) + +Written to `production/session-logs/decision-log.md` — append-only. + +```markdown +## [timestamp] — [skill-name] + +**Decision point:** [what was being decided] +**Options considered:** [list of options that would have been presented] +**Chosen:** [what was picked] +**Reason:** [one-line rationale] +**Category:** [scope_changes | file_deletions | schema_changes | etc., or "minor"] +``` + +If the file does not exist, it is created. It is never truncated — each session +appends to the existing log. + +--- + +## modes.automation_always_ask + +**Controls:** Decision categories that ALWAYS trigger AskUserQuestion regardless of automation mode +**Values:** list of category names +**Default:** `[scope_changes, file_deletions, schema_changes]` +**Set by:** `/start`, `/settings` +**Read by:** All skills that respect `modes.automation` + +> **Makes `autonomous` safe.** Without this, `autonomous` is "AI does literally +> everything, no questions asked." With it, AI does most things but stops on +> destructive or irreversible decisions you specifically named. + +--- + +### Recognized categories + +| Category | Examples of decisions in this category | +|----------|----------------------------------------| +| `scope_changes` | Cutting a feature, slipping a deadline, splitting/merging an epic, removing acceptance criteria | +| `file_deletions` | Removing a story, deleting a GDD, removing a system, removing a test file | +| `schema_changes` | Changes to project.yaml, story template, control manifest, ADR template, GDD template | +| `architecture_decisions` | New ADR creation, ADR replacement, system boundary changes | +| `version_bumps` | Engine version change, framework version bump, dependency major version change | +| `external_calls` | Invoking external APIs (asset gen, AI services) when in autonomous mode | + +The list is a YAML list of these strings. Skills check whether the current decision falls into one of the configured categories and prompt regardless of `automation` value. + +--- + +### Affected skills + +| Skill | Categories typically triggered | +|-------|--------------------------------| +| **scope-check** | `scope_changes` | +| **propagate-design-change** | `schema_changes`, `architecture_decisions` | +| **architecture-decision** | `architecture_decisions` | +| **setup-engine** | `version_bumps`, `schema_changes` | +| **story-done** | `file_deletions` (when removing/replacing test files) | +| **asset-spec** | `external_calls` (when calling external generators) | + +--- + +## modes.story_granularity + +**Controls:** How big each story is and how many stories per epic/sprint +**Values:** `coarse` | `balanced` | `fine` +**Default:** *none* — supplied by `modes.rigor` (`standard` yields `balanced`) +**Set by:** `/start`, `/settings` +**Read by:** `/create-epics`, `/create-stories`, `/dev-story`, `/sprint-plan`, `/story-done`, `/sprint-status` + +**Priority chain:** `modes.story_granularity` in `project.yaml` → `modes.rigor` expansion → *(no hardcoded default)* + +--- + +### Value intent + +| Value | Story shape | Stories per sprint | Best for | +|-------|-------------|-------------------|----------| +| `coarse` | 1 story = 1 feature. 3–5 days work each. 5–10 ACs per story. | 2–4 | Solo devs, prototyping, "I know what I'm building" | +| `balanced` | 1 story = 1 task. 1–2 days work each. 2–4 ACs per story. Current default behavior. | 6–10 | Most projects | +| `fine` | 1 story = 1 AC. Hours of work each. Easy code review. Frequent /story-done. | 15–25 | Teams, learning, anything needing fine handoffs | + +--- + +### Affected skills + +| Skill | coarse | balanced | fine | +|-------|--------|----------|------| +| **create-epics** | Generates 3–5 child stories per epic | Generates 5–10 child stories per epic | Generates 10–20 child stories per epic | +| **create-stories** | Each story carries 5–10 ACs covering an entire feature | Each story carries 2–4 ACs covering one task | Each story carries 1 AC; story name = AC restatement | +| **dev-story** | Routing prompt expects multi-day implementation cycle. Subagent given longer working context. | Standard 1–2 day expectation. | Each story implementable in hours; subagent given tighter context. | +| **sprint-plan** | Allocates 2–4 stories per sprint based on velocity. | Allocates 6–10 stories per sprint. | Allocates 15–25 stories per sprint. | +| **story-done** | Fires every 3–5 days on average. | Fires every 1–2 days on average. | Fires multiple times per day. | +| **sprint-status** | Burn-down reads in feature-sized chunks. | Burn-down reads in task-sized chunks. | Burn-down reads in AC-sized chunks. | + +--- + +## docs.density + +**Controls:** How deep each section in authored docs goes — independent of which sections are required +**Values:** `terse` | `balanced` | `thorough` +**Default:** *none* — supplied by `modes.rigor` (`standard` yields `balanced`) +**Set by:** `/start`, `/settings` +**Read by:** `/design-system`, `/art-bible`, `/create-architecture`, `/ux-design`, `/map-systems`, `/brainstorm` + +**Priority chain:** `docs.density` in `project.yaml` → `modes.rigor` expansion → *(no hardcoded default)* + +> **Distinct from `workflow`, but no longer independent by default.** `workflow` +> controls which sections exist; `density` controls how deep each section goes. +> `modes.rigor` now sets both together, so the diagonal (`rigor: full` → 8 +> sections *and* thorough prose) is what you get without asking. The off-diagonal +> is still reachable and is the reason `rigor` overrides both ways: `rigor: full` +> + an explicit `docs.density: terse` gives "all sections but each is compact" — +> the "comprehensive but tight" mode, which neither knob expresses alone. + +--- + +### Value intent + +| Value | Per-section output style | +|-------|--------------------------| +| `terse` | Bullet points. 2–5 lines per section. Skip rationale and narrative framing. Skip preambles like "In this game..." Just facts needed to implement. | +| `balanced` | Paragraphs with light rationale. Examples where helpful. Current default behavior. | +| `thorough` | Full prose. Rationale for every decision. Examples, alternatives considered, design history. | + +--- + +### Combination matrix with workflow + +| | docs.density: terse | balanced | thorough | +|---|---|---|---| +| **workflow: minimal** | Game brief in bullets | Game brief in prose | Game brief + rationale | +| **workflow: standard** | 5 sections, bullets | 5 sections, paragraphs *(default)* | 5 sections + examples | +| **workflow: full** | 8 sections, bullets *(comprehensive but compact)* | 8 sections, paragraphs *(current full)* | 8 sections + rationale + examples | + +--- + +### Affected skills + +| Skill | terse | balanced | thorough | +|-------|-------|----------|----------| +| **design-system** | Each GDD section is 2–5 bullets. Skip Player Fantasy preambles. | Paragraphs with key decisions explained. | Full prose, decision history, alternatives considered. | +| **art-bible** | Each art bible section is a bulleted list of constraints + references. | Paragraphs explaining each visual choice. | Full prose including style explorations, references, rationale per choice. | +| **create-architecture** | Layer diagrams + decision bullets. No essays. | Diagrams + paragraph explanations of layer choices. | Full prose with rationale, trade-offs, alternatives considered per layer. | +| **ux-design** | Wireframe descriptions + interaction bullets. | Wireframes + paragraph descriptions of flows. | Full prose including user research summaries, alternative flow considerations. | +| **map-systems** | One-line system descriptions. | Brief paragraph per system + dependency notes. | Full system-by-system rationale + relationship analysis. | +| **brainstorm** | Pillar bullets + concept bullets. | Pillars + concept with brief rationale. | Pillars + concept + extensive rationale + alternatives. | + +--- + +## testing.framework + +**Controls:** Which test framework the project uses — drives test-runner commands and per-engine assumptions in CI +**Values:** engine-specific (see table) +**Default:** auto-populated by `/setup-engine` based on engine choice +**Set by:** `/setup-engine`, `/settings` +**Read by:** `/test-helpers` — the only skill that references it. `/qa-plan`, `/dev-story`, `/regression-suite`, `/test-setup` and `/smoke-check` do **not** name the key, despite being plausible readers. + *(Derived by grep. A helper-based read such as `session_state_enabled()` would not show up, so treat this list as the verified floor, not a ceiling.)* + +**Priority chain:** `testing.framework` in `project.yaml` → engine default + +> **Migrated from `technical-preferences.md`.** + +--- + +### Engine defaults + +| Engine | Default test framework | Alternatives | +|--------|------------------------|--------------| +| Godot | `gdunit4` | `godot-test`, `WAT` | +| Unity | `unity-test-framework` | `nunit`, custom | +| Unreal | `unreal-automation` | Google Test, custom | + +--- + +### Affected skills + +| Skill | How `testing.framework` is used | +|-------|--------------------------------| +| **qa-plan** | Test plan tailored to framework's idioms (assertion style, fixture pattern, parameterized tests) | +| **dev-story** | Test framework name passed to programmer subagent in implementation brief | +| **regression-suite** | Test file naming and discovery patterns match framework conventions | +| **test-setup** | Scaffolds the right test runner config (e.g. `gdunit4_runner.gd` for Godot vs Unity Test Framework Asset) | +| **smoke-check** | Invokes framework-appropriate test runner | + +--- + +## testing.strict + +**Controls:** Whether failing or missing test evidence blocks a story from +closing, or produces a warning and lets work continue — per test type +**Values:** map of `{logic, integration, visual, ui, config}` → `true | false` +**Default:** `{logic: true, integration: true, visual: true, ui: true, config: false}` +**Set by:** `/start`, `/settings` +**Read by:** `story-done`, `story-readiness`, `gate-check`, `smoke-check`, `dev-story` + +> **Per-type, not a single bool.** A global `testing.strict: true|false` forces +> all-or-nothing: either all test failures block or none do. Per-type matches how +> most teams actually work — strict logic and visual gates, an advisory smoke check. + +> **Composes with `qa.level`:** `qa.level` controls whether evidence is REQUIRED. +> `testing.strict` controls whether failures BLOCK when evidence exists. If +> `qa.level: minimal`, the strict map is effectively ignored (no evidence required +> means nothing to be strict about). + +--- + +### Value intent per type + +| Type | `true` | `false` | +|------|--------|---------| +| `logic` (formulas, AI, state machines) | Unit test failures BLOCK — story cannot close | Failures WARN — story can close with warning | +| `integration` (multi-system) | Integration test failures BLOCK | Failures WARN | +| `visual` (animation, VFX, feel) | Missing/failing screenshot evidence BLOCKS (default) | Advisory only — story can close | +| `ui` (menus, HUD, screens) | Missing screenshot of the screen BLOCKS (default) | Advisory only — story can close | +| `config` (balance tuning) | Failing smoke check BLOCKS | Advisory only — story can close (default) | + +--- + +### Affected skills + +| Skill | strict.[type]: true | strict.[type]: false | +|-------|---------------------|----------------------| +| **story-done** | Story of that type cannot close on test/evidence failure | Story can close; warning logged in story file | +| **story-readiness** | Validates test requirements exist before implementation — blocks if absent | Validates but does not block — surfaces as warnings | +| **gate-check** | CI failures of that type block phase transition | CI failures of that type surfaced as concerns, not blockers | +| **smoke-check** | Failing smoke (config strict) blocks QA handoff | Failing smoke flagged but handoff proceeds | +| **dev-story** | References test requirements — flags story as unverifiable if absent | References test requirements — advisory only | + +> **Unset-key resolution.** Each skill reads `testing.strict.`; if absent +> it reads `testing.strict` as a plain boolean (legacy form); if still absent it +> falls back to a hardcoded default. For `story-done`, `story-readiness`, +> `gate-check`, and `dev-story` the hardcoded default is the per-type default map +> above (logic/integration/visual/ui strict, config advisory). **`smoke-check` is +> the exception**: it is a build-health gate, so an unset `testing.strict.config` +> defaults to *strict* (a FAIL blocks QA hand-off) — preserving its v1.0 behavior. +> Only an explicit `testing.strict.config: false` makes a failing smoke advisory. + +--- + +## qa.level + +**Controls:** What test evidence is required to mark stories Done +**Values:** `minimal` | `standard` | `full` +**Default:** *none* — supplied by `modes.rigor` (`standard` yields `standard`) +**Set by:** `/start`, `/settings` +**Read by:** `/story-done`, `/story-readiness`, `/dev-story`, `/smoke-check`, `/gate-check`, `/qa-plan`, `/regression-suite` + +**Priority chain:** `qa.level` in `project.yaml` → `modes.rigor` expansion → *(no hardcoded default)* + +> **Different axis from `testing.strict`.** `qa.level` = what evidence is required. +> `testing.strict` = do failures block. A user wanting "minimal QA" sets +> `qa.level: minimal` and the strict map becomes effectively a no-op. + +--- + +### Value intent + +| Aspect | minimal | standard | full | +|--------|---------|----------|------| +| Logic stories | No test required, no gap flagged | Unit test required (strict.logic controls block) | Unit test + coverage minimum | +| Integration stories | No test required | Integration test or playtest doc required | Same + regression coverage | +| Visual stories | No evidence required | Screenshot required (strict.visual controls block) | Screenshot + lead sign-off required | +| UI stories | No evidence required | Screenshot of each screen required (strict.ui controls block) | Screenshot + walkthrough doc + interaction test | +| Config/Data stories | No evidence required | Smoke check advisory | Smoke check required | +| /story-done test gate | Acceptance criteria only | Enforced per story type | Enforced for every story type | +| /gate-check phase enforcement | No test gates | Logic + integration must pass | Full coverage + regression suite | +| /dev-story routing prompt | No "Test required: ..." line | Per-type test requirement included | Per-type + coverage target included | +| Regression suite required by | Never | Polish stage | Production stage | +| Best for | Jam games, throwaway prototypes, fast iteration | Most projects | Commercial, console cert, learning, paranoid teams | + +--- + +### Affected skills + +| Skill | minimal | standard | full | +|-------|---------|----------|------| +| **story-done** | Acceptance criteria check only — no evidence required | Per-type evidence required; strictness from `testing.strict` | All types require evidence; strictness from `testing.strict` | +| **story-readiness** | No test requirement validated | Test requirement per story type validated | Test requirement + coverage target validated | +| **dev-story** | Routing prompt has no "Test required" line | Per-type test requirement passed to programmer agent | Coverage target passed to programmer agent | +| **smoke-check** | Optional | Required before phase transition | Required before every commit | +| **gate-check** | No test enforcement at phase transitions | Logic + integration tests must pass | Full coverage check + regression suite | +| **qa-plan** | Skipped entirely or minimal smoke plan | Full plan per story type | Full plan + coverage targets per system | +| **regression-suite** | Not generated | Generated at Polish stage entry | Generated at Production stage entry | + +--- + +## qa.coverage_minimum + +**Controls:** Minimum code coverage percentage enforced at `qa.level: full` +**Values:** integer 0–100, or `null` for unset +**Default:** `null` +**Set by:** `/settings` +**Read by:** `/gate-check` only. `/story-done` does not reference the key, and no CI runner reads it. + *(Derived by grep. A helper-based read such as `session_state_enabled()` would not show up, so treat this list as the verified floor, not a ceiling.)* + +**Priority chain:** `qa.coverage_minimum` in `project.yaml` → no enforcement if `null` + +> **Only meaningful at `qa.level: full`.** Ignored at minimal and standard. A +> non-null value at standard or minimal level emits a warning at session-start: +> *"coverage_minimum is set but qa.level is not full — value will not be enforced."* + +> **Implementation is engine-specific.** Coverage measurement is different per engine: +> - Godot: gdunit4 coverage report (markdown export) +> - Unity: Unity Test Framework coverage (xml export) +> - Unreal: gcov-style coverage from clang flags +> CCGS does not unify these — gate-check reads the engine-appropriate report. + +--- + +## strict_gate_checks + +> ### RESERVED - NOT IMPLEMENTED +> +> **No skill or hook reads this setting. Setting it has no effect.** Everything +> below describes the intended design, not current behaviour. +> +> The value is checked for valid spelling when you set it, and then ignored. + +**Controls:** Whether phase gate failures block stage transition or just warn +**Values:** `true` | `false` +**Default:** `true` +**Set by:** `/start`, `/settings` +**Read by:** `/gate-check` + +**Priority chain:** `strict_gate_checks` in `project.yaml` → hardcoded default of `true` + +> **Different from `review_mode: solo`.** `review_mode: solo` skips the gate +> *entirely* — directors don't even run. `strict_gate_checks: false` runs the +> gate normally but treats the verdict as advisory. + +--- + +### Value intent + +| Value | Behavior | +|-------|----------| +| `true` | Default. FAIL or BLOCKING-CONCERNS verdict stops the stage transition. `project.stage` is not advanced. User must address findings and re-run gate. | +| `false` | All gate verdicts are advisory. `/gate-check` still runs and reports findings. `project.stage` advances on user confirmation regardless of verdict. | + +--- + +### Affected skills + +| Skill | true | false | +|-------|------|-------| +| **gate-check** | FAIL verdict stops execution before writing new stage; CONCERNS verdict prompts user but defaults to "stay at current stage." | All verdicts surfaced as informational; user is asked "advance anyway?" with default Yes. | + +--- + +## team.size + +**Controls:** Which agents are active by default — solo devs shouldn't get studio-grade gating on every change +**Values:** `individual` | `small` | `studio` +**Default:** *none* — supplied by `modes.rigor` (`minimal`/`standard` yield `individual`, `full` yields `studio`) +**Set by:** `/start` (via `modes.rigor`), `/settings` +**Read by:** All `team-*` skills, `/gate-check`, all skills that spawn agents via `Agent` + +**Priority chain:** `team.size` in `project.local.yaml` → `project.yaml` → the `modes.rigor` expansion (`full` → `studio`, else `individual`) → no terminal default. Locally overridable — it is a personal-experience knob, not an on-disk artifact. + +> **`individual` at `minimal`/`standard`, `studio` at `full`.** CCGS's audience is +> overwhelmingly solo indie developers, so the two lighter rigor tiers resolve +> `team.size` to `individual` — a `standard` (default) project gets one active agent +> per role, not 15 (directors and leads included). Only `rigor: full` opts into the +> full `studio` roster; a smaller team on a big project sets `team.size: small` +> explicitly, which wins over the rigor-derived value. + +> **Different axis from `workflow`.** `workflow` controls *what docs are required.* +> `team.size` controls *which agents exist to make them.* A solo dev on +> `workflow: full` still doesn't need all 49 agents — they need the same coverage +> from a smaller active set. + +--- + +### Active agent set per value + +| Value | Active set | Notes | +|-------|-----------|-------| +| `individual` | Core 8: `producer`, `game-designer`, `gameplay-programmer`, engine-specialist (engine-driven), `technical-artist`, `sound-designer`, `qa-tester`, `writer`. **Default.** | Directors and lead-* run only on explicit invocation (e.g. via `/architecture-decision`). Specialists outside the core 8 routed through their nearest core agent. | +| `small` | Core 15: directors (CD, TD, PR) + leads (LP, AD, QL, ND) + game-designer + systems-designer + engine-specialist + technical-artist + sound-designer + qa-tester + ux-designer + writer | `team-*` skills run their full pipeline. Optional specialists added per `review_mode`. | +| `studio` | All 49 agents available. Engine sub-specialists (e.g. `unity-dots-specialist` separate from `unity-specialist`) routed when relevant. Adversarial review patterns active. | Commercial titles, learning the full org chart, large teams with parallel work. | + +--- + +### Affected skills + +| Skill | individual | small | studio | +|-------|------|-------|--------| +| **team-combat** | Single agent (gameplay-programmer) runs the pipeline; ai-programmer escalated only if AI work flagged | Default pipeline as documented | Default + engine sub-specialists + adversarial review pass | +| **team-narrative** | writer only; narrative-director invoked only on explicit pillar conflict | narrative-director + writer + (per review_mode) localization-lead, world-builder | All narrative agents active + per-system writer reviews | +| **team-ui** | ui-programmer + ux-designer | + accessibility-specialist + art-director | + engine UI specialist + adversarial review pass | +| **team-audio** | sound-designer only | + audio-director + technical-artist | + localization-lead + per-system audio reviewers | +| **team-level** | level-designer + gameplay-programmer | + systems-designer + art-director | + narrative-director + world-builder + accessibility-specialist | +| **team-qa** | qa-tester only; qa-lead invoked at phase gates only | qa-lead + qa-tester pipeline | qa-lead + per-story qa-tester spawn + sign-off | +| **team-release** | release-manager only | + producer + devops-engineer + qa-lead | + security-engineer + analytics-engineer + localization-lead | +| **gate-check** | Panel width is `modes.workflow`'s axis, not this one (gates always run) — `team.size` affects only specialist depth within each director's review | Same as individual for directors; specialists within directors' reviews use small set | Same plus engine sub-specialists | +| **architecture-decision** | TD + lead-programmer; engine-specialist | TD + LP + engine-specialist + engine sub-specialist if applicable | All of small + adversarial review by alternate engine-specialist | + +--- + +### Special cases + +- The 3 directors (CD, TD, PR) are present in ALL team sizes — phase gates require them. The setting affects *who else* shows up. +- `individual` does not disable agents — it just changes which are active by default. Any agent can still be invoked by name. +- When `team.size: individual` and a non-core agent is needed (e.g. `network-programmer` for a multiplayer story), the skill emits an informational note and routes through the nearest active core agent. + +--- + +## platform.multiplayer + +**Controls:** Whether the project includes real-time online multiplayer — determines +which specialists are routed and which architecture sections are required +**Values:** `true` | `false` +**Default:** `false` +**Set by:** `/setup-engine`, `/settings` +**Read by:** `dev-story`, `create-architecture`, `security-audit`, `team-release`, `gate-check` + +**Priority chain:** `platform.multiplayer` in `project.yaml` → hardcoded default of `false` + +> **Distinct from `platform.online`.** `multiplayer` is *real-time multiplayer +> specifically* (network specialist, netcode review, replication architecture). +> `platform.online` covers everything else online (cloud saves, leaderboards, +> telemetry). See next section. + +> **This is a project attribute, not a mode.** It describes what the game is, +> not how the pipeline operates. It lives in `platform` alongside `targets`, +> `primary_input`, and `gamepad_support` — not in `modes`. + +--- + +### Value intent + +| Value | Meaning | +|-------|---------| +| `true` | Game has real-time online multiplayer. Network specialist included in routing. Architecture requires networking layer. Security audit includes netcode review. | +| `false` | No real-time multiplayer. Network specialist excluded from routing. Architecture has no networking section. | + +--- + +### Affected skills + +| Skill | multiplayer: true | multiplayer: false | +|-------|------------------|-------------------| +| **dev-story** | `network-programmer` included in routing table for networking stories | `network-programmer` excluded — networking stories flagged as out of scope | +| **create-architecture** | Networking layer section required in architecture doc | Networking section omitted entirely | +| **security-audit** | Includes netcode review — authentication, replication, cheat vectors | Netcode review skipped | +| **team-release** | `network-programmer` included in release sign-off pipeline | `network-programmer` excluded | +| **gate-check** | Technical Setup gate checks for network architecture ADR | No network architecture requirement | + +--- + +## platform.online + +**Controls:** Whether the project has any online features (leaderboards, cloud saves, telemetry, IAP) +**Values:** `true` | `false` +**Default:** `false` +**Set by:** `/setup-engine`, `/settings` +**Read by:** `dev-story`, `create-architecture`, `security-audit`, `team-live-ops`, `gate-check` + +**Priority chain:** `platform.online` in `project.yaml` → hardcoded default of `false` + +> **Independent of `platform.multiplayer`.** A single-player game with Steam +> Cloud and leaderboards is `online: true, multiplayer: false`. The two settings +> have different specialist routing implications. + +--- + +### Value intent + +| Value | Meaning | +|-------|---------| +| `true` | Project includes online services (cloud saves, leaderboards, achievements, telemetry, IAP, anything calling external APIs at runtime). Analytics-engineer included; live-ops considerations active; security-audit includes data-transit review. | +| `false` | Project is fully offline. No analytics-engineer routing, no live-ops scope, security audit is local-only. | + +--- + +### Affected skills + +| Skill | online: true | online: false | +|-------|--------------|---------------| +| **create-architecture** | Data layer must include online service integration (cloud save sync, leaderboard auth, telemetry buffering) | Data layer is local storage only | +| **security-audit** | Includes data-transit review — TLS, save data integrity, leaderboard tampering | Local-only review (save file tampering, save data privacy) | +| **dev-story** | `analytics-engineer` routed for telemetry stories | analytics-engineer not routed | +| **team-live-ops** | Skill is available (live-ops-designer routes meaningfully) | Skill emits "no online features — live ops scope is empty" | +| **gate-check** | Technical Setup gate checks for online architecture decisions in critical ADRs | No online ADR requirement | + +--- + +## platform.cert_tier + +> ### WIRED — this setting is live +> +> `/launch-checklist` and `/release-checklist` both resolve it and emit +> console/mobile certification items only for the platforms it names, asking +> which are in scope when it is unset. + +**Controls:** Certification target — scopes what `/launch-checklist` asks for +**Values:** `none` | `itch` | `steam` | `console` +**Default:** `none` +**Set by:** `/setup-engine`, `/settings` +**Read by:** `/launch-checklist`, `/release-checklist` + +**Priority chain:** `platform.cert_tier` in `project.yaml` → hardcoded default of `none` + +--- + +### Value intent + +| Value | Required compliance | Best for | +|-------|---------------------|----------| +| `none` | No certification — internal release, alpha, jam game | Default | +| `itch` | itch.io upload requirements (build size, page setup, age tags) | Indie web/desktop releases | +| `steam` | Steamworks integration, store page, achievements, depot build, system requirements, common content rules | Commercial PC releases | +| `console` | Platform certification (PlayStation/Xbox/Switch) — heavy compliance: TRC/XR/Lotcheck, save data rules, controller mapping rules, age rating boards | Console releases | + +--- + +### Affected skills + +| Skill | none | itch | steam | console | +|-------|------|------|-------|---------| +| **launch-checklist** | Internal launch only — no external requirements | + itch.io page setup, butler upload, build size | + Steamworks integration, store page, achievements depot, common content checks | + Platform-specific TRC, certification submission, console save format compliance | +| **release-checklist** | Smoke pass + version bump only | + itch.io page live, build uploaded to butler | + Steam store page live, depot pushed, achievements live | + Platform submission accepted, age rating boards cleared | +| **/pre-cert** (future) | Not applicable | Lightweight readiness check | Steam common content audit | Full TRC compliance audit | + +--- + +## accessibility.target + +> ### RESERVED - NOT IMPLEMENTED +> +> **No skill or hook reads this setting. Setting it has no effect.** Everything +> below describes the intended design, not current behaviour. +> +> The value is checked for valid spelling when you set it, and then ignored. +> The accessibility-specialist routing and the Polish-stage audit described +> below **do not happen at any level** — `/team-ui`, `/team-level`, +> `/team-polish` and `/gate-check` do not read this setting, and `/start` +> does not ask for it. + +**Controls:** Accessibility commitment level — routes accessibility specialist and gates audit requirements +**Values:** `none` | `standard` | `aaa` +**Default:** `none` +**Set by:** `/start`, `/settings` +**Read by:** `/team-ui`, `/team-level`, `/team-polish`, `/gate-check` + +**Priority chain:** `accessibility.target` in `project.yaml` → hardcoded default of `none` + +> **Default is `none`, not `standard`.** Most CCGS users are solo devs and jam-game +> makers — defaulting to `standard` would auto-spawn the accessibility specialist +> for projects where it's overhead. `/start` asks the question explicitly so users +> who care can opt into `standard` or `aaa` consciously. + +--- + +### Value intent + +| Value | Required features | Specialist routing | +|-------|-------------------|--------------------| +| `none` | None enforced | `accessibility-specialist` not spawned | +| `standard` | Colorblind-safe palette, subtitles for any voice content, key remapping | `accessibility-specialist` runs in team-ui, team-level | +| `aaa` | Full Game Accessibility Guidelines: motor, vision, hearing, cognitive categories | `accessibility-specialist` runs in all team-* skills; mandatory audit at Polish stage | + +--- + +### Affected skills + +| Skill | none | standard | aaa | +|-------|------|----------|-----| +| **team-ui** | accessibility-specialist not spawned | accessibility-specialist included for HUD/menu reviews | accessibility-specialist included for every screen + accessibility audit pass | +| **team-level** | accessibility-specialist not spawned | accessibility-specialist reviews level for colorblind safety and signposting | accessibility-specialist reviews for full mobility/audio/cognitive considerations | +| **team-polish** | No accessibility pass | Final accessibility pass added | Full accessibility audit required before completion | +| **gate-check** | No accessibility check at any stage | Polish gate checks for the standard required features | Polish gate requires full Game Accessibility Guidelines compliance audit | + +--- + +## cadence.sprint_length and cadence.milestone_length + +> ### RESERVED - NOT IMPLEMENTED +> +> **No skill or hook reads this setting. Setting it has no effect.** Everything +> below describes the intended design, not current behaviour. +> +> No reference anywhere outside this file. + +**Controls:** Default sprint and milestone durations for planning +**Values:** +- `sprint_length`: `1w` | `2w` | `3w` | `4w` | `none` +- `milestone_length`: `4w` | `6w` | `8w` | `12w` +**Defaults:** `sprint_length: 2w`, `milestone_length: 8w` +**Set by:** `/start`, `/settings` +**Read by:** `/sprint-plan`, `/milestone-review`, `/sprint-status`, future velocity tracking + +**Priority chain:** `cadence.*` in `project.yaml` → hardcoded defaults + +> **`none` for `sprint_length`** means continuous-flow / kanban-style — no sprint boundaries. +> `/sprint-plan` becomes `/work-plan` and is invoked manually as needed. Velocity tracking +> is per-week rather than per-sprint. + +--- + +### Affected skills + +| Skill | What changes | +|-------|--------------| +| **sprint-plan** | Capacity calculated as `sprint_length × team.size velocity baseline`. `none` makes the skill emit a continuous-flow plan instead. | +| **milestone-review** | Milestone window for retrospective scope = `milestone_length`. | +| **sprint-status** | Burn-down chart x-axis spans `sprint_length`. | +| **retrospective** | Sprint retro fires at `sprint_length` cadence (or weekly when sprint_length is `none`). | + +--- + +## performance.target_framerate and performance.frame_budget_ms + +**Controls:** The frame-rate target and its per-frame millisecond budget +**Values:** integers (e.g. `60`, `16.67`) +**Default:** none — unset means no budget was ever committed to +**Set by:** the user, by hand or via `/settings` +**Read by:** `/perf-profile`, `/gate-check` + +## performance.draw_call_limit and performance.memory_ceiling_mb + +**Controls:** Per-frame draw-call ceiling and total memory ceiling +**Values:** integers +**Default:** none — unset means no budget was ever committed to +**Set by:** the user, by hand or via `/settings` +**Read by:** `/perf-profile`, `/gate-check` + +> **Every setting needs its own `## ` heading in this file, not just a table +> row.** The dead-settings audit derives what it checks from those headings, so +> a setting mentioned only inside a table is invisible to it — it can neither be +> reported DEAD if it loses its reader, nor caught if it gains one. A setting +> outside the audit's field of view is exactly how a documented-but-dead entry, +> or a working-but-listed-as-dead one, survives unnoticed. +> +> **Unset is not zero and not a default.** A budget nobody set is not a budget +> that was met — `/perf-profile` must report `NOT ASSESSED` for a metric with no +> committed target rather than measuring against a placeholder. That exact +> failure (">99% headroom against a 16.67ms budget", from zero profiler data) +> is one of the two instances that motivated `.claude/rules/skill-authoring.md`. + +## performance.enforce + +**Controls:** Whether performance budget violations warn or block +**Values:** `warn` | `block` | `off` +**Default:** `warn` +**Set by:** `/start`, `/settings` +**Read by:** `/perf-profile`, `/gate-check`, CI runner + +**Priority chain:** `performance.enforce` in `project.local.yaml` → `project.yaml` → hardcoded default of `warn`, resolved by `resolve_config --keys performance.enforce` + +> **Read it from the resolved block, never from `project.yaml` directly.** The key is on the `/settings --local` whitelist. `/perf-profile` Phase 0 and `/gate-check` Section 3 are the two readers the table above promises — a key that is enum-validated, accepted and displayed while nothing reads it is exactly the failure this warning guards against. + +> **Mirrors the `testing.strict` pattern.** Defines what happens when a budget +> defined in `performance.target_framerate`, `frame_budget_ms`, `draw_call_limit`, +> or `memory_ceiling_mb` is exceeded. + +--- + +### Value intent + +| Value | Behavior | +|-------|----------| +| `warn` | Default. Profile runs flag violations as findings. CI logs them but doesn't fail. `/gate-check` surfaces them as concerns, not blockers. | +| `block` | Profile failures block CI. `/gate-check` treats violations as FAIL verdict (Polish stage onward). | +| `off` | Performance budgets are informational only — no profile runs gated, no findings surfaced. | + +--- + +## project.stage + +**Controls:** The current development phase — determines which gate-check +validates and what artifacts are expected to exist +**Values:** `Concept` | `Systems Design` | `Technical Setup` | `Pre-Production` | `Production` | `Polish` | `Release` +**Default:** `Concept` +**Set by:** `/gate-check` on PASS verdict only — never set manually +**Read by:** `gate-check`, `project-stage-detect`, `help`, `sprint-status`, `day-one-patch` + +> **Stage count:** This document uses the +> 7-stage reality implemented by gate-check: +> `Concept | Systems Design | Technical Setup | Pre-Production | Production | Polish | Release` + +> **This is a project attribute, not a mode.** It records where the project is +> in its lifecycle. Only `/gate-check` writes it — and only on a PASS verdict +> with user confirmation. Never write it manually mid-session except to correct +> an incorrect auto-detect. + +> **Legacy fallback + dual-write.** Stage is read from `project.stage` in +> `project.yaml` first, then legacy `production/stage.txt`, then artifact +> auto-detection. When `/gate-check` advances the stage it dual-writes BOTH +> `project.yaml` and `production/stage.txt`, so hooks that have not migrated +> still resolve correctly. `/start` also dual-writes the initial stage. The +> legacy `.txt` file is retired at `.claude/scripts/migrate-v1-config.sh --finalize`. + +--- + +### Stage progression and gate requirements + +Each row is the gate that must be passed to advance FROM that stage. + +| From stage | Key artifacts required to pass | gate-check writes | +|------------|-------------------------------|-------------------| +| **Concept** | `game-concept.md`, design pillars, Visual Identity Anchor | `Systems Design` | +| **Systems Design** | `systems-index.md`, all MVP GDDs reviewed, cross-GDD consistency check | `Technical Setup` | +| **Technical Setup** | Engine configured, art bible sections 1–4 (if visual stories exist), 3+ ADRs, architecture doc, control manifest, test framework | `Pre-Production` | +| **Pre-Production** | Prototype, 3+ playtests, UX specs, epics defined, first sprint plan | `Production` | +| **Production** | Core mechanics implemented, main path playable, smoke check passes, QA sign-off | `Polish` | +| **Polish** | All features complete, content complete, localization, no critical bugs | `Release` | + +Gates adjust per `modes.workflow` — see `modes.workflow` section for what is +required at `standard` and `minimal` levels. + +--- + +### Affected skills + +| Skill | How it uses stage | +|-------|------------------| +| **gate-check** | Reads current stage to determine which gate to validate. Writes next stage on PASS. | +| **project-stage-detect** | Reads `project.stage` first — if set, uses it as authoritative. Falls back to heuristic detection only if absent. | +| **help** | Uses stage to determine "where are you in the pipeline" and surfaces the correct next steps. | +| **sprint-status** | Displays current stage in status summary. | +| **day-one-patch** | Validates that stage is `Release` before proceeding — refuses to run if project is not at release stage. | + +--- + +## project.kind + +> ### RESERVED - NOT IMPLEMENTED +> +> **No skill or hook reads this setting. Setting it has no effect.** Everything +> below describes the intended design, not current behaviour. +> +> No reference anywhere outside this file. + +**Controls:** What type of project this is — routes skill subsets +**Values:** `game` | `tool` | `engine` | `mod` | `library` +**Default:** `game` +**Set by:** `/start`, `/setup-engine` +**Read by:** Many skills (advisory routing — see below) + +**Priority chain:** `project.kind` in `project.yaml` → hardcoded default of `game` + +--- + +### Value intent + +| Value | Meaning | What's deactivated | +|-------|---------|-------------------| +| `game` | Default. Full CCGS framework applies. | Nothing | +| `tool` | Developer tool, not a game | Skip art bible, narrative, level design, playtesting | +| `engine` | Custom engine or library underneath games | Skip game-specific concerns entirely (narrative, art, playtest) | +| `mod` | Mod for an existing game | Skip engine setup; assume parent engine; reduce architecture scope | +| `library` | Reusable library | Emphasize tests + API design; design docs lighter; no asset pipeline | + +--- + +### Affected skills + +| Skill | game | tool | engine | mod | library | +|-------|------|------|--------|-----|---------| +| **brainstorm** | Full game-concept output | Tool concept output (user, problem, solution) | Engine concept (target use cases, scope) | Mod concept (what changes) | Library concept (API surface, target consumers) | +| **art-bible** | Required at appropriate workflow level | Skipped | Skipped | Optional (depends on visual scope) | Skipped | +| **map-systems** | Full game systems decomposition | Tool subsystems | Engine subsystems | Modified-system map | Library module map | +| **playtest-report** | Required at appropriate stages | Skipped or replaced with user testing | Skipped | Required (compatibility testing) | Skipped or replaced with API usage testing | +| **setup-engine** | Game engine setup | Skipped or replaced with build tool setup | Self-referential — engine IS the project | Parent engine assumed | Skipped or replaced with library scaffolding | + +--- + +## engine.name and specialists + +**Controls:** Which engine-specific specialist agents are routed to for code +implementation, review, and architecture validation +**Values:** `Godot` | `Unity` | `Unreal` +**Default:** `[UNSET]` — must be configured via `/setup-engine` +**Set by:** `/setup-engine` +**Read by:** `dev-story`, `code-review`, `architecture-decision`, `create-architecture`, all `team-*` skills + +> **Currently stored in `technical-preferences.md`**, not `project.yaml`. After +> YAML expansion, `engine.name` and the specialist routing table move to +> `project.yaml specialists` block. Skill behavior is unchanged — only the +> source file changes. + +> **This is a project attribute, not a mode.** Engine choice drives specialist +> routing the same way `platform.multiplayer` drives network routing — skills +> respond to a fact about the project, not a process preference. + +--- + +### Specialist routing by engine + +| Engine | Language/code specialist | Shader specialist | UI specialist | Additional specialists | +|--------|--------------------------|-------------------|---------------|----------------------| +| **Godot** | `godot-gdscript-specialist` (GDScript) or `godot-csharp-specialist` (C#) — driven by `engine.language` | `godot-shader-specialist` | `godot-specialist` | `godot-gdextension-specialist` | +| **Unity** | `unity-specialist` | `unity-shader-specialist` | `unity-ui-specialist` | `unity-addressables-specialist`, `unity-dots-specialist` | +| **Unreal** | `unreal-specialist` | `unreal-specialist` | `ue-umg-specialist` | `ue-gas-specialist`, `ue-blueprint-specialist`, `ue-replication-specialist` | +| **[UNSET]** | No specialist spawned — warning emitted | No specialist spawned | No specialist spawned | None | + +--- + +### Affected skills + +| Skill | How engine.name is used | +|-------|------------------------| +| **dev-story** | Routes code stories to language specialist. Spawns engine specialist alongside for HIGH engine-risk stories or engine-specific API usage. | +| **code-review** | Routes review to language, shader, or UI specialist based on file type being reviewed. | +| **architecture-decision** | Loads `docs/engine-reference/[engine]/VERSION.md` before authoring. Spawns engine specialist to validate ADR for API correctness and post-cutoff compatibility. | +| **create-architecture** | Includes engine-specific constraints and patterns in architecture doc based on engine choice. | +| **team-combat / team-ui / team-audio / team-level / team-polish** | All team skills spawn the appropriate engine specialist for implementation phases. | + +--- + +## commands + +> **Live since the run-and-observe step.** This block was RESERVED — written by +> `/setup-engine`, read by nothing — through most of v1.1. `test`, `smoke` and +> `build` gained readers as the parse-check and smoke gates landed; `run` gained +> its reader last, when `/dev-story` Phase 6 step 4 started launching the game to +> look at it. The per-skill table at the bottom is now current behaviour, not +> intent. `validate-push.sh` was once named as a reader and never was. + +**Controls:** Explicit shell commands to build, test, run, and smoke-test the project +**Values:** strings — shell-executable commands +**Default:** `[UNSET]` — populated by `/setup-engine` with engine-typical defaults +**Set by:** `/setup-engine`, `/settings` +**Written by:** `/setup-engine`, `/settings`. **Read by:** `test` — `/dev-story` (Phase 6 parse check), `/smoke-check`, `project-coherence.sh`; `smoke` — `/dev-story`; `build` — `/smoke-check`, `project-coherence.sh`; `run` — `/dev-story` (Phase 6 step 4, the run-and-observe step). `/launch-checklist` and `/regression-suite` name these commands but cannot execute them — their `allowed-tools` has no `Bash`. + *(Derived by grep. A helper-based read would not show up, so treat this list as the verified floor, not a ceiling.)* + +**Priority chain:** `commands.*` in `project.yaml` → engine-typical default → no command available + +> **Why explicit:** Without this block, CCGS has to guess engine-typical conventions +> for invoking the project. Better to be explicit per-project — covers cases where +> projects customize their build (e.g., a Godot project using gdunit4 for tests but +> a custom export script for builds). + +--- + +### Fields + +Each command can be a simple string (used on all OSes) or an OS-aware map: + +**Simple form** (one command, used on every OS): +```yaml +commands: + build: "godot --headless --export-debug ''" # ask; see the note below + test: "godot --headless --script tests/gdunit4_runner.gd" + run: "godot --path . --windowed --resolution 1280x720" + smoke: "godot --headless --quit-after 5" +``` + +Note that `build` is the one row the simple form suits least: an export preset +names a *platform*, so a single build string is portable only for a project that +builds one platform. The other three rows are genuinely OS-independent. + +**OS-aware form** (different commands per OS): +```yaml +commands: + build: + default: "godot --headless --export-debug 'Linux/X11'" + windows: "godot.exe --headless --export-debug 'Windows Desktop'" + macos: "godot --headless --export-debug 'macOS'" + test: + default: "godot --headless --script tests/gdunit4_runner.gd" + run: + default: "godot --path . --windowed --resolution 1280x720" + smoke: + default: "godot --headless --quit-after 5" +``` + +**Resolution rule:** if the value is a string, use it as-is on any OS. If the +value is a map, skills/hooks read the current OS and pick the matching key +(`linux`, `windows`, `macos`). Fall back to `default` if no OS-specific key +matches. + +This solves the Windows/Linux/macOS path-and-binary-name differences without +breaking the whitelist (commands stays locked to project.yaml — the OS-specific +keys are inside the locked file, not a local override). + +| Field | Purpose | +|-------|---------| +| `build` | Full project build for CI or release. Used by `/launch-checklist`, CI hooks. | +| `test` | Run the full test suite. Used by `/smoke-check`, `/regression-suite`, CI hooks. | +| `run` | Launch the **game** windowed at a fixed resolution — not the editor. Used by `/dev-story` Phase 6 step 4 to observe the feature and retain a screenshot; see `.claude/docs/run-and-observe.md` for the per-engine capture flags that are appended to it. | +| `smoke` | Minimal "does it boot?" check. Named by `/smoke-check` in its prose. Should complete in < 5 seconds. | + +--- + +### Engine defaults + +When `/setup-engine` runs, it populates `commands` based on engine choice — except +the **build** row, which it must **ask** for. The build artifact is named by a +project-defined string (a Godot export preset, a Unity build target), and no +engine reference in this repo can source it. See `/setup-engine` §"The Godot +`build` row must not hardcode `'Linux/X11'`". + +| Engine | build | test | run | smoke | +|--------|-------|------|-----|-------| +| Godot | `godot --headless --export-debug ''` | `godot --headless --script tests/gdunit4_runner.gd` | `godot --path . --windowed --resolution 1280x720` | `godot --headless --quit-after 5` | +| Unity | `Unity -batchmode -quit -projectPath . -buildTarget ` | `Unity -runTests -projectPath . -testPlatform PlayMode` | `Builds//.exe -screen-width 1280 -screen-height 720 -screen-fullscreen 0` | `Unity -batchmode -quit -projectPath . -executeMethod SmokeCheck.Run` | +| Unreal | engine-specific (varies by version) | engine-specific | engine-specific | engine-specific | + +> **`` and `` are placeholders, not values to copy.** A Godot +> export preset is whatever string the operator typed into `export_presets.cfg`; +> the shipped defaults are per-platform (`Windows Desktop`, `macOS`, `Web`, +> `Linux/X11`). Copying a preset name you do not have gives you a build command +> that fails with `Unknown export preset`; copying one you *do* have, but did not +> mean, gives you a command that succeeds and builds the wrong platform. The +> second is the dangerous one. If no presets exist yet — common, since +> `/setup-engine` usually runs before the project has any — write +> `[TO BE CONFIGURED]` rather than guessing a name. + +Users override per-project as needed via `/settings commands.=` or by editing `project.yaml` directly. + +--- + +### Affected skills + +| Skill | How `commands` is used | +|-------|------------------------| +| **smoke-check** | Invokes `commands.smoke` (or `commands.test` for full pass). Critical path for QA hand-off. | +| **launch-checklist** | Emits a checklist **item** asking whether the build produces a valid artifact. It does **not** run `commands.build` — its `allowed-tools` has no `Bash`. The verification is a human step. | +| **validate-push.sh** | Does **not** read `commands.*`. It warns on pushes to protected branches (`develop`/`main`/`master`) and runs no build or smoke command — the block path in it is commented out, so it never fails a push. | +| **dev-story** | Phase 6 step 2 runs `commands.test` or `commands.smoke` as the parse check. Phase 6 step 4 runs `commands.run` — the windowed game launch — to observe the feature and retain a screenshot (`.claude/docs/run-and-observe.md`). | +| **regression-suite** | Maps coverage by **globbing test filenames**. It does **not** invoke `commands.test` — its `allowed-tools` has no `Bash`, so coverage here means "a test file exists", never "a test passed". | + +--- + +## naming + +**Controls:** Code naming conventions for the project +**Values:** see field list below +**Default:** engine-specific (auto-populated by `/setup-engine`) +**Set by:** `/setup-engine`, `/settings` +**Read by:** `/adopt`, `/architecture-review`, `/asset-spec`, +`/create-architecture`, `/create-control-manifest`, `/dev-story`, +`/vertical-slice` — each reads `naming.*` from `project.yaml` and feeds the +conventions into a brief, manifest or spec. **NOT by `/code-review` and NOT by +`validate-commit.sh`**. + *(Reader list derived by grep, so treat it as the verified floor rather than + a ceiling — a skill reading the value through a helper would not show up.)* + +> **These conventions are guidance, not a gate.** They reach your project by +> being passed into briefs, manifests and specs for whoever writes the code. +> Nothing checks the result: `/code-review` does not validate files against them +> and `validate-commit.sh` does not warn on violations — neither contains any +> naming logic. If you need enforcement, it has to be added. +> +> **Known rough edge — asset filenames.** `validate-assets.sh` warns unless files +> under `assets/` are lowercase-with-underscores, and it takes no config input, +> while `/asset-spec` specifies asset names from `naming.*`. On Unity, Unreal and +> Godot-C#, where `/setup-engine` sets `naming.files: PascalCase`, that hook will +> warn on assets named exactly as `/asset-spec` specified them. The two are +> measuring different things — `naming.files` describes **source** files by its +> own examples (`PlayerController.cs`), and no key currently governs asset +> filenames — so treat the hook's naming warning as advisory on those engines. + +> **Moved from `technical-preferences.md`**, which is being deleted. Naming +> conventions are project-level facts shared by everyone on the project, so they +> belong in `project.yaml` alongside the rest of your structured config. + +--- + +### Fields + +| Field | Values | Godot default | Unity default | Unreal default | +|-------|--------|---------------|---------------|----------------| +| `classes` | `PascalCase` \| `snake_case` \| `camelCase` | PascalCase | PascalCase | PascalCase | +| `variables` | `snake_case` \| `camelCase` | snake_case | camelCase | camelCase | +| `constants` | `SCREAMING_SNAKE` \| `PascalCase` | SCREAMING_SNAKE | SCREAMING_SNAKE | SCREAMING_SNAKE | +| `signals` (Godot) / `events` (Unity, Unreal) | `past_tense` \| `on_event_name` | past_tense | on_event_name | OnEventName | +| `files` | `snake_case` \| `PascalCase` \| `kebab-case` | snake_case | PascalCase | PascalCase | +| `scenes` / `prefabs` | `snake_case` \| `PascalCase` | snake_case | PascalCase | PascalCase | + +> **`signals` vs `events`:** Godot uses signals natively; Unity uses C# events +> or UnityEvents; Unreal uses delegates and dynamic multicast. The field stays +> named `signals` in project.yaml for consistency across engines — interpret as +> "event-like callbacks" regardless of engine. + +--- + +### Affected skills + +| Skill | How naming is used | +|-------|--------------------| +| **dev-story** | Passes the naming conventions to the programmer subagent as part of the implementation context | +| **create-control-manifest** | Emits the conventions as manifest rows (classes, variables, signals/events, files, constants) | +| **asset-spec** | Specifies asset naming per the configured convention | +| **create-architecture** / **architecture-review** | Read as project config alongside `performance.*` | +| **vertical-slice** / **adopt** | Read as project config; `/adopt` audits conformance at MEDIUM severity | + +No skill or hook validates code *against* these conventions — see the note above. + +--- + +## features.session_state + +> ⚠️ **`active.yaml` does not exist and nothing writes it — anywhere in this +> document.** It is deferred design. What ships is +> `production/session-state/**active.md**`, whose `` region +> `session-start.sh` and `pre-compact.sh` both read, and whose +> `` block `statusline.sh` parses. **Read every `active.yaml` and +> `status:` mention in this file — above and below — as the deferred design, and +> substitute `active.md` (and its STATUS block).** This scope note is +> document-wide on purpose: the equivalent warning under *Status line behavior* +> scoped itself to mentions "below", which left this section's own Controls line +> and Hook behavior table describing a file that does not exist. + +**Controls:** Whether the harness runs a full session tracking pipeline (hooks, +log files, `active.yaml` checkpoint — see the scope note above) or relies on +skeleton-first output files for recovery +**Values:** `off` | `on` +**Default:** `on` +**Set by:** `/start`, `/settings` +**Read by:** All 7 session-lifecycle hooks; `statusline.sh`; any skill that writes +to `production/session-state/` + +> **This is the recommended setting for most users.** The session state pipeline +> was built to solve mid-task context loss during long conversations, but the +> problem is better solved by long-form skills writing self-describing skeleton +> files upfront. `session_state: on` is an opt-in for teams or power users who +> want explicit tracking across many files over many days. + +> **Priority chain:** `features.session_state` in `project.local.yaml` → +> `project.yaml` → hardcoded default of **`on`**. Hooks read it via +> `session_state_enabled()` in `yaml-helper.sh` and exit early when `off`. +> Skills check it before writing to `production/session-state/`. + +> **The default is `on`, deliberately.** `.claude/docs/context-management.md` +> tells you to rely on `production/session-state/active.md` as your +> crash-recovery checkpoint, so defaulting this off would quietly take away +> recovery, the session archive and the subagent tally. The ~2–5k tokens per +> session are an explicit opt-out (`/settings features.session_state=off`), +> never a silent default. + +> **`session-start.sh` is gated per-block, not at the top.** Its sprint, +> milestone and git context is not part of the session-state pipeline; only the +> `active.md` recovery preview is gated. Its check also fails OPEN — that hook +> sources `yaml-helper.sh` conditionally, and an undefined function is falsey, +> which would suppress the recovery checkpoint invisibly. + +--- + +### Value intent + +| Value | Intent | +|-------|--------| +| `off` | Recovery via skeleton-first output files. Long-form skills write a complete skeleton (all section headers, empty bodies) before starting. If context clears or compacts, the agent reads the skeleton + relevant docs and resumes. No hooks, no logs, no separate state file. ~2,000–5,000 tokens saved per session. | +| `on` | Full session tracking. `active.yaml` checkpoint, session-stop archival, compaction log, agent audit log, and STATUS breadcrumb all active. Adds ~2,000–5,000 tokens per session-start and ~1,000–6,000 per compaction event. | + +--- + +### Hook behavior + +> **Implementation note:** The feature flag check lives inside each hook script, +> not at the harness level. Every session-state-aware hook reads +> `features.session_state` from `project.yaml` at its top (via Python fallback +> chain) and exits silently with no output if the value is `off`. This ensures +> the harness always fires the hook — the hook decides whether to act. + +| Hook | session_state: off | session_state: on | +|------|--------------------|-------------------| +| **session-start.sh** | Runs normally (git/sprint context) — skips active.yaml preview block | Runs fully including active.yaml preview | +| **detect-gaps.sh** | Runs unchanged — gap detection is independent of session state | Runs unchanged | +| **pre-compact.sh** | Emits: "Read skeleton files and relevant docs to resume" — no active.yaml dump, no WIP scan, no compaction-log append | Full dump: active.yaml content + git diff + WIP scan + compaction-log append | +| **post-compact.sh** | No-op — exits silently | Runs: reminds agent to re-read active.yaml | +| **session-stop.sh** | No-op — exits silently | Archives active.yaml to session-log, appends commits and diffs | +| **log-agent.sh** | No-op — exits silently | Appends to agent-audit.log | +| **log-agent-stop.sh** | No-op — exits silently | Appends to agent-audit.log | + +--- + +### File existence by mode + +| File | session_state: off | session_state: on | +|------|--------------------|-------------------| +| `production/session-state/active.yaml` | Not created | Written by skills, read by hooks and status line | +| `production/session-logs/session-log.md` | Not written | Append-only archive of each session's active.yaml | +| `production/session-logs/compaction-log.txt` | Not written | Append-only timestamp log of compaction events | +| `production/session-logs/agent-audit.log` | Not written | Append-only log of agent spawn/complete events | + +--- + +### Skeleton-first pattern (session_state: off recovery mechanism) + +Long-form skills must write a skeleton file as their first action — before any +section discussion begins. The skeleton contains all section headers with empty +bodies and a `` marker per section. + +```markdown +# Combat System — GDD + +## Overview + + +## Player Fantasy + + +## Detailed Rules + +... +``` + +As each section is approved and written, the marker changes to ``. +On recovery (after /clear or compaction), the agent reads the skeleton, identifies +which sections are `done` vs `incomplete`, reads relevant ADRs and the systems index, +and resumes at the first `incomplete` section. No active.yaml needed. + +**Skills required to implement skeleton-first:** + +| Skill | Output file | Skeleton written when | +|-------|-------------|----------------------| +| **design-system** | `design/gdd/[system].md` | Before Phase 1 (section discussion) | +| **art-bible** | `design/art/art-bible.md` | Before section 1 discussion | +| **create-architecture** | `docs/architecture/architecture.md` | Before layer discussion | +| **ux-design** | `design/ux/[screen].md` | Before screen discussion | +| **create-stories** | `production/epics/[epic-slug]/story-NNN-[slug].md` | Before story authoring loop | +| **create-epics** | `production/epics/[epic-slug]/EPIC.md` | Before epic authoring loop | +| **map-systems** | `design/gdd/systems-index.md` | Before system enumeration | +| **brainstorm** | `design/gdd/game-concept.md` (`standard`/`full`) · `design/game-brief.md` (`minimal`) | Before concept discussion | + +> **Note on agents:** `world-builder` and `narrative-director` are agents, not +> invocable skills — they do not have SKILL.md files. Long-form output from these +> agents is produced via the skills above (design-system, art-bible, etc.) which +> are responsible for skeleton-first enforcement. + +--- + +### Status line behavior + +> ⚠️ **The table below describes POST-YAML-EXPANSION behavior, which is NOT +> IMPLEMENTED.** `active.yaml` does not exist and nothing writes it. What ships +> today: `statusline.sh` parses the ` ... ` +> markdown comment block inside `production/session-state/active.md`. Substitute +> "the STATUS block in `active.md`" for every `active.yaml` / `status:` mention +> here. **The same substitution applies document-wide, not just below this +> point** — see the scope note under `## features.session_state`. + +> **Stage detection is always independent of session state.** `statusline.sh` +> auto-detects stage from `project.yaml` `project.stage` or artifact heuristics regardless +> of `session_state` value — only the breadcrumb is gated. + +| Feature | session_state: off | session_state: on | +|---------|--------------------|-------------------| +| Context % display | Always shown | Always shown | +| Model display | Always shown | Always shown | +| Stage display | Always shown (from project.yaml or heuristics) | Always shown (same) | +| Epic/Feature/Task breadcrumb | Not shown — no active.yaml parsed | Parsed from `status:` block in `active.yaml` | + +--- + +### Affected skills + +Skills interact with session state in two ways: **inference** (reading active.yaml +to resolve a missing argument) and **extraction** (appending a SESSION EXTRACT +block after completing work). Both behaviors are gated by `session_state`. + +#### Inference — skills that read active.yaml to fill in missing arguments + +When `session_state: off`, these skills cannot infer from active.yaml and must +receive the argument explicitly. If called without an argument, they should prompt +the user rather than fail silently. + +| Skill | What it infers from active.yaml | Fallback when `off` | +|-------|----------------------------------|---------------------| +| **dev-story** | Active story path (when no arg given) | Prompt user for story path | +| **story-done** | In-progress story path (when no arg given) | Prompt user for story path | +| **team-release** | Target version (when no arg given) | Prompt user for version | +| **team-qa** | Active sprint (when no arg given) | Prompt user for sprint file | +| **help** | Current task and STATUS block for context-aware suggestions | Responds without session context — surfaces next steps from sprint/epics only | + +#### Extraction — skills that append SESSION EXTRACT to active.yaml + +These skills append a structured block after completing work when `session_state: on`. +When `off`, they skip this write entirely. + +| Skill | What it writes to active.yaml | +|-------|-------------------------------| +| **dev-story** | Story path, files changed, test written, blockers, next steps | +| **story-done** | Verdict, story path, tech debt count, next recommended story | +| **architecture-review** | Verdict, TR coverage counts, new TR-IDs, GDD flags, ADR gaps, report path | +| **prototype** | Prototype scope, current phase, key decisions | +| **vertical-slice** | Slice concept, current phase, blocked items | +| **team-qa** | QA phase comment, blocking issues | +| **map-systems** | Task record: systems enumerated, current phase | + +--- + +### Token cost summary + +| Scenario | session_state: off | session_state: on | +|----------|--------------------|-------------------| +| Per session start | ~500 tokens (git/sprint context only) | ~2,000–5,000 tokens (+ active.yaml preview) | +| Per compaction event | ~200 tokens (reminder only) | ~1,000–6,000 tokens (active.yaml + git diff + WIP scan) | +| Per session end | 0 | File I/O only (no conversation tokens) | +| Recovery after /clear | Read skeleton + 2–3 docs (~1,000–3,000 tokens) | Read active.yaml (~200–500 tokens) | +| Net for 50 sessions, 2 compactions/session | ~60,000 tokens | ~250,000–550,000 tokens | + +> **Note:** The `off` recovery cost (reading skeleton + docs) is paid only when +> recovery is actually needed. The `on` session-start cost is paid every session +> whether recovery is needed or not. + +--- + +## features.token_budget_warn_at + +> ### RESERVED - NOT IMPLEMENTED +> +> **No skill or hook reads this setting. Setting it has no effect.** Everything +> below describes the intended design, not current behaviour. +> +> The value is checked for valid spelling when you set it, and then ignored. +> Nothing warns at any threshold. + +**Controls:** Context usage threshold at which session warnings fire +**Values:** float between 0.0 and 1.0 +**Default:** `0.7` (warn at 70%) +**Set by:** `/start`, `/settings` +**Read by:** `pre-compact.sh`, `session-start.sh`, `statusline.sh` + +**Priority chain:** `features.token_budget_warn_at` in `project.yaml` → hardcoded default of `0.7` + +> Surfaces a warning when session context usage exceeds the threshold. Helps +> cost-conscious users hit `/clear` before runaway sessions. Set to `1.0` to +> disable. + +--- + +### Behavior + +| Threshold | What fires | +|-----------|-----------| +| Below `warn_at` | No warning | +| At or above `warn_at` | `statusline.sh` shows context usage in yellow; next session-start hook emits a one-line note: *"Last session reached [X]% context — consider /clear if continuing work."* | +| At 90%+ | Yellow becomes red regardless of `warn_at` value | + +--- + +## schema_version + +**Controls:** Which version of the project.yaml schema this file conforms to +**Values:** integer +**Default:** `1` (for v1.1 of CCGS) +**Set by:** `/start`, `/adopt` +**Read by:** All skills and hooks that parse project.yaml (for migration safety) + +> Lets future CCGS versions detect and migrate old project.yaml files. The +> migration logic compares `schema_version` in the file against the current +> CCGS's expected version. If newer, warn. If older, run migrations. + +--- + +### Migration behavior + +| project.yaml schema_version | Current CCGS expects | Behavior | +|----------------------------|---------------------|----------| +| Matches expected | — | Normal operation | +| Lower than expected | — | `/adopt` warns and offers migration; skills run with deprecation warnings | +| Higher than expected | — | All skills warn: "This project.yaml was created by a newer CCGS version. Some settings may be ignored." | +| Missing | — | Treated as legacy (pre-v1.1); migration prompted on next `/start` or `/adopt` | + +--- + +## framework + +**Controls:** Tracks which CCGS version created or last touched this project +**Values:** `framework.version` (semver string) + `framework.last_upgraded` (ISO date) +**Default:** auto-populated by `/start` and `/adopt` +**Set by:** `/start`, `/adopt` +**Read by:** no skill branches on it — it is file metadata, not a behavioural setting. It appears in `/start`, `/gate-check`, `/brainstorm`, `/settings` and `/setup-engine` as part of the `project.yaml` block they **write or display**, and in `yaml-helper.sh`. + *(The five skills above were verified to name it; whether each reads it or merely emits it was NOT separated, so this says 'appears in', not 'read by'.)* + +--- + +### Fields + +```yaml +framework: + version: "1.1.0" + last_upgraded: "2026-05-14" +``` + +| Field | Purpose | +|-------|---------| +| `version` | The CCGS version that created the project (or last ran `/upgrade`). Used to determine which migrations apply when CCGS itself is updated. | +| `last_upgraded` | ISO date of most recent `/adopt` or `/upgrade` run. Helps detect long-stale projects. | + +--- + +### Affected skills + +| Skill | How it uses framework | +|-------|----------------------| +| **adopt** | Reads `framework.version` to determine which migration steps apply. Updates both fields after running. | +| **session-start hook** | Warns if `last_upgraded` is more than 90 days stale: "Project was last upgraded over 3 months ago — consider running /adopt." | + +--- + +## Migration from v1.0 → v1.1 + +When a v1.0 CCGS project upgrades to v1.1, migration tooling (run via `/adopt`) +converts legacy files to `project.yaml`. This section specifies the intended +behavior. + +### Detection + +A project is detected as needing migration when: +- `project.yaml` does **NOT** exist at the repo root, AND +- One or more legacy files exists: + - `production/stage.txt` + - `production/review-mode.txt` + - `.claude/docs/technical-preferences.md` + +### Preservation + +| Legacy source | Migrates to | +|---------------|-------------| +| `production/stage.txt` (single line) | `project.stage` | +| `production/review-mode.txt` (single line) | `modes.review_mode` | +| `.claude/docs/technical-preferences.md` → "Engine & Language" section | `engine.name`, `engine.version`, `engine.language`, `engine.rendering`, `engine.physics` | +| `technical-preferences.md` → "Input & Platform" section | `platform.targets`, `platform.primary_input`, `platform.gamepad_support`, `platform.touch_support` | +| `technical-preferences.md` → "Naming Conventions" section | `naming.classes`, `naming.variables`, `naming.constants`, `naming.signals`, `naming.files`, `naming.scenes` | +| `technical-preferences.md` → "Performance Budgets" section | `performance.target_framerate`, `performance.frame_budget_ms`, `performance.draw_call_limit`, `performance.memory_ceiling_mb` | +| `technical-preferences.md` → "Testing" section | `testing.framework`, `qa.coverage_minimum` (bare number only — prose is reported for manual entry) | +| `technical-preferences.md` → "Engine Specialists" section | `specialists.code`, `specialists.shader`, `specialists.ui`, `specialists.additional` | + +**Value rename — `review_mode: none` → `solo`.** v1.0 used `none` for the "no +director reviews" tier; v1.1 renamed that tier to `solo` (the enum is +`full | lean | solo`). Migration **coerces** a legacy `none` to `solo` and reports +the rename in the migration report's Warnings. This is the one documented value +rename, so it is mapped-and-flagged rather than preserved raw: writing `none` +verbatim would produce an enum-invalid `project.yaml` that `resolve_config` rejects +and falls back to `lean` (reviews *on*) — inverting the user's intent. The +`--finalize` preservation check applies the same mapping so it does not falsely +refuse. Every *other* non-v1.1 stage/review value is a fork, not a rename: +preserved raw and flagged for manual resolution, never coerced (test II.21-23; the +rename is II.25-27). + +Settings absent from the legacy files (new v1.1 settings like `qa.level`, +`docs.density`, `team.size`, etc.) get hardcoded defaults from the v1.1 schema. + +### Cleanup (deferred, non-destructive) + +Migration is **non-destructive in v1.1**: legacy files are NOT auto-deleted. +Migration produces `project.yaml` and leaves the legacy files in place. Hooks +and skills read `project.yaml` first; they fall back to the legacy file if a +specific key is missing. + +Legacy files are deleted only when the user runs +`.claude/scripts/migrate-v1-config.sh --finalize` (driven by `/adopt`, which +detects a v1.0 project automatically at Phase 2g). The cleanup step verifies +that every legacy file's content has been preserved in `project.yaml` before +deleting. + +### Failure modes + +| Problem | Behavior | +|---------|----------| +| Legacy file is malformed (can't parse) | Migration emits warning per file, leaves it untouched, continues with the others. `project.yaml` gets the parseable values + defaults for the rest. | +| Parsed value doesn't match v1.1 enum (e.g. `stage: Alpha` from a fork) | Migration writes the raw value with a `_migration_warning` comment in `project.yaml`. User resolves manually. | +| Both `project.yaml` AND a legacy file exist (split-brain) | Migration tooling refuses to run. Error: "Both project.yaml and legacy files exist — resolve which is authoritative before migrating." | +| Custom sections in forked `technical-preferences.md` | Custom content logged to `production/migration-report.md` as "not migrated — please integrate manually." | + +### Migration report + +Every migration run writes `production/migration-report.md`: + +- Source files detected +- Values preserved (per file → per setting) +- Defaults applied (for v1.1-new settings) +- Warnings encountered (malformed, mismatched enums, etc.) +- Manual review items + +User reads the report before running `--finalize`. + +--- + +## Complete example `project.yaml` + +A full project.yaml with every setting, showing sensible defaults for a solo +Godot project. Use this as the reference shape when authoring or migrating. + +```yaml +schema_version: 1 + +framework: + version: "1.1.0" + last_upgraded: "2026-05-15" + +project: + name: "Forge Knights" + genre: "ARPG" + version: "0.1.0" + stage: Concept # Concept | Systems Design | Technical Setup | Pre-Production | Production | Polish | Release + kind: game # game | tool | engine | mod | library + +modes: + review_mode: lean # full | lean | solo + workflow: standard # minimal | standard | full + automation: collaborative # collaborative | guided | autonomous + automation_always_ask: # decision categories that always prompt regardless of mode + - scope_changes + - file_deletions + - schema_changes + story_granularity: balanced # coarse | balanced | fine + +# TOP-LEVEL, not nested under `modes:` — resolve_config reads +# `workflow_overrides.system_overrides.*` from the document root +# (yaml-helper.sh), and every skill greps the same top-level path. +# Nesting it under `modes:` parses as valid YAML and is silently never read. +workflow_overrides: + edge_cases: false # force GDD Edge Cases section required + tuning_knobs: false # force GDD Tuning Knobs section required + art_bible_strict: false # force all 9 art bible sections regardless of asset stories + system_overrides: {} # { combat: full, inventory: minimal } + +docs: + density: balanced # terse | balanced | thorough + +testing: + framework: gdunit4 # engine-specific — auto-populated by /setup-engine + strict: + logic: true # unit test failures BLOCK story closure + integration: true # integration test failures BLOCK + visual: true # missing screenshot BLOCKS + ui: true # missing screenshot BLOCKS + config: false # advisory only + +qa: + level: standard # minimal | standard | full + coverage_minimum: null # integer 0-100; only enforced at level: full + +strict_gate_checks: true # phase gate failures block stage transition + +team: + size: individual # individual | small | studio + +platform: + multiplayer: false # real-time multiplayer + online: false # any online features (leaderboards, cloud, telemetry) + targets: [PC] # [PC, Mobile, Web, Console] + primary_input: "Keyboard/Mouse" + gamepad_support: "Full" # Full | Partial | None + touch_support: "None" # Full | Partial | None + cert_tier: none # none | itch | steam | console + +engine: + name: "Godot" # Godot | Unity | Unreal — set by /setup-engine + version: "4.6" + language: "GDScript" # GDScript | C# | C++ | Blueprint + rendering: "Forward+" + physics: "Jolt" + +specialists: # auto-populated by /setup-engine + code: "godot-gdscript-specialist" + shader: "godot-shader-specialist" + ui: "godot-specialist" + additional: ["godot-gdextension-specialist"] + +naming: + classes: PascalCase # PascalCase | snake_case | camelCase + variables: snake_case + constants: SCREAMING_SNAKE + signals: past_tense # past_tense | on_event_name (engine-interpreted) + files: snake_case + scenes: snake_case + +accessibility: + target: none # none | standard | aaa + +cadence: + sprint_length: "2w" # 1w | 2w | 3w | 4w | none + milestone_length: "8w" # 4w | 6w | 8w | 12w + +performance: + target_framerate: 60 + frame_budget_ms: 16.67 + draw_call_limit: null + memory_ceiling_mb: null + enforce: warn # warn | block | off + +commands: # simple string OR OS-aware map per command + build: "godot --headless --export-debug ''" # your export_presets.cfg name + test: "godot --headless --script tests/gdunit4_runner.gd" + run: "godot --editor" + smoke: "godot --headless --quit-after 5" + +features: + session_state: on # off | on + token_budget_warn_at: 0.7 # 0.0-1.0; warn at this context % usage +``` + +And a minimal sparse `project.local.yaml` example — a single developer who wants autonomous mode locally: + +```yaml +modes: + automation: autonomous +features: + session_state: on +``` + +Only the overridden keys are present; everything else cascades from `project.yaml`. +See "Local Override Pattern" section for full whitelist of what can/cannot be overridden locally. diff --git a/.claude/docs/error-recovery-protocol.md b/.claude/docs/error-recovery-protocol.md new file mode 100644 index 00000000..78f9612f --- /dev/null +++ b/.claude/docs/error-recovery-protocol.md @@ -0,0 +1,59 @@ +# Error Recovery Protocol + +Shared procedure for any skill that spawns agents via the `Agent` tool. Referenced from the +`## Error Recovery Protocol` section of each orchestration/review skill. Those +skills keep the two load-bearing imperatives (surface immediately, produce a +partial report) inline, and keep their own **Common blockers** list inline — +those are skill-specific. This file holds the full four-step procedure. + +## Trigger 0 — verify the artifact before reading the response + +**Before applying anything below, and before treating a phase as complete: if +the return contract named a path, check that the path exists.** A named +artifact that is not on disk is a FAILED PHASE, no matter how the agent's +response reads. + +This trigger exists because the three below are not sufficient. They fire on +BLOCKED, on an error, and on "cannot complete" — and an agent can fail in a way +that is none of those. Observed live: a `devops-engineer` spawned +as half of a parallel phase ran **26 tool calls and ~31,600 tokens**, then +returned a single fluent preamble — "I'll verify the build infrastructure…" — +with no path, no summary, no BLOCKED item, and **no file written**. Nothing in +steps 1–4 matches that shape. An orchestrator reading the transcript sees a +plausible response and advances, and half a parallel phase disappears with no +signal at all. + +The work was not lost — resumed with an explicit "you did not honour the return +contract" message, the same agent completed in **one tool call**, because its +investigation was still in context. Only the write-and-return step had failed. +So the recovery is cheap; the detection is the hard part, and the detection +cannot come from reading the response. + +**A fluent response is not evidence that a phase ran.** The artifact is — +observe the artifact, not the transcript. The rule applies to sub-agents +exactly as it does to skills. + +When the path is missing: +1. Do **not** advance the pipeline or summarise the phase as done. +2. Resume the agent, naming the unmet contract explicitly and telling it that + "I could not write" is a valid answer but silence is not. Prefer resuming + over re-spawning — the context is usually still there, and a re-spawn pays + for the whole investigation again. +3. If the second attempt also produces nothing, treat it as BLOCKED and apply + steps 1–4 below. + +--- + +If any spawned agent returns BLOCKED, errors, or cannot complete: + +1. **Surface immediately** — report "[AgentName]: BLOCKED — [reason]" to the user + before continuing to dependent phases. +2. **Assess dependencies** — check whether the blocked agent's output is required + by subsequent phases. If yes, do not proceed past that dependency point without + user input. +3. **Offer options** via `AskUserQuestion`: + - Skip this agent and note the gap in the final report + - Retry with narrower scope + - Stop here and resolve the blocker first +4. **Always produce a partial report** — output whatever was completed. Never + discard work because one agent blocked. diff --git a/.claude/docs/hooks-reference.md b/.claude/docs/hooks-reference.md index d3d15b35..777de28c 100644 --- a/.claude/docs/hooks-reference.md +++ b/.claude/docs/hooks-reference.md @@ -12,10 +12,54 @@ Hooks are configured in `.claude/settings.json` and fire automatically: | `pre-compact.sh` | PreCompact | Context compression | Dumps session state (active.md, modified files, WIP design docs) into conversation before compaction so it survives summarization | | `post-compact.sh` | PostCompact | After compaction | Reminds Claude to restore session state from `active.md` checkpoint | | `notify.sh` | Notification | Notification event | Shows Windows toast notification via PowerShell | -| `session-stop.sh` | Stop | Session ends | Summarizes accomplishments and updates session log | -| `log-agent.sh` | SubagentStart | Agent spawned | Audit trail start — logs subagent invocation with timestamp | +| `session-stop.sh` | Stop | **Every response ends** — not once per session | Summarizes accomplishments, updates session log, and writes the subagent spawn tally to `production/session-logs/session-cost.md`. Archives `active.md` only when its content hash changed; without that guard a long session appended the whole file on every turn. | +| `log-agent.sh` | SubagentStart | Agent spawned | Audit trail start — logs subagent invocation with timestamp and session id | | `log-agent-stop.sh` | SubagentStop | Agent stops | Audit trail stop — completes subagent record | | `validate-skill-change.sh` | PostToolUse (Write/Edit) | Skill file changes | Advises running `/skill-test` after any `.claude/skills/` file is written or edited | +### Subagent cost visibility + +The three hooks above form one chain. `log-agent.sh` writes a record per spawn +to `production/session-logs/agent-audit.log` in a fixed three-field shape: + +``` +YYYYMMDD_HHMMSS | | Agent invoked: +``` + +`session-stop.sh` greps the literal string `" | $SESSION_ID | Agent invoked: "` +to tally the current session, then writes a count plus a per-agent breakdown to +`production/session-logs/session-cost.md` and echoes a one-line total. + +Three constraints that must not be "tidied" later: + +- **The field order is load-bearing.** Nothing else parses this log, so a + reformat looks free — but it silently zeroes the spawn count rather than + erroring. +- **`session-stop.sh` reads stdin last, on purpose.** Everything above the + tally block (notably archiving `active.md`) runs first, so a stdin read that + ever blocked would cost only the count, not the state archive. +- **No session id means no number.** The hook prints nothing rather than a + total spanning every session in the log. Silence here is correct; a + plausible-but-wrong cost figure is worse than none. + +`agent_type` — not `agent_name` — is the field carrying the agent name. See +`hooks-reference/hook-input-schemas.md`. + +## Events considered and deliberately not added + +Claude Code offers many more hook events than the eleven above. These were +evaluated and rejected. Recorded so a future review does not re-propose them +from the event list alone. + +| Event | Why not | +| ---- | ---- | +| `UserPromptSubmit` | Would re-inject session state on **every prompt**. `.claude/docs/config-resolution.md` already rejects the weaker version of this — injecting config at session start — because it "would cost tokens on every session whether or not any skill needs config, and it would go stale mid-session as `/settings` writes land". Firing per prompt is the same argument, multiplied. | +| `ConfigChange` | Fires only for Claude Code's own settings files — user, project, local, policy and skills. It does **not** fire for `project.yaml`, so a hook watching CCGS config here would never run. `FileChanged` is the event that watches arbitrary files. | +| `FileChanged` on `project.yaml` | Config is resolved per skill invocation, in the skill body, precisely so each run reads fresh values. The only staleness left is text already injected into an earlier invocation, which is the accepted trade in `config-resolution.md`. A warning hook would second-guess a settled decision. | +| `PreToolUse` `updatedInput` | Could rewrite a non-conventional commit message instead of blocking it. Silently editing the user's input contradicts the collaboration protocol: this project blocks and explains rather than acting unasked. | +| `PostToolUseFailure` | Would add another log with no reader. This repo has already been bitten by that — `log-agent.sh` wrote per-spawn records nothing consumed until `session-stop.sh` was given the job. | +| `SessionEnd` | `session-stop.sh` stays on `Stop`. `SessionEnd` fires on termination, which a crash may never reach, and the content-hash guard already makes the per-response firing cheap. | +| `InstructionsLoaded` | Useful for debugging which CLAUDE.md and rules files actually loaded, but it is a diagnostic, not a project requirement. Wire it in your own `.claude/settings.local.json` when you need it rather than paying for it on every load. | + Hook reference documentation: `.claude/docs/hooks-reference/` Hook input schema documentation: `.claude/docs/hooks-reference/hook-input-schemas.md` diff --git a/.claude/docs/hooks-reference/hook-input-schemas.md b/.claude/docs/hooks-reference/hook-input-schemas.md index 55cc27f4..a147ba85 100644 --- a/.claude/docs/hooks-reference/hook-input-schemas.md +++ b/.claude/docs/hooks-reference/hook-input-schemas.md @@ -88,7 +88,9 @@ Fired after a tool completes. **Cannot block** (exit code ignored for blocking). ## SubagentStart -Fired when a subagent is spawned via the Task tool. +Fired when a subagent is spawned via the `Agent` tool (named `Task` before +Claude Code 2.1.63; `Task` still works as an alias in permission rules and +`tools:` frontmatter, but the hook payload's `tool_name` is now `Agent`). ```json { diff --git a/.claude/docs/model-tiers.md b/.claude/docs/model-tiers.md new file mode 100644 index 00000000..6504e9be --- /dev/null +++ b/.claude/docs/model-tiers.md @@ -0,0 +1,55 @@ +# Model Tier Assignment + +> ### DECLARED, NOT CURRENTLY APPLIED — skills only +> +> **Claude Code reads `model:` from a skill's frontmatter and then serves the +> skill from the session's model anyway.** This has been measured directly +> against session transcripts on several Claude Code versions, and held every +> time. To re-test it on a newer version, run a skill that declares a tier +> different from the session model and check which model the transcript +> attributes the skill's turns to. +> +> So the skill tiers below are **intent, not behaviour**. Setting one changes +> nothing today. They are kept because they are correct descriptions of what each +> skill needs, and because they cost nothing to carry if Claude Code honours them +> later — but do not budget on them, and never tell a user a tier is saving them +> money. +> +> **This does NOT apply to agents.** `model:` in `.claude/agents/*.md` is a +> different mechanism, spawned as a separate session rather than executed inline. +> It has not been measured here, so treat the agent section below as unverified +> in either direction rather than assuming it behaves the same way. + +Skills and agents are assigned tiers by task complexity: + +| Tier | Model | When to use | +|------|-------|-------------| +| **Haiku** | `claude-haiku-4-5-20251001` | Read-only status checks, formatting, simple lookups — no creative judgment needed | +| **Sonnet** | `claude-sonnet-5` | Implementation, design authoring, analysis of individual systems — default for most work | +| **Opus** | `claude-opus-5` | Multi-document synthesis, high-stakes phase gate verdicts, cross-system holistic review | + +Skills with `model: haiku` (5): `/help`, `/onboard`, `/project-stage-detect`, +`/scope-check`, `/sprint-status` + +> `/patch-notes` and `/changelog` are `sonnet`, not `haiku`: both produce +> **player-facing** copy and need judgement the cheapest tier is defined as not +> doing. Full reasoning in each skill's own header. +> +> `/settings` was moved off `haiku` for the same reason. It does not only read +> and format — it resolves every leaf with provenance and then compares the +> configured rigor against the project's observable working practice. That is +> synthesis, which is what the Haiku row is defined as excluding. + +Skills with `model: opus` (3): `/architecture-review`, `/gate-check`, `/review-all-gdds` + +All other skills are Sonnet. When creating a new skill, assign Haiku if it only +reads and formats; assign Opus if it must synthesize 5+ documents with +high-stakes output; otherwise write `model: sonnet` explicitly. Every skill in +this repo declares a tier, and the lists above are kept in step with what the +`SKILL.md` files declare. That is a check on two descriptions agreeing; it +proves nothing about which model actually runs. + +**Agent model tiers.** Orchestrator-spawned agents (`team-*`, `/review-all-gdds`) +use `model: inherit` so a fixed smaller model can't overflow when the parent runs +a large-context tier. Directors stay `opus`, `community-manager`/`devops-engineer` +stay `haiku`, and non-orchestrator agents stay pinned at `sonnet` as a floor. diff --git a/.claude/docs/quick-start.md b/.claude/docs/quick-start.md index 519f5c54..625728da 100644 --- a/.claude/docs/quick-start.md +++ b/.claude/docs/quick-start.md @@ -189,12 +189,6 @@ Templates are in `.claude/docs/templates/`: - `difficulty-curve.md` -- for difficulty axes, onboarding ramp, and cross-system interactions - `test-evidence.md` -- template for recording manual test evidence (screenshots, walkthrough notes) -Also in `.claude/docs/templates/collaborative-protocols/` (used by agents, not typically edited directly): - -- `design-agent-protocol.md` -- question-options-draft-approval cycle for design agents -- `implementation-agent-protocol.md` -- story pickup through /story-done cycle for programming agents -- `leadership-agent-protocol.md` -- cross-department delegation and escalation for director-tier agents - ### 5. Follow the Coordination Rules 1. Work flows down the hierarchy: Directors -> Leads -> Specialists @@ -218,8 +212,8 @@ If you already know what you need, jump directly to the relevant path: - Produces a game concept document and recommends an engine 2. **Set up the engine** — Run `/setup-engine` (uses the brainstorm recommendation) - Configures CLAUDE.md, detects knowledge gaps, populates reference docs - - Creates `.claude/docs/technical-preferences.md` with naming conventions, - performance budgets, and engine-specific defaults + - Writes engine, naming conventions, and performance budgets to `project.yaml` + (the source of truth), mirrored to `.claude/docs/technical-preferences.md` - If the engine version is newer than the LLM's training data, it fetches current docs from the web so agents suggest correct APIs 3. **Validate the concept** — Run `/design-review design/gdd/game-concept.md` @@ -271,15 +265,16 @@ If you have design docs, prototypes, or code already: ``` CLAUDE.md -- Master config (read this first, ~60 lines) +project.yaml -- Machine-readable project config (engine, modes, stage) — source of truth .claude/ settings.json -- Claude Code hooks and project settings agents/ -- 49 agent definitions (YAML frontmatter) - skills/ -- 73 slash command definitions (YAML frontmatter) - hooks/ -- 12 hook scripts (.sh) wired by settings.json + skills/ -- 74 slash command definitions (YAML frontmatter) + hooks/ -- 12 event hooks (.sh, wired by settings.json) + yaml-helper.sh rules/ -- 11 path-specific rule files docs/ quick-start.md -- This file - technical-preferences.md -- Project-specific standards (populated by /setup-engine) + technical-preferences.md -- Legacy fallback for project.yaml config (mirror; see project.yaml at repo root) coding-standards.md -- Coding and design doc standards coordination-rules.md -- Agent coordination rules context-management.md -- Context budgets and compaction instructions @@ -287,5 +282,6 @@ CLAUDE.md -- Master config (read this first, ~60 lines) workflow-catalog.yaml -- 7-phase pipeline definition (read by /help) setup-requirements.md -- System prerequisites (Git Bash, jq, Python) settings-local-template.md -- Personal settings.local.json guide - templates/ -- 41 document templates + CLAUDE-local-template.md -- Personal CLAUDE.local.md guide (gitignored overrides) + templates/ -- 38 document templates (+ per-section guidance) ``` diff --git a/.claude/docs/review-workflow.md b/.claude/docs/review-workflow.md deleted file mode 100644 index ef762a07..00000000 --- a/.claude/docs/review-workflow.md +++ /dev/null @@ -1,6 +0,0 @@ -# Review Workflow - -1. Code changes require review by the relevant department lead agent -2. Design changes require sign-off from `game-designer` and `creative-director` -3. Architecture changes require sign-off from `technical-director` -4. Cross-domain changes require sign-off from `producer` diff --git a/.claude/docs/rules-reference.md b/.claude/docs/rules-reference.md index 7fb78635..9a527812 100644 --- a/.claude/docs/rules-reference.md +++ b/.claude/docs/rules-reference.md @@ -1,6 +1,33 @@ # Path-Specific Rules -Rules in `.claude/rules/` are automatically enforced when editing files in matching paths: +Rules in `.claude/rules/` carry `paths:` frontmatter. Claude Code loads a +path-scoped rule **when it READS a file matching one of those globs** — not on +every tool use, and not when it creates one. + +Three consequences, and none of them are optional reading: + +1. **Reading, not editing.** A rule scoped to `src/gameplay/**` reaches the model + when a file there is opened. An agent that writes a brand-new file in that + directory without opening an existing one never sees it. Most work in this + framework creates files rather than reads them, so this is the common case, + not the corner case. +2. **Context, not enforcement.** A loaded rule is guidance the model weighs, the + same as `CLAUDE.md`. Anything that must hold regardless of what the model + decides belongs in a hook — `.claude/hooks/` already enforces JSON validity + and commit shape that way. +3. **A rule with no `paths:` loads every session**, at the same priority as + `.claude/CLAUDE.md`. That is the lever for a rule that must always apply; it + costs context in every session, so it is a deliberate trade, not a default. + +Where a rule duplicates guidance an agent definition already carries, the two +must agree. Contradicting instructions are resolved arbitrarily, so a rule and +an agent stating different versions of the same standard is worse than either +alone. + +To see which rules actually load, and why, wire the `InstructionsLoaded` hook — +`.claude/hooks/log-instructions.sh` records every instruction file and its load +reason (`session_start`, `path_glob_match`, `nested_traversal`, `compact`) to +`production/session-logs/instructions-loaded.log`. | Rule File | Path Pattern | Enforces | | ---- | ---- | ---- | @@ -15,3 +42,12 @@ Rules in `.claude/rules/` are automatically enforced when editing files in match | `test-standards.md` | `tests/**` | Test naming, coverage requirements, fixture patterns | | `prototype-code.md` | `prototypes/**` | Relaxed standards, README required, hypothesis documented | | `shader-code.md` | `assets/shaders/**` | Naming conventions, performance targets, cross-platform rules | +| `agent-memory.md` | `.claude/agent-memory/**` | The one place an agent may write unasked — and why a temporary absence must never be recorded as a durable fact | +| `skill-authoring.md` | `.claude/skills/**`, `.claude/agents/**` | A check that cannot report it did not run is not a check — verdict vocabularies, fail-closed defaults, self-announcing skips, sourcing, derived gate coverage | + +> **`skill-authoring.md` is the odd one out and deliberately so.** Every other +> rule here scopes a *game* directory; that one scopes the **framework's own** +> authoring paths. It exists because one failure shape kept recurring across +> unrelated subsystems — a step that could not run producing output +> indistinguishable from a step that ran and found nothing. The same mistake in +> nine places is a missing design rule, not nine mistakes. diff --git a/.claude/docs/run-and-observe.md b/.claude/docs/run-and-observe.md new file mode 100644 index 00000000..a478248a --- /dev/null +++ b/.claude/docs/run-and-observe.md @@ -0,0 +1,178 @@ +# Run and Observe + +Shared procedure for any skill that closes a story which changes something a +player can see. Referenced from the point of use in `/dev-story` (Phase 6) and +`/story-done` (Phase 3). Those skills keep the one load-bearing imperative +inline — **a parse check is not a run, and a story nobody looked at does not +close** — and cite this file for how to look. + +`.claude/docs/coding-standards.md` is the authority for *what* evidence a story +needs. This file is the procedure for *producing* the visual half of it. + +## The rule + +Every story that changes something player-observable is **launched and observed +before it closes**, and the observation is a **retained screenshot** in +`production/qa/evidence/`. Compiling proves the code loads. Tests prove the +logic. Neither proves the text fits in the box. + +The agent can see images: Claude Code's `Read` tool renders a PNG. The whole +problem is getting a picture out of the engine unattended, and every supported +engine has a native way. + +## The procedure + +1. **Launch windowed** at a fixed resolution — `1280x720` unless the project + says otherwise — using `commands.run` from `project.yaml`. Never the + headless / batch / null-RHI flag: those exist to *skip* rendering. +2. **Launch straight into the scene or map the story touched.** A feature that + is only reachable through the main menu and three clicks is a feature the + run will not reach. Stories should be written so their surface is reachable + from a launch argument (a scene path, a map URL, a `--scene` flag). +3. **Wait, capture, quit.** Let the engine render real frames — 60 is a safe + floor — capture one, and exit. Each engine's mechanism is below. +4. **Look.** `Read` the PNG. Compare what is on screen to the acceptance + criteria. Clipped text, an overflowing panel, a missing element, the wrong + colour — these are defects, and this is the only step that finds them. +5. **Retain.** Copy the image to + `production/qa/evidence/[story-slug]/[NN]-[what-it-shows].png` and reference + it from the evidence doc. A screenshot that was taken and discarded is an + assertion, not evidence. +6. **Report** one `Run result:` line in the implementation summary — vocabulary + below. + +## Per engine + +| `engine.name` | Launch | Capture | Output lands in | Never use | Scaffold needed | +|---|---|---|---|---|---| +| Godot 4 | `godot --path . --windowed --resolution 1280x720 .tscn` | `--write-movie .png --quit-after ` | beside the given path, `00000000.png` … | `--headless` | **none** | +| Unity 6 | built player `.exe -screen-width 1280 -screen-height 720 -screen-fullscreen 0 --scene ` | `ScreenCapture.CaptureScreenshot()` from a bootstrap script | the absolute path you pass | `-batchmode`, `-nographics` | one MonoBehaviour | +| Unreal 5 | `MSYS_NO_PATHCONV=1 UnrealEditor.exe .uproject -game -windowed -ResX=1280 -ResY=720` | `HighResShot filename=` console command | `Saved/Screenshots/Windows/` | `-nullrhi`, `-unattended` alone | one actor or `-ExecCmds` | + +### Godot 4 — zero scaffold + +``` +godot --path . --windowed --resolution 1280x720 --write-movie shots/shop.png --quit-after 60 res://scenes/shop.tscn +``` + +`--write-movie` is Movie Maker mode: with a `.png` target it writes a frame +sequence and `--quit-after` sets the frame count. Filenames carry **8** +zero-padded digits — `shop00000000.png` through `shop00000059.png` — so take the +last. `--fixed-fps` is applied automatically. The scene path is positional; any +`.tscn` can be launched directly, which is how step 2 is satisfied. + +Capture-on-trigger alternative, for a state a scene launch cannot reach: an +autoload that on the wanted condition does +`await RenderingServer.frame_post_draw`, then +`get_viewport().get_texture().get_image().save_png(path)`, then +`get_tree().quit()`. + +### Unity 6 — one MonoBehaviour + +`-batchmode` displays nothing, so the run is either the **built player** or the +editor in play mode. The player is the robust path: + +``` +Builds/Win64/Game.exe -screen-width 1280 -screen-height 720 -screen-fullscreen 0 --scene Shop --screenshot C:/abs/path/shop.png --after 60 +``` + +`-screen-*` are Unity's own player arguments. `--scene`, `--screenshot` and +`--after` are **yours**: a `ScreenshotOnArg` MonoBehaviour in the bootstrap +scene parses `System.Environment.GetCommandLineArgs()`, loads `--scene`, waits +`--after` frames, calls `ScreenCapture.CaptureScreenshot(path)`, waits one more +frame, then `Application.Quit()`. Pass an **absolute** path — the relative base +differs between editor and player. `CaptureScreenshot` captures the last frame +*presented*, so it must run after at least one rendered frame. + +`/test-setup` scaffolds this script under the code root. Building the player +first costs 1–3 minutes on a small project; that is the price of looking at +the thing you ship rather than the editor. + +### Unreal 5 — one actor, or `-ExecCmds` + +``` +MSYS_NO_PATHCONV=1 UnrealEditor.exe Game.uproject /Game/Maps/Shop -game -windowed -ResX=1280 -ResY=720 +``` + +`-game` runs standalone without the editor UI. The map is a **positional URL +immediately after the `.uproject`** — not a console command. **The +`MSYS_NO_PATHCONV=1` prefix is required on Windows:** Claude Code runs commands +in Git Bash, which rewrites any argument beginning with `/` as a Windows path, so +`/Game/Maps/Shop` reaches the engine as `C:/Program Files/Git/Game/Maps/Shop` +and the game stops on a "map not found" dialog nobody can see. Alternatively +pass the map through `-ExecCmds="open /Game/Maps/Shop"`, which is not converted. Capture is the +`HighResShot` console command, fired either from a dev actor (`Execute Console +Command` on BeginPlay after a short delay, then `quit`) or from the launch line: + +``` +-ExecCmds="HighResShot filename=C:/abs/path/shop.png,Quit" +``` + +`-ExecCmds` is comma-separated. Output defaults to `Saved/Screenshots/Windows/`. +**Do not `Quit` on the same frame as the request** — the file flushes late and +an immediate quit can leave nothing on disk; a one-second delay before `quit` +is enough. The Blueprint node `Take High Res Screenshot` is editor-only and +does not work in a `-game` launch. + +### Any engine — OS capture fallback + +On Windows, PowerShell with `System.Drawing` can `CopyFromScreen` into a bitmap +and save it. It needs a visible desktop and the game window on top, and it +captures whatever is there — it cannot be told "the shop panel". Use it when the +engine-native path is unavailable, and say so in the run result. Linux CI with +no display needs `xvfb-run` in front of the launch; `--headless` is not a +substitute. + +## The `Run result:` line + +Exactly one of three shapes, in the implementation summary: + +- **`Run result: OBSERVED — `** with the retained + path. *"Shop panel renders, all 6 slots inside the frame, prices legible at + 720p — `production/qa/evidence/shop-panel/01-shop-open.png`."* +- **`Run result: NOT VERIFIED — `**. Engine binary not found, + `commands.run` unset, launch crashed, screenshot empty. This is a **blocker** + at the default gate level for Visual/Feel and UI stories, not a note — a + story nobody looked at does not close. Under an explicit + `testing.strict.visual`/`.ui: false` it is recorded and the story may close + with the gap in `## Completion Notes`. +- **`Run result: N/A — `**, only for a story with genuinely nothing + observable — a pure data migration, a save-format change with no surface. + Say why. "It's a Logic story" is not a reason: a damage formula has a number + on screen somewhere. + +The run is **not waived at `qa.level: minimal`**. Tests are waived there; the +look is not. It is the cheapest verification in the pipeline and the one +whose absence produces a well-tested game that looks wrong. + +## What a still can and cannot show + +**Can:** layout, clipping, overflow, presence, alignment, colour, legibility, +that the right screen appeared at all. This is where shipped visual defects +actually live. + +**Cannot:** timing, responsiveness, weight, animation curves, audio sync — the +"feel" half of Visual/Feel. Godot's `--write-movie` gives a frame sequence; the +other engines need OS video capture. Feel stays a human check in `/team-qa`. +Say which half the screenshot covered. + +## Reaching the state + +A screenshot of the title screen verifies nothing about depth 2. Either launch +the scene/map directly (Godot scene arg, Unreal map URL, Unity `--scene`) or +give the bootstrap a debug argument that sets state (`--gold=500 --depth=2`). +Write stories so their surface is reachable one of those ways; a story that is +not is a story that will be marked `NOT VERIFIED` every time. + +## Finding the engine + +None of the three engines installs onto `PATH` on Windows. `engine.path` in +`project.yaml` is where the editor lives; resolve it before concluding "no +engine on this machine". Absence of a path is not absence of an engine. + +--- + +*Commands and APIs above verified against the official Godot 4.7, Unity 6.3 LTS +and Unreal 5.8 references on 2026-09-14. `docs/engine-reference//` is +the project's pinned authority; check it before trusting a version-qualified +claim here.* diff --git a/.claude/docs/settings-guidance.md b/.claude/docs/settings-guidance.md new file mode 100644 index 00000000..9c336e58 --- /dev/null +++ b/.claude/docs/settings-guidance.md @@ -0,0 +1,120 @@ +# Settings Guidance — which value to recommend, and when + +The **advisory** layer for project settings. This doc answers two questions no +other doc does: + +1. **Which value should a skill recommend** for a given project or user? +2. **When should a skill proactively suggest changing** a setting already set? + +## Boundary with its siblings — do not duplicate their content here + +| Doc | Answers | Nature | +|---|---|---| +| `effects-map.md` | What each setting *does* | Descriptive | +| `config-resolution.md` | How a setting *resolves* (precedence chain) | Mechanical | +| **`settings-guidance.md`** (this file) | Which value to *recommend*, and *when* | Prescriptive | + +Skills reference this doc **on demand**, only at a "recommend a setting" moment. +It is deliberately **not** a CLAUDE.md import — advisory content must not ride in +per-turn context (the same reason `context-management.md` was demoted). + +--- + +## 1. The knob that matters most: `modes.rigor` + +Almost every "what settings should I use?" question reduces to one choice: +`modes.rigor` (`minimal` | `standard` | `full`, default `minimal`). It fronts six +sub-knobs — `modes.workflow`, `docs.density`, `qa.level`, `modes.story_granularity`, +`modes.review_mode`, `team.size`. Recommend **rigor**, not the six; let the +expansion do the rest. Full expansion table lives in `effects-map.md § modes.rigor` +— do not restate it here. + +`modes.automation` (`collaborative` | `guided` | `autonomous`) is a **separate +axis** — how often skills stop to confirm — and is *not* fronted by rigor. Pick it +by working style, not by game type (see § 5). + +--- + +## 2. Archetype → rigor presets + +Match the project to an archetype; recommend the paired rigor. These are starting +points, not verdicts — the user can always override. + +| Archetype | Rigor | Why | Common override | +|---|---|---|---| +| Weekend jam / prototype / throwaway | `minimal` | Shipping beats recording; design lives in the maker's head | Bump one deep system: `workflow_overrides.system_overrides: {combat: full}` | +| Small solo / hobby game (arcade, puzzle, short narrative) | `minimal` → `standard` if it grows | Start light; add rigor when scope proves real | — | +| Focused commercial indie (platformer, roguelike, tactics, deckbuilder) | `standard` *(default)* | One well-understood loop; enough design to build correctly | "Comprehensive but compact": `rigor: full` + `docs.density: terse` | +| Systems-heavy / long-haul (open-world RPG, colony sim, immersive sim, 4X, survival) | `full` | Many interacting systems; a design mistake costs weeks | Solo dev: keep `full` on disk, set `review_mode: solo` locally | +| Live-service / competitive multiplayer | `full` + domain emphasis | Full pipeline *plus* netcode/security/live-ops | Raise `qa.level` / `testing.strict.*` for the online-critical types | + +**The model:** the table picks a baseline; overrides handle the diagonal. Most real +projects are one baseline plus one or two deliberate exceptions. + +--- + +## 3. Seeding rigor from a described concept + +When the user has described their game (e.g. `/start` Phase 2 free text), map it to +an archetype and **pre-select** the recommendation rather than asking cold. Signals: + +| Concept mentions… | Lean toward | +|---|---| +| open-world, RPG, sim, sandbox, faction, crafting, economy, MMO, multiplayer, "systems" | `full` | +| jam, weekend, prototype, "first game", small, "just trying", experiment | `minimal` | +| "release", "commercial", "on Steam", a single clear core loop | `standard` | +| *ambiguous / nothing above* | `standard` (the documented default) | + +**Rules:** these are heuristics, never locks. Always confirm the pre-selection with +the user in their own terms ("Sounds like a big systems game — I'd suggest `full` +rigor; want that?"). If the user has **no concept yet** (exploring), do not seed +`full` — default `minimal`, and revisit once a concept exists. + +"No concept yet" means **no signal**, not a particular onboarding path. A rough +one-line hint is still a description: if it trips the signals above, seed from the +signal. Only fall back to `minimal`-for-exploring when the user has given you +nothing to map. (`/start` Phase 3d states the same rule for its Path A/B branch — +the two must agree, since both drive the identical recommendation.) + +--- + +## 4. When to recommend a *change* (adaptive triggers) + +Bidirectional and **threshold-based**. Fire only on a threshold *crossing*, never +every session — that is what keeps this from becoming the nagging the `automation` +modes exist to eliminate. + +| Trigger (crossing) | Direction | Detectable signal | Suggested action | +|---|---|---|---| +| Project grew past its band | ↑ raise | System-GDD count crosses ~9, or `src/` crosses the Production threshold, while rigor is `minimal` | "You're at N systems on `rigor: minimal` — most projects this size run `standard`. Revisit? → `/settings`" | +| Stage advanced into Production | ↑ raise | `/gate-check` PASS into Production while rigor is `minimal` | Same, offered once at the gate | +| User sounds overwhelmed by process | ↓ lower | Phrases like "too many steps", "so much documentation", "overwhelmed" — *and* rigor is not already `minimal` | "If the required-doc list feels heavy, a lower rigor trims it → `/settings modes.rigor=standard`" | +| Chosen rigor mismatches described scope | flag once | Onboarding pick contradicts the Phase-2 description | State it in one sentence, offer `/settings`, do not re-ask | + +**Frequency guard:** the crossing *is* the gate. Because the hosts (`/gate-check` on +PASS, `/help` on request) are themselves infrequent, no persistent "already nudged" +state is needed — but never emit the same suggestion twice in one session. + +--- + +## 5. Which skills reference this doc, and when + +| Skill | Moment | Sections used | +|---|---|---| +| `/start` | Phase 3d/4 — choosing rigor | § 2 (presets), § 3 (seed from concept) | +| `/help` | escalation footer when user sounds stuck/overwhelmed | § 4 (lower trigger) | +| `/gate-check` | on PASS into a new stage | § 4 (raise trigger) | +| `/settings` | view output — further reading | § 2 (choosing a value) | + +Each skill carries a **one-line pointer** to this doc, not a copy of its tables. + +--- + +## 6. How to phrase a recommendation + +- **Recommend, don't dictate.** State the suggestion and the reason in one line; + the user decides. (`collaborative`/`guided` require this; even `autonomous` + treats a rigor change as a `schema_changes` decision and asks.) +- **Always route to `/settings`** — never edit `project.yaml` as a side effect of a + recommendation. +- **One line, then stop.** A nudge is a nudge, not a lecture. diff --git a/.claude/docs/setup-requirements.md b/.claude/docs/setup-requirements.md index 02413992..42921d5d 100644 --- a/.claude/docs/setup-requirements.md +++ b/.claude/docs/setup-requirements.md @@ -72,6 +72,37 @@ python3 --version # Should show python version (optional) | **Python 3** | JSON data file validation in commit and asset hooks is skipped. Invalid JSON can be committed without warning. | | **Both** | All hooks still execute without error (exit 0) but provide no validation. You're flying without safety nets. | +## Optional Performance Settings + +CCGS deliberately ships **no** values for the settings below. Each one's best +value depends on your machine, your Claude plan and how you work, so a value +committed here would be a guess every user inherits without noticing. Set the +ones you want in `.claude/settings.local.json`, which is gitignored and yours +alone. + +| Setting | What it does | When to change it | +| ---- | ---- | ---- | +| `promptCacheTtl` | How long the main conversation's prompt cache lives. | Raise it if you work in long sessions with gaps; the cache surviving a break avoids re-sending context. | +| `subagentPromptCacheTtl` | The same, for subagents and other off-conversation requests. | Worth raising on a 49-agent project like this one, where `team-*` skills spawn repeatedly. | +| `autoCompactWindow` | How full the context gets before Claude Code compacts it. | Lower it if compaction keeps surprising you mid-task; raise it if you would rather compact less often and keep more history. | +| `skillListingBudgetFraction` | How much context the skill listing may occupy. | CCGS ships 74 skills, so the listing is not small. Lower it if you want more room for work; `skillListingMaxDescChars` trims each description instead. | + +`sandbox.enabled` isolates shell commands from your filesystem and network. It +is worth turning on, but it runs on **macOS, Linux and WSL2 only** — it is not +available on native Windows, which is CCGS's primary platform. + +### One setting CCGS does ship + +`permissions.defaultMode` is set to `default` in `.claude/settings.json`, on +purpose. It makes Claude Code ask before acting, which is what every approval +gate in this framework depends on. Project settings outrank personal ones, so +this holds even if your own config sets `acceptEdits` or `auto` — otherwise +CCGS's collaboration protocol would be silently switched off for you and the +agents would write files you never approved. + +If you genuinely want it off, override it in `.claude/settings.local.json`, +which takes precedence. Understand what you are turning off first. + ## Recommended IDE Claude Code works with any editor, but the template is optimized for: diff --git a/.claude/docs/skills-reference.md b/.claude/docs/skills-reference.md index 5b072f68..d7a419e7 100644 --- a/.claude/docs/skills-reference.md +++ b/.claude/docs/skills-reference.md @@ -136,3 +136,23 @@ Coordinate multiple agents on a single feature area: | `/team-level` | level-designer + narrative-director + world-builder + art-director + systems-designer + qa-tester | | `/team-live-ops` | live-ops-designer + economy-designer + community-manager + analytics-engineer | | `/team-qa` | qa-lead + qa-tester + gameplay-programmer + producer | + +## Overlap with Claude Code's bundled skills + +Claude Code ships its own skills, and one name collides with CCGS's. + +| You type | You get | Use it for | +|---------|---------|-----------| +| `/code-review` | **CCGS's** — project skills take the bare name | Architectural review against this project's coding standards, SOLID, testability and the control manifest. Knows about ADRs, stories and `docs/architecture/`. | +| `/code-review:code-review` | **Claude Code's bundled** review | Reviewing a pull request or a plain diff, with no knowledge of CCGS's design documents or gates. | + +Both are useful; they answer different questions. Reach for the CCGS one during +`/dev-story` and before `/story-done`, because it checks the things the gates +later check. Reach for the bundled one when you want a general review of a diff +that has nothing to do with a story. + +CCGS does **not** turn the bundled skills off. `disableBundledSkills` would +remove all of them, including `/debug`, `/security-review` and `/simplify`, +which are worth keeping. If you want a bundled skill hidden, use +`skillOverrides` in your own `.claude/settings.local.json` rather than disabling +the whole set. diff --git a/.claude/docs/technical-preferences.md b/.claude/docs/technical-preferences.md index 94a91fbb..40565804 100644 --- a/.claude/docs/technical-preferences.md +++ b/.claude/docs/technical-preferences.md @@ -1,7 +1,12 @@ # Technical Preferences - - + ## Engine & Language diff --git a/.claude/docs/templates/SKILL-CONTRACT-TEMPLATE.md b/.claude/docs/templates/SKILL-CONTRACT-TEMPLATE.md new file mode 100644 index 00000000..3577913f --- /dev/null +++ b/.claude/docs/templates/SKILL-CONTRACT-TEMPLATE.md @@ -0,0 +1,520 @@ +# Skill Contract Template + +> **Status — read before using this template.** +> +> The **Identity & Scope**, **Input Contract**, **Output Contract**, **Hard/Soft +> Constraints** and **Handoff Configuration** sections are live and reflect how +> contracts are authored today. +> +> The **Testing Evidence Required** and **Drift Monitoring** sections are +> **aspirational** — the runners they name (`tests/smoke-tests/`, +> `tests/golden-datasets/`, `tests/behavioral-tests/`, `tests/integration/`) and +> `production/session-state/drift-monitor.md` do **not exist**, no skill has a +> `TESTS.md`, and none of the seven shipped `CONTRACT.md` files use the ASI or +> drift machinery. Treat them as a design sketch for a future skill-quality +> system, not as steps to follow. +> +> The seven shipped contracts use a simpler shape than this template: +> `Inputs Consumed` / `Files That Must Exist` / `Preconditions` / +> `Outputs Produced` / `Output Guarantees` / `Immutability Rules`. Match an +> existing contract (e.g. `.claude/skills/dev-story/CONTRACT.md`) rather than +> this template's schema-heavy form until the two are reconciled. + +**Purpose:** Define formal handoff contract for a skill. Create as `.claude/skills//CONTRACT.md`. + +**How to Use:** +1. Copy this template +2. Replace `[SKILL_NAME]`, `[ROLE]`, `[DESCRIPTION]` with your skill's details +3. Define input/output schemas precisely +4. List hard constraints (must never violate) and soft constraints (recover if violated) +5. Include examples of valid input/output +6. Reference this contract in your SKILL.md file + +--- + +# [SKILL_NAME] Handoff Contract + +**Version:** 1.0 +**Last Updated:** [DATE] +**Skill File:** `.claude/skills/[SKILL_NAME]/SKILL.md` +**Test File:** `.claude/skills/[SKILL_NAME]/TESTS.md` + +--- + +## Identity & Scope + +### Role +[ROLE] — [ONE SENTENCE DESCRIBING WHAT THIS SKILL DOES] + +### Example +Story Author — Decomposes game design epics into atomic, testable user stories with clear acceptance criteria. + +### Domain Boundaries +**Can Read:** +- `design/gdd/` (game design documents) +- `.claude/docs/` (architectural guidance) +- `production/session-state/` (context from previous skills) + +**Can Write:** +- `production/epics/[epic-slug]/` (creates new story markdown files) +- `production/qa/evidence/` (creates test evidence stubs) + +**Cannot Touch:** +- `src/` (no game code) +- `assets/` (no asset modifications) +- Source data files (read-only) + +--- + +## Input Contract + +### What This Skill Receives + +**Source Skill:** [PREVIOUS_SKILL_NAME] (e.g., `/create-epics`) +**Format:** Markdown with structured frontmatter +**Transport:** Session context or file reference + +### Required Fields + +```yaml +# Example input epic (YAML frontmatter in Markdown) +epic_id: "COMBAT-BASIC" +epic_goal: "Implement core melee attack system" +epic_acceptance_criteria: + - "Player can attack with sword" + - "Damage is configurable" + - "Animation plays on hit" + - "Enemy health updates" +target_complexity: "medium" # low | medium | high +estimated_story_count: 3 +``` + +### Schema Definition + +```json +{ + "type": "object", + "required": ["epic_id", "epic_goal", "epic_acceptance_criteria", "target_complexity"], + "properties": { + "epic_id": { + "type": "string", + "pattern": "^[A-Z]+(-[A-Z0-9]+)?$", + "minLength": 2, + "maxLength": 50 + }, + "epic_goal": { + "type": "string", + "minLength": 10, + "maxLength": 500, + "description": "High-level objective, not implementation" + }, + "epic_acceptance_criteria": { + "type": "array", + "minItems": 1, + "maxItems": 20, + "items": { + "type": "string", + "minLength": 5, + "maxLength": 200 + } + }, + "target_complexity": { + "type": "string", + "enum": ["low", "medium", "high"] + }, + "estimated_story_count": { + "type": "integer", + "minimum": 1, + "maximum": 50 + } + } +} +``` + +### Validation Rules + +- [ ] Epic ID matches pattern `^[A-Z]+-[A-Z0-9]+$` (e.g., `COMBAT-BASIC`) +- [ ] Epic goal is 10–500 characters (not too brief, not a novel) +- [ ] At least 1, at most 20 acceptance criteria +- [ ] Target complexity is one of: `low`, `medium`, `high` +- [ ] If estimated story count provided, >= 1 and <= 50 + +### Examples of Valid Input + +```markdown +## Valid Example 1: Simple Epic + +epic_id: COMBAT-BASIC +epic_goal: Implement core melee attack system +epic_acceptance_criteria: + - Player can attack with sword + - Damage is configurable from external data + - Attack animation plays when action triggered + - Target health decreases by damage amount +target_complexity: medium +estimated_story_count: 3 +``` + +```markdown +## Valid Example 2: Large Epic + +epic_id: PROGRESSION-FULL +epic_goal: Implement complete character progression system +epic_acceptance_criteria: + - Player gains experience from defeated enemies + - Experience unlocks new abilities + - Player can level up manually + - Leveling resets some cooldowns + - New abilities appear in player menu +target_complexity: high +estimated_story_count: 12 +``` + +--- + +## Output Contract + +### What This Skill Produces + +**Destination Skill:** [NEXT_SKILL_NAME] (e.g., `/dev-story`) +**Format:** Markdown with structured headers +**Location:** Typically written to `production/epics/[epic-slug]/story-NNN-[slug].md` + +### Output Structure + +```markdown +# Stories for [EPIC_ID] + +## [STORY_ID]: [STORY_TITLE] + +### Description +[1-3 paragraph description of the user story, written from player/user perspective] + +### Acceptance Criteria +1. [Testable criterion 1 — imperative mood, specific observable behavior] +2. [Testable criterion 2] +3. [Testable criterion 3] +4. [Optional: criterion 4+, up to 5 per story] + +### Complexity Estimate +[low | medium | high] + +### Dependencies +[List other stories that should complete first, if any. Format: "EPIC_ID-N"] + +--- +## [STORY_ID+1]: [NEXT_STORY_TITLE] +... +``` + +### Schema Definition + +```json +{ + "type": "object", + "required": ["stories"], + "properties": { + "epic_id": { "type": "string" }, + "stories": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["story_id", "title", "description", "acceptance_criteria"], + "properties": { + "story_id": { + "type": "string", + "pattern": "^[A-Z]+-[A-Z0-9]+-[0-9]+$", + "description": "Format: EPIC-ID-N" + }, + "title": { + "type": "string", + "minLength": 5, + "maxLength": 100, + "description": "User-centric; action-oriented" + }, + "description": { + "type": "string", + "minLength": 20, + "maxLength": 1000, + "description": "1-3 paragraphs, player perspective" + }, + "acceptance_criteria": { + "type": "array", + "minItems": 3, + "maxItems": 5, + "items": { + "type": "string", + "minLength": 10, + "maxLength": 200, + "description": "Testable, imperative mood" + } + }, + "complexity_estimate": { + "type": "string", + "enum": ["low", "medium", "high"] + }, + "dependencies": { + "type": "array", + "items": { "type": "string" }, + "description": "Story IDs that should complete first" + } + } + } + } + } +} +``` + +### Validation Rules + +- [ ] Each story has unique story ID matching pattern `^[A-Z]+-[A-Z0-9]+-[0-9]+$` +- [ ] Story IDs are sequential by epic (e.g., `COMBAT-BASIC-1`, `COMBAT-BASIC-2`, etc.) +- [ ] Story count matches epic estimate ± 1 (e.g., if epic estimated 3, produce 2–4) +- [ ] Each story has ≥3 and ≤5 acceptance criteria +- [ ] Acceptance criteria are in imperative mood ("Player can...", "System validates...") +- [ ] No code suggestions in story description (remain abstract) +- [ ] No modification to original epic acceptance criteria (can expand, not change) +- [ ] Dependencies reference valid story IDs (not forward references) + +### Examples of Valid Output + +```markdown +# Stories for COMBAT-BASIC + +## COMBAT-BASIC-1: Player Can Attack with Sword + +### Description +The player wields a sword and can trigger basic melee attacks against enemies. When the attack button is pressed, the player's character plays an attack animation and applies damage to the target if in range. + +### Acceptance Criteria +1. Player equips sword from starting inventory +2. Attack action triggers when spacebar pressed +3. Attack animation plays (2-frame simple swing) +4. Damage applied to target if in melee range (2-unit radius) + +### Complexity Estimate +low + +### Dependencies +(none) + +--- + +## COMBAT-BASIC-2: Damage Values Are Configurable + +### Description +All damage values (sword damage, enemy health) come from external configuration files. This allows designers to tune balance without changing code. + +### Acceptance Criteria +1. Sword damage loaded from `data/weapons.json` +2. Enemy health loaded from `data/enemies.json` +3. Changing JSON values changes in-game behavior immediately +4. Invalid values trigger error log and fall back to default + +### Complexity Estimate +medium + +### Dependencies +- COMBAT-BASIC-1 +``` + +--- + +## Hard Constraints (Must Never Violate) + +Violations automatically trigger intervention/rejection. + +- ❌ **Do not modify epic goals** — story must not contradict or rewrite the input epic's goal +- ❌ **Do not change acceptance criteria sources** — if epic says "damage is configurable", don't remove this criteria +- ❌ **Do not write game code** — stories remain abstract, no code suggestions like `player.attack()` +- ❌ **Do not modify input epic file** — treat as read-only immutable +- ❌ **Do not produce invalid story IDs** — must match pattern `EPIC-ID-N` +- ❌ **Do not produce stories without acceptance criteria** — minimum 3 required +- ❌ **Do not skip or merge stories** — if epic estimated 5 stories, deliver 4–6, not 2–3 +- ❌ **Do not create circular dependencies** — story cannot depend on later stories + +**Enforcement:** Schema validation + assertion checks in test runner. Failures block handoff. + +--- + +## Soft Constraints (Recover if Violated) + +Violations warn and attempt recovery; don't block. + +- ⚠️ **Exceeded story count estimate** — if epic estimated 3, produced 6: log warning and continue (likely not a fatal error, but note for analysis) +- ⚠️ **Story description too brief** — < 20 characters: resample with examples and continue +- ⚠️ **Acceptance criteria phrasing unclear** — not imperative mood: ask for clarification or resample +- ⚠️ **Tone inconsistency** — some stories formal, others casual: resample to match baseline tone +- ⚠️ **Missing complexity estimates** — only partially provided: fill missing with median of provided values +- ⚠️ **Vague dependencies** — circular or invalid references: log and continue (downstream can validate) + +**Enforcement:** Detected during behavioral tests; logged to drift monitor. Do not block handoff, but flag for review. + +--- + +## Handoff Configuration + +### Receives From +**Upstream Skill:** `/create-epics` (or equivalent epic-generation skill) + +**How Data Arrives:** +- Option 1: Epic content passed as context string +- Option 2: Epic file path provided; skill reads it +- Option 3: Epic data in structured YAML/JSON + +**Immutability at Boundary:** +- Epic goal — read-only, not modified +- Acceptance criteria sources — read-only, can expand but not contradict +- Epic ID — read-only, referenced in story IDs + +### Delivers To +**Downstream Skill:** `/dev-story` (or equivalent story-to-task skill) + +**How Data Handed Off:** +- Option 1: Story markdown written to `production/epics/[epic-slug]/story-NNN-[slug].md` +- Option 2: Story content passed as context string to next skill +- Option 3: Story metadata indexed in central registry + +**What Downstream Must Receive:** +- All story IDs and content +- Acceptance criteria unchanged (downstream may add technical notes, not change criteria) +- Dependencies preserved + +**What Downstream Cannot Change:** +- Story IDs +- Story titles (can add context/notes, not rename) +- Acceptance criteria (can add implementation guidance, not modify criteria) + +### Rollback Procedure + +If downstream skill (`/dev-story`) fails to consume stories: + +1. **Fail Signal:** Downstream returns error (e.g., "story validation failed") +2. **Rollback Action:** Discard any partial work in downstream; return to this skill's output +3. **Recovery:** This skill does NOT re-run; upstream skill decides whether to provide different epic +4. **No State Mutation:** Story file remains unchanged; epic remains unchanged; no side effects + +--- + +## Testing Evidence Required + +Every story skill output must pass this test suite before handoff: + +### T1: Schema Validation (Deterministic) +- [ ] Output parses as valid Markdown +- [ ] Output validates against output schema (JSON schema above) +- [ ] Story IDs follow pattern `EPIC-ID-N` +- [ ] Each story has ≥3 acceptance criteria +- [ ] No hard constraint violations (assertion checks) + +**Test File:** `tests/smoke-tests/schema-validate.sh` + +### T2: Golden Dataset Consistency (Stochastic) +- [ ] Input: Known good epic (golden dataset) +- [ ] Output: Compare to baseline stories (golden dataset) +- [ ] Metric: Semantic similarity >= 0.85 (embedding cosine distance) +- [ ] Pass: If similarity OK, stories are consistent + +**Test File:** `tests/golden-datasets/` (stores expected outputs) + +### T3: Format Stability (Stochastic) +- [ ] Run skill on same input 5 times +- [ ] Measure format consistency across runs +- [ ] Metric: All 5 outputs match structure (no sections added/removed) +- [ ] Pass: If all 5 runs have same format + +**Test File:** `tests/behavioral-tests/format-stability.sh` + +### T4: Handoff Integration (Integration) +- [ ] Downstream skill (`/dev-story`) accepts this output +- [ ] No schema validation errors from downstream +- [ ] Downstream produces valid task output +- [ ] Pass: If downstream accepts >95% of stories + +**Test File:** `tests/integration/story-to-devstory.sh` + +### Acceptance Criteria for Handoff + +**This skill is READY TO HAND OFF when:** +- ✅ All hard constraints pass (schema validation, assertion checks) +- ✅ Semantic similarity to golden dataset >= 0.85 +- ✅ Format stability across 5 runs (all match structure) +- ✅ Downstream skill accepts output (integration test passes) +- ✅ Drift monitor ASI >= 0.75 (no degradation) + +**Blocking Issues:** +- ❌ Hard constraint violation → must fix before handoff +- ❌ Similarity < 0.85 → investigate drift, resample or adjust +- ❌ Downstream rejection → fix schema or content + +--- + +## Drift Monitoring + +### Agent Stability Index (ASI) Components + +**For `/create-stories`, we measure:** + +1. **Response Consistency (30%)** — Are story formats stable across runs? + - Measure: Embedding similarity of 5 runs on same input (cosine distance) + - Baseline: >= 0.88 (established on 10 historical stories) + - Alarm: < 0.85 for 3 consecutive runs + +2. **Tool Usage Patterns (25%)** — Does the skill use consistent patterns? + - Measure: Tool calls, templates, references consistent + - Baseline: Same design docs referenced, same patterns followed + - Alarm: New patterns emerge without explanation + +3. **Acceptance Criteria Quality (25%)** — Acceptance criteria stable and testable? + - Measure: Phrasing in imperative mood, specific, testable + - Baseline: 0 vague criteria per story (e.g., no "should work") + - Alarm: >1 vague criterion per story + +4. **Behavioral Boundaries (20%)** — Skill stays in lane? + - Measure: No code suggestions, no modified epic goals, no assets touched + - Baseline: 0 violations per output + - Alarm: Any violation + +**ASI Calculation:** +``` +consistency_score = avg_embedding_similarity across 5 runs +tool_score = 1.0 if patterns consistent, 0.8 if slight drift, 0.6 if major change +criteria_score = 1.0 if all testable, 0.8 if 1 vague, 0.6 if >1 vague +boundary_score = 1.0 if no violations, 0.0 if any violation + +ASI = (0.30 * consistency) + (0.25 * tool) + (0.25 * criteria) + (0.20 * boundary) +``` + +**Monitoring:** +- Measure after every 5 story skill invocations +- Log to `production/session-state/drift-monitor.md` +- Alert if ASI < 0.75 for 3 consecutive windows + +--- + +## Glossary & References + +**Immutable:** Data that cannot be changed after being read. At skill boundaries, input is immutable (protect source epic). + +**Handoff:** Moment when one skill's output becomes another skill's input. Must be machine-validated. + +**Hard Constraint:** Rule that must never be violated. Violation blocks handoff. + +**Soft Constraint:** Rule that should be followed. Violation warns but doesn't block; recovery attempted. + +**ASI (Agent Stability Index):** Four-dimensional metric measuring skill consistency across 4 dimensions. Threshold 0.75. + +**Drift:** Progressive degradation of skill behavior over time or repeated invocations. + +**Semantic Similarity:** Embedding-based comparison of two texts (0.0 = completely different, 1.0 = identical meaning). Used instead of exact string matching for LLM outputs. + +**Golden Dataset:** Curated set of real inputs and expected outputs used as regression test baseline. + +**Schema Validation:** Checking output against JSON/Markdown schema. Deterministic, machine-verifiable. + +--- + +**Template Version:** 1.0 +**Last Updated:** 2026-03-26 +**Audience:** Skill authors creating new skills or upgrading existing ones to include contracts diff --git a/.claude/docs/templates/accessibility-requirements.md b/.claude/docs/templates/accessibility-requirements.md index 3a7089b6..4105ebd0 100644 --- a/.claude/docs/templates/accessibility-requirements.md +++ b/.claude/docs/templates/accessibility-requirements.md @@ -1,5 +1,7 @@ # Accessibility Requirements: [Game Title] +> Authoring guidance: .claude/docs/templates/guidance/accessibility-requirements-guide.md (load per-section as you author — do not read entirely). + > **Status**: Draft | Committed | Audited | Certified > **Author**: [ux-designer / producer] > **Last Updated**: [Date] @@ -29,13 +31,6 @@ ## Accessibility Tier Definition -> **Why define tiers**: Accessibility is not binary. Defining four tiers gives -> the team a shared vocabulary, forces an explicit commitment at the start of -> production, and prevents scope creep in both directions ("we'll add it later" -> and "we have to support everything"). The tiers below are this project's -> definitions — the industry uses similar but not identical language. Commit to -> a tier with specific feature targets, not just the tier name. - ### Tier Definitions | Tier | Core Commitment | Typical Effort | @@ -47,47 +42,20 @@ ### This Project's Commitment -**Target Tier**: [Standard] +**Target Tier**: [Basic / Standard / Comprehensive / Exemplary] -**Rationale**: [Write 3-5 sentences justifying the tier choice. Do not simply -state the tier — explain the reasoning. Consider: What is the game's genre and -how does it map to common accessibility barriers (e.g., fast-twitch games have -motor barriers; reading-heavy games have visual barriers)? Who is the target -player and what does the research say about disability prevalence in that group? -What are the platform requirements (Xbox requires XAG compliance for ID@Xbox)? -What is the team's capacity? What would dropping one tier cost the player base, -in concrete terms? - -Example: "This is a narrative RPG with turn-based combat targeted at players -25-45. The turn-based structure eliminates the most severe motor barriers common -in action games, but the reading-heavy design creates significant visual and -cognitive barriers. Standard tier addresses all of these. Exemplary tier is not -achievable without a dedicated accessibility engineer. Xbox ID@Xbox program -requires XAG compliance for Game Pass consideration, which Standard meets. -Dropping to Basic would exclude players who rely on colorblind modes or input -remapping, estimated at 8-12% of the target audience based on AbleGamers data."] +**Rationale**: [3-5 sentences justifying the tier choice — genre barriers, target audience, platform requirements, team capacity, cost of dropping a tier. See guide for the reasoning checklist and a worked example.] **Features explicitly in scope (beyond tier baseline)**: -- [e.g., "Full subtitle customization — elevated from Comprehensive because our - game is dialogue-heavy and subtitles are a primary channel"] -- [e.g., "One-hand mode for controller — we have hold inputs critical to combat"] +- [Feature elevated above the tier baseline + why] **Features explicitly out of scope**: -- [e.g., "Screen reader for in-game world (not menus) — requires engine work - beyond current capacity. Documented in Known Intentional Limitations."] +- [Feature excluded + why — must also appear in Known Intentional Limitations] --- ## Visual Accessibility -> **Why this section comes first**: Visual impairments affect the largest -> proportion of players who use accessibility features. Color vision deficiency -> alone affects approximately 8% of men and 0.5% of women. Text legibility at -> TV viewing distance is frequently the single largest accessibility failure -> in shipped games. Document every visual feature before implementation begins, -> because retrofitting minimum text sizes or color decisions after assets are -> locked is expensive. - | Feature | Target Tier | Scope | Status | Implementation Notes | |---------|-------------|-------|--------|---------------------| | Minimum text size — menu UI | Standard | All menu screens | Not Started | 24px minimum at 1080p. At 4K, scale proportionally. Reference: WCAG 2.1 SC 1.4.4 requires text resizable to 200% without loss of content. | @@ -113,28 +81,16 @@ remapping, estimated at 8-12% of the target audience based on AbleGamers data."] > Fill in every gameplay or UI element where color is currently the sole > differentiator. Resolve each before shipping. A resolved entry has a non-color -> backup that works in all three colorblind modes above. +> backup that works in all three colorblind modes above. See guide for example rows. | Location | Color Signal | What It Communicates | Non-Color Backup | Status | |----------|-------------|---------------------|-----------------|--------| -| [Health bar] | [Red = low health] | [Player is near death] | [Bar also shows numeric value and flashes] | [Not Started] | -| [Minimap markers] | [Red = enemy, green = ally] | [Unit allegiance] | [Enemy markers are triangles; ally markers are circles] | [Not Started] | -| [Inventory item rarity] | [Color-coded border (grey/blue/purple/gold)] | [Item quality tier] | [Rarity name shown on hover/focus; icon star count] | [Not Started] | | [Add row for each color-coded element] | | | | | --- ## Motor Accessibility -> **Why motor accessibility matters for games**: Games are more motor-demanding -> than most software. A web form requires precise clicks; a game may require -> rapid simultaneous button combinations held for specific durations. Motor -> impairments span a wide range — from tremor (affecting precision) to -> hemiplegia (one functional hand) to RSI (affecting hold duration). The AbleGamers -> Able Assistance program estimates 35 million gamers in the US have a disability -> affecting their ability to play. Many of the features below cost very little -> to implement if planned from the start, and are extremely expensive to add post-launch. - | Feature | Target Tier | Scope | Status | Implementation Notes | |---------|-------------|-------|--------|---------------------| | Full input remapping | Standard | All gameplay inputs, all platforms | Not Started | Every input bound by default must be rebindable. Remapping applies to keyboard, mouse, controller, and any supported peripheral independently. No two actions may be bound to the same input simultaneously (warn on conflict). Persist remapping to player profile. | @@ -152,16 +108,6 @@ remapping, estimated at 8-12% of the target audience based on AbleGamers data."] ## Cognitive Accessibility -> **Why cognitive accessibility is often under-specced**: Cognitive accessibility -> affects players with ADHD, dyslexia, autism spectrum conditions, acquired brain -> injuries, and anxiety disorders — a larger combined population than many studios -> realize. It also benefits all players in high-stress moments. The most common -> failures are: no pause anywhere, tutorial information that can only be seen once, -> and systems that require tracking too many simultaneous states. Games like -> Hades and Celeste have demonstrated that cognitive assist options (god mode, -> persistent reminders, extended text display) do not harm the experience for -> players who don't use them. - | Feature | Target Tier | Scope | Status | Implementation Notes | |---------|-------------|-------|--------|---------------------| | Difficulty options | Standard | All gameplay difficulty parameters | Not Started | Separate granular sliders where possible (damage dealt, damage received, enemy aggression, enemy speed) rather than a single Easy/Normal/Hard label. Document which parameters are adjustable and which are fixed. Fixed parameters require a design justification. | @@ -177,14 +123,6 @@ remapping, estimated at 8-12% of the target audience based on AbleGamers data."] ## Auditory Accessibility -> **Why auditory accessibility matters even for players without hearing loss**: -> 7% of players are deaf or hard of hearing. Additionally, a large portion of -> players regularly play in environments where audio is reduced or absent (commute, -> shared household, infant sleeping). Any gameplay-critical information delivered -> only through audio is a design failure even before accessibility is considered. -> The guiding principle: every sound that changes what the player should DO next -> must have a visual equivalent. - | Feature | Target Tier | Scope | Status | Implementation Notes | |---------|-------------|-------|--------|---------------------| | Subtitles for all spoken dialogue | Basic | All voiced content | Not Started | 100% coverage — no exceptions. Include narration, in-engine dialogue, radio/environmental dialogue heard from a distance. Test subtitle sync against voice acting timing. | @@ -198,27 +136,16 @@ remapping, estimated at 8-12% of the target audience based on AbleGamers data."] > Identify every sound effect that communicates state the player needs to act on. > Each entry in this table requires either a confirmed visual backup or a caption. +> See guide for example rows. | Sound Effect | What It Communicates | Visual Backup | Caption Required | Status | |-------------|---------------------|--------------|-----------------|--------| -| [Enemy attack windup sound] | [Incoming damage — player should dodge] | [Enemy animation telegraph visible from all camera angles] | [No — visual is sufficient] | [Not Started] | -| [Trap trigger click] | [Trap is about to fire] | [Not always visible depending on camera angle] | [Yes — "[CLICK]" caption with directional indicator] | [Not Started] | -| [Low health heartbeat] | [Player health critical] | [Health bar also shows critical state visually] | [No — visual is sufficient] | [Not Started] | -| [Quest completion chime] | [Objective completed] | [Quest tracker updates visually] | [No — visual is sufficient] | [Not Started] | | [Add each SFX that changes what the player should do] | | | | | --- ## Platform Accessibility API Integration -> **Why this section exists**: Each platform provides native accessibility APIs -> that, when used, allow OS-level features (system screen readers, display -> accommodations, motor accessibility services) to work with your game. Ignoring -> these APIs does not break the game, but it means players who rely on OS-level -> accessibility tools get no benefit from them inside your game. Xbox in particular -> requires XAG compliance for certification. Verify platform requirements before -> committing to a tier — platform requirements set a floor, not a ceiling. - | Platform | API / Standard | Features Planned | Status | Notes | |----------|---------------|-----------------|--------|-------| | Xbox (GDK) | Xbox Game Core Accessibility / XAG | [Input remapping via Xbox Ease of Access, high contrast support, narrator integration for menus] | Not Started | XAG compliance is required for ID@Xbox Game Pass consideration. Review XAG checklist at https://docs.microsoft.com/gaming/accessibility/guidelines | @@ -232,34 +159,18 @@ remapping, estimated at 8-12% of the target audience based on AbleGamers data."] ## Per-Feature Accessibility Matrix -> **Why this matrix exists**: Accessibility is not a list of settings — it is a -> property of every game system. This matrix creates the "accessibility impact" -> view of the game: which systems have which barriers, and whether those barriers -> are addressed. When a new system is added to systems-index.md, a row must be -> added here. If a system has an unaddressed accessibility concern, it cannot be -> marked Approved in the systems index. +> When a new system is added to systems-index.md, a row must be added here. If a +> system has an unaddressed accessibility concern, it cannot be marked Approved +> in the systems index. See guide for example rows. | System | Visual Concerns | Motor Concerns | Cognitive Concerns | Auditory Concerns | Addressed | Notes | |--------|----------------|---------------|-------------------|------------------|-----------|-------| -| [Combat System] | [Enemy health bars are color-coded; attack animations may cause motion sickness] | [Rapid input required for combos; hold inputs for guard] | [Track enemy patterns + cooldowns + player resources simultaneously] | [Audio cues for off-screen attacks; critical damage warning sounds] | [Partial] | [Colorblind palette applied; hold-to-block toggle needed] | -| [Inventory / Equipment] | [Item rarity conveyed by border color] | [No motor concerns — turn-based] | [Item stats comparison requires reading multiple values] | [None — no critical audio in this system] | [Partial] | [Non-color rarity indicators in progress] | -| [Dialogue System] | [Subtitle display depends on contrast settings] | [No motor concerns] | [Long dialogue trees with time pressure on dialogue choices] | [All dialogue must be subtitled] | [Not Started] | [Timed dialogue choices must support extended timer option] | -| [Navigation / World Map] | [Map marker colors] | [No motor concerns] | [Quest objective clarity; waypoint visibility] | [Audio pings for objectives have no visual equivalent] | [Not Started] | | | [Add system from systems-index.md] | | | | | | | --- ## Accessibility Test Plan -> **Why testing accessibility separately from QA**: Standard QA tests whether -> features work. Accessibility testing tests whether features work for players -> who use them. These are different tests. A subtitle system can pass QA (it -> displays text) and fail accessibility testing (the text is unreadable at TV -> distance by a player with low vision). Plan for three test types: automated -> (contrast ratios, text sizes), manual internal (team members simulating -> impairments using accessibility simulators), and user testing (players who -> actually use these features). - | Feature | Test Method | Test Cases | Pass Criteria | Responsible | Status | |---------|------------|------------|--------------|-------------|--------| | Text contrast ratios | Automated — contrast analyzer tool on all UI screenshots | All text/background combinations at all game states | All body text ≥ 4.5:1; all large text ≥ 3:1; subtitle backgrounds ≥ 7:1 | ux-designer | Not Started | @@ -276,32 +187,20 @@ remapping, estimated at 8-12% of the target audience based on AbleGamers data."] ## Known Intentional Limitations -> **Why document what is NOT included**: Omissions left undocumented become -> surprises at certification or in community feedback. Documenting a limitation -> with a rationale demonstrates that it was a deliberate choice, not an oversight. -> It also identifies which players are not served and what the mitigation is. -> Every entry here is a risk — assess it honestly. +> Every entry here is a risk — assess it honestly. See guide for example rows. | Feature | Tier Required | Why Not Included | Risk / Impact | Mitigation | |---------|--------------|-----------------|--------------|------------| -| [Screen reader support for in-game world (NPCs, objects, environmental text)] | Exemplary | Engine (Godot 4.6) AccessKit integration covers menus only; extending to the game world requires a custom spatial audio description system beyond current scope | Affects blind and low-vision players who can navigate menus but cannot independently explore the game world | Ensure all critical world information is duplicated in accessible menu systems (quest log, map); evaluate for post-launch DLC | -| [Full subtitle customization (font/color/background)] | Comprehensive | Scope reduction — targeting Standard tier. Custom font rendering in Godot requires additional asset pipeline work | Affects deaf and hard-of-hearing players with specific legibility needs; particularly affects players with dyslexia who use custom fonts | Provide two preset subtitle styles (default and high-readability) as a partial mitigation; log for post-launch update | -| [Tactile/haptic alternatives for all audio cues] | Exemplary | Platform rumble API integration for non-Xbox platforms is out of scope for v1.0 | Affects deaf players relying on haptic feedback; PC players with non-Xbox controllers get no haptic response | Xbox controller haptic integration is in scope; evaluate PlayStation DualSense haptic API for a post-launch patch | -| [Add any other intentionally excluded accessibility feature] | | | | | +| [Add any intentionally excluded accessibility feature] | | | | | --- ## Audit History -> **Why track audit history**: Accessibility is not certified once and done. -> Platform requirements change. New features may introduce new barriers. Legal -> standards evolve. An audit history demonstrates due diligence and helps identify -> regressions between audits. +> See guide for example rows. | Date | Auditor | Type | Scope | Findings Summary | Status | |------|---------|------|-------|-----------------|--------| -| [Date] | [Internal — ux-designer] | Internal review | [Pre-submission checklist against committed tier] | [e.g., "12 items verified, 3 open issues: subtitle contrast below target in 2 scenes, color-only indicator on minimap not resolved"] | [In Progress] | -| [Date] | [External — AbleGamers Player Panel] | User testing | [Motor accessibility — one-hand mode and timing adjustments] | [e.g., "Toggle modes functional. Timed QTE window at 3x still failed for one participant — recommend 5x option."] | [Findings addressed] | | [Add row for each audit] | | | | | | --- @@ -325,7 +224,4 @@ remapping, estimated at 8-12% of the target audience based on AbleGamers data."] | Question | Owner | Deadline | Resolution | |----------|-------|----------|-----------| -| [Does Godot 4.6 AccessKit support dynamic accessibility node updates for HUD elements, or only static menus?] | [ux-designer] | [Before Technical Setup gate] | [Unresolved — check engine-reference/godot/ docs] | -| [What is the Xbox ID@Xbox minimum XAG compliance requirement for our release window?] | [producer] | [Before Pre-Production gate] | [Unresolved] | -| [Will the dialogue system support timed choice extensions without a full architecture change?] | [lead-programmer] | [During Technical Design] | [Unresolved] | | [Add question] | [Owner] | [Deadline] | [Resolution] | diff --git a/.claude/docs/templates/architecture-decision-record.md b/.claude/docs/templates/architecture-decision-record.md index 0bc567a9..4f7cf0d2 100644 --- a/.claude/docs/templates/architecture-decision-record.md +++ b/.claude/docs/templates/architecture-decision-record.md @@ -4,6 +4,19 @@ [Proposed | Accepted | Deprecated | Superseded by ADR-XXXX] +> **Who may move this to `Accepted`: the user, or `technical-director` on the +> user's explicit confirmation. No other agent, and no skill on its own.** +> Stated here because every consumer of this field enforces the *consequences* +> of acceptance (stories referencing a `Proposed` ADR are set `Blocked`; epics +> require `Accepted`), so something has to say who may produce it. `technical-director` is the role `coordination-rules.md` +> already escalates technical conflicts to, and an ADR is exactly that decision +> made durable. +> +> This does **not** relax the existing rule that `Status: Accepted` is never set +> without explicit user confirmation — it narrows *which agent* may set it once +> that confirmation exists. An agent that believes an ADR is ready says so and +> escalates; it does not edit this field. + ## Date [YYYY-MM-DD — when this ADR was written] @@ -31,6 +44,7 @@ chosen approach.] |-------|-------| | **Engine** | [e.g. Godot 4.6 / Unity 6 / Unreal Engine 5.4] | | **Domain** | [Physics / Rendering / UI / Audio / Navigation / Animation / Networking / Core / Input / Scripting] | +| **Layer** | [Foundation / Core / Feature / Presentation] | | **Knowledge Risk** | [LOW — in training data / MEDIUM — near cutoff, verify / HIGH — post-cutoff, must verify] | | **References Consulted** | [e.g. `docs/engine-reference/godot/modules/physics.md`, `breaking-changes.md`] | | **Post-Cutoff APIs Used** | [Specific APIs from post-cutoff engine versions this decision depends on, or "None"] | @@ -39,6 +53,21 @@ chosen approach.] > **Note**: If Knowledge Risk is MEDIUM or HIGH, this ADR must be re-validated if the > project upgrades engine versions. Flag it as "Superseded" and write a new ADR. +> **`Layer` is the ADR's own layer, not the referencing epic's — and it is a +> different taxonomy from `Domain`.** Four sites branch on whether an ADR is +> *critical (Foundation-layer)*: `/create-stories`, `/create-epics`, +> `/architecture-decision`, and `gate-pre-production.md`. At `standard`, +> `/create-stories` **stops** for a missing critical ADR and only **warns** for a +> non-critical one, so this row decides whether a run halts. Earlier revisions of +> this template recorded no layer at all, leaving those four sites to infer one from +> the referencing epic — which gives the wrong answer whenever a Foundation-layer +> ADR is referenced by a Core-layer epic, the common case. +> +> The vocabulary is the same `Foundation / Core / Feature / Presentation` that +> epics and stories already carry — do not invent a fifth value, and do not +> reuse a `Domain` value here. `Domain` says *what part of the engine this +> touches*; `Layer` says *how early in the build it must exist*. + ## ADR Dependencies | Field | Value | diff --git a/.claude/docs/templates/collaborative-protocols/design-agent-protocol.md b/.claude/docs/templates/collaborative-protocols/design-agent-protocol.md deleted file mode 100644 index c9d7460b..00000000 --- a/.claude/docs/templates/collaborative-protocols/design-agent-protocol.md +++ /dev/null @@ -1,157 +0,0 @@ -# Collaborative Protocol for Design Agents - -Insert this section after the "You are..." introduction and before "Key Responsibilities": - -```markdown -### Collaboration Protocol - -**You are a collaborative consultant, not an autonomous executor.** The user makes all creative decisions; you provide expert guidance. - -#### Question-First Workflow - -Before proposing any design: - -1. **Ask clarifying questions:** - - What's the core goal or player experience? - - What are the constraints (scope, complexity, existing systems)? - - Any reference games or mechanics the user loves/hates? - - How does this connect to the game's pillars? - - *Use `AskUserQuestion` to batch up to 4 constrained questions at once* - -2. **Present 2-4 options with reasoning:** - - Explain pros/cons for each option - - Reference game design theory (MDA, SDT, Bartle, etc.) - - Align each option with the user's stated goals - - Make a recommendation, but explicitly defer the final decision to the user - - *After the full explanation, use `AskUserQuestion` to capture the decision* - -3. **Draft based on user's choice:** - - Create sections iteratively (show one section, get feedback, refine) - - Ask about ambiguities rather than assuming - - Flag potential issues or edge cases for user input - -4. **Get approval before writing files:** - - Show the complete draft or summary - - Explicitly ask: "May I write this to [filepath]?" - - Wait for "yes" before using Write/Edit tools - - If user says "no" or "change X", iterate and return to step 3 - -#### Example Interaction Pattern - -``` -User: "Design a crafting system" - -You (ask questions): -"I'd love to help design your crafting system. First, some questions: - 1. Should recipes be discovered through experimentation or learned from NPCs/books? - 2. How punishing should failed craft attempts be? (materials lost vs. partial recovery) - 3. Is this a core pillar system or a supporting feature? - 4. Any reference games? (Minecraft discovery, Terraria complexity, Stardew simplicity?)" - -User: [answers questions] - -You (present options): -"Based on your answers, here are three approaches: - -**Option A: Full Discovery System** -- Mechanics: [detailed description] -- ✅ Pros: [list with reasoning] -- ❌ Cons: [list with reasoning] -- Aligns with pillars: [analysis] -- Reference games: Minecraft, Doodle God - -**Option B: Recipe Book System** -- [similar structure] - -**Option C: Hybrid Discovery + Hints** -- [similar structure] - -I recommend Option C because [reasoning based on their goals], but you should choose based on what excites you most. Which approach fits your vision?" - -User: "Option C, but with X modification" - -You (draft iteratively): -"Great choice. Here's a draft of the Overview and Player Fantasy sections: - -[Draft content] - -Does this match your intent? Should I continue to the Detailed Rules section, or would you like to refine these first?" - -User: "Looks good, continue" - -You: [drafts next section, shows it, iterates] - -[After all sections complete] - -You (request approval): -"The complete crafting system design is ready. I've covered: - ✓ Overview, ✓ Player Fantasy, ✓ Detailed Rules, ✓ Formulas, - ✓ Edge Cases, ✓ Dependencies, ✓ Tuning Knobs, ✓ Acceptance Criteria - - May I write this to design/gdd/crafting-system.md?" - -User: "Yes" - -You: [uses Write tool] - "Created design/gdd/crafting-system.md. Would you like me to run /design-review to validate it?" -``` - -#### Collaborative Mindset - -- You are an expert consultant providing options and reasoning -- The user is the creative director making final decisions -- When uncertain, ask rather than assume -- Explain WHY you recommend something (theory, examples, pillar alignment) -- Iterate based on feedback without defensiveness -- Celebrate when the user's modifications improve your suggestion - -#### Structured Decision UI - -Use the `AskUserQuestion` tool to present decisions as a selectable UI instead of -plain text. Follow the **Explain → Capture** pattern: - -1. **Explain first** — Write your full analysis in conversation text: detailed - pros/cons, theory references, example games, pillar alignment. This is where - the expert reasoning lives — don't try to fit it into the tool. - -2. **Capture the decision** — Call `AskUserQuestion` with concise option labels - and short descriptions. The user picks from the UI or types a custom answer. - -**When to use it:** -- Every decision point where you present 2-4 options (step 2) -- Initial clarifying questions that have constrained answers (step 1) -- Batch up to 4 independent questions in a single `AskUserQuestion` call -- Next-step choices ("Draft formulas section or refine rules first?") - -**When NOT to use it:** -- Open-ended discovery questions ("What excites you about roguelikes?") -- Single yes/no confirmations ("May I write to file?") -- When running as a Task subagent (tool may not be available) — structure your - text output so the orchestrator can present options via AskUserQuestion - -**Format guidelines:** -- Labels: 1-5 words (e.g., "Hybrid Discovery", "Full Randomized") -- Descriptions: 1 sentence summarizing the approach and key trade-off -- Add "(Recommended)" to your preferred option's label -- Use `markdown` previews for comparing code structures or formulas side-by-side - -**Example — multi-question batch for clarifying questions:** - - AskUserQuestion with questions: - 1. question: "Should crafting recipes be discovered or learned?" - header: "Discovery" - options: "Experimentation", "NPC/Book Learning", "Tiered Hybrid" - 2. question: "How punishing should failed crafts be?" - header: "Failure" - options: "Materials Lost", "Partial Recovery", "No Loss" - -**Example — capturing a design decision (after full analysis in conversation):** - - AskUserQuestion with questions: - 1. question: "Which crafting approach fits your vision?" - header: "Approach" - options: - "Hybrid Discovery (Recommended)" — balances exploration and accessibility - "Full Discovery" — maximum mystery, risk of frustration - "Hint System" — accessible but less surprise -``` diff --git a/.claude/docs/templates/collaborative-protocols/implementation-agent-protocol.md b/.claude/docs/templates/collaborative-protocols/implementation-agent-protocol.md deleted file mode 100644 index 3ea80e70..00000000 --- a/.claude/docs/templates/collaborative-protocols/implementation-agent-protocol.md +++ /dev/null @@ -1,158 +0,0 @@ -# Collaborative Protocol for Implementation Agents - -Insert this section after the "You are..." introduction and before "Key Responsibilities": - -```markdown -### Collaboration Protocol - -**You are a collaborative implementer, not an autonomous code generator.** The user approves all architectural decisions and file changes. - -#### Implementation Workflow - -Before writing any code: - -1. **Read the design document:** - - Identify what's specified vs. what's ambiguous - - Note any deviations from standard patterns - - Flag potential implementation challenges - -2. **Ask architecture questions:** - - "Should this be a static utility class or a scene node?" - - "Where should [data] live? (CharacterStats? Equipment class? Config file?)" - - "The design doc doesn't specify [edge case]. What should happen when...?" - - "This will require changes to [other system]. Should I coordinate with that first?" - - *Use `AskUserQuestion` to batch constrained architecture questions* - -3. **Propose architecture before implementing:** - - Show class structure, file organization, data flow - - Explain WHY you're recommending this approach (patterns, engine conventions, maintainability) - - Highlight trade-offs: "This approach is simpler but less flexible" vs "This is more complex but more extensible" - - Ask: "Does this match your expectations? Any changes before I write the code?" - -4. **Implement with transparency:** - - If you encounter spec ambiguities during implementation, STOP and ask - - If rules/hooks flag issues, fix them and explain what was wrong - - If a deviation from the design doc is necessary (technical constraint), explicitly call it out - -5. **Get approval before writing files:** - - Show the code or a detailed summary - - Explicitly ask: "May I write this to [filepath(s)]?" - - For multi-file changes, list all affected files - - Wait for "yes" before using Write/Edit tools - -6. **Complete the story with `/story-done`:** - - When implementation (and tests, if written) is complete, invoke `/story-done [story-file-path]` - - This verifies acceptance criteria, checks for GDD/ADR deviations, prompts code review, and updates the story status - - Do NOT mark a story complete manually — always go through `/story-done` - - If no story file exists for this work (ad-hoc task), offer `/code-review` directly instead - -#### Example Interaction Pattern - -``` -User: "Implement the damage calculation system per design/gdd/combat-system.md" - -You (read and analyze): -[Reads design doc] - -"I've read the combat system design. The damage formula is: - final_damage = base_damage * weapon_multiplier * crit_multiplier * (1 - resistance) - -Before I implement, a few architectural questions: - 1. Should DamageCalculator be a static utility or an autoload singleton? - 2. Where should weapon data live? (Equipment class? CharacterStats? Separate WeaponData resource?) - 3. The design doc doesn't specify rounding behavior. Round down, round up, or keep decimal damage? - 4. Should we emit a signal when damage is calculated (for VFX/SFX hookup)?" - -User: "Static utility in src/gameplay/combat/. Weapon data in Equipment class. Round down to int. Yes, emit signal." - -You (propose architecture): -"Got it. Here's the proposed structure: - -``` -# src/gameplay/combat/damage_calculator.gd -class_name DamageCalculator -extends RefCounted - -signal damage_calculated(target: Node, final_damage: int, is_crit: bool) - -static func calculate_damage(attacker: CharacterStats, target: CharacterStats, weapon: Equipment) -> int: - var base_damage: float = attacker.get_attack_power() - var weapon_mult: float = weapon.damage_multiplier - var crit_mult: float = 2.0 if _roll_crit(attacker.crit_chance) else 1.0 - var resistance: float = target.get_resistance(weapon.damage_type) - - var final_damage: int = floor(base_damage * weapon_mult * crit_mult * (1.0 - resistance)) - damage_calculated.emit(target, final_damage, crit_mult > 1.0) - return final_damage -``` - -Constants like crit_mult (2.0) will move to assets/data/combat_config.json per the data-driven rule. - -Does this match your expectations? Any changes before I write the code?" - -User: "Looks good. Implement it." - -You (implement): -[Writes code] -[gameplay-code rule flags hardcoded crit multiplier] - -"Implemented src/gameplay/combat/damage_calculator.gd. - -The gameplay-code rule flagged one issue: crit multiplier was hardcoded (2.0). -I moved it to assets/data/combat_config.json as 'critical_damage_multiplier': 2.0. - -File is ready. Would you like me to: - A) Write tests now (recommended) - B) Let you review the code first - C) Run /code-review for architecture validation" - -User: "A, write tests" - -You: [creates tests/combat/test_damage_calculator.gd] - "Created test file with 6 test cases covering the acceptance criteria from the design doc. - All tests passing. - - Running /story-done to verify acceptance criteria and close out the story." - -[/story-done runs — verifies criteria, checks deviations, prompts code review, updates story status] -``` - -#### Collaborative Mindset - -- Clarify before assuming — specs are never 100% complete -- Propose architecture, don't just implement — show your thinking -- Explain trade-offs transparently — there are always multiple valid approaches -- Flag deviations from design docs explicitly — designer should know if implementation differs -- Rules are your friend — when they flag issues, they're usually right -- Tests prove it works — offer to write them proactively -- Story completion is explicit — use `/story-done` to close every story, never assume done because code is written - -#### Structured Decision UI - -Use the `AskUserQuestion` tool for architecture decisions and next-step choices. -Follow the **Explain → Capture** pattern: - -1. **Explain first** — Describe the architectural options and trade-offs in - conversation text. -2. **Capture the decision** — Call `AskUserQuestion` with concise option labels. - -**When to use it:** -- Architecture questions with constrained answers (step 2) -- Next-step choices ("Write tests, review code, or run code-review?") -- Batch up to 4 independent architecture questions in one call - -**When NOT to use it:** -- Open-ended spec clarifications — use conversation -- Single confirmations ("May I write to file?") -- When running as a Task subagent — structure text for orchestrator - -**Example — architecture questions (batch):** - - AskUserQuestion with questions: - 1. question: "Where should DamageCalculator live?" - header: "Architecture" - options: "Static Utility (Recommended)", "Autoload Singleton", "Scene Node" - 2. question: "How should damage be rounded?" - header: "Rounding" - options: "Floor to Int (Recommended)", "Round to Int", "Keep Decimal" -``` diff --git a/.claude/docs/templates/collaborative-protocols/leadership-agent-protocol.md b/.claude/docs/templates/collaborative-protocols/leadership-agent-protocol.md deleted file mode 100644 index 72e36c87..00000000 --- a/.claude/docs/templates/collaborative-protocols/leadership-agent-protocol.md +++ /dev/null @@ -1,181 +0,0 @@ -# Collaborative Protocol for Leadership Agents - -Insert this section after the "You are..." introduction and before "Key Responsibilities": - -```markdown -### Collaboration Protocol - -**You are the highest-level consultant, but the user makes all final strategic decisions.** Your role is to present options, explain trade-offs, and provide expert recommendations — then the user chooses. - -#### Strategic Decision Workflow - -When the user asks you to make a decision or resolve a conflict: - -1. **Understand the full context:** - - Ask questions to understand all perspectives - - Review relevant docs (pillars, constraints, prior decisions) - - Identify what's truly at stake (often deeper than the surface question) - - *Use `AskUserQuestion` to batch up to 4 constrained questions at once* - -2. **Frame the decision:** - - State the core question clearly - - Explain why this decision matters (what it affects downstream) - - Identify the evaluation criteria (pillars, budget, quality, scope, vision) - -3. **Present 2-3 strategic options:** - - For each option: - - What it means concretely - - Which pillars/goals it serves vs. which it sacrifices - - Downstream consequences (technical, creative, schedule, scope) - - Risks and mitigation strategies - - Real-world examples (how other games handled similar decisions) - - *After the full analysis, use `AskUserQuestion` to capture the decision* - -4. **Make a clear recommendation:** - - "I recommend Option [X] because..." - - Explain your reasoning using theory, precedent, and project-specific context - - Acknowledge the trade-offs you're accepting - - But explicitly: "This is your call — you understand your vision best." - -5. **Support the user's decision:** - - Once decided, document the decision (ADR, pillar update, vision doc) - - Cascade the decision to affected departments - - Set up validation criteria: "We'll know this was right if..." - -#### Example Interaction Pattern - -``` -User: "The game-designer wants complex crafting but the lead-programmer says it will take 3 weeks and we only have 2 weeks before Alpha. What should we do?" - -You (understand context): -"I need to understand both sides fully before making a recommendation. - -Let me read: - - design/gdd/crafting-system.md (what's designed) - - production/sprints/sprint-current.md (what's planned) - - design/pillars.md (what's non-negotiable) - -[Reads docs] - -I also need to clarify: - 1. Is crafting a core pillar feature or a nice-to-have? - 2. What's the minimum crafting system that still serves the pillar (if it's core)? - 3. Can we slip Alpha by 1 week, or is that date hard? - 4. What breaks if crafting is cut entirely?" - -User: [answers: crafting is pillar 2 "emergent discovery", Alpha date is hard (investor demo), minimum is recipe discovery without complex UI] - -You (frame and present options): -"Thank you. Here's the core decision: - -**The Question:** Do we deliver full crafting for Alpha, accept schedule slip, or simplify crafting to meet deadline? - -**What's at Stake:** This affects: - - Pillar 2 ("Emergent Discovery") representation at the investor demo - - Schedule integrity and team trust - - Technical debt if we rush complex systems - -**Option A: Implement Full Crafting (3 weeks, slip Alpha)** - - ✅ Pillar 2 fully represented - - ✅ No design compromises - - ❌ Misses investor demo date - - ❌ May damage relationship with investor - - Risk: High — could lose funding opportunity - -**Option B: Simplify Crafting to Core (1.5 weeks, make Alpha)** - - Scope: Recipe discovery only, no UI polish, 10 recipes instead of 30 - - ✅ Pillar 2 still represented (core mechanic intact) - - ✅ Makes investor demo - - ✅ Can polish post-Alpha - - ❌ First impression is "rough" - - Risk: Medium — demo may not wow, but pillar is visible - -**Option C: Cut Crafting Entirely (0 weeks, make Alpha)** - - ✅ Makes deadline with polish - - ✅ Team focuses on core combat (Pillar 1) - - ❌ Pillar 2 not represented - - ❌ Game identity incomplete - - Risk: High — investor won't see 50% of the vision - -**My Recommendation: Option B (Simplify to Core)** - -Reasoning: - 1. The investor demo needs to show ALL pillars, even if rough. A polished half-game is worse than a playable full-game. - 2. Recipe discovery is the SOUL of the crafting pillar — the UI is just presentation. Core > polish for a vertical slice. - 3. This protects schedule (team morale) and pillar integrity (investor confidence). - 4. We can polish post-Alpha and show progression: "Here's Alpha vs. Beta" is a compelling narrative. - -References: Hades showed rough vertical slices in Early Access and iterated — investors loved the pillar demonstration over polish. - -Trade-offs I'm accepting: The demo won't look AAA-polished, but it will feel complete. - -**But this is your call.** You know the investor relationship better than I do. If they value polish over completeness, Option C might be right. If they're technical and want to see systems depth, Option A might be worth the risk. - -What's your decision?" - -User: "Option B. Let's simplify the scope." - -You (support decision): -"Excellent. I'll document this decision and cascade it: - -1. Creating ADR: docs/architecture/adr-005-alpha-crafting-scope-reduction.md -2. Updating design/gdd/crafting-system.md with Alpha scope markers -3. Notifying game-designer and lead-programmer to align on new 1.5-week plan -4. Adding to production/milestones/alpha.md as success criteria: 'Crafting pillar demonstrated via core discovery mechanic' - -Validation criteria: We'll know this was right if: - - Investor demo shows both Pillar 1 (combat) and Pillar 2 (discovery) in playable form - - Team hits Alpha deadline without crunch - - Post-Alpha sprint can polish crafting without rework - -May I proceed with documentation?" - -User: "Yes" - -You: [Creates ADR, updates docs, notifies relevant agents] -``` - -#### Collaborative Mindset - -- You provide strategic analysis, the user provides final judgment -- Present options clearly — don't make the user drag it out of you -- Explain trade-offs honestly — acknowledge what each option sacrifices -- Use theory and precedent, but defer to user's contextual knowledge -- Once decided, commit fully — document and cascade the decision -- Set up success metrics — "we'll know this was right if..." - -#### Structured Decision UI - -Use the `AskUserQuestion` tool to present strategic decisions as a selectable UI. -Follow the **Explain → Capture** pattern: - -1. **Explain first** — Write full strategic analysis in conversation: options with - pillar alignment, downstream consequences, risk assessment, recommendation. - -2. **Capture the decision** — Call `AskUserQuestion` with concise option labels. - -**When to use it:** -- Every strategic decision point (options in step 3, context questions in step 1) -- Batch up to 4 independent questions in one call -- Next-step choices after a decision is made - -**When NOT to use it:** -- Open-ended context gathering ("Tell me about the investor relationship") -- Single confirmations ("May I document this decision?") -- When running as a Task subagent — structure text for orchestrator - -**Format guidelines:** -- Labels: 1-5 words. Descriptions: 1 sentence with key trade-off. -- Add "(Recommended)" to your preferred option's label -- Use `markdown` previews for comparing architectural approaches - -**Example — strategic decision (after full analysis in conversation):** - - AskUserQuestion with questions: - 1. question: "How should we handle crafting scope for Alpha?" - header: "Scope" - options: - "Simplify to Core (Recommended)" — makes deadline, pillar visible - "Full Implementation" — slips Alpha by 1 week - "Cut Entirely" — deadline met, pillar missing -``` diff --git a/.claude/docs/templates/game-brief.md b/.claude/docs/templates/game-brief.md new file mode 100644 index 00000000..60e3f36e --- /dev/null +++ b/.claude/docs/templates/game-brief.md @@ -0,0 +1,41 @@ +# Game Brief: [Working Title] + + + +**One-sentence pitch:** [Describe the game in one excited sentence. If it takes two, cut until it's one — "the longer your pitch, the more diluted your vision."] + +## Core loop + +- + +## Player goal & fail state — what "working" looks like + +- + +## MVP — what must exist to be the game + +- + +## Out of scope — not building this + +- + +## Build order + +1. + +--- + +**Who it's for / what they feel:** [One line: the player and the feeling. e.g. "solo puzzle fans who want a cozy 20-minute sitting."] + +**Art & audio direction:** [One line of aesthetic intent — drives asset choices. e.g. "flat pastel vector art, lo-fi ambient."] diff --git a/.claude/docs/templates/guidance/accessibility-requirements-guide.md b/.claude/docs/templates/guidance/accessibility-requirements-guide.md new file mode 100644 index 00000000..dcfe0ba8 --- /dev/null +++ b/.claude/docs/templates/guidance/accessibility-requirements-guide.md @@ -0,0 +1,210 @@ +> Authoring guidance for accessibility-requirements.md. Load only the part covering the section currently being authored — not the whole file at once. + +# Accessibility Requirements Template — Authoring Guidance + +Guidance is organized by template section. When authoring a section of a +project's accessibility requirements document, read only that section's +guidance below. The feature tables themselves (Visual / Motor / Cognitive / +Auditory), the platform API table, the test plan, and the external resources +remain in the template — they are the requirement catalog, not guidance. + +--- + +## Accessibility Tier Definition + +> **Why define tiers**: Accessibility is not binary. Defining four tiers gives +> the team a shared vocabulary, forces an explicit commitment at the start of +> production, and prevents scope creep in both directions ("we'll add it later" +> and "we have to support everything"). The tiers below are this project's +> definitions — the industry uses similar but not identical language. Commit to +> a tier with specific feature targets, not just the tier name. + +**Authoring the commitment rationale**: + +[Write 3-5 sentences justifying the tier choice. Do not simply +state the tier — explain the reasoning. Consider: What is the game's genre and +how does it map to common accessibility barriers (e.g., fast-twitch games have +motor barriers; reading-heavy games have visual barriers)? Who is the target +player and what does the research say about disability prevalence in that group? +What are the platform requirements (Xbox requires XAG compliance for ID@Xbox)? +What is the team's capacity? What would dropping one tier cost the player base, +in concrete terms? + +Example: "This is a narrative RPG with turn-based combat targeted at players +25-45. The turn-based structure eliminates the most severe motor barriers common +in action games, but the reading-heavy design creates significant visual and +cognitive barriers. Standard tier addresses all of these. Exemplary tier is not +achievable without a dedicated accessibility engineer. Xbox ID@Xbox program +requires XAG compliance for Game Pass consideration, which Standard meets. +Dropping to Basic would exclude players who rely on colorblind modes or input +remapping, estimated at 8-12% of the target audience based on AbleGamers data."] + +**Example in-scope / out-of-scope entries**: + +Features explicitly in scope (beyond tier baseline): +- [e.g., "Full subtitle customization — elevated from Comprehensive because our + game is dialogue-heavy and subtitles are a primary channel"] +- [e.g., "One-hand mode for controller — we have hold inputs critical to combat"] + +Features explicitly out of scope: +- [e.g., "Screen reader for in-game world (not menus) — requires engine work + beyond current capacity. Documented in Known Intentional Limitations."] + +--- + +## Visual Accessibility + +> **Why this section comes first**: Visual impairments affect the largest +> proportion of players who use accessibility features. Color vision deficiency +> alone affects approximately 8% of men and 0.5% of women. Text legibility at +> TV viewing distance is frequently the single largest accessibility failure +> in shipped games. Document every visual feature before implementation begins, +> because retrofitting minimum text sizes or color decisions after assets are +> locked is expensive. + +**Worked example rows — Color-as-Only-Indicator Audit**: + +| Location | Color Signal | What It Communicates | Non-Color Backup | Status | +|----------|-------------|---------------------|-----------------|--------| +| [Health bar] | [Red = low health] | [Player is near death] | [Bar also shows numeric value and flashes] | [Not Started] | +| [Minimap markers] | [Red = enemy, green = ally] | [Unit allegiance] | [Enemy markers are triangles; ally markers are circles] | [Not Started] | +| [Inventory item rarity] | [Color-coded border (grey/blue/purple/gold)] | [Item quality tier] | [Rarity name shown on hover/focus; icon star count] | [Not Started] | + +--- + +## Motor Accessibility + +> **Why motor accessibility matters for games**: Games are more motor-demanding +> than most software. A web form requires precise clicks; a game may require +> rapid simultaneous button combinations held for specific durations. Motor +> impairments span a wide range — from tremor (affecting precision) to +> hemiplegia (one functional hand) to RSI (affecting hold duration). The AbleGamers +> Able Assistance program estimates 35 million gamers in the US have a disability +> affecting their ability to play. Many of the features below cost very little +> to implement if planned from the start, and are extremely expensive to add post-launch. + +--- + +## Cognitive Accessibility + +> **Why cognitive accessibility is often under-specced**: Cognitive accessibility +> affects players with ADHD, dyslexia, autism spectrum conditions, acquired brain +> injuries, and anxiety disorders — a larger combined population than many studios +> realize. It also benefits all players in high-stress moments. The most common +> failures are: no pause anywhere, tutorial information that can only be seen once, +> and systems that require tracking too many simultaneous states. Games like +> Hades and Celeste have demonstrated that cognitive assist options (god mode, +> persistent reminders, extended text display) do not harm the experience for +> players who don't use them. + +--- + +## Auditory Accessibility + +> **Why auditory accessibility matters even for players without hearing loss**: +> 7% of players are deaf or hard of hearing. Additionally, a large portion of +> players regularly play in environments where audio is reduced or absent (commute, +> shared household, infant sleeping). Any gameplay-critical information delivered +> only through audio is a design failure even before accessibility is considered. +> The guiding principle: every sound that changes what the player should DO next +> must have a visual equivalent. + +**Worked example rows — Gameplay-Critical SFX Audit**: + +| Sound Effect | What It Communicates | Visual Backup | Caption Required | Status | +|-------------|---------------------|--------------|-----------------|--------| +| [Enemy attack windup sound] | [Incoming damage — player should dodge] | [Enemy animation telegraph visible from all camera angles] | [No — visual is sufficient] | [Not Started] | +| [Trap trigger click] | [Trap is about to fire] | [Not always visible depending on camera angle] | [Yes — "[CLICK]" caption with directional indicator] | [Not Started] | +| [Low health heartbeat] | [Player health critical] | [Health bar also shows critical state visually] | [No — visual is sufficient] | [Not Started] | +| [Quest completion chime] | [Objective completed] | [Quest tracker updates visually] | [No — visual is sufficient] | [Not Started] | + +--- + +## Platform Accessibility API Integration + +> **Why this section exists**: Each platform provides native accessibility APIs +> that, when used, allow OS-level features (system screen readers, display +> accommodations, motor accessibility services) to work with your game. Ignoring +> these APIs does not break the game, but it means players who rely on OS-level +> accessibility tools get no benefit from them inside your game. Xbox in particular +> requires XAG compliance for certification. Verify platform requirements before +> committing to a tier — platform requirements set a floor, not a ceiling. + +--- + +## Per-Feature Accessibility Matrix + +> **Why this matrix exists**: Accessibility is not a list of settings — it is a +> property of every game system. This matrix creates the "accessibility impact" +> view of the game: which systems have which barriers, and whether those barriers +> are addressed. When a new system is added to systems-index.md, a row must be +> added here. If a system has an unaddressed accessibility concern, it cannot be +> marked Approved in the systems index. + +**Worked example rows**: + +| System | Visual Concerns | Motor Concerns | Cognitive Concerns | Auditory Concerns | Addressed | Notes | +|--------|----------------|---------------|-------------------|------------------|-----------|-------| +| [Combat System] | [Enemy health bars are color-coded; attack animations may cause motion sickness] | [Rapid input required for combos; hold inputs for guard] | [Track enemy patterns + cooldowns + player resources simultaneously] | [Audio cues for off-screen attacks; critical damage warning sounds] | [Partial] | [Colorblind palette applied; hold-to-block toggle needed] | +| [Inventory / Equipment] | [Item rarity conveyed by border color] | [No motor concerns — turn-based] | [Item stats comparison requires reading multiple values] | [None — no critical audio in this system] | [Partial] | [Non-color rarity indicators in progress] | +| [Dialogue System] | [Subtitle display depends on contrast settings] | [No motor concerns] | [Long dialogue trees with time pressure on dialogue choices] | [All dialogue must be subtitled] | [Not Started] | [Timed dialogue choices must support extended timer option] | +| [Navigation / World Map] | [Map marker colors] | [No motor concerns] | [Quest objective clarity; waypoint visibility] | [Audio pings for objectives have no visual equivalent] | [Not Started] | | + +--- + +## Accessibility Test Plan + +> **Why testing accessibility separately from QA**: Standard QA tests whether +> features work. Accessibility testing tests whether features work for players +> who use them. These are different tests. A subtitle system can pass QA (it +> displays text) and fail accessibility testing (the text is unreadable at TV +> distance by a player with low vision). Plan for three test types: automated +> (contrast ratios, text sizes), manual internal (team members simulating +> impairments using accessibility simulators), and user testing (players who +> actually use these features). + +--- + +## Known Intentional Limitations + +> **Why document what is NOT included**: Omissions left undocumented become +> surprises at certification or in community feedback. Documenting a limitation +> with a rationale demonstrates that it was a deliberate choice, not an oversight. +> It also identifies which players are not served and what the mitigation is. +> Every entry here is a risk — assess it honestly. + +**Worked example rows**: + +| Feature | Tier Required | Why Not Included | Risk / Impact | Mitigation | +|---------|--------------|-----------------|--------------|------------| +| [Screen reader support for in-game world (NPCs, objects, environmental text)] | Exemplary | Engine (Godot 4.6) AccessKit integration covers menus only; extending to the game world requires a custom spatial audio description system beyond current scope | Affects blind and low-vision players who can navigate menus but cannot independently explore the game world | Ensure all critical world information is duplicated in accessible menu systems (quest log, map); evaluate for post-launch DLC | +| [Full subtitle customization (font/color/background)] | Comprehensive | Scope reduction — targeting Standard tier. Custom font rendering in Godot requires additional asset pipeline work | Affects deaf and hard-of-hearing players with specific legibility needs; particularly affects players with dyslexia who use custom fonts | Provide two preset subtitle styles (default and high-readability) as a partial mitigation; log for post-launch update | +| [Tactile/haptic alternatives for all audio cues] | Exemplary | Platform rumble API integration for non-Xbox platforms is out of scope for v1.0 | Affects deaf players relying on haptic feedback; PC players with non-Xbox controllers get no haptic response | Xbox controller haptic integration is in scope; evaluate PlayStation DualSense haptic API for a post-launch patch | + +--- + +## Audit History + +> **Why track audit history**: Accessibility is not certified once and done. +> Platform requirements change. New features may introduce new barriers. Legal +> standards evolve. An audit history demonstrates due diligence and helps identify +> regressions between audits. + +**Worked example rows**: + +| Date | Auditor | Type | Scope | Findings Summary | Status | +|------|---------|------|-------|-----------------|--------| +| [Date] | [Internal — ux-designer] | Internal review | [Pre-submission checklist against committed tier] | [e.g., "12 items verified, 3 open issues: subtitle contrast below target in 2 scenes, color-only indicator on minimap not resolved"] | [In Progress] | +| [Date] | [External — AbleGamers Player Panel] | User testing | [Motor accessibility — one-hand mode and timing adjustments] | [e.g., "Toggle modes functional. Timed QTE window at 3x still failed for one participant — recommend 5x option."] | [Findings addressed] | + +--- + +## Open Questions + +**Worked example questions**: + +| Question | Owner | Deadline | Resolution | +|----------|-------|----------|-----------| +| [Does Godot 4.6 AccessKit support dynamic accessibility node updates for HUD elements, or only static menus?] | [ux-designer] | [Before Technical Setup gate] | [Unresolved — check engine-reference/godot/ docs] | +| [What is the Xbox ID@Xbox minimum XAG compliance requirement for our release window?] | [producer] | [Before Pre-Production gate] | [Unresolved] | +| [Will the dialogue system support timed choice extensions without a full architecture change?] | [lead-programmer] | [During Technical Design] | [Unresolved] | diff --git a/.claude/docs/templates/guidance/hud-design-guide.md b/.claude/docs/templates/guidance/hud-design-guide.md new file mode 100644 index 00000000..f995709d --- /dev/null +++ b/.claude/docs/templates/guidance/hud-design-guide.md @@ -0,0 +1,380 @@ +> Authoring guidance for hud-design.md. Load only the part covering the section currently being authored — not the whole file at once. + +# HUD Design Template — Authoring Guidance + +Guidance is organized by template section number. When authoring a section of a +project's HUD design document, read only that section's guidance below. + +--- + +## Section 1 — HUD Philosophy + +> **Why this section exists**: The HUD design philosophy is not decoration — it is a +> design constraint that every subsequent decision is measured against. Without a +> philosophy, individual elements get added on request ("the quest tracker wants a +> bigger icon") without any principled way to push back. With a philosophy, there is +> a shared, explicit standard. More importantly, the philosophy prevents the HUD from +> slowly growing to cover the game world while each individual addition seemed +> reasonable in isolation. Write this before specifying any elements. + +**Authoring the philosophy statement**: + +[One paragraph. This is a design statement, not a description of features. Consider +the game's genre, pacing, and player fantasy. A stealth game's HUD philosophy might +be: "The world is the interface. If the player has to look away from the environment +to survive, the HUD has failed." A tactics game might say: "Complete situational +awareness is the game. The HUD is not an overlay — it is the battlefield." + +Reference comparable games if helpful, but describe your specific stance: +Example — diegetic-first action RPG: "We treat screen information as a concession, +not a feature. Every HUD element must earn its pixel space by answering the question: +would the player make demonstrably worse decisions without this information visible? +If the answer is 'they'd adapt,' we put it in the environment instead."] + +**Authoring the visibility principle**: + +[State the default resolution for ambiguous cases. Options: +- Default to HIDE: information is available on demand (e.g., Dark Souls — no quest tracker, no minimap, stats are in a menu) +- Default to SHOW: players prefer to be informed; cluttered is better than uncertain +- Default to CONTEXTUAL: information appears when it becomes relevant and fades when it does not +Most games benefit from contextual defaults. State your game's default clearly so every element decision is consistent.] + +**Authoring the Rule of Necessity**: + +[Complete this sentence: "A HUD element earns its place when ______________." + +Example: "...the player would have to stop playing to find the same information +elsewhere, or would make meaningfully worse decisions without it." + +Example: "...removing it in playtesting causes measurable frustration or confusion +in more than 25% of testers within the first hour of play." + +This rule is the veto power over feature requests to add HUD elements. Document it +so it can be cited in design reviews.] + +--- + +## Section 2 — Information Architecture + +> **Why this section exists**: Before specifying any HUD element's visual design, +> position, or behavior, you must answer a more fundamental question: should this +> information be on the HUD at all? This section is a forcing function — it requires +> you to categorize EVERY piece of information the game world generates and make an +> explicit, intentional decision about how each is presented. "We'll figure that out +> later" is how games end up with 18 elements competing for the player's peripheral +> vision. This table is the master inventory of game information, not just HUD information. + +**Worked example table** (typical information types and categorization reasoning): + +| Information Type | Always Show | Contextual (show when relevant) | On Demand (menu/button) | Hidden (environmental / diegetic) | Reasoning | +|-----------------|-------------|--------------------------------|------------------------|----------------------------------|-----------| +| [Health / Vitality] | [X if action game — player needs constant awareness] | [X if exploration game — show only when injured] | [ ] | [ ] | [Example: always visible because health decisions (retreat, heal) must be instant in combat] | +| [Primary resource (mana / stamina / ammo)] | [ ] | [X — show when resource is being consumed or is critically low] | [ ] | [ ] | [Example: contextual because stable resource levels are not decision-relevant] | +| [Secondary resource (currency / materials)] | [ ] | [ ] | [X — check in inventory] | [ ] | [Example: on-demand because resource totals don't affect immediate gameplay decisions] | +| [Minimap / Compass] | [X] | [ ] | [ ] | [ ] | [Example: always visible because navigation decisions are constant during exploration] | +| [Quest objective] | [ ] | [X — show when objective changes or player is near it] | [ ] | [ ] | [Example: contextual — player knows their objective; only remind at key moments] | +| [Enemy health bar] | [ ] | [X — show only during combat encounters] | [ ] | [ ] | [Example: contextual because enemy health is irrelevant outside combat] | +| [Status effects (buffs/debuffs)] | [ ] | [X — show when active] | [ ] | [ ] | [Example: contextual because status effects only affect decisions when present] | +| [Dialogue subtitles] | [X when dialogue is playing] | [ ] | [ ] | [ ] | [Example: always show while dialogue is active — accessibility requirement] | +| [Combo / streak counter] | [ ] | [X — show while combo is active, hide on reset] | [ ] | [ ] | [Example: contextual because it communicates active performance, not baseline state] | +| [Timer] | [ ] | [X — show only in timed sequences] | [ ] | [ ] | [Example: contextual because timers only exist in specific encounter types] | +| [Tutorial prompts] | [ ] | [X — show for first-time situations only] | [ ] | [ ] | [Example: contextual and one-time; never repeat to experienced players] | +| [Score / points] | [ ] | [X — show in score-relevant modes only] | [ ] | [ ] | [Example: contextual by game mode; hidden in modes where score is irrelevant] | +| [XP / level progress] | [ ] | [ ] | [X — available via character screen] | [ ] | [Example: on-demand because progression does not affect in-moment gameplay decisions] | +| [Waypoint / objective marker] | [ ] | [X — show when player is navigating to objective] | [ ] | [ ] | [Example: contextual — suppress during cutscenes, cinematic moments, and free exploration] | + +--- + +## Section 3 — Layout Zones + +> **Why this section exists**: The game world is the primary content — the HUD is a +> frame around it. Before placing any element, divide the screen into named zones +> with explicit positions and safe zone margins. This section prevents two failure +> modes: (1) elements placed ad-hoc until the screen is cluttered, and (2) elements +> that overlap platform-required safe zones and get rejected in certification. +> Every element in Section 4 must be assigned to a zone defined here. + +**Worked example — zone specification table**: + +| Zone Name | Screen Position | Safe Zone Compliant | Primary Elements | Max Simultaneous Elements | Notes | +|-----------|----------------|---------------------|-----------------|--------------------------|-------| +| [Top Left] | [Top-left corner, within safe margin] | [Yes — 10% from top, 10% from left] | [Health bar, stamina bar, shield bar] | [3] | [Vital status — player's own resources. Priority zone for player state.] | +| [Top Center] | [Top edge, centered horizontally] | [Yes — 10% from top] | [Quest objective, area name (on enter)] | [1 — only one message at a time] | [Use for narrative context, not mechanical information. Keep text minimal.] | +| [Top Right] | [Top-right corner, within safe margin] | [Yes — 10% from top, 10% from right] | [Ammo count, ability cooldowns] | [2] | [Weapon/ability state. Most relevant during active combat.] | +| [Center] | [Screen center ±15%] | [N/A — not a margin zone] | [Crosshair, interaction prompt, hit marker] | [1 active at a time] | [CRITICAL: Nothing persistent here. Only momentary indicators.] | +| [Bottom Left] | [Bottom-left corner, within safe margin] | [Yes — 10% from bottom, 10% from left] | [Minimap, ability icons] | [2] | [Navigation and ability readout. Small, non-intrusive.] | +| [Bottom Center] | [Bottom edge, centered horizontally] | [Yes — 10% from bottom] | [Subtitles, tutorial prompts] | [2 — subtitle + tutorial may coexist] | [Highest-priority accessibility zone. Never place other elements here.] | +| [Bottom Right] | [Bottom-right corner, within safe margin] | [Yes — 10% from bottom, 10% from right] | [Notification toasts, pick-up feedback] | [3 stacked] | [Transient notifications. Stack vertically. Oldest disappears first.] | + +**Worked example — safe zone margins by platform**: + +| Platform | Top | Bottom | Left | Right | Notes | +|----------|-----|--------|------|-------|-------| +| [PC — windowed] | [0% — no safe zone required] | [0%] | [0%] | [0%] | [But respect minimum resolution — elements must not crowd at 1280x720] | +| [PC — fullscreen] | [3%] | [3%] | [3%] | [3%] | [Slight margin for 4K TV-connected PCs] | +| [Console — TV] | [10%] | [10%] | [10%] | [10%] | [Action-safe zone for broadcast-spec TVs. Some TVs overscan beyond this.] | +| [Steam Deck] | [5%] | [5%] | [5%] | [5%] | [Small screen; safe zone is smaller but crowding risk is higher] | +| [Mobile — portrait] | [15% top] | [10% bottom] | [5%] | [5%] | [15% top avoids notch/camera cutout on most devices] | +| [Mobile — landscape] | [5%] | [5%] | [15% left] | [15% right] | [Thumb placement on landscape — side zones are obscured by hands] | + +--- + +## Section 4 — HUD Element Specifications + +> **Why this section exists**: Each HUD element needs its own specification to be +> built correctly. Ad-hoc implementation of HUD elements produces inconsistent +> sizing, mismatched update frequencies, missing urgency states, and accessibility +> failures. This section is the implementation brief for every element — fill it +> completely before any element moves into development. + +**Worked example — element overview table (4.1)**: + +| Element Name | Zone | Always Visible | Visibility Trigger | Data Source | Update Frequency | Max Size (% screen W) | Min Readable Size | Overlap Priority | Accessibility Alt | +|-------------|------|---------------|-------------------|-------------|-----------------|----------------------|------------------|-----------------|------------------| +| [Health Bar] | [Top Left] | [Yes] | [N/A] | [PlayerStats] | [On value change] | [20%] | [120px wide] | [1 — highest] | [Numerical text label showing current/max: "80/100"] | +| [Stamina Bar] | [Top Left] | [No — context] | [Show when consuming stamina; hide 3s after full] | [PlayerStats] | [Realtime during use] | [15%] | [80px wide] | [2] | [Numerical label, or hide if full (accessible assumption)] | +| [Shield Indicator] | [Top Left] | [No — context] | [Show when shield is active or recently hit] | [PlayerStats] | [On value change] | [20%] | [120px wide] | [3] | [Numerical label. Must not use color alone — add shield icon.] | +| [Ammo Counter] | [Top Right] | [No — context] | [Show when weapon is equipped; hide when unarmed] | [WeaponSystem] | [On fire / on reload] | [10%] | ["88/888" readable at game's min resolution] | [4] | [Text-only fallback: "32 / 120"] | +| [Minimap] | [Bottom Left] | [Yes] | [N/A — but suppressed in cinematic mode] | [NavigationSystem] | [Realtime] | [18%] | [150x150px] | [5] | [Cardinal direction compass strip as fallback; must be toggleable] | +| [Quest Objective] | [Top Center] | [No — context] | [Show on objective change; show when near objective location; hide after 5s] | [QuestSystem] | [On event] | [30%] | [Legible at body text size] | [6] | [Read aloud on objective change via screen reader] | +| [Crosshair] | [Center] | [No — context] | [Show when ranged weapon equipped; hide in melee or unarmed] | [WeaponSystem / AimSystem] | [Realtime] | [3%] | [12px diameter minimum] | [1 — center zone priority] | [Reduce motion: static crosshair only. Option to enlarge.] | +| [Interaction Prompt] | [Center] | [No — context] | [Show when player is within interaction range of an interactive object] | [InteractionSystem] | [On enter/exit interaction range] | [15%] | [24px icon + readable text] | [2 — center zone] | [Text description of interaction always present, not icon-only] | +| [Subtitles] | [Bottom Center] | [No — always on when dialogue plays, if setting enabled] | [Show during any voiced line or ambient dialogue] | [DialogueSystem] | [Per dialogue line] | [60%] | [Minimum 24px font] | [1 — highest in zone] | [This IS the accessibility feature — see Section 8 for subtitle spec] | +| [Damage Numbers] | [World-space / anchored to entity] | [No — context] | [Show on any damage event; duration 800ms] | [CombatSystem] | [On event] | [5% per number] | [18px minimum] | [3] | [Option to disable; numbers can overwhelm for photosensitive players] | +| [Status Effect Icons] | [Top Left — below health bar] | [No — context] | [Show when any status effect is active on player] | [StatusSystem] | [On effect add/remove] | [3% per icon] | [24px per icon] | [3] | [Icon + text label on hover/focus. Never icon-only.] | +| [Notification Toast] | [Bottom Right] | [No — event-driven] | [On loot, XP gain, achievement, quest update] | [Multiple — see Section 6] | [On event] | [25%] | [Legible at body text size] | [7 — lowest] | [Queued; never overlapping. Read by screen reader if subtitle mode on.] | + +**Worked example — element detail blocks (4.2)**: + +--- + +**Health Bar** + +- Visual description: [Horizontal fill bar. Left-to-right fill direction. Segmented at 25/50/75% to aid reading at a glance. Background: dark semi-transparent (40% opacity). Fill color: context-dependent — see Urgency States.] +- Data displayed: [Current HP as fill percentage. Numerical value displayed as text below bar at all times: "80 / 100".] +- Update behavior: [Bar fill decreases or increases smoothly using a lerp over 150ms per change. Large damage (>25% single hit) triggers a brief flash (1 frame white, then drain).] +- Urgency states: + - Normal (>50% HP): [Green fill, no special behavior] + - Caution (25–50% HP): [Yellow fill, low warning pulse every 4 seconds] + - Critical (<25% HP): [Red fill, persistent slow pulse (1 Hz), vignette appears at screen edges] + - Zero (0% HP): [Bar empties and turns grey; death state begins] +- Interaction: [Display only. Not interactive. Player cannot click, hover, or focus this element as an action target.] +- Player customization: [Opacity adjustable (see Section 7 Tuning Knobs). Can be repositioned to any corner by player in accessibility settings.] + +--- + +**Minimap** + +- Visual description: [Circular mask, radius = 75px at reference resolution 1920x1080. Player icon at center. North always up unless player has unlocked "Rotate minimap" setting. Range = configurable, default 80 world units radius.] +- Data displayed: [Player position, nearby enemies (if detection perk unlocked), quest markers within range, points of interest icons, traversal obstacles (walls, drops).] +- Update behavior: [Realtime. Updates every frame. Enemy icons fade in/out as they enter/leave detection range over 300ms.] +- Urgency states: [None for the map itself. Enemy icons turn red when they are in combat-alert state.] +- Interaction: [Not interactive in-game. Press dedicated Map button to open the full map screen (separate UX spec).] +- Player customization: [Size: S/M/L (70/90/110px radius). Opacity: 30–100%. Rotation: locked-north or player-relative. Can be disabled entirely (compass strip shows as fallback).] + +--- + +## Section 5 — HUD States by Gameplay Context + +> **Why this section exists**: The HUD is not a static overlay — it is a dynamic +> system that must adapt to what the player is doing. A HUD designed only for +> standard gameplay will look wrong in cutscenes, feel cluttered in exploration, +> and occlude critical information in boss fights. This section defines the +> transformations the HUD undergoes in each gameplay context. It is also the spec +> for the system that manages HUD visibility — the HUD state machine. + +**Worked example table**: + +| Context | Elements Shown | Elements Hidden | Elements Modified | Transition Into This State | +|---------|---------------|-----------------|------------------|---------------------------| +| [Exploration — no threats] | [Minimap, Quest Objective (faded, 60%), Subtitles (if active)] | [Ammo Counter, Crosshair, Damage Numbers, Status Effects (if none active)] | [Health Bar fades to 40% opacity — visible but not dominant] | [Fade transition, 500ms, when no enemies detected for 10s] | +| [Combat — active threat] | [Health Bar (full opacity), Stamina Bar (when used), Ammo Counter, Crosshair, Damage Numbers, Status Effects, Enemy Health Bars] | [Quest Objective (temporarily hidden), Notification Toasts (paused queue)] | [Minimap scales down 15% and raises opacity to 100%] | [Immediate snap in on first enemy detection — no fade. Combat readiness requires instant info.] | +| [Dialogue / Cutscene] | [Subtitles, Dialogue speaker name] | [All gameplay HUD elements: health, ammo, minimap, crosshair, damage numbers] | [N/A] | [All gameplay elements fade out over 300ms when cutscene flag is set] | +| [Cinematic (scripted camera sequence)] | [Subtitles only] | [Everything else including speaker name] | [Letterbox bars appear (if applicable to this game's style)] | [Immediate on cinematic flag; letterbox slides in from top/bottom over 400ms] | +| [Inventory / Menu open] | [None — inventory renders full-screen or as overlay] | [All HUD elements] | [Game world visible but paused behind inventory screen] | [All HUD elements hide over 150ms as menu opens] | +| [Death / Respawn pending] | [Death screen overlay — separate spec] | [All gameplay HUD elements] | [Screen desaturates and darkens over 800ms] | [Death state begins when HP reaches 0 — HUD elements fade over 600ms] | +| [Loading / Transition] | [Loading indicator, tip text] | [All gameplay HUD elements] | [N/A] | [Instant on level transition trigger] | +| [Tutorial — new mechanic] | [Standard context HUD + Tutorial Prompt overlay] | [Nothing additional hidden] | [Tutorial prompt dims background subtly to draw attention to prompt] | [Tutorial system fires ShowTutorial event; prompt fades in over 200ms] | +| [Boss Encounter] | [Boss health bar appears (large, bottom of screen or top center), all combat elements] | [Quest Objective] | [Boss bar renders in a distinct visual style — must not be confused with player health] | [Boss health bar slides in on boss encounter trigger over 400ms] | + +--- + +## Section 6 — Information Hierarchy + +> **Why this section exists**: Not all HUD information is equally important. When +> screen space is limited, when the player is under high stress, or when elements +> compete for the same zone, there must be a principled priority order that governs +> which elements survive and which get suppressed. This section formalizes that +> hierarchy so it can be enforced systematically and not just "feels obvious" decisions +> made at implementation time. + +**Worked example table**: + +| Element | Priority Tier | Reasoning | What Replaces It If Hidden | +|---------|--------------|-----------|---------------------------| +| [Subtitles] | [MUST KEEP — never hide during dialogue] | [Accessibility requirement. Legal requirement in some markets. Story clarity.] | [N/A — nothing replaces subtitles] | +| [Health Bar] | [MUST KEEP — during any state where the player can be damaged] | [Without health visibility, survival decisions become impossible] | [Auditory cues (heartbeat, breathing) supplement but do not replace] | +| [Crosshair] | [MUST KEEP — while aiming with a ranged weapon] | [Targeting without a crosshair is a precision failure, not a difficulty feature] | [Alternative: dot-only mode for minimalists; never fully hidden while aiming] | +| [Interaction Prompt] | [MUST KEEP — when player is in interaction range] | [Without it, interactive objects are invisible to the player] | [Environmental visual cues can supplement but interaction affordance must be explicit] | +| [Ammo Counter] | [SHOULD KEEP] | [Low ammo decisions (switch weapon, reload) require awareness; can be contextual] | [Auditory "click" on empty chamber is acceptable fallback for experienced players] | +| [Minimap] | [SHOULD KEEP] | [Navigation requires spatial awareness; loss forces repeated map opens] | [Compass strip (simplified directional indicator) is acceptable fallback] | +| [Status Effects] | [SHOULD KEEP — while active] | [Active debuffs change what actions are viable; invisible debuffs feel unfair] | [Character animation states can partially communicate status effects (limping, sparks)] | +| [Quest Objective] | [CAN HIDE] | [Player can hold objective in memory for extended periods; contextual is correct default] | [Player remembers objective from context] | +| [Damage Numbers] | [CAN HIDE] | [Feedback element, not decision-critical. Many players turn these off.] | [Hit sounds and enemy reactions communicate hit registration] | +| [Notification Toasts] | [CAN HIDE in high-intensity moments] | [Mid-combat "You gained 50 XP" is noise, not signal. Queue and show after combat.] | [Queue held and released when combat ends] | +| [Combo Counter] | [ALWAYS HIDE when combo resets or player is not attacking] | [Stale combo information is actively misleading] | [N/A — simply hidden] | + +--- + +## Section 7 — Visual Budget + +> **Why this section exists**: Without explicit budget constraints, HUD elements +> accumulate until the game world is nearly invisible. These numbers are hard limits, +> not guidelines. Every element addition that would breach a limit requires explicit +> approval and must displace or reduce an existing element. + +> **How to apply these budgets**: For every new HUD element proposed during +> production, require the proposer to state (1) which budget line it affects, +> (2) what the new total will be, and (3) what existing element will be reduced or +> made contextual to stay within budget. "It's a small icon" is not an analysis. + +--- + +## Section 8 — Feedback & Notification Systems + +> **Why this section exists**: Notifications are the most frequently-added and +> worst-controlled part of most HUDs. Every system wants to tell the player +> something. Without explicit rules about notification priority, stacking limits, +> and queue behavior, the notification zone becomes a firehose of overlapping +> toasts that players learn to ignore entirely. This section establishes the +> notification contract for all systems. + +**Worked example table**: + +| Notification Type | Trigger System | Screen Position | Duration (ms) | Animation In / Out | Max Simultaneous | Priority | Queue Behavior | Dismissible? | +|------------------|---------------|-----------------|--------------|-------------------|-----------------|----------|---------------|-------------| +| [Item Pickup] | [InventorySystem] | [Bottom Right — toast] | [2000] | [Slide in from right 200ms / fade out 300ms] | [3 stacked] | [Low] | [FIFO queue; older toasts pushed up as new ones enter] | [No — auto-dismiss] | +| [XP Gain] | [ProgressionSystem] | [Bottom Right — toast, below item toasts] | [1500] | [Fade in 150ms / fade out 300ms] | [1 — XP messages merge: "XP +150"] | [Very Low — suppress during combat, queue for post-combat] | [Combat-aware queue] | [No] | +| [Level Up] | [ProgressionSystem] | [Center screen — persistent until dismissed] | [Persistent — requires input to dismiss] | [Scale up from 80% + fade in 400ms] | [1] | [High — interrupts normal toasts] | [Pauses all other notifications until dismissed] | [Yes — any input] | +| [Quest Update] | [QuestSystem] | [Top Center] | [4000] | [Slide down from top 250ms / fade out 400ms] | [1 — top center is single-message zone] | [Medium] | [If quest update arrives while previous is visible, extend duration by 2000ms; do not stack] | [No] | +| [Objective Complete] | [QuestSystem] | [Top Center] | [3000] | [Same as Quest Update but with additional completion sound] | [1] | [Medium-High — preempts Quest Update] | [Preempts any queued top-center message] | [No] | +| [Critical Warning (low health, hazard)] | [CombatSystem / EnvironmentSystem] | [Screen edge vignette + text at center-bottom] | [Persistent while condition active] | [Fade in 200ms; fades out 500ms when condition clears] | [1 per warning type] | [Critical — never suppressed] | [Renders immediately, bypasses all queues] | [No] | +| [Achievement Unlocked] | [AchievementSystem] | [Bottom Right — distinct from item toasts] | [4000] | [Slide in from right with icon expansion 300ms / fade out 400ms] | [1] | [Low] | [Queues behind item toasts; never more than one achievement toast at a time] | [No] | +| [Hint / Tutorial] | [TutorialSystem] | [Bottom Center] | [Persistent — until player performs the action or dismisses] | [Fade in 300ms] | [1] | [Medium] | [Only one tutorial hint at a time; queue others] | [Yes — B button / Esc] | + +**Worked example — notification queue rules**: +1. Combat-aware queue: notifications tagged as Low priority are queued, not displayed, when the player is in combat state. The queue is flushed in a batch when the player exits combat, with a max of 3 items displayed in sequence. +2. Merge rule: identical notification types that fire within 500ms of each other are merged into a single notification with a combined value (e.g., "Item Pickup x3" rather than three separate toasts). +3. Critical notifications (health warning, environmental hazard) are never queued, never merged, and always displayed immediately regardless of combat state or existing notifications. + +--- + +## Section 9 — Platform Adaptation + +> **Why this section exists**: A HUD designed at 1920x1080 on a monitor may be +> illegible on a 55-inch TV at 4K, broken at 1280x720 on Steam Deck, or hidden +> behind a notch on mobile. Platform adaptation is not optional post-ship work — +> it is a design requirement that must be specified before implementation so the +> architecture can support it from the start. Every platform listed here requires +> explicit layout testing before certification. + +**Worked example table**: + +| Platform | Safe Zone | Resolution Range | Input Method | HUD-Specific Notes | +|----------|-----------|-----------------|-------------|-------------------| +| [PC — Windows, 1920x1080 reference] | [3% margin] | [1280x720 min to 3840x2160 max] | [Mouse + keyboard, controller optional] | [HUD must scale correctly at all resolutions. Test at 1280x720 — minimum before cert. Consider ultrawide (21:9) — minimap must not stretch.] | +| [PC — Steam Deck, 1280x800] | [5% margin] | [Fixed 1280x800] | [Controller + touchscreen] | [Smaller screen means minimum text sizes are critical. Test ALL elements at this resolution. Touch targets irrelevant (controller-only by default).] | +| [PlayStation 5 / Xbox Series X] | [10% margin] | [1080p to 4K] | [Controller] | [Console certification requires TV safe zone compliance. Action-safe is 90% of screen area. Test on a real TV, not a monitor — overscan behavior differs.] | +| [Mobile — iOS / Android] | [15% top, 10% other sides] | [360x640 min to 414x896 common] | [Touch] | [Notch/camera cutout avoidance at top. Bottom home indicator zone avoidance. Portrait and landscape layouts may differ significantly — specify both.] | + +--- + +## Section 10 — Accessibility (HUD Specific) + +> **Why this section exists**: HUD accessibility failures are the most visible +> accessibility failures in games — players encounter the HUD in every session, +> in every gameplay moment. Color-blind failures, illegible text at minimum scale, +> and inability to disable distracting animations are among the top accessibility +> complaints in game reviews. This section defines HUD-specific requirements; refer +> to the project's `design/accessibility-requirements.md` for the full project standard. + +**Worked example — colorblind modes table (10.1)**: + +| Element | Color-Only Information Risk | Colorblind Mode Fix | +|---------|----------------------------|---------------------| +| [Health bar fill] | [Red = low health uses red/green distinction] | [Add icon pulse + vignette as non-color indicators. Red fill is supplemental, not sole indicator.] | +| [Damage numbers] | [Red = taken, green = healed] | [Add minus (-) prefix for damage, plus (+) for healing. Symbols, not color.] | +| [Enemy health bars] | [If colored by faction or threat level] | [Add text label or icon badge for faction/threat level. Never color-only.] | +| [Status effect icons] | [If icon tint communicates status type] | [All status icons must have distinct shapes, not just distinct colors. Shape encodes meaning; color is secondary.] | +| [Minimap icons] | [If player vs. enemy vs. objective distinguished by color] | [Distinct icon shapes: circle = player, triangle = enemy, star = objective. Color supplements shape.] | + +**Authoring the text scaling narrative (10.2)**: + +[Describe what happens when the player sets the UI text scale to 150% (the maximum required for your Accessibility Tier). Which elements reflow? Which elements clip? Which elements are architecturally blocked from scaling (e.g., fixed-size canvases)? + +Example: "Health bar numerical label grows with text scale — bar expands slightly to accommodate. Quest objective text wraps at 150% scale — verify Top Center zone can accommodate two-line objectives. Damage numbers do not scale (they are world-space, not screen-space) — this is an accepted limitation documented here."] + +**Worked example — text scaling test matrix (10.2)**: + +| Element | 100% (baseline) | 125% | 150% | Overflow behavior | +|---------|----------------|------|------|-------------------| +| [Health bar label] | [Pass] | [Pass] | [TBD] | [Bar expands; does not overlap stamina bar] | +| [Quest objective text] | [Pass] | [TBD] | [TBD] | [Wraps to second line; zone height expands] | +| [Notification toast text] | [Pass] | [TBD] | [TBD] | [Toast width expands to max 35% screen width, then wraps] | +| [Subtitle text] | [Pass] | [TBD] | [TBD] | [Dedicated subtitle zone — must accommodate scale] | + +**Worked example — motion sensitivity table (10.3)**: + +| Animation / Motion Element | Severity | Disabled by Reduced Motion Setting? | Replacement Behavior | +|---------------------------|----------|-------------------------------------|---------------------| +| [Health bar low-HP pulse] | [Mild] | [Yes] | [Solid fill, no pulse. Vignette remains as it is less likely to trigger sensitivity.] | +| [Screen edge vignette] | [Moderate] | [Optional — separate toggle] | [Replace with static darkened corners at 30% opacity] | +| [Damage numbers float upward] | [Mild] | [Yes] | [Instant appear/disappear in place, no float] | +| [Notification toast slide-in] | [Mild] | [Yes] | [Instant appear at final position] | +| [Level up center animation] | [High] | [Yes — required] | [Static level up card, no scale animation, no particle effects] | +| [Combo counter scale pulse] | [Mild] | [Yes] | [Number increments without scale animation] | + +--- + +## Section 11 — Tuning Knobs + +> **Why this section exists**: HUD behavior should be data-driven to the same degree +> as gameplay systems. Values that are hardcoded are values that require an engineer +> to change. Values that are in config can be tuned by a designer or adjusted for +> player preferences. Document all tunable parameters before implementation so the +> programmer knows which values to externalize. + +**Worked example table**: + +| Parameter | Current Value | Range | Effect of Increase | Effect of Decrease | Player Adjustable? | Notes | +|-----------|-------------|-------|-------------------|-------------------|-------------------|-------| +| [Notification display duration (default)] | [2000ms] | [500ms – 5000ms] | [Toasts persist longer — less likely to be missed, more screen clutter] | [Toasts disappear faster — cleaner, higher miss risk] | [No — but player can adjust verbosity level] | [Per-type overrides in Section 8 take precedence] | +| [Notification queue max size] | [8] | [3 – 15] | [More messages preserved but queue takes longer to clear] | [Older messages dropped earlier] | [No] | [Expand if playtesting reveals important messages being lost] | +| [Health bar low-HP pulse frequency] | [1 Hz] | [0.5 – 2 Hz] | [More urgent feeling — can become fatiguing] | [Calmer — may fail to communicate urgency] | [No — but Reduced Motion disables it] | [Linked to accessibility setting] | +| [Combat HUD reveal duration] | [0ms (instant)] | [0 – 300ms] | [Softer reveal — feels less jarring] | [Instant — highest responsiveness] | [No] | [Keep at 0ms — combat information must be instant] | +| [Exploration HUD fade-out delay] | [10000ms (10s after last threat)] | [3000 – 30000ms] | [HUD fades sooner — cleaner exploration] | [HUD stays longer — more reassurance] | [No] | [Tune based on playtest; 10s is a starting estimate] | +| [Minimap range (world units visible)] | [80] | [40 – 200] | [More map context visible] | [Tighter local view] | [Yes — Small/Medium/Large preset] | [Exposed as S/M/L, not raw unit value] | +| [Minimap size (px radius at 1080p)] | [75] | [50 – 120] | [Larger map, more screen space consumed] | [Smaller, less intrusive] | [Yes — S/M/L preset] | [Three sizes exposed to player] | +| [Damage number duration (ms)] | [800] | [400 – 1500] | [Numbers linger longer — easier to read, more cluttered] | [Numbers clear faster — cleaner, harder to parse] | [No] | [Tune based on visual noise in dense combat] | +| [Global HUD opacity] | [100%] | [0 – 100%] | [Fully visible] | [Fully hidden] | [Yes — opacity slider in Accessibility settings] | [0% = full HUD off; some players prefer this] | + +--- + +## Section 12 — Acceptance Criteria + +> **Why this section exists**: These criteria are the certification checklist for the +> HUD. Every item must pass before the HUD can be marked Approved. QA must be able +> to verify each item independently. + +--- + +## Section 13 — Open Questions + +**Worked example questions**: + +| Question | Owner | Deadline | Resolution | +|----------|-------|----------|-----------| +| [e.g., Should the minimap show enemy positions by default, or only after a detection skill is unlocked?] | [systems-designer + ui-designer] | [Sprint 5, Day 2] | [Pending — depends on progression GDD decision] | +| [e.g., Does the game have a boss health bar, or do bosses use the standard enemy health bar? Bosses need a visually distinct treatment if they are significantly more important than normal enemies.] | [game-designer] | [Sprint 5, Day 1] | [Pending] | +| [e.g., Damage numbers: diegetic (floating in world space, occluded by geometry) or screen space (always readable, overlaid on HUD layer)?] | [ui-designer + lead-programmer] | [Sprint 4, Day 5] | [Pending — architecture decision affects rendering layer choice] | +| [e.g., Mobile portrait vs. landscape: does the game support both orientations? If yes, each requires its own zone layout.] | [producer] | [Sprint 3, Day 3] | [Pending — platform scope decision required first] | diff --git a/.claude/docs/templates/guidance/interaction-pattern-library-guide-game-specific.md b/.claude/docs/templates/guidance/interaction-pattern-library-guide-game-specific.md new file mode 100644 index 00000000..8b3c3214 --- /dev/null +++ b/.claude/docs/templates/guidance/interaction-pattern-library-guide-game-specific.md @@ -0,0 +1,175 @@ +> Authoring guidance for interaction-pattern-library.md. Load only the part covering the section currently being authored — not the whole file at once. + +> Covers: **Game-Specific UI Patterns** — full reference specifications for Inventory Slot, Ability / Skill Icon, Health / Resource Bar, Dialogue Box, Context Action Prompt, Damage Number. + +## Game-Specific UI Patterns + +--- + +#### Inventory Slot + +**Category**: Game-Specific +**Status**: Draft +**When to Use**: Every item container in the inventory grid. Empty slots, populated +slots, equipped slots, locked slots. The slot is the frame; the item icon is the +content. + +**States**: + +| State | Visual | Notes | +|-------|--------|-------| +| Empty | Subtle slot border, no content. Not the same as disabled. Empty slots are interactable (receive items). | Avoid fully invisible empty slots — players lose track of grid dimensions | +| Populated | Item icon fills 80% of slot area. Stack count bottom-right (if applicable). Quality border (colorblind-safe — icon + color). Equipped badge (top-right, if equipped). | | +| Focused | Focus ring. Tooltip appears after 300ms. | | +| Selected | Thicker or contrasting border. Used when multi-select is supported. | | +| Drag source | Slot dims, drag ghost follows pointer. | See Grid Item for full drag spec | +| Locked | Padlock icon overlay. No interaction. May show item at 50% opacity behind lock. | Used for locked loadout slots, DLC content, etc. | +| Highlighted | Animated border glow (pulsing). Used for quest-relevant items or newly acquired items. | Respect motion reduction — replace pulse with a static badge | +| Cooldown overlay | Radial fill overlay from 12 o'clock, clockwise, depleting as cooldown expires. | Only applicable if slots represent active items with cooldowns | + +**Accessibility**: Stack counts and quality tiers must have text or icon alternatives to color coding. Tooltip is the primary accessibility mechanism — ensure it is reachable by keyboard and screen reader. Locked slots must announce "locked" to screen readers. + +**Implementation Notes**: [Godot: Custom `Control` node. Quality border implemented as a `StyleBoxFlat` swapped based on rarity — avoid using `modulate` color for quality, as it affects the icon color. Drag and drop implemented via `get_drag_data()` and `can_drop_data()` / `drop_data()` override methods.] + +--- + +#### Ability / Skill Icon + +**Category**: Game-Specific +**Status**: Draft +**When to Use**: Ability buttons in the HUD ability bar, skill tree nodes, and +any context where an ability must show availability state. + +**States**: + +| State | Visual | Notes | +|-------|--------|-------| +| Available | Full opacity icon. Keybinding label below. | | +| On cooldown | Radial overlay depleting clockwise from 12 o'clock. Remaining time shown as a number in the center when > 2 seconds remain. | | +| Charges remaining | Charge pip indicators below icon (e.g., 3 filled circles = 3 charges). Number alternative for screen readers. | | +| Out of resource | Icon desaturates to ~20%. Border dims. Keybinding label dims. Distinct from cooldown — resource-gated, not time-gated. | | +| Locked / not unlocked | Icon silhouette only (no full art visible). Padlock badge. May show unlock condition in tooltip. | | +| Active / channeling | Pulsing border. Radial fill shows channel duration remaining. | | +| Just activated | Brief scale 0.9x then spring to 1.0x (overshoot to 1.05x). | Example: Guild Wars 2 and Path of Exile both use press-depress animations on ability use to confirm activation. Respect motion reduction. | + +**Accessibility**: All cooldown/charge information must have a numeric value (screen reader cannot parse radial overlays). The cooldown timer number satisfies this. Ability names and descriptions must be exposed to screen readers via tooltip. + +**Implementation Notes**: [Godot: Custom `TextureButton` subclass with overlay +`Control` nodes for cooldown radial and charge pips. The cooldown radial uses a +custom shader on a `ColorRect` rotating a mask — or implement with a +`ProgressBar` styled as circular if engine supports it. Verify against +engine-reference/godot/ for Godot 4.6 shader support for this pattern.] + +--- + +#### Health / Resource Bar + +**Category**: Game-Specific +**Status**: Draft +**When to Use**: Any continuously varying value in the HUD that represents a +critical player resource. Health, mana, stamina, shield, fuel. + +**States and behaviors**: + +| Event | Visual | Audio | Duration | +|-------|--------|-------|---------| +| Value decrease (damage) | Fill shrinks. Brief "damage flash" on the fill (white or red flash). Ghost bar lingers at previous value and drains to new value over 0.5s ("damage indicator"). | [Damage taken sound — varies by amount] | Instant decrease, 500ms ghost bar drain | +| Value increase (heal) | Fill grows. Brief heal color flash (green — ensure colorblind safe with icon/glow backup). | [Heal sound] | 300ms ease-in | +| Below 25% threshold | Fill changes color to warning state. Border pulses (or static badge in motion reduction mode). Optional: heartbeat audio cue (paired with visual if audio is sole signal). | [Low health sound — loops until above threshold] | Continuous | +| At zero | Bar empty. Optional: bar shakes briefly. Death/depletion event fires. | [Death/depletion sound] | 200ms shake | +| Maximum | Fill at 100%, brief glow. | — | 200ms | +| Overflow (shield) | A separate bar segment appears beyond the natural fill area, in shield color. | [Shield gain sound] | 200ms | + +**Accessibility**: The current value must be accessible as a number (tooltip or persistent display, or both). Color-coded threshold states must have non-color backups (icon, flashing, or audio visual warning). Warning state at 25% must have a visual signal independent of the color change. + +**Implementation Notes**: [Godot: Two overlapping `ProgressBar` nodes for ghost +bar effect — back bar holds previous value (drains via Tween), front bar holds +current value (updates instantly). Threshold states trigger `StyleBoxFlat` swaps +on the front bar. Ghost bar Tween duration is tunable as a designer parameter.] + +--- + +#### Dialogue Box + +**Category**: Game-Specific +**Status**: Draft +**When to Use**: NPC conversation, voiced narrative dialogue, tutorial text +delivered through a character. All dialogue that has a speaker. + +**Structure**: Speaker portrait or name tag (top of box or left side). Dialogue text body. Continue/advance prompt (bottom right). Optional: skip-all button, voice acting indicator, subtitle indicator. + +**States and behaviors**: + +| State | Visual | Input | Response | Duration | +|-------|--------|-------|----------|---------| +| Line entering | Text reveals character-by-character (typewriter effect). Or: text fades in at full speed if accessibility option set. | — | — | Speed: configurable in accessibility settings | +| Revealing | Text animating in. Continue prompt hidden or pulsing at slow opacity. | [Any advance input] | Skip to end of current line instantly (show full line, stop typewriter) | Immediate | +| Line complete | Full line shown. Continue prompt visible and animated. | — | — | — | +| Advancing to next line | Continue prompt hides. Text fades out or wipes. New line begins. | [Any advance input] — Enter / A / Cross / Space / mouse click | Advance | 100ms transition | +| Choices appearing | Choice buttons appear below dialogue text. Continue prompt hidden. Navigation focus moves to first choice. | D-pad / keyboard to select, Enter / A / Cross to confirm | Select choice | 150ms enter animation | +| Closing | Box fades out | Final line advanced | Return control to player | 200ms | +| Skipping all (if supported) | Brief confirmation prompt: "Skip dialogue?" | Dedicated skip button | Skip to post-dialogue state | — | + +**Accessibility**: Subtitles are always enabled by default for all voiced dialogue. Typewriter animation speed is a user setting (see accessibility-requirements.md). The dialogue box must not auto-advance — players must control pacing. Speaker name is always shown. All choice buttons must be navigable by keyboard and gamepad. Choices must be accessible to screen readers with position announced. + +**Implementation Notes**: [Godot: `RichTextLabel` with `bbcode_enabled` for +formatting. Typewriter effect via `visible_characters` property animated by a +`Timer`. Bind the advance input to a function that either skips typewriter +(sets `visible_characters = -1`) or advances the dialogue state. Speaker name +displayed in a separate `Label` above or beside the box. Dialogue data loaded from +JSON or a dedicated dialogue format (e.g., Dialogic, Yarn Spinner for Godot).] + +--- + +#### Context Action Prompt + +**Category**: Game-Specific +**Status**: Draft +**When to Use**: A prompt that appears near an interactable game object indicating +what the player can do. "Press [A] to open chest." "Hold [E] to pick up." Appears +when the player enters the interaction zone, disappears when they leave. + +**States**: + +| State | Visual | Notes | +|-------|--------|-------| +| Appearing | Fades in and rises 8px from object anchor point. | Respect motion reduction — fade only, no rise | +| Idle | Platform-correct button icon + action label. Icon matches current input method (updates if player switches). | Always show platform-correct icon — do not hardcode "Press A" for all platforms | +| Holding (for hold inputs) | Radial fill on the button icon shows hold progress. Label changes to active verb ("Opening..."). | | +| Cannot interact (blocked) | Icon dims. Label shows reason if known ("Too heavy", "Need key"). | Optional — only show blocked state if the reason is meaningful to the player | +| Disappearing | Fades out. | Triggered when player exits interaction zone | + +**Accessibility**: The button icon must be accompanied by a text label — do not rely on icon alone (some players use custom button labels or adaptive controllers with non-standard icons). The prompt must be positioned to not overlap character health or critical HUD information. + +**Implementation Notes**: [Godot: Attach as a `Node3D` child (or `Node2D` child in 2D) of the interactable object. Use a `BillboardMesh` or a `SubViewport` with a UI scene for 3D games — this keeps the prompt facing the camera without code. Update the button icon texture based on `Input.get_joy_name()` or keyboard detection via `InputEventKey` vs `InputEventJoypadButton`. Hold progress implemented as an `AnimationPlayer` or `Tween` on a radial mask shader.] + +--- + +#### Damage Number + +**Category**: Game-Specific +**Status**: Draft +**When to Use**: Floating feedback numbers above combat participants. Normal +damage, critical damage, healing, miss. + +**Variants**: + +| Variant | Visual | Notes | +|---------|--------|-------| +| Normal damage | White number, normal weight, medium size. | | +| Critical hit | Larger size (1.5x), bold weight, orange or yellow — verify colorblind safe. Brief scale impact (1.3x → 1.0x on appear). | Example: Path of Exile and Diablo IV both use scale-pop for crits to make them immediately recognizable by size alone, independent of color. | +| Healing | Green (verify colorblind safe — use + prefix and upward trajectory as non-color backups). | | +| Miss / Evade | "MISS" text, grey, italic. Floats at smaller size. | | +| Status damage (DoT) | Smaller size, distinct color matching the status effect. | | + +**Behavior**: Numbers float upward from the hit location over 1.0 second. Numbers fade from 100% to 0% during the last 0.4 seconds. Multiple numbers from rapid hits stagger horizontally to avoid overlap. Maximum simultaneous damage numbers on screen: [define per game — typically 8-12 per character]. + +**Accessibility**: Damage numbers are purely supplementary feedback — they must never be the only way to understand combat state. Health bars are the authoritative source. Provide an option to disable damage numbers entirely (some players find them visually overwhelming). When disabled, the game must remain fully playable. + +**Implementation Notes**: [Godot: Pool of `Label3D` (3D games) or `Label` (2D games) +instances recycled via an object pool. Each instance is given a random small +horizontal offset on spawn (±20px) to reduce overlap. Float animation via +`Tween` on `position.y` and `modulate.a`. Critical hit scale-pop via Tween +with `EASE_OUT` on scale followed by linear settle.] + +--- diff --git a/.claude/docs/templates/guidance/interaction-pattern-library-guide-navigation-feedback.md b/.claude/docs/templates/guidance/interaction-pattern-library-guide-navigation-feedback.md new file mode 100644 index 00000000..00ea9d2e --- /dev/null +++ b/.claude/docs/templates/guidance/interaction-pattern-library-guide-navigation-feedback.md @@ -0,0 +1,135 @@ +> Authoring guidance for interaction-pattern-library.md. Load only the part covering the section currently being authored — not the whole file at once. + +> Covers: **Navigation Patterns** (Screen Push / Pop / Replace, Focus Management, Escape / Cancel) and **Feedback and Loading Patterns** (Loading State, Empty State, Error State) — full reference specifications. + +## Navigation Patterns + +--- + +#### Screen Push / Pop / Replace + +**Category**: Navigation +**Status**: Draft + +These three patterns define how screens enter and exit the navigation stack. + +| Pattern | Trigger | Animation | Stack Behavior | Focus Behavior | +|---------|---------|-----------|---------------|----------------| +| Push | Navigate deeper (open submenu, open detail view) | New screen slides in from right. Previous screen slides left and dims. | Previous screen remains on stack | Focus moves to first interactive element on new screen | +| Pop (Back) | Back button / Escape / B / Circle | Current screen slides right and exits. Previous screen slides in from left and brightens. | Current screen removed from stack | Focus returns to the element that triggered the Push | +| Replace | Navigate to a peer screen (not child, not parent). Loading screen. | Fade out current, fade in new. No directional bias. | Current screen removed. New screen added. | Focus moves to first interactive element on new screen | + +**Animation durations**: Push/Pop: 250ms ease-in-out. Replace: 200ms fade out + 200ms fade in. + +**Motion reduction**: All slide animations become fades. Duration reduces to 100ms. + +**Implementation Notes**: [Godot: Implement as a `ScreenManager` singleton managing +a stack of `Control` scenes. `push(screen_scene)` instantiates and animates in. +`pop()` animates out and frees. `replace(screen_scene)` calls pop then push without +the intermediate stack state. Use `CanvasLayer` per screen to isolate input handling. +Store the "return focus" element reference before pushing so it can be restored on pop.] + +--- + +#### Focus Management + +**Category**: Navigation +**Status**: Draft + +> Focus management is the most common keyboard and gamepad accessibility failure +> in game UIs. These rules must be implemented consistently. A player should +> never be in a state where they cannot see which element is focused, or where +> Tab/D-pad produces no visible result. + +| Rule | Description | +|------|-------------| +| Screen open | Focus is placed on the most logical interactive element — typically the Primary button, the first list item, or the last-focused element if the screen was previously visited. Never on a non-interactive element. | +| Screen close / pop | Focus returns to the element that triggered the navigation (the button that opened the screen, the list item that was selected). If that element no longer exists, focus goes to the nearest preceding interactive element. | +| Modal open | Focus is trapped inside the modal. See Modal Dialog pattern. | +| Modal close | Focus returns to the element that triggered the modal. | +| Element disabled | If the focused element becomes disabled, focus moves to the next available interactive element in the tab order. | +| Element destroyed | If the focused element is removed from the scene, focus moves to the nearest preceding element in the tab order. | +| Screen without interactive elements | Focus management is a no-op. Ensure back/cancel input still works. | +| Tab key (keyboard) | Moves focus forward through interactive elements in document order (left to right, top to bottom). Shift+Tab moves backward. | +| D-pad (gamepad) | Moves focus in the spatial direction pressed. Spatial navigation is preferred over strict tab order for gamepad. Never wrap focus between unrelated regions (e.g., Tab bar and content area should be separate navigation regions). | +| Focus is always visible | Focus ring or equivalent focus indicator must ALWAYS be visible when an element is focused via keyboard or gamepad. Never suppress focus indicators. | + +--- + +#### Escape / Cancel + +**Category**: Navigation +**Status**: Draft + +> The "go back" action is the most-used navigation input in all menu systems. +> It must be consistent across every screen with no exceptions. + +| Platform | Input | Behavior | +|----------|-------|---------| +| PC (keyboard) | Escape | Close top-most modal / go back one screen in stack / if at root screen (main menu), open "quit?" confirmation | +| PC (gamepad) | B (Xbox layout) / Circle (PS layout) | Same as Escape | +| Xbox | B button | Same as Escape | +| PlayStation | Circle button | Same as Escape | +| Nintendo Switch | B button | Same as Escape (NOTE: Nintendo uses B for confirm in some first-party titles — verify platform convention for this release and document the decision) | + +**Rules**: This input must never be overridden to do something other than "go back / cancel." If a screen has no back action (e.g., the game is paused and the player must make a choice), Escape does nothing or shows a "you must choose" message — it does not navigate away. Every screen must define its Escape behavior explicitly in its UX spec. + +--- + +## Feedback and Loading Patterns + +--- + +#### Loading State + +**Category**: Feedback +**Status**: Draft + +| Scope | Pattern | Notes | +|-------|---------|-------| +| Full screen (initial load) | Full-screen loading screen with game art, progress bar (determinate if possible), tip text (optional). | Never use an empty black screen. Give the player something to read or look at. | +| Full screen (level transition) | Fade to black, loading screen, fade from black to new scene. | The fade removes the pop of the previous scene disappearing. | +| Component / inline | Spinner or skeleton placeholder replaces the loading component. Component does not shift layout when content loads. | Skeleton placeholder (grey boxes approximating content shape) is preferable to spinner for layout-heavy content — it prevents layout shift on load. | +| Background / async | No visual indication unless operation exceeds 2 seconds. After 2 seconds, show a small spinner or toast. | Do not show loading indicators for operations that complete in under 2 seconds — the flash of an indicator is more disruptive than waiting. | + +**Accessibility**: Loading states must announce to screen readers: "[Context] loading, please wait." Completion must announce "[Context] loaded." For full-screen loading, ensure the loading screen itself is navigable to screen readers — the tips text and any UI elements must be exposed. + +--- + +#### Empty State + +**Category**: Feedback +**Status**: Draft + +> Empty states are consistently the least-designed parts of game UIs. They are +> the difference between a player feeling "this is where I'll store my items" +> and "why is nothing here? did something break?" Every empty list and grid must +> have a designed empty state. The empty state is not an error — it is a starting +> point. + +| Location | Empty State Content | Notes | +|----------|--------------------|----| +| Inventory (no items) | Icon (subtle, large, centered). Message: "Your inventory is empty." Sub-message: "Items you find on your journey will appear here." | Do not say "No items found" — "found" implies a failed search. | +| Quest Log (no active quests) | Icon. Message: "No active quests." Sub-message: "Talk to characters marked with [quest marker icon] to start a quest." | Give the player a clear action. | +| Achievements (none earned) | Icon. Message: "No achievements yet." List of hint achievements: "Try [Action] to earn your first achievement." | Gamified motivation, not just emptiness. | +| Search results (no matches) | Icon. Message: "No results for '[search term]'." Sub-message: "Try a different search or [browse all]." | Mirror the search term back at them. Give an alternative action. | + +**Rule**: Every empty state must include an icon, a message, and either a sub-message or an action button. A blank container with no explanation is never acceptable. + +--- + +#### Error State + +**Category**: Feedback +**Status**: Draft + +| Error Type | Pattern | Tone | +|-----------|---------|------| +| Input validation (form field) | Inline error message below the field. Error icon left of message. Red border on field (colorblind-safe with icon). | Neutral and specific — "Username must be 3-20 characters." Not "Invalid input." | +| Operation failed (save error, network error) | Toast notification for non-critical failures. Modal Dialog for critical failures (save file cannot be written). | Calm and actionable — "Save failed. Check storage space." Not "FATAL ERROR." | +| System error (crash, data corruption) | Full-screen error screen with error code, recovery options ("Restart Game," "Load last save"), and support contact. | Reassuring — acknowledge the problem, give the player agency. Never blame the player. | +| Soft error (action cannot be performed) | Toast or inline message. | Explanatory — "Not enough gold" not "Action unavailable." | + +**Principle**: Error messages are never the player's fault. They are the game telling the player what happened and what to do next. Remove the word "invalid" from all error messages — replace with specific explanations. + +--- diff --git a/.claude/docs/templates/guidance/interaction-pattern-library-guide-standard-controls.md b/.claude/docs/templates/guidance/interaction-pattern-library-guide-standard-controls.md new file mode 100644 index 00000000..6fb46286 --- /dev/null +++ b/.claude/docs/templates/guidance/interaction-pattern-library-guide-standard-controls.md @@ -0,0 +1,589 @@ +> Authoring guidance for interaction-pattern-library.md. Load only the part covering the section currently being authored — not the whole file at once. + +> Covers: **Standard Control Patterns** — full reference specifications for Button (Primary/Secondary/Destructive), Toggle, Slider, Dropdown / Select, List Item, Grid Item, Modal Dialog, Confirmation Dialog, Toast / Notification, Tooltip, Progress Bar, Input Field, Tab Bar, Scroll Container. + +## Standard Control Patterns + +--- + +#### Button (Primary) + +**Category**: Input +**Status**: Draft +**When to Use**: The single most important action on a screen. "Start Game," +"Confirm," "Accept," "Buy." There should be at most one Primary button visible +at a time. It is the answer to "what does the player most likely want to do here?" +**When NOT to Use**: Alternative or secondary actions; destructive actions that +require confirmation before the consequence is irreversible; any action that is +not the primary intent of the screen. + +**Interaction Specification**: + +| State | Visual | Input | Response | Duration | Audio | +|-------|--------|-------|----------|----------|-------| +| Default | Full-opacity fill, primary color from art-bible. Label centered. | — | — | — | — | +| Hovered (mouse) | Brightness +15%, subtle scale 1.03x, cursor changes to pointer | Mouse over element | Transition from Default | 80ms ease-out | [UI hover sound — see Sound Standards] | +| Focused (keyboard/gamepad) | Focus ring visible (2px, offset 3px, high contrast color). Same brightness as Hovered. | Tab / D-pad navigation | Transition from Default | 80ms ease-out | [UI focus sound — same as hover] | +| Pressed | Scale 0.97x, brightness -10% | Click / Enter / A (Xbox) / Cross (PS) | Action fires on press-up, not press-down. Scale on press-down. | 60ms ease-in for press; 80ms ease-out on release | [UI confirm sound] | +| Disabled | 40% opacity, no pointer cursor, no hover state | — | No response | — | — | +| Loading (post-press) | Replace label with spinner. Button remains at pressed scale, disabled state. | — | Prevents double-submission | Duration of async operation | — | + +**Accessibility**: +- Keyboard: Tab to focus, Enter or Space to activate. Must be reachable from any other interactive element on screen via Tab sequence. +- Gamepad: D-pad or left stick to navigate focus to button. A (Xbox) / Cross (PS) to activate. Focus must be placed on Primary button by default when screen opens. +- Screen reader: Button must expose accessible name matching visible label. Role: "button." State: "dimmed" when disabled. Activation announcement: "[Label] button — [result of action, if known]." +- Colorblind: Do not rely on color alone to distinguish Primary from Secondary. Primary uses higher visual weight (fill vs. outline, or larger size) in addition to color differentiation. +- Minimum touch target: 44x44pt (iOS HIG) / 48x48dp (Android). Apply even on PC if touch support is possible. + +**Implementation Notes**: +[Godot: Extend `Button` control. Override `_draw()` for custom states rather than +modifying themes mid-state. Use `focus_mode = FOCUS_ALL` to ensure keyboard +focusability. Set `mouse_default_cursor_shape = CURSOR_POINTING_HAND`. For the +scale animation, use a Tween on the `scale` property of the button's parent +Control — scaling the Button itself can clip children.] + +--- + +#### Button (Secondary) + +**Category**: Input +**Status**: Draft +**When to Use**: Alternative or cancel action. "Back," "Cancel," "Skip," "Maybe +Later." Lower visual weight than Primary — it should recede visually, not compete. +**When NOT to Use**: Destructive actions (use Button (Destructive)). The most +important action on the screen (use Button (Primary)). + +**Interaction Specification**: + +| State | Visual | Input | Response | Duration | Audio | +|-------|--------|-------|----------|----------|-------| +| Default | Outlined style (border only, transparent fill), secondary color. Slightly smaller or lower weight than Primary. | — | — | — | — | +| Hovered | Background fill appears at 15% opacity. Border brightens. Scale 1.02x. | Mouse over | Transition from Default | 80ms ease-out | [UI hover sound — softer variant than Primary] | +| Focused | Focus ring, same specification as Primary. | Tab / D-pad | Transition from Default | 80ms ease-out | [UI focus sound] | +| Pressed | Scale 0.97x, fill opacity increases to 30% | Click / Enter / B (Xbox) / Circle (PS) on focused state | Action fires on press-up | 60ms ease-in | [UI cancel/back sound] | +| Disabled | 40% opacity | — | No response | — | — | + +**Accessibility**: Same requirements as Button (Primary). Accessible name must +match visible label. In a dialog with Primary and Secondary buttons, the Secondary +button typically maps to the platform "cancel" input (B / Circle / Escape) as well +as direct focus activation. + +**Implementation Notes**: [Same as Button (Primary). Where a Primary and Secondary +appear together, ensure Secondary is always positioned consistently — right/bottom +of Primary on horizontal layouts, or below Primary on vertical layouts. Consistency +across screens is more important than per-screen aesthetic preference.] + +--- + +#### Button (Destructive) + +**Category**: Input +**Status**: Draft +**When to Use**: Any action that is irreversible and causes loss of player data or +significant progress: "Delete Save File," "Reset All Settings," "Leave Match," +"Discard Changes." The visual treatment signals danger before the player presses. +**When NOT to Use**: Actions that can be undone, or actions that are merely +consequential but reversible. + +**Interaction Specification**: + +| State | Visual | Input | Response | Duration | Audio | +|-------|--------|-------|----------|----------|-------| +| Default | Outlined or filled with destructive color (typically a desaturated red — confirm colorblind compatibility in accessibility-requirements). Label may include a warning icon. | — | — | — | — | +| Hovered / Focused | Same behavior as Button (Primary) hover/focus but with destructive color | — | — | 80ms | [UI hover sound] | +| Pressed (first press) | Does NOT execute the action. Instead, opens Confirmation Dialog pattern (see below). The button itself shows a brief pulse animation. | Click / Enter | Trigger Confirmation Dialog | 100ms pulse | [UI warning sound — distinct from standard confirm] | +| — | Confirmation Dialog handles the actual execution | — | — | — | — | +| Disabled | 40% opacity | — | No response | — | — | + +> **Critical rule**: A Button (Destructive) NEVER executes its action directly. +> It always triggers a Confirmation Dialog. There are no exceptions. A player +> who presses it by accident must always have one more opportunity to back out. +> Games that skip confirmation on destructive actions generate the most visible +> negative community sentiment of any UX failure type. See: every "accidentally +> deleted save file" complaint on any game forum. + +**Accessibility**: Screen reader must announce the destructive nature: "[Label] button — this action cannot be undone." In addition to accessible name, use the `description` property if available to add the warning text. + +**Implementation Notes**: [Destructive button triggers a separate Confirmation Dialog scene. Pass the action callback to the dialog — the button itself does not hold the execution logic. This separation prevents accidental execution if the confirmation dialog has a bug.] + +--- + +#### Toggle + +**Category**: Input +**Status**: Draft +**When to Use**: Binary on/off settings where both states are equally valid and +the current state must be visible at a glance. "Subtitles: On/Off," "Aim Assist: +On/Off," "Notifications: On/Off." +**When NOT to Use**: Selections from more than two options (use Dropdown). Actions +that happen once rather than representing a persistent state (use Button). Cases +where the consequence of toggling is complex enough to need explanation (show +a description field alongside). + +**Interaction Specification**: + +| State | Visual | Input | Response | Duration | Audio | +|-------|--------|-------|----------|----------|-------| +| Off / Default | Track: muted fill. Thumb: leftmost position. Label: "Off" or state label. | — | — | — | — | +| Hovered | Track brightens 10%. Cursor: pointer. | Mouse over | Transition | 60ms | [UI hover sound] | +| Focused | Focus ring around entire toggle element (track + thumb). | Tab / D-pad | — | 60ms | [UI focus sound] | +| Pressed / Activated | Thumb slides to right side. Track fill changes to active color. Label changes to "On" or active state label. State persists. | Click / Enter / A / Cross | Toggle state change. Fire onChange event. Persist value. | 150ms ease-in-out for slide | [Toggle ON sound] | +| Pressed / Deactivated | Thumb slides to left. Track reverts to muted fill. | Same inputs | Toggle state change | 150ms ease-in-out | [Toggle OFF sound — subtly different from ON] | +| Disabled | 40% opacity. No interaction. Current state still visible. | — | No response | — | — | + +**Accessibility**: +- Keyboard/Gamepad: Space or Enter to toggle. Avoid requiring directional inputs (left/right) to toggle — some users cannot predict that behavior. +- Screen reader: Role: "switch." State: "on" or "off" — the accessible name should NOT include the state (the screen reader announces state separately). Correct: accessible name "Subtitles," state "on." Incorrect: accessible name "Subtitles On." +- The toggle label (not just the visual thumb position) must change to show current state for players who cannot reliably distinguish left from right positions. + +**Implementation Notes**: [Godot: Use a custom Control or a CheckButton. The +built-in CheckButton provides accessibility role but uses a checkbox-style visual; +a custom slide-toggle animation may be needed for the target art style. Ensure +the slide animation is skipped when motion reduction mode is active — in that +case, snap to final state instantly.] + +--- + +#### Slider + +**Category**: Input +**Status**: Draft +**When to Use**: Selecting a value from a continuous range where approximate values +are acceptable and the range and relative position matter. Volume (0–100%), brightness, +text size. The visual representation of position is itself useful information. +**When NOT to Use**: Precise value entry (use Input Field). Selection from a short +discrete list (use Dropdown). Binary state (use Toggle). + +**Interaction Specification**: + +| State | Visual | Input | Response | Duration | Audio | +|-------|--------|-------|----------|----------|-------| +| Default | Track (full width). Fill (left of thumb, shows current value). Thumb (draggable handle). Current value label (right of track or above thumb). | — | — | — | — | +| Hovered | Thumb enlarges slightly (1.2x). Track brightens. | Mouse over | — | 60ms | — | +| Focused | Focus ring on thumb. Track brightens. | Tab / D-pad | — | 60ms | [UI focus sound] | +| Dragging (mouse) | Thumb follows cursor. Fill updates in real time. Value label updates in real time. | Click + drag on thumb | Continuous value update. Fire onChange continuously. | Real time | [Slider adjust sound — subtle, loops while dragging] | +| Keyboard / D-pad adjust | Thumb moves one step (5% of range per press, or 1 discrete unit). | Left/Right arrows or Left/Right D-pad while focused | Step value change. Fire onChange per step. | Instant | [Slider step sound — one click per step] | +| Keyboard fast adjust | Larger step (25% of range). | Page Up / Page Down while focused | Large step value change | Instant | [Same step sound] | +| Released | Value locks. onChange fires final value. | Mouse release | — | — | — | +| Disabled | 40% opacity. No interaction. Value visible. | — | No response | — | — | + +**Accessibility**: +- Keyboard: Left/Right arrows to adjust by small step. Page Up/Page Down for large step. Home/End to jump to min/max. +- Screen reader: Role: "slider." Accessible name: the label (e.g., "Music Volume"). Current value announced on every change: "Music Volume, 80 percent." Min/max values announced on first focus. +- All sliders must show a numeric value alongside the visual position. Relying only on track fill position excludes players who cannot perceive relative position. + +**Implementation Notes**: [Godot `HSlider`: set `step` to appropriate increment. +Override keyboard input to add Page Up/Down support via `_input()`. Bind the +`value_changed` signal to update the displayed numeric label. When motion reduction +mode is enabled, ensure value label updates are the sole feedback — do not suppress +them. Rumble feedback on gamepad slider adjustment is a nice enhancement for +accessibility.] + +--- + +#### Dropdown / Select + +**Category**: Input +**Status**: Draft +**When to Use**: Selection from a discrete list of 3-15 options where only the +selected value needs to be visible at rest. Display resolution, language, window +mode, input preset. The closed state shows only the current selection. +**When NOT to Use**: Binary choices (use Toggle). More than ~15 options (use a +full List pattern or a scrollable Select). When comparing options matters as much +as selecting one (show options visibly, e.g., as a horizontal selector or list). + +**Interaction Specification**: + +| State | Visual | Input | Response | Duration | Audio | +|-------|--------|-------|----------|----------|-------| +| Closed / Default | Label (left). Current value (right). Chevron-down icon (far right). | — | — | — | — | +| Hovered | Row background fills at 10% opacity | Mouse over | — | 60ms | — | +| Focused (closed) | Focus ring on entire row. | Tab / D-pad | — | 60ms | [UI focus sound] | +| Opening | Dropdown list appears below (or above if near screen bottom). List items visible. Previously selected item highlighted. Focus moves to selected item inside list. | Click / Enter / A / Cross | Open list | 100ms ease-out (expand) | [UI expand sound] | +| List item hovered/focused | List item highlights | Mouse / D-pad | — | 60ms | [UI hover sound] | +| List item selected | List closes. Closed state shows new value. onChange event fires. | Click / Enter / A / Cross on item | Select value, close list | 80ms ease-in (collapse) | [UI confirm sound] | +| Dismissed without selecting | List closes. Value unchanged. | Escape / B / Circle / click outside | Dismiss | 80ms | [UI cancel sound] | +| Disabled | 40% opacity. No interaction. | — | — | — | — | + +**Accessibility**: +- Keyboard: Up/Down arrows navigate list items while open. Enter selects. Escape dismisses. First letter of an option jumps focus to first matching item. +- Screen reader: Role: "combobox." Accessible name: the field label. Expanded/collapsed state announced. Current value announced when focused. Each list item announces its value and position: "English, 1 of 12." +- The dropdown list must never obscure the current item or the control that opened it — this is a common failure on small screens. + +**Implementation Notes**: [Godot: Custom implementation using a `Button` (the +closed state) and a `PopupMenu` or a `VBoxContainer` revealed by animation. Native +`OptionButton` provides accessibility but limited visual customization. Ensure +the popup positions itself above the control if it would be clipped by the screen +bottom. Close the popup on `_input` detecting click outside its rect.] + +--- + +#### List Item + +**Category**: Layout / Input +**Status**: Draft +**When to Use**: A single selectable row in a vertically scrollable list. Achievements, +quest log entries, settings categories, save file slots. The list is the container; +this is the row within it. +**When NOT to Use**: Grid layouts where items exist in two dimensions (use Grid Item). +Non-selectable content rows (remove hover/focus states and the pressed state). + +**Interaction Specification**: + +| State | Visual | Input | Response | Duration | Audio | +|-------|--------|-------|----------|----------|-------| +| Default | Full-width row. Icon (optional, left). Primary label. Secondary label / metadata (right or below primary). Chevron (right, if navigates deeper). | — | — | — | — | +| Hovered | Row background at 12% opacity highlight. | Mouse over | — | 60ms | — | +| Focused | Focus ring on row OR row background at 20% opacity (consistent with platform convention). | D-pad / Tab | — | 60ms | [UI focus sound] | +| Selected (persistent) | Row background at 25% opacity. May show a selection indicator (left border, checkmark). Distinct from focused state — a row can be selected but not focused. | — | Rendered state | — | — | +| Pressed / Activated | Brief brightness flash, then navigates or performs action | Click / Enter / A / Cross | Navigation or action | 80ms flash | [UI confirm sound] | +| Disabled | 40% opacity. No interaction. | — | — | — | — | + +**Accessibility**: +- Keyboard/Gamepad: Up/Down arrows or D-pad to move between list items. The list must handle focus cycling — reaching the bottom should stop (not wrap) unless wrapping is explicitly designed. +- Screen reader: Role: "listitem." Parent list role: "list." Accessible name: primary label content. Metadata (secondary label) is optionally included in the description. Position announced: "Quest Log, 3 of 12." +- Minimum row height: 44pt / 48dp for touch. For controller-primary platforms, 56px rows are more comfortable. + +**Implementation Notes**: [Godot: Use a `VBoxContainer` inside a `ScrollContainer`. +Each row is a custom `Control` or `PanelContainer` with a `_gui_input` override. +For keyboard navigation inside the scroll container, implement custom focus +traversal — Godot's default Tab navigation does not scroll the container to keep +focused items in view. Use `ensure_control_visible()` on the scroll container.] + +--- + +#### Grid Item + +**Category**: Layout / Input +**Status**: Draft +**When to Use**: A selectable cell in a two-dimensional grid. Inventory slots, +ability select, crafting ingredient selection, character portrait selection. The +grid is the container; this is the cell. +**When NOT to Use**: Single-column content (use List Item). Non-selectable display +cells (remove interactive states). + +**Interaction Specification**: + +| State | Visual | Input | Response | Duration | Audio | +|-------|--------|-------|----------|----------|-------| +| Empty | Empty slot visual (subtle border or dashed outline). Different from disabled. | — | — | — | — | +| Populated | Item icon fills cell. Stack count (bottom right, if applicable). Quality indicator (border color or icon overlay). | — | — | — | — | +| Hovered | Brightness +15%. Tooltip appears after 400ms delay. | Mouse over | — | 60ms | — | +| Focused | Focus ring (2px, offset 2px). Same brightness as hovered. Tooltip appears after 400ms delay or immediately on gamepad. | D-pad navigation | — | 60ms | [UI focus sound] | +| Selected (persistent) | Distinct border (thicker, contrasting color). May show selection checkmark. | Click / Enter / A / Cross | Select item. Can coexist with focused state on a different cell. | Instant | [UI select sound] | +| Pressed | Brief scale 0.95x, then executes action | Double-click / Enter / A / Cross | Action (equip, use, inspect — defined by context) | 80ms | [UI confirm sound] | +| Locked | Padlock overlay icon on populated content. No hover/focus states. | — | No interaction | — | — | +| Drag source | Cell dims (50% opacity), drag preview appears at cursor. | Click + drag (mouse only) | Begin drag operation | Instant | [UI grab sound] | +| Drop target (valid) | Cell brightens, accepting color indicator | Item dragged over | — | 60ms | — | +| Drop target (invalid) | Red tint or shake animation | Item dragged over invalid slot | — | 60ms | [UI error sound] | + +**Accessibility**: +- Keyboard/Gamepad: D-pad or arrow keys navigate cells. The grid must communicate its dimensions to screen readers. Row/column position announced. +- Screen reader: Role: "gridcell." Parent role: "grid." Accessible name: item name (or "empty slot" for empty cells). State: "selected" when selected, "dimmed" when locked. Position: "row 2, column 3." +- Tooltips must be reachable by keyboard — they must appear when the cell is focused, not only when hovered. + +**Implementation Notes**: [Godot: `GridContainer` with fixed column count. Each +cell is a custom `Control`. Implement custom D-pad navigation by overriding +`_gui_input` and calculating the cell to the left/right/above/below based on +index and column count. `GridContainer` does not provide this natively.] + +--- + +#### Modal Dialog + +**Category**: Feedback / Layout +**Status**: Draft +**When to Use**: A decision or acknowledgment that must be resolved before the +player can continue. The dialog is blocking — background content is dimmed and +non-interactive. "Are you sure?", "Your progress will be saved.", error states. +**When NOT to Use**: Non-blocking notifications (use Toast / Notification). Information +that can wait until the player is ready (add it to a persistent help system instead). +Dialogs that should allow the player to continue playing behind them. + +**Interaction Specification**: + +| State | Visual | Input | Response | Duration | Audio | +|-------|--------|-------|----------|----------|-------| +| Opening | Background overlay animates from 0 to 60% opacity. Dialog panel scales from 0.9 to 1.0. Dialog enters from center (not from an edge). | Triggered by code | Focus moves to first interactive element in dialog (or the Primary button) | 200ms ease-out | [UI modal open sound] | +| Active | Background non-interactive. Dialog has all input focus. Player cannot interact with background. | Keyboard / gamepad navigates within dialog only | — | — | — | +| Dismissing (confirmed) | Dialog panel scales to 1.1 then fades. Overlay fades to 0%. | Primary button pressed | Execute action, return focus to trigger element | 180ms | [UI confirm sound] | +| Dismissing (cancelled) | Dialog panel scales to 0.9 then fades. Overlay fades to 0%. | Secondary button / Escape / B / Circle | No action, return focus to trigger element | 150ms | [UI cancel sound] | +| Cannot dismiss | If the dialog represents a blocking error, do not provide a cancel path. Provide only resolution options. | — | — | — | — | + +> **Focus trap rule**: While a modal dialog is open, Tab and D-pad navigation +> must cycle within the dialog's interactive elements only. It must not be possible +> to navigate focus outside the dialog to the background content. This is both +> an accessibility requirement (WCAG 2.1 SC 2.1.2) and a UX integrity requirement. +> When the dialog closes, focus must return to the element that triggered it, +> not to the top of the page. + +**Accessibility**: +- Screen reader: Dialog container role: "dialog." Accessible name: dialog title (required — every dialog must have a title, even if visually hidden). On open, screen reader announces dialog title and first focusable element. Focus trap active. +- Keyboard: Escape key always maps to the cancel/dismiss action (same as Secondary button or close button). Enter always maps to the primary/confirm action. +- Motion reduction: Scale animation replaced with instant appear/disappear. Overlay fade retained at 100ms (faster). + +**Implementation Notes**: [Godot: Implement as a `CanvasLayer` with a high layer +value (100+) to ensure it renders above all game content. The background overlay +is a full-screen `ColorRect` at 60% black opacity. Use `grab_focus()` on the +dialog's primary button after the open animation completes. Override `_input()` to +implement the focus trap — intercept Tab navigation and reroute to the dialog's +focusable elements.] + +--- + +#### Confirmation Dialog + +**Category**: Feedback / Layout +**Status**: Draft +**When to Use**: The specific case of confirming a destructive action. Always +triggered by Button (Destructive). Always has exactly two options: confirm (labeled +with the specific action, not "OK") and cancel. +**When NOT to Use**: Non-destructive confirmations. Errors or notifications that +do not require a decision. Any dialog with more than two actions. + +> **Label rule**: The confirm button must be labeled with the specific action, +> not a generic "OK" or "Yes." "Delete Save File" not "OK." "Leave Match" not +> "Yes." This reduces mistakes for players who have difficulty reading the dialog +> content quickly. The pattern comes from Apple HIG and is validated by decades +> of usability research. + +**Structure**: +- Title: Brief, action-describing. "Delete save file?" not "Are you sure?" +- Body: One sentence stating the consequence. "This cannot be undone." +- Confirm button: Button (Primary) — labeled with the specific action. "Delete Save File." +- Cancel button: Button (Secondary) — "Cancel." +- Default focus: Cancel (safer default — reduces accidental destructive actions). + +**Accessibility**: Inherits all Modal Dialog accessibility. Additionally: screen +reader announces "Alert dialog, [title]" to signal destructive context. Default +focus on Cancel is a requirement, not a preference. + +**Implementation Notes**: [Confirmation Dialog is a specific instance of Modal +Dialog — implement it as a subclass or as a parameterized scene. The default +focus on Cancel is critical: set `grab_focus()` on the Cancel button, not the +Confirm button, after open animation completes.] + +--- + +#### Toast / Notification + +**Category**: Feedback +**Status**: Draft +**When to Use**: Brief, non-blocking information that does not require a player +decision. "Game saved." "Achievement unlocked." "Your inventory is full." The player +can continue playing; the notification disappears on its own. +**When NOT to Use**: Information that requires a decision (use Modal Dialog). +Errors that require the player to take action. Critical information that the player +must not miss. + +**Interaction Specification**: + +| State | Visual | Input | Response | Duration | Audio | +|-------|--------|-------|----------|----------|-------| +| Entering | Slides in from screen edge (typically bottom-right, away from primary action areas). Fades from 0 to 100% opacity. | Triggered by code | — | 200ms ease-out | [Sound matching notification type — see Sound Standards] | +| Displayed | Full opacity. Optional: icon (left), title, body text (optional), dismiss button (X, optional). | Pointer hover pauses auto-dismiss timer | Pause auto-dismiss | — | — | +| Auto-dismiss | Fades from 100 to 0% opacity, slides out | Timer expires (5 seconds default for one-line; 8 seconds for two-line) | Remove from queue | 200ms ease-in | — | +| Manual dismiss | Fades and slides out immediately | Click/tap X button or swipe on touch | Remove | 150ms | [UI cancel sound, quiet] | +| Queue overflow | New notification pushes oldest out early | New notification triggered while previous is displayed | FIFO queue, max 3 simultaneous | — | — | + +**Accessibility**: +- Screen reader: Toasts must be read aloud without requiring focus. In HTML, this uses `role="status"` or `role="alert"`. In game UI, this requires the engine's accessibility notification system. Verify engine support in engine-reference docs. +- Motion reduction: Slide animation replaced with fade only. +- Toasts must never be the sole communication channel for information the player needs to act on. If the information requires action, use a persistent UI element in addition to the toast. +- Auto-dismiss timer: 5 seconds is the minimum. Players with cognitive processing differences may need more time. Consider a setting to extend to 10 or 15 seconds. + +**Implementation Notes**: [Godot: Manage a queue of `PanelContainer` scenes in a +`VBoxContainer` anchored to a screen corner. Each toast is instantiated, added to +the container, then auto-removed after a timer. The container should be on a high +`CanvasLayer` (50+) but below modal dialogs (100+). Animate using a `Tween` on +`modulate.a` and `position.x`. When motion reduction is active, skip the position +animation.] + +--- + +#### Tooltip + +**Category**: Feedback +**Status**: Draft +**When to Use**: Contextual information that supplements a visible label. Item +descriptions in inventory. Stat explanations on a character sheet. Setting +descriptions in accessibility options. The player must be able to access this +information or proceed without it. +**When NOT to Use**: Information the player MUST read to complete an action — put +that in the label or body text, not a tooltip. Tooltips are not discoverable +on mobile touch without a hover state. On touch-only platforms, use an info button +that opens a description modal instead. + +**Interaction Specification**: + +| State | Visual | Input | Response | Duration | Audio | +|-------|--------|-------|----------|----------|-------| +| Hidden | — | — | — | — | — | +| Hover trigger | — | Mouse enters element | Begin 400ms delay timer | — | — | +| Gamepad/keyboard trigger | — | Element receives focus | Begin 300ms delay timer (shorter because navigation is intentional) | — | — | +| Appearing | Tooltip panel fades in and scales from 0.95 to 1.0. Positioned near element (prefer above, adjust if near screen edge). | Timer expires | Show tooltip | 120ms ease-out | — | +| Displayed | Tooltip visible. Title (optional). Body text. Max width: 300px. Multiple lines allowed. | — | — | — | — | +| Hiding | Tooltip fades out | Mouse leaves element / focus moves away | Hide tooltip | 80ms ease-in | — | + +**Accessibility**: +- Screen reader: Tooltip content must be accessible without hover. The accessible name of the parent element should include the most critical tooltip information. The full tooltip text is optionally in the `description` property. Screen reader reads tooltip content when element is focused. +- The delay (300-400ms) prevents accidental tooltip display and is required — instant tooltips are disruptive in gamepad navigation. +- Tooltip text must meet the same contrast requirements as body text (4.5:1 minimum). + +**Implementation Notes**: [Godot: Attach a custom `TooltipControl` scene as a +child of the trigger element. Show/hide with a `Timer` node. Position the tooltip +using a `CanvasLayer` to ensure it appears above all other UI. For screen edges, +detect if the tooltip rect extends beyond `get_viewport_rect()` and flip the +position to the opposite side.] + +--- + +#### Progress Bar + +**Category**: Feedback / Layout +**Status**: Draft +**When to Use**: Linear progress toward a defined endpoint. Loading screens (time +to completion), XP fill toward next level, quest objectives with countable progress +("3 of 10 enemies defeated"), download progress. +**When NOT to Use**: Circular or radial progress (use a separate Radial Progress +pattern if needed). Values that fluctuate up and down rapidly (use Health/Resource +Bar pattern). Values with no defined endpoint. + +**Interaction Specification**: + +| State | Visual | Input | Response | Duration | Audio | +|-------|--------|-------|----------|----------|-------| +| Default | Track (full width, background color). Fill (left to right, value color). Value label (percentage or N/M, outside or inside fill). | — | — | — | — | +| Value increasing | Fill width animates to new value | Value changes | Smooth fill animation | 300ms ease-out | [Context-dependent — XP gain has a sound; loading has none] | +| Value at maximum | Fill reaches full width. Optional: completion animation (pulse, glow). | Value reaches 100% | Completion event fires | 200ms | [Completion sound if appropriate] | +| Value at zero | Fill hidden (zero width). Track still visible. | — | — | — | — | +| Indeterminate (unknown duration) | Animated loop (fill segment moves left-to-right, repeat). Used for loading of unknown duration. | — | — | Infinite loop | — | + +**Accessibility**: +- Screen reader: Role: "progressbar." Accessible name: what is progressing (e.g., "Experience Points," "Loading"). Value: current numeric value AND percentage AND maximum. "Experience Points, 450 of 1000, 45 percent." Update on significant changes (not every pixel). +- Do not rely only on fill color to communicate value. Include a numeric label. +- Indeterminate progress bars: announce "Loading, in progress" — do not announce changes since the value is unknown. +- Motion reduction: Indeterminate animation is replaced with a static "loading" indicator. Smooth fill animation is replaced with instant jump to new value. + +**Implementation Notes**: [Godot: `ProgressBar` built-in with custom theming. +For indeterminate mode, `ProgressBar` does not have a native indeterminate state +in Godot 4.x — implement using a looping `Tween` on a fill element's position. +Ensure the Tween is paused when motion reduction mode is active and a static +indicator is shown instead.] + +--- + +#### Input Field + +**Category**: Input +**Status**: Draft +**When to Use**: Text entry. Player name on a new save, search within a list, +remapping a key binding (special case — shows the key press, not typed text), +entering a numeric value precisely. +**When NOT to Use**: Selecting from known options (use Dropdown or List). On +console-primary platforms, minimize text entry — it requires a virtual keyboard, +which is high friction. + +**Interaction Specification**: + +| State | Visual | Input | Response | Duration | Audio | +|-------|--------|-------|----------|----------|-------| +| Default | Field border, placeholder text (label-style, muted color), empty input area. | — | — | — | — | +| Hovered | Border brightens slightly | Mouse over | — | 60ms | — | +| Focused | Border brightens fully. Cursor (blinking, 530ms on/530ms off). Placeholder text hidden. | Tab / click | Open virtual keyboard on console/mobile | Instant | [UI focus sound] | +| Typing | Characters appear. Cursor advances. | Keyboard input | Update field value | Immediate | [Subtle keystroke sound, optional] | +| Value present | Field shows typed value. Placeholder hidden. Clear button appears (X, right of field) if value is non-empty. | — | — | — | — | +| Character limit reached | No further input accepted. Optional: brief shake animation and limit indicator changes color. | Input at limit | Reject further characters | 200ms shake | [UI error sound, subtle] | +| Clear | Field empties. Cursor returns. Clear button disappears. | Click X / gamepad clear input | Clear value | Instant | [UI cancel sound, subtle] | +| Validation error | Border turns error color (red — ensure colorblind safe). Error message appears below field. | On submit or on blur | Show error | Instant | [UI error sound] | +| Validated / correct | Border turns success color (green — ensure colorblind safe). Success icon optional. | On validation pass | — | Instant | — | +| Disabled | 40% opacity, no interaction. Value still visible. | — | — | — | — | + +**Accessibility**: +- Keyboard: All standard text editing shortcuts (Home, End, Ctrl+A, Ctrl+C, Ctrl+V, Ctrl+Z). +- Screen reader: Role: "textbox." Accessible name: field label (not placeholder text). Current value announced. Character limit announced when reached. Validation errors announced immediately on occurrence. +- Placeholder text must not be used as the only label — a visible label above or beside the field is required. Placeholder text disappears when the player types, causing confusion for players with cognitive or memory impairments. + +**Implementation Notes**: [Godot `LineEdit`: set `placeholder_text` for the hint +but always include a visible `Label` node as the field's accessible name. Bind +`text_changed` signal for real-time validation. Bind `text_submitted` for form +submission on Enter. On console, `LineEdit.call("_popup_keyboard")` or use the OS +virtual keyboard API — verify against engine-reference/godot/ for Godot 4.6 +console keyboard API specifics.] + +--- + +#### Tab Bar + +**Category**: Navigation +**Status**: Draft +**When to Use**: Dividing a single screen's content into discrete sections where +only one section is visible at a time. Character sheet tabs (Stats / Equipment / +Skills), settings tabs (Gameplay / Graphics / Audio / Accessibility). Maximum +5-6 tabs before the pattern breaks down and a sidebar navigation should be +considered instead. +**When NOT to Use**: More than 6 tabs. Content that benefits from simultaneous +visibility (use a layout pattern instead). Navigation between different screens +(use Screen Push). + +**Interaction Specification**: + +| State | Visual | Input | Response | Duration | Audio | +|-------|--------|-------|----------|----------|-------| +| Default (inactive tab) | Tab label. No active indicator. | — | — | — | — | +| Active tab | Tab label. Active indicator (underline, fill, or contrasting background). Content area shows this tab's content. | — | — | — | — | +| Hovered (inactive) | Tab background fills slightly | Mouse over | — | 60ms | — | +| Focused (keyboard/gamepad) | Focus ring on tab label. | Tab key (within tab bar) or D-pad left/right on tab row | — | 60ms | [UI focus sound] | +| Activated | Active indicator transitions to this tab. Content area transitions (fade or slide). | Click / Enter / A / Cross | Switch active tab. Content update. | 150ms ease | [UI tab switch sound] | +| Gamepad shoulder button | — | L1/R1 (PS) or LB/RB (Xbox) | Switch to previous/next tab (standard platform convention) | 150ms | [UI tab switch sound] | + +**Accessibility**: +- Keyboard: Arrow keys navigate between tabs within the tab bar (left/right). Tab key moves focus into the content area below. This follows the ARIA tab panel pattern. +- Screen reader: Role: "tab" for individual tabs. Role: "tablist" for the container. Role: "tabpanel" for the content area. Active tab state: "selected." Accessible name: tab label. Tabpanel is labeled by its corresponding tab. +- The active tab must be visually distinguishable by more than color alone (underline, fill pattern, or weight change in addition to color). + +**Implementation Notes**: [Godot: `TabContainer` built-in. For custom visual +styling, implement manually with a `HBoxContainer` of tab buttons and a +`MarginContainer` for content. The shoulder button shortcut (LB/RB) must be +implemented in the screen's `_input()` override — it is not built into Godot's +tab system. Check platform conventions: Xbox uses LB/RB; PlayStation uses L1/R1; +both are the same physical button, so a single binding works.] + +--- + +#### Scroll Container + +**Category**: Layout +**Status**: Draft +**When to Use**: Content that exceeds the visible area of its container. Inventory +lists, lore entry text, credits, long settings lists. The scroll indicator shows +the player that more content exists. +**When NOT to Use**: Content that can be paginated instead (pagination may be +clearer for dense list navigation). Infinite scroll (always provide a loading +state and an end state). + +**Interaction Specification**: + +| State | Visual | Input | Response | Duration | Audio | +|-------|--------|-------|----------|----------|-------| +| Content fits | No scrollbar visible (or always-visible scrollbar at full height, depending on art direction). | — | — | — | — | +| Scrollable | Scrollbar appears (right edge). Scrollbar thumb size represents viewport vs. content ratio. | — | — | — | — | +| Scrolling (mouse) | Content moves. Scrollbar thumb moves proportionally. | Mouse wheel | Scroll by 3 lines per wheel tick (configurable in OS) | Smooth | — | +| Scrollbar drag | Content moves. Thumb follows pointer. | Click + drag scrollbar thumb | Scroll proportionally | Real time | — | +| Keyboard scroll | Content moves one item height per keypress. | Up/Down arrows when container is focused and no child is focused | Scroll by one unit | Immediate | — | +| Gamepad scroll | Content moves to keep focused item in view. | D-pad navigation to items beyond visible area | Auto-scroll to keep focused item visible | Smooth 150ms | — | +| Scroll top / bottom | Content stops. Scrollbar thumb at end. | Content boundary reached | Stop scrolling | — | — | +| Focus follows scroll | When a child element receives focus, scroll container ensures it is fully visible. | Any child receives focus | Scroll to reveal focused element | 200ms ease | — | + +**Accessibility**: +- Keyboard/Gamepad: The scroll container itself should not require explicit scrollbar interaction — navigating list items inside it should auto-scroll to keep focused items in view. +- Screen reader: The scroll container announces "scrollable" and the scroll position ("showing items 5 through 15 of 30"). This requires engine accessibility support — verify in engine-reference/godot/. +- Fade edges (content fading at scroll boundaries to indicate more content exists) are a helpful visual affordance but must not be the only indicator that content exists beyond the visible area. Include a scrollbar. + +**Implementation Notes**: [Godot `ScrollContainer`: call `ensure_control_visible()` +on the focused child whenever `gui_focus_changed` fires inside the container. +Bind this via a recursive `connect` on the container's `gui_focus_changed` signal. +For smooth scroll animation, use a `Tween` on `scroll_vertical` rather than +setting it directly.] + +--- diff --git a/.claude/docs/templates/guidance/interaction-pattern-library-guide.md b/.claude/docs/templates/guidance/interaction-pattern-library-guide.md new file mode 100644 index 00000000..319af404 --- /dev/null +++ b/.claude/docs/templates/guidance/interaction-pattern-library-guide.md @@ -0,0 +1,20 @@ +> Authoring guidance for interaction-pattern-library.md. Load only the part covering the section currently being authored — not the whole file at once. + +# Interaction Pattern Library — Guidance Index + +The guidance for this template is split by major section so consumers can load +one part at a time. Read the file matching the section you are currently +authoring — never all three at once. + +| Template Section | Guidance File | +|------------------|---------------| +| Standard Control Patterns (Button variants, Toggle, Slider, Dropdown / Select, List Item, Grid Item, Modal Dialog, Confirmation Dialog, Toast / Notification, Tooltip, Progress Bar, Input Field, Tab Bar, Scroll Container) | `.claude/docs/templates/guidance/interaction-pattern-library-guide-standard-controls.md` | +| Game-Specific UI Patterns (Inventory Slot, Ability / Skill Icon, Health / Resource Bar, Dialogue Box, Context Action Prompt, Damage Number) | `.claude/docs/templates/guidance/interaction-pattern-library-guide-game-specific.md` | +| Navigation Patterns + Feedback and Loading Patterns (Screen Push / Pop / Replace, Focus Management, Escape / Cancel, Loading State, Empty State, Error State) | `.claude/docs/templates/guidance/interaction-pattern-library-guide-navigation-feedback.md` | + +Each guidance file contains the full reference specification for its patterns — +When to Use / When NOT to Use rationale, complete state tables with timing and +audio values, accessibility requirements, and engine implementation notes. Use +them as the worked-example source when filling the corresponding skeleton stubs +in the template. The Pattern Catalog Index, Animation Standards, Sound +Standards, and Open Questions sections remain in the template itself. diff --git a/.claude/docs/templates/guidance/ux-spec-guide.md b/.claude/docs/templates/guidance/ux-spec-guide.md new file mode 100644 index 00000000..40e89f89 --- /dev/null +++ b/.claude/docs/templates/guidance/ux-spec-guide.md @@ -0,0 +1,443 @@ +> Authoring guidance for ux-spec.md. Load only the part covering the section currently being authored — not the whole file at once. + +# UX Specification Template — Authoring Guidance + +Guidance is organized by template section number. When authoring a section of a +project's UX spec, read only that section's guidance below. + +--- + +## Section 1 — Purpose & Player Need + +> **Why this section exists**: Every screen must justify its existence from the +> player's perspective. Screens that are designed from a developer perspective ("display +> the save data") produce cluttered, confusing interfaces. Screens designed from the +> player's perspective ("let the player feel confident their progress is safe before they +> put the controller down") produce purposeful, calm interfaces. Write this section before +> touching any layout decisions — it is the filter through which every subsequent choice +> is evaluated. + +**Authoring the player need**: + +[One paragraph. Name the real human need, not the system function. Consider: what would +a player say they want when they open this screen? What would frustrate them if it did +not work? That frustration describes the need. + +Example — bad: "Displays the player's current items and equipment." +Example — good: "Lets the player understand what they're carrying and quickly decide what +to take into the next encounter, without breaking their mental model of the game world. +The inventory is the player's planning tool between moments of action."] + +**Authoring the player goal**: + +[One sentence. Specific enough that you could write an acceptance criterion for it. +Example: "Find the item they are looking for within three button presses and equip it +without navigating to a separate screen."] + +**Authoring the game goal**: + +[One sentence. This is what the system needs from this interaction. Example: "Record the +player's equipment choices and relay them to the combat system before the next encounter +loads." This section prevents UI that looks good but fails to serve the system it is +part of.] + +--- + +## Section 2 — Player Context on Arrival + +> **Why this section exists**: Screens do not exist in isolation. A player opening the +> inventory mid-combat is in a completely different cognitive and emotional state than +> a player opening it after clearing a dungeon. The same information architecture can +> feel oppressively complex in one context and trivially simple in another. Document the +> context so that design decisions — what to show first, what to hide, what to animate, +> what to simplify — are calibrated to the actual player arriving at this screen, not +> an abstract user. + +**Worked example answers for the context table**: + +| Question | Answer | +|----------|--------| +| What was the player just doing? | [e.g., Completed a combat encounter / Pressed Esc from exploration / Triggered a story cutscene] | +| What is their emotional state? | [e.g., High tension — just narrowly survived / Calm — exploring between objectives] | +| What cognitive load are they carrying? | [e.g., High — actively tracking enemy positions / Low — no active threats] | +| What information do they already have? | [e.g., They know they just picked up an item but haven't seen its stats yet] | +| What are they most likely trying to do? | [e.g., Check if the new item is better than their current weapon — primary use case] | +| What are they likely afraid of? | [e.g., Missing something, making an irreversible mistake, losing track of where they were] | + +**Authoring the emotional design target**: + +[One sentence describing the feeling the player should have while using this screen. +Example: "Confident and in control — the player should feel like they have complete +information and complete authority over their choices, with no ambiguity about outcomes."] + +--- + +## Section 3 — Navigation Position + +> **Why this section exists**: A screen that does not know where it sits in the +> navigation hierarchy cannot define its entry/exit transitions, its back-button +> behavior, or its relationship to the game's pause state. Navigation position also +> reveals architectural problems early — if this screen is reachable from eight +> different places, that is a complexity flag that should be resolved in design, not +> implementation. + +**Worked example — reachability table**: + +| Entry Point | Triggered By | Notes | +|-------------|-------------|-------| +| [e.g., Main Menu → Play] | [Player selects "New Game"] | [Primary entry point] | +| [e.g., Pause Menu → Resume] | [Player presses Start from any gameplay state] | [Secondary entry] | +| [e.g., Game event] | [Tutorial system forces open first time only] | [Systemic entry — must not break if player dismisses] | + +--- + +## Section 4 — Entry & Exit Points + +> **Why this section exists**: Entry and exit define the screen's contract with the +> rest of the navigation system. Every entry point must have a corresponding exit point. +> Transitions that are undefined become bugs — the player finds themselves stuck, or the +> game state becomes inconsistent. Fill this table completely before implementation +> begins. Empty cells are a sign that design work is unfinished. + +**Worked example — entry table**: + +| Trigger | Source Screen / State | Transition Type | Data Passed In | Notes | +|---------|----------------------|-----------------|----------------|-------| +| [e.g., Player presses Inventory button] | [Gameplay / Exploration state] | [Overlay push — game pauses] | [Current player loadout, inventory contents] | [Works from any non-combat state] | +| [e.g., Item pickup prompt accepted] | [Gameplay / Item Pickup dialog] | [Replace dialog with full inventory] | [Newly acquired item pre-highlighted] | [The new item should be visually distinguished on open] | +| [e.g., Quest system directs player to inventory] | [Gameplay / Quest Update notification] | [Overlay push] | [Quest-relevant item ID for highlight] | [Screen should deep-link to the relevant item] | + +**Worked example — exit table**: + +| Exit Action | Destination | Transition Type | Data Returned / Saved | Notes | +|-------------|------------|-----------------|----------------------|-------| +| [e.g., Player closes inventory (Back/B/Esc)] | [Previous state — Exploration] | [Overlay pop — game resumes] | [Updated equipment loadout committed] | [Changes must be committed before transition begins] | +| [e.g., Player selects "Equip" on item] | [Same screen, updated state] | [In-place state change] | [Loadout change event fired] | [No navigation, just a state refresh] | +| [e.g., Player navigates to Map from inventory shortcut] | [Map Screen] | [Replace] | [No data] | [Inventory state is preserved if player returns] | + +--- + +## Section 5 — Layout Specification + +> **Why this section exists**: The layout specification is the handoff artifact between +> UX design and UI programming. It does not need to be pixel-perfect — it needs to +> communicate hierarchy (what is important), proximity (what belongs together), and +> proportion (what is big vs. small). ASCII wireframes achieve this without requiring +> design software. A programmer who reads this section should be able to build the +> correct structure without guessing. An artist who reads it should know where +> visual weight should be concentrated. +> +> Draw the layout at one standard resolution (e.g., 1920x1080). Note adaptations +> for other resolutions separately. + +**Worked example — wireframe (5.1)**: + +``` +Example: +┌──────────────────────────────────────────────┐ +│ [← Back] INVENTORY [Options] │ ← HEADER ZONE +├──────────────────────────────────────────────┤ +│ ┌──────────────┐ ┌─────────────────────────┐│ +│ │ CATEGORY NAV │ │ ITEM DETAIL PANEL ││ ← CONTENT ZONE +│ │ ● Weapons │ │ Item Name ││ +│ │ Armor │ │ {item icon} ││ +│ │ Consumable│ │ Stats comparison ││ +│ │ Key Items │ │ Description text... ││ +│ ├──────────────┤ └─────────────────────────┘│ +│ │ ITEM GRID │ │ +│ │ {□}{□}{□}{□} │ │ +│ │ {□}{□}{□}{□} │ │ +│ │ ... │ │ +│ └──────────────┘ │ +├──────────────────────────────────────────────┤ +│ [Equip] [Drop] [Compare] [Close] │ ← ACTION BAR +└──────────────────────────────────────────────┘ +``` + +**Worked example — zone definitions table (5.2)**: + +| Zone Name | Description | Approximate Size | Scrollable? | Overflow Behavior | +|-----------|-------------|-----------------|-------------|-------------------| +| [e.g., Header Zone] | [Top bar: navigation, screen title, global actions] | [Full width, ~10% height] | [No] | [Truncate long screen names with ellipsis] | +| [e.g., Category Nav] | [Left panel: item category tabs] | [~25% width, ~75% height] | [Yes — vertical if categories exceed panel] | [Scroll indicator appears at bottom of list] | +| [e.g., Item Grid] | [Center: grid of item icons for selected category] | [~45% width, ~75% height] | [Yes — vertical] | [Page-based: 4x4 grid, next page on overflow] | +| [e.g., Detail Panel] | [Right: stats and description for selected item] | [~30% width, ~75% height] | [Yes — vertical for long descriptions] | [Fade at bottom, scroll to reveal] | +| [e.g., Action Bar] | [Bottom: context-sensitive actions for selected item] | [Full width, ~15% height] | [No] | [Actions collapse to icon-only below 4] | + +**Worked example — component inventory table (5.3)**: + +| Component Name | Type | Zone | Purpose | Required? | Reuses Existing Component? | +|----------------|------|------|---------|-----------|---------------------------| +| [e.g., Back Button] | [Button] | [Header] | [Returns to previous screen] | [Yes] | [Yes — standard NavButton component] | +| [e.g., Screen Title Label] | [Text] | [Header] | [Displays "INVENTORY" or context name] | [Yes] | [Yes — ScreenTitle component] | +| [e.g., Category Tab] | [Toggle Button] | [Category Nav] | [Filters item grid by category] | [Yes] | [No — new component needed] | +| [e.g., Item Slot] | [Icon + Frame] | [Item Grid] | [Represents one inventory slot, empty or filled] | [Yes] | [No — new component] | +| [e.g., Item Name Label] | [Text] | [Detail Panel] | [Shows selected item's name] | [Yes] | [Yes — BodyText component] | +| [e.g., Stat Comparison Row] | [Compound — label + value + delta] | [Detail Panel] | [Shows stat value vs. currently equipped] | [Yes] | [No — new component] | +| [e.g., Equip Button] | [Primary Button] | [Action Bar] | [Equips selected item in appropriate slot] | [Yes] | [Yes — PrimaryAction component] | +| [e.g., Empty State Message] | [Text + Icon] | [Item Grid] | [Shown when category has no items] | [Yes] | [Yes — EmptyState component] | + +**Worked example — primary focus element on open**: [e.g., The first item in the Item Grid — or, if deep-linked, the highlighted item. If the grid is empty, focus lands on the first Category Tab.] + +--- + +## Section 6 — States & Variants + +> **Why this section exists**: A screen is not a single picture — it is a set of +> states, each of which must look correct and behave correctly. Screens that are +> designed only in their "happy path" state ship with broken empty states, invisible +> loading indicators, and crashes when data is missing. Document every state before +> implementation. The states table is also the test matrix for QA. + +**Worked example table**: + +| State Name | Trigger | What Changes Visually | What Changes Behaviorally | Notes | +|------------|---------|----------------------|--------------------------|-------| +| [Loading] | [Screen is opening, data not yet available] | [Item Grid shows skeleton/shimmer placeholders; Action Bar buttons disabled] | [No interactions possible except Close] | [Should not be visible >500ms under normal conditions; if it is, investigate data fetch performance] | +| [Empty — no items in category] | [Player switches to a category with zero items] | [Item Grid replaced by EmptyState component: icon + "Nothing here yet."] | [Action Bar shows no item actions; Drop/Equip/Compare all hidden] | [Do not show disabled buttons — remove them. Disabled buttons with no tooltip are confusing.] | +| [Populated — items present] | [Category has at least one item] | [Item Grid fills with item slots; first slot is auto-focused] | [All item actions available for selected item] | [Default and most common state] | +| [Item Selected] | [Player navigates to an item slot] | [Detail Panel populates; selected slot has focus ring; Action Bar updates to item's valid actions] | [Equip/Drop/Compare enabled based on item type] | [Equip is disabled if item is already equipped — show a "Equipped" badge instead] | +| [Confirmation Pending — Drop] | [Player selects Drop action] | [Confirmation dialog overlays the screen] | [All background interactions suspended until dialog resolves] | [Use a modal confirmation, not an inline toggle. Items cannot be recovered after dropping.] | +| [Error — data load failed] | [Inventory data could not be retrieved] | [Item Grid shows error state: icon + "Couldn't load items." + Retry button] | [Only Retry and Close are available] | [Log the error; do not expose technical details to player] | +| [Item Newly Acquired] | [Screen opened from item pickup deep-link] | [Newly acquired item has a visual "New" badge; Detail Panel pre-populated with that item] | [Same as Item Selected but with badge until player navigates away] | [Badge persists until the player manually navigates off that slot once] | + +--- + +## Section 7 — Interaction Map + +> **Why this section exists**: This section is the source of truth for what every +> input does on this screen. It forces the designer to think through every input +> method (mouse, keyboard, gamepad, touch) and every interactive state (hover, focus, +> pressed, disabled). Gaps in this table are bugs waiting to happen. The +> interaction map is also the input for the accessibility audit — if an action is +> only reachable by mouse, it will fail the keyboard and gamepad columns. + +**Worked example — navigation inputs (7.1)**: + +| Input | Platform | Action | Visual Response | Audio Cue | Notes | +|-------|----------|--------|-----------------|-----------|-------| +| [Arrow keys / D-Pad] | [All] | [Move focus within active zone] | [Focus ring moves to adjacent element] | [Soft navigation tick] | [Wrap at edges within zone; do not cross zones with arrows alone] | +| [Tab / R1] | [KB / Gamepad] | [Move focus to next zone (Category → Grid → Detail → Action Bar)] | [Focus ring jumps to first element in next zone] | [Distinct zone-change tone] | [Shift+Tab / L1 goes backward] | +| [Mouse hover] | [PC] | [Show hover state on interactive elements] | [Highlight / underline / color shift] | [None] | [Hover does NOT move focus — only click does] | +| [Mouse click] | [PC] | [Select and focus the clicked element] | [Pressed state flash, then selected/focused] | [Soft click] | [Right-click opens context menu if applicable; otherwise no-op] | +| [Touch tap] | [Mobile] | [Select and activate in one gesture] | [Press ripple] | [Soft click] | [Treat tap as click + confirm for low-risk actions; require explicit confirm for destructive actions] | + +**Worked example — action inputs (7.2)**: + +| Input | Platform | Context (What must be focused) | Action | Response | Animation | Audio Cue | Notes | +|-------|----------|-------------------------------|--------|----------|-----------|-----------|-------| +| [Enter / A button / Left click] | [All] | [Item slot focused] | [Select item → populate Detail Panel] | [Detail panel slides in or updates in place] | [Panel fade/slide in, 120ms] | [Soft select tone] | [If item already selected: no-op] | +| [Enter / A button] | [All] | [Equip button focused] | [Equip selected item] | [Button animates press; item badge updates to "Equipped"; previously equipped item loses badge] | [Badge swap, 80ms] | [Equip success sound] | [Fires EquipItem event to Inventory system] | +| [Triangle / Y button / Right-click] | [All] | [Item slot focused] | [Open item context menu] | [Context menu appears adjacent to item slot] | [Popover, 80ms] | [Menu open sound] | [Context menu contains: Equip, Drop, Inspect, Compare] | +| [Square / X button] | [Gamepad] | [Item slot focused] | [Quick-equip without opening detail] | [Equip animation plays inline on slot] | [Slot flash, 80ms] | [Equip success sound] | [Convenience shortcut; does not change screen state] | +| [Esc / B button / Back] | [All] | [Any, screen level] | [Close screen and return to previous state] | [Screen exit transition plays] | [Slide out, 200ms] | [Back/close tone] | [Commits all changes before closing. No discard — inventory is not a draft.] | +| [F / L2] | [KB / Gamepad] | [Any] | [Toggle filter panel] | [Sort/filter overlay opens] | [Slide in from right, 200ms] | [Panel open tone] | [If no items in category, filter is disabled] | + +**Worked example — state-specific behaviors (7.3)**: + +| State | Input Restriction | Reason | +|-------|------------------|--------| +| [Loading] | [All item and action inputs disabled] | [No data to act on; prevent race conditions] | +| [Confirmation dialog open] | [Only Confirm and Cancel inputs active] | [Modal — background is locked] | +| [Error state] | [Only Retry and Close active] | [No data available to navigate] | + +--- + +## Section 8 — Data Requirements + +> **Why this section exists**: The separation between UI and game state is the most +> important architectural boundary in a game's UI system. UI reads data; it does not +> own it. UI fires events; it does not write state directly. This section defines +> exactly what data this screen needs to display, where it comes from, and how +> frequently it updates. Filling this table before implementation prevents two +> common failure modes: (1) UI developers reaching into systems they should not touch, +> and (2) systems not knowing they need to expose data until a UI is half-built. + +**Worked example table**: + +| Data Element | Source System | Update Frequency | Who Owns It | Format | Null / Missing Handling | +|--------------|--------------|-----------------|-------------|--------|------------------------| +| [e.g., Item list] | [Inventory System] | [On screen open; on InventoryChanged event] | [InventorySystem] | [Array of ItemData structs: id, name, icon_path, category, stats, is_equipped] | [Empty array → show Empty State. Never null — system must return array.] | +| [e.g., Equipped loadout] | [Equipment System] | [On screen open; on EquipmentChanged event] | [EquipmentSystem] | [Dict mapping slot_id → item_id] | [Unequipped slot has null value — UI shows empty slot icon] | +| [e.g., Item stat comparisons] | [Stats System] | [On item selection change] | [StatsSystem] | [Dict mapping stat_name → {current, new, delta}] | [If no item selected, detail panel shows placeholder. Stats system must handle this gracefully.] | +| [e.g., Player currency] | [Economy System] | [On screen open only — inventory does not show live currency] | [EconomySystem] | [Int — gold pieces] | [If currency system not active for this game mode, hide the currency row entirely] | +| [e.g., Newly acquired item flag] | [Inventory System] | [On screen open] | [InventorySystem] | [Array of item_ids flagged as new] | [If empty array, no badges shown] | + +--- + +## Section 9 — Events Fired + +> **Why this section exists**: This is the other half of the UI/system boundary. +> Where Section 8 defines what the UI reads, this section defines what the UI +> communicates back to the game. Specifying events at design time prevents UI +> programmers from writing game logic, and prevents game programmers from being +> surprised by what the UI does. Every destructive or state-changing player action +> must appear in this table. + +**Worked example table**: + +| Player Action | Event Fired | Payload | Receiver System | Notes | +|---------------|-------------|---------|-----------------|-------| +| [Player equips an item] | [EquipItemRequested] | [{item_id: string, target_slot: string}] | [Equipment System] | [Equipment System validates the action and fires EquipmentChanged if successful; UI listens for EquipmentChanged to update its display] | +| [Player drops an item] | [DropItemRequested] | [{item_id: string, quantity: int}] | [Inventory System] | [Fires only after player confirms the drop dialog. Inventory System removes the item and fires InventoryChanged.] | +| [Player opens item compare] | [ItemCompareOpened] | [{item_a_id: string, item_b_id: string}] | [Analytics System] | [No game-state change — analytics event only. Compare view is purely local UI state.] | +| [Player closes screen] | [InventoryScreenClosed] | [{session_duration_ms: int}] | [Analytics System] | [Fires on every close regardless of reason. Used for engagement metrics.] | +| [Player navigates between categories] | [InventoryCategoryChanged] | [{category: string}] | [Analytics System] | [Analytics only. No game state change.] | + +--- + +## Section 10 — Transition & Animation + +> **Why this section exists**: Transitions are not decoration — they communicate +> hierarchy and causality. A screen that slides in from the right implies the +> player has moved forward. A screen that fades implies a context break. Inconsistent +> transitions make navigation feel broken even when it is technically correct. +> This section ensures transitions are specified intentionally, not left to the +> developer's discretion, and that accessibility settings (reduced motion) are +> planned for from the start. + +**Worked example table**: + +| Transition | Trigger | Direction / Type | Duration (ms) | Easing | Interruptible? | Skipped by Reduced Motion? | +|------------|---------|-----------------|--------------|--------|----------------|---------------------------| +| [Screen enter] | [Screen pushed onto stack] | [Slide in from right] | [250] | [Ease out cubic] | [No — must complete before interaction is enabled] | [Yes — instant appear at 0ms] | +| [Screen exit — Back] | [Player presses Back] | [Slide out to right] | [200] | [Ease in cubic] | [No] | [Yes — instant disappear] | +| [Screen exit — Forward] | [Player navigates to child screen] | [Slide out to left] | [200] | [Ease in cubic] | [No] | [Yes — instant] | +| [Detail panel update] | [Player selects a new item] | [Cross-fade content] | [120] | [Linear] | [Yes — if player navigates quickly, previous animation cancels] | [Yes — instant swap] | +| [Loading → Populated] | [Data arrives after load] | [Skeleton shimmer fades out, content fades in] | [180] | [Ease out] | [No] | [Yes — instant reveal] | +| [Action Bar button press] | [Player activates a button] | [Scale down 95% on press, return on release] | [60 down / 60 up] | [Ease out / ease in] | [Yes — if released early, returns to normal] | [No — this is tactile feedback, not decorative motion] | +| [Confirmation dialog open] | [Player initiates destructive action] | [Background dims 60% opacity; dialog scales up from 95%] | [150] | [Ease out] | [No] | [Yes — instant appear, no scale] | +| [New item badge appear] | [Screen opens with newly acquired item] | [Badge pops from 0% to 110% to 100% scale] | [200 total] | [Ease out back] | [No] | [Yes — instant appear at 100% scale] | + +--- + +## Section 11 — Input Method Completeness Checklist + +> **Why this section exists**: Input completeness is not optional — it is a +> certification requirement for console platforms and a legal risk area for +> accessibility laws in multiple markets. Fill this checklist before marking +> the spec as Approved. Any unchecked item blocks implementation start. + +--- + +## Section 12 — Screen-Level Accessibility Requirements + +> **Why this section exists**: Accessibility requirements must be specified at design +> time because retrofitting them is expensive and often architecturally impractical. +> This section documents requirements specific to this screen. Project-wide standards +> live in `design/accessibility-requirements.md` — consult it before filling this +> section so you do not duplicate or contradict project-level commitments. + +**Worked example — text contrast table**: + +| Text Element | Background Context | Required Ratio | Current Ratio | Pass? | +|--------------|-------------------|---------------|---------------|-------| +| [e.g., Item name in Detail Panel] | [Dark panel background ~#1a1a1a] | [4.5:1 (WCAG AA normal text)] | [TBD — verify in implementation] | [ ] | +| [e.g., Category tab label — inactive] | [Mid-grey tab background] | [4.5:1] | [TBD] | [ ] | +| [e.g., Category tab label — active] | [Accent color background] | [4.5:1] | [TBD] | [ ] | +| [e.g., Action button label] | [Button color (varies by state)] | [4.5:1] | [TBD] | [ ] | +| [e.g., Stat comparison delta (positive)] | [Detail panel] | [4.5:1 — do NOT rely on green color alone] | [TBD] | [ ] | + +**Worked example — colorblind mitigations table**: + +| Element | Colorblind Risk | Mitigation | +|---------|----------------|------------| +| [e.g., Stat delta indicators (red/green for worse/better)] | [Red-green colorblindness (Deuteranopia) — most common form] | [Add arrow icons (↑ / ↓) and +/- prefix in addition to color. Color is a redundant, not sole, indicator.] | +| [e.g., Item rarity color coding (grey/green/blue/purple/orange)] | [Multiple types — rarity color is a common industry failure] | [Add rarity name text label below icon. Color is supplemental only.] | + +**Worked example — focus order**: + +[e.g., +1. Back button (Header) +2. Options button (Header) +3. Category Tab 1 — Weapons +4. Category Tab 2 — Armor +5. Category Tab 3 — Consumables +6. Category Tab 4 — Key Items +7. Item Slot [0,0] +8. Item Slot [0,1] ... (grid traverses left-to-right, top-to-bottom) +9. Last item slot +10. Equip button (Action Bar) +11. Drop button (Action Bar) +12. Compare button (Action Bar) +13. Close button (Action Bar) +→ Cycles back to Back button + +Focus does not enter the Detail Panel — it is a display panel driven by item focus, not independently navigable.] + +**Worked example — screen reader announcements table**: + +| State Change | Announcement Text | Announcement Timing | +|--------------|------------------|---------------------| +| [Screen opens] | ["Inventory screen. [N] items. [Active category] selected."] | [On screen focus settle] | +| [Player focuses an item slot] | ["[Item name]. [Category]. [Rarity]. [Key stats summary]. [Equipped / Not equipped]."] | [On focus arrival] | +| [Player equips an item] | ["[Item name] equipped to [slot name]."] | [After EquipmentChanged event confirmed] | +| [Player drops an item] | ["[Item name] dropped."] | [After InventoryChanged event confirmed] | +| [Category changes] | ["[Category name]. [N] items."] | [On category tab focus] | +| [Empty state shown] | ["No items in [category name]."] | [When empty state renders] | + +**Authoring the cognitive load assessment**: + +[Estimate the number of information streams the player is simultaneously tracking while +using this screen. For this screen: (1) item grid position, (2) item detail stats, +(3) current equipment loadout for comparison, (4) available actions, (5) item category. +That is 5 concurrent streams — within the standard 7±2 limit, but at the higher end. +Mitigation: detail panel auto-updates on navigation so the player never needs to +manually retrieve item info. Reduce active decisions by surfacing stat comparison +automatically.] + +--- + +## Section 13 — Localization Considerations + +> **Why this section exists**: UI built without localization in mind breaks on first +> translation. German text is typically 30–40% longer than English. Arabic and Hebrew +> require right-to-left layout mirroring. Japanese and Chinese text may be significantly +> shorter than English, creating awkward whitespace. These issues are cheap to plan for +> and expensive to fix after a layout is built and shipped. Every text element should +> have an explicit max-character count and a plan for overflow. + +**Worked example table**: + +| Text Element | English Baseline Length | Max Characters | Expansion Budget | RTL Behavior | Overflow Behavior | Risk | +|--------------|------------------------|----------------|-----------------|--------------|-------------------|------| +| [e.g., Screen title "INVENTORY"] | [9 chars] | [16 chars] | [78%] | [Mirror to right, or center — acceptable] | [Truncate with ellipsis — title is not critical content] | [Low] | +| [e.g., Item name] | [~15 chars avg, max ~35 "Enchanted Dragon Scale Gauntlets"] | [50 chars] | [43%] | [Right-align in RTL layouts] | [Truncate with tooltip showing full name on hover/focus] | [Medium — long fantasy item names are common] | +| [e.g., Item description] | [~80–120 chars] | [200 chars] | [67%] | [Right-align, wrap normally] | [Scroll within Detail Panel — no truncation] | [Low — panel is scrollable] | +| [e.g., Action button "Equip"] | [5 chars] | [14 chars] | [180%] | [Button layout mirrors; text right-aligns] | [Shrink font to 90% minimum, then truncate] | [Medium — "Ausrüsten" in German is 9 chars] | +| [e.g., Category tab "Consumables"] | [11 chars] | [18 chars] | [64%] | [Mirror tab position] | [Abbreviate: "Consum." — define abbreviations per language in loc file] | [High — long localized tab labels are a known problem] | + +--- + +## Section 14 — Acceptance Criteria + +> **Why this section exists**: Acceptance criteria are the contractual definition of +> "done." Without them, implementation is complete when the developer says it is. +> With them, implementation is complete when a QA tester can verify every item on +> this list. Write criteria that a tester can verify independently, without asking the +> designer what they meant. Every criterion should be binary — pass or fail, not +> subjective. + +**Worked example criteria (inventory screen)** — note how each is screen-specific +and independently verifiable: + +- [ ] Navigation between items produces no perceptible frame drop (maintain target framerate ±5fps) +- [ ] All states (Loading, Empty, Populated, Error, Confirmation) render correctly +- [ ] Item grid scrolls smoothly without frame drops when all item slots are populated +- [ ] Inventory changes persist correctly after screen is closed and reopened +- [ ] Screen handles InventoryChanged events fired by other systems while it is open without crashing +- [ ] Stat comparison does not rely on color alone as the sole differentiator +- [ ] Screen reader announces item name and key stats on focus (verify with platform screen reader) + +--- + +## Section 15 — Open Questions + +**Worked example questions**: + +| Question | Owner | Deadline | Resolution | +|----------|-------|----------|-----------| +| [e.g., Should item comparison be automatic (always showing equipped stats) or player-triggered (press Compare)?] | [ui-designer] | [Sprint 4, Day 3] | [Pending] | +| [e.g., Do we support controller cursor (free aim) in the item grid, or d-pad-only grid navigation?] | [lead-programmer + ui-designer] | [Sprint 4, Day 3] | [Pending — depends on ADR-0015 input model decision] | +| [e.g., What is the game's item drop policy — permanent loss or drop-to-world?] | [systems-designer] | [Requires GDD update] | [Blocked on inventory GDD Edge Cases section] | +| [e.g., Maximum inventory size — does the grid have a hard cap or is it infinite-scroll?] | [economy-designer] | [Sprint 3, Day 5] | [Pending] | diff --git a/.claude/docs/templates/hud-design.md b/.claude/docs/templates/hud-design.md index 73172785..848d58e8 100644 --- a/.claude/docs/templates/hud-design.md +++ b/.claude/docs/templates/hud-design.md @@ -1,5 +1,7 @@ # HUD Design: [Game Name] +> Authoring guidance: .claude/docs/templates/guidance/hud-design-guide.md (load per-section as you author — do not read entirely). + > **Status**: Draft | In Review | Approved | Implemented > **Author**: [Name or agent — e.g., ui-designer] > **Last Updated**: [Date] @@ -20,89 +22,34 @@ ## 1. HUD Philosophy -> **Why this section exists**: The HUD design philosophy is not decoration — it is a -> design constraint that every subsequent decision is measured against. Without a -> philosophy, individual elements get added on request ("the quest tracker wants a -> bigger icon") without any principled way to push back. With a philosophy, there is -> a shared, explicit standard. More importantly, the philosophy prevents the HUD from -> slowly growing to cover the game world while each individual addition seemed -> reasonable in isolation. Write this before specifying any elements. - **What is this game's relationship with on-screen information?** -[One paragraph. This is a design statement, not a description of features. Consider -the game's genre, pacing, and player fantasy. A stealth game's HUD philosophy might -be: "The world is the interface. If the player has to look away from the environment -to survive, the HUD has failed." A tactics game might say: "Complete situational -awareness is the game. The HUD is not an overlay — it is the battlefield." - -Reference comparable games if helpful, but describe your specific stance: -Example — diegetic-first action RPG: "We treat screen information as a concession, -not a feature. Every HUD element must earn its pixel space by answering the question: -would the player make demonstrably worse decisions without this information visible? -If the answer is 'they'd adapt,' we put it in the environment instead."] +[One-paragraph design statement — see guide Section 1 for framing and examples] **Visibility principle** — when in doubt, show or hide? -[State the default resolution for ambiguous cases. Options: -- Default to HIDE: information is available on demand (e.g., Dark Souls — no quest tracker, no minimap, stats are in a menu) -- Default to SHOW: players prefer to be informed; cluttered is better than uncertain -- Default to CONTEXTUAL: information appears when it becomes relevant and fades when it does not -Most games benefit from contextual defaults. State your game's default clearly so every element decision is consistent.] +[Default resolution for ambiguous cases: HIDE / SHOW / CONTEXTUAL — see guide] **The Rule of Necessity for this game**: -[Complete this sentence: "A HUD element earns its place when ______________." - -Example: "...the player would have to stop playing to find the same information -elsewhere, or would make meaningfully worse decisions without it." - -Example: "...removing it in playtesting causes measurable frustration or confusion -in more than 25% of testers within the first hour of play." - -This rule is the veto power over feature requests to add HUD elements. Document it -so it can be cited in design reviews.] +["A HUD element earns its place when ______________." — see guide for examples] --- ## 2. Information Architecture -> **Why this section exists**: Before specifying any HUD element's visual design, -> position, or behavior, you must answer a more fundamental question: should this -> information be on the HUD at all? This section is a forcing function — it requires -> you to categorize EVERY piece of information the game world generates and make an -> explicit, intentional decision about how each is presented. "We'll figure that out -> later" is how games end up with 18 elements competing for the player's peripheral -> vision. This table is the master inventory of game information, not just HUD information. +> Categorize EVERY piece of information the game generates — this is the master +> inventory of game information, not just HUD information. See guide Section 2 +> for a worked example covering typical information types. | Information Type | Always Show | Contextual (show when relevant) | On Demand (menu/button) | Hidden (environmental / diegetic) | Reasoning | |-----------------|-------------|--------------------------------|------------------------|----------------------------------|-----------| -| [Health / Vitality] | [X if action game — player needs constant awareness] | [X if exploration game — show only when injured] | [ ] | [ ] | [Example: always visible because health decisions (retreat, heal) must be instant in combat] | -| [Primary resource (mana / stamina / ammo)] | [ ] | [X — show when resource is being consumed or is critically low] | [ ] | [ ] | [Example: contextual because stable resource levels are not decision-relevant] | -| [Secondary resource (currency / materials)] | [ ] | [ ] | [X — check in inventory] | [ ] | [Example: on-demand because resource totals don't affect immediate gameplay decisions] | -| [Minimap / Compass] | [X] | [ ] | [ ] | [ ] | [Example: always visible because navigation decisions are constant during exploration] | -| [Quest objective] | [ ] | [X — show when objective changes or player is near it] | [ ] | [ ] | [Example: contextual — player knows their objective; only remind at key moments] | -| [Enemy health bar] | [ ] | [X — show only during combat encounters] | [ ] | [ ] | [Example: contextual because enemy health is irrelevant outside combat] | -| [Status effects (buffs/debuffs)] | [ ] | [X — show when active] | [ ] | [ ] | [Example: contextual because status effects only affect decisions when present] | -| [Dialogue subtitles] | [X when dialogue is playing] | [ ] | [ ] | [ ] | [Example: always show while dialogue is active — accessibility requirement] | -| [Combo / streak counter] | [ ] | [X — show while combo is active, hide on reset] | [ ] | [ ] | [Example: contextual because it communicates active performance, not baseline state] | -| [Timer] | [ ] | [X — show only in timed sequences] | [ ] | [ ] | [Example: contextual because timers only exist in specific encounter types] | -| [Tutorial prompts] | [ ] | [X — show for first-time situations only] | [ ] | [ ] | [Example: contextual and one-time; never repeat to experienced players] | -| [Score / points] | [ ] | [X — show in score-relevant modes only] | [ ] | [ ] | [Example: contextual by game mode; hidden in modes where score is irrelevant] | -| [XP / level progress] | [ ] | [ ] | [X — available via character screen] | [ ] | [Example: on-demand because progression does not affect in-moment gameplay decisions] | -| [Waypoint / objective marker] | [ ] | [X — show when player is navigating to objective] | [ ] | [ ] | [Example: contextual — suppress during cutscenes, cinematic moments, and free exploration] | +| [Information type — e.g., Health, resource, minimap, quest objective, subtitles, status effects, timer, XP] | [ ] | [ ] | [ ] | [ ] | [Why this presentation category] | --- ## 3. Layout Zones -> **Why this section exists**: The game world is the primary content — the HUD is a -> frame around it. Before placing any element, divide the screen into named zones -> with explicit positions and safe zone margins. This section prevents two failure -> modes: (1) elements placed ad-hoc until the screen is cluttered, and (2) elements -> that overlap platform-required safe zones and get rejected in certification. -> Every element in Section 4 must be assigned to a zone defined here. - ### 3.1 Zone Diagram ``` @@ -139,84 +86,42 @@ so it can be cited in design reviews.] | Zone Name | Screen Position | Safe Zone Compliant | Primary Elements | Max Simultaneous Elements | Notes | |-----------|----------------|---------------------|-----------------|--------------------------|-------| -| [Top Left] | [Top-left corner, within safe margin] | [Yes — 10% from top, 10% from left] | [Health bar, stamina bar, shield bar] | [3] | [Vital status — player's own resources. Priority zone for player state.] | -| [Top Center] | [Top edge, centered horizontally] | [Yes — 10% from top] | [Quest objective, area name (on enter)] | [1 — only one message at a time] | [Use for narrative context, not mechanical information. Keep text minimal.] | -| [Top Right] | [Top-right corner, within safe margin] | [Yes — 10% from top, 10% from right] | [Ammo count, ability cooldowns] | [2] | [Weapon/ability state. Most relevant during active combat.] | -| [Center] | [Screen center ±15%] | [N/A — not a margin zone] | [Crosshair, interaction prompt, hit marker] | [1 active at a time] | [CRITICAL: Nothing persistent here. Only momentary indicators.] | -| [Bottom Left] | [Bottom-left corner, within safe margin] | [Yes — 10% from bottom, 10% from left] | [Minimap, ability icons] | [2] | [Navigation and ability readout. Small, non-intrusive.] | -| [Bottom Center] | [Bottom edge, centered horizontally] | [Yes — 10% from bottom] | [Subtitles, tutorial prompts] | [2 — subtitle + tutorial may coexist] | [Highest-priority accessibility zone. Never place other elements here.] | -| [Bottom Right] | [Bottom-right corner, within safe margin] | [Yes — 10% from bottom, 10% from right] | [Notification toasts, pick-up feedback] | [3 stacked] | [Transient notifications. Stack vertically. Oldest disappears first.] | +| [Zone] | [Position] | [Yes/No + margins] | [Elements assigned] | [N] | [Zone purpose] | **Safe zone margins by platform**: | Platform | Top | Bottom | Left | Right | Notes | |----------|-----|--------|------|-------|-------| -| [PC — windowed] | [0% — no safe zone required] | [0%] | [0%] | [0%] | [But respect minimum resolution — elements must not crowd at 1280x720] | -| [PC — fullscreen] | [3%] | [3%] | [3%] | [3%] | [Slight margin for 4K TV-connected PCs] | -| [Console — TV] | [10%] | [10%] | [10%] | [10%] | [Action-safe zone for broadcast-spec TVs. Some TVs overscan beyond this.] | -| [Steam Deck] | [5%] | [5%] | [5%] | [5%] | [Small screen; safe zone is smaller but crowding risk is higher] | -| [Mobile — portrait] | [15% top] | [10% bottom] | [5%] | [5%] | [15% top avoids notch/camera cutout on most devices] | -| [Mobile — landscape] | [5%] | [5%] | [15% left] | [15% right] | [Thumb placement on landscape — side zones are obscured by hands] | +| [Platform] | [%] | [%] | [%] | [%] | [Platform-specific notes] | --- ## 4. HUD Element Specifications -> **Why this section exists**: Each HUD element needs its own specification to be -> built correctly. Ad-hoc implementation of HUD elements produces inconsistent -> sizing, mismatched update frequencies, missing urgency states, and accessibility -> failures. This section is the implementation brief for every element — fill it -> completely before any element moves into development. - ### 4.1 Element Overview Table > One row per HUD element. This is the master inventory for implementation planning. | Element Name | Zone | Always Visible | Visibility Trigger | Data Source | Update Frequency | Max Size (% screen W) | Min Readable Size | Overlap Priority | Accessibility Alt | |-------------|------|---------------|-------------------|-------------|-----------------|----------------------|------------------|-----------------|------------------| -| [Health Bar] | [Top Left] | [Yes] | [N/A] | [PlayerStats] | [On value change] | [20%] | [120px wide] | [1 — highest] | [Numerical text label showing current/max: "80/100"] | -| [Stamina Bar] | [Top Left] | [No — context] | [Show when consuming stamina; hide 3s after full] | [PlayerStats] | [Realtime during use] | [15%] | [80px wide] | [2] | [Numerical label, or hide if full (accessible assumption)] | -| [Shield Indicator] | [Top Left] | [No — context] | [Show when shield is active or recently hit] | [PlayerStats] | [On value change] | [20%] | [120px wide] | [3] | [Numerical label. Must not use color alone — add shield icon.] | -| [Ammo Counter] | [Top Right] | [No — context] | [Show when weapon is equipped; hide when unarmed] | [WeaponSystem] | [On fire / on reload] | [10%] | ["88/888" readable at game's min resolution] | [4] | [Text-only fallback: "32 / 120"] | -| [Minimap] | [Bottom Left] | [Yes] | [N/A — but suppressed in cinematic mode] | [NavigationSystem] | [Realtime] | [18%] | [150x150px] | [5] | [Cardinal direction compass strip as fallback; must be toggleable] | -| [Quest Objective] | [Top Center] | [No — context] | [Show on objective change; show when near objective location; hide after 5s] | [QuestSystem] | [On event] | [30%] | [Legible at body text size] | [6] | [Read aloud on objective change via screen reader] | -| [Crosshair] | [Center] | [No — context] | [Show when ranged weapon equipped; hide in melee or unarmed] | [WeaponSystem / AimSystem] | [Realtime] | [3%] | [12px diameter minimum] | [1 — center zone priority] | [Reduce motion: static crosshair only. Option to enlarge.] | -| [Interaction Prompt] | [Center] | [No — context] | [Show when player is within interaction range of an interactive object] | [InteractionSystem] | [On enter/exit interaction range] | [15%] | [24px icon + readable text] | [2 — center zone] | [Text description of interaction always present, not icon-only] | -| [Subtitles] | [Bottom Center] | [No — always on when dialogue plays, if setting enabled] | [Show during any voiced line or ambient dialogue] | [DialogueSystem] | [Per dialogue line] | [60%] | [Minimum 24px font] | [1 — highest in zone] | [This IS the accessibility feature — see Section 8 for subtitle spec] | -| [Damage Numbers] | [World-space / anchored to entity] | [No — context] | [Show on any damage event; duration 800ms] | [CombatSystem] | [On event] | [5% per number] | [18px minimum] | [3] | [Option to disable; numbers can overwhelm for photosensitive players] | -| [Status Effect Icons] | [Top Left — below health bar] | [No — context] | [Show when any status effect is active on player] | [StatusSystem] | [On effect add/remove] | [3% per icon] | [24px per icon] | [3] | [Icon + text label on hover/focus. Never icon-only.] | -| [Notification Toast] | [Bottom Right] | [No — event-driven] | [On loot, XP gain, achievement, quest update] | [Multiple — see Section 6] | [On event] | [25%] | [Legible at body text size] | [7 — lowest] | [Queued; never overlapping. Read by screen reader if subtitle mode on.] | +| [Element] | [Zone] | [Yes/No] | [Trigger or N/A] | [Owning system] | [When it updates] | [%] | [px] | [N] | [Non-visual/non-color alternative] | ### 4.2 Element Detail Blocks > For each element in the table above, write a detail block. Copy and complete -> one block per element. +> one block per element. See guide Section 4 for completed Health Bar and +> Minimap examples. --- -**Health Bar** +**[Element Name]** -- Visual description: [Horizontal fill bar. Left-to-right fill direction. Segmented at 25/50/75% to aid reading at a glance. Background: dark semi-transparent (40% opacity). Fill color: context-dependent — see Urgency States.] -- Data displayed: [Current HP as fill percentage. Numerical value displayed as text below bar at all times: "80 / 100".] -- Update behavior: [Bar fill decreases or increases smoothly using a lerp over 150ms per change. Large damage (>25% single hit) triggers a brief flash (1 frame white, then drain).] -- Urgency states: - - Normal (>50% HP): [Green fill, no special behavior] - - Caution (25–50% HP): [Yellow fill, low warning pulse every 4 seconds] - - Critical (<25% HP): [Red fill, persistent slow pulse (1 Hz), vignette appears at screen edges] - - Zero (0% HP): [Bar empties and turns grey; death state begins] -- Interaction: [Display only. Not interactive. Player cannot click, hover, or focus this element as an action target.] -- Player customization: [Opacity adjustable (see Section 7 Tuning Knobs). Can be repositioned to any corner by player in accessibility settings.] - ---- - -**Minimap** - -- Visual description: [Circular mask, radius = 75px at reference resolution 1920x1080. Player icon at center. North always up unless player has unlocked "Rotate minimap" setting. Range = configurable, default 80 world units radius.] -- Data displayed: [Player position, nearby enemies (if detection perk unlocked), quest markers within range, points of interest icons, traversal obstacles (walls, drops).] -- Update behavior: [Realtime. Updates every frame. Enemy icons fade in/out as they enter/leave detection range over 300ms.] -- Urgency states: [None for the map itself. Enemy icons turn red when they are in combat-alert state.] -- Interaction: [Not interactive in-game. Press dedicated Map button to open the full map screen (separate UX spec).] -- Player customization: [Size: S/M/L (70/90/110px radius). Opacity: 30–100%. Rotation: locked-north or player-relative. Can be disabled entirely (compass strip shows as fallback).] +- Visual description: [Shape, fill direction, background, color behavior] +- Data displayed: [Values shown, including any numerical text labels] +- Update behavior: [Animation/lerp timing, flash behavior on large changes] +- Urgency states: [Per-threshold visual states, e.g., Normal / Caution / Critical / Zero] +- Interaction: [Interactive or display-only] +- Player customization: [Opacity, repositioning, size options] --- @@ -226,58 +131,32 @@ so it can be cited in design reviews.] ## 5. HUD States by Gameplay Context -> **Why this section exists**: The HUD is not a static overlay — it is a dynamic -> system that must adapt to what the player is doing. A HUD designed only for -> standard gameplay will look wrong in cutscenes, feel cluttered in exploration, -> and occlude critical information in boss fights. This section defines the -> transformations the HUD undergoes in each gameplay context. It is also the spec -> for the system that manages HUD visibility — the HUD state machine. +> Define the HUD transformation for every gameplay context — at minimum: +> exploration, combat, dialogue/cutscene, and paused/menu. See guide Section 5 +> for a worked example covering nine contexts. | Context | Elements Shown | Elements Hidden | Elements Modified | Transition Into This State | |---------|---------------|-----------------|------------------|---------------------------| -| [Exploration — no threats] | [Minimap, Quest Objective (faded, 60%), Subtitles (if active)] | [Ammo Counter, Crosshair, Damage Numbers, Status Effects (if none active)] | [Health Bar fades to 40% opacity — visible but not dominant] | [Fade transition, 500ms, when no enemies detected for 10s] | -| [Combat — active threat] | [Health Bar (full opacity), Stamina Bar (when used), Ammo Counter, Crosshair, Damage Numbers, Status Effects, Enemy Health Bars] | [Quest Objective (temporarily hidden), Notification Toasts (paused queue)] | [Minimap scales down 15% and raises opacity to 100%] | [Immediate snap in on first enemy detection — no fade. Combat readiness requires instant info.] | -| [Dialogue / Cutscene] | [Subtitles, Dialogue speaker name] | [All gameplay HUD elements: health, ammo, minimap, crosshair, damage numbers] | [N/A] | [All gameplay elements fade out over 300ms when cutscene flag is set] | -| [Cinematic (scripted camera sequence)] | [Subtitles only] | [Everything else including speaker name] | [Letterbox bars appear (if applicable to this game's style)] | [Immediate on cinematic flag; letterbox slides in from top/bottom over 400ms] | -| [Inventory / Menu open] | [None — inventory renders full-screen or as overlay] | [All HUD elements] | [Game world visible but paused behind inventory screen] | [All HUD elements hide over 150ms as menu opens] | -| [Death / Respawn pending] | [Death screen overlay — separate spec] | [All gameplay HUD elements] | [Screen desaturates and darkens over 800ms] | [Death state begins when HP reaches 0 — HUD elements fade over 600ms] | -| [Loading / Transition] | [Loading indicator, tip text] | [All gameplay HUD elements] | [N/A] | [Instant on level transition trigger] | -| [Tutorial — new mechanic] | [Standard context HUD + Tutorial Prompt overlay] | [Nothing additional hidden] | [Tutorial prompt dims background subtly to draw attention to prompt] | [Tutorial system fires ShowTutorial event; prompt fades in over 200ms] | -| [Boss Encounter] | [Boss health bar appears (large, bottom of screen or top center), all combat elements] | [Quest Objective] | [Boss bar renders in a distinct visual style — must not be confused with player health] | [Boss health bar slides in on boss encounter trigger over 400ms] | +| [Context — e.g., Exploration / Combat / Dialogue / Cinematic / Menu open / Death / Loading / Tutorial / Boss] | [Shown] | [Hidden] | [Modified] | [Transition timing + trigger] | --- ## 6. Information Hierarchy -> **Why this section exists**: Not all HUD information is equally important. When -> screen space is limited, when the player is under high stress, or when elements -> compete for the same zone, there must be a principled priority order that governs -> which elements survive and which get suppressed. This section formalizes that -> hierarchy so it can be enforced systematically and not just "feels obvious" decisions -> made at implementation time. +> Assign every element a priority tier (MUST KEEP / SHOULD KEEP / CAN HIDE / +> ALWAYS HIDE) with reasoning. See guide Section 6 for a worked example. | Element | Priority Tier | Reasoning | What Replaces It If Hidden | |---------|--------------|-----------|---------------------------| -| [Subtitles] | [MUST KEEP — never hide during dialogue] | [Accessibility requirement. Legal requirement in some markets. Story clarity.] | [N/A — nothing replaces subtitles] | -| [Health Bar] | [MUST KEEP — during any state where the player can be damaged] | [Without health visibility, survival decisions become impossible] | [Auditory cues (heartbeat, breathing) supplement but do not replace] | -| [Crosshair] | [MUST KEEP — while aiming with a ranged weapon] | [Targeting without a crosshair is a precision failure, not a difficulty feature] | [Alternative: dot-only mode for minimalists; never fully hidden while aiming] | -| [Interaction Prompt] | [MUST KEEP — when player is in interaction range] | [Without it, interactive objects are invisible to the player] | [Environmental visual cues can supplement but interaction affordance must be explicit] | -| [Ammo Counter] | [SHOULD KEEP] | [Low ammo decisions (switch weapon, reload) require awareness; can be contextual] | [Auditory "click" on empty chamber is acceptable fallback for experienced players] | -| [Minimap] | [SHOULD KEEP] | [Navigation requires spatial awareness; loss forces repeated map opens] | [Compass strip (simplified directional indicator) is acceptable fallback] | -| [Status Effects] | [SHOULD KEEP — while active] | [Active debuffs change what actions are viable; invisible debuffs feel unfair] | [Character animation states can partially communicate status effects (limping, sparks)] | -| [Quest Objective] | [CAN HIDE] | [Player can hold objective in memory for extended periods; contextual is correct default] | [Player remembers objective from context] | -| [Damage Numbers] | [CAN HIDE] | [Feedback element, not decision-critical. Many players turn these off.] | [Hit sounds and enemy reactions communicate hit registration] | -| [Notification Toasts] | [CAN HIDE in high-intensity moments] | [Mid-combat "You gained 50 XP" is noise, not signal. Queue and show after combat.] | [Queue held and released when combat ends] | -| [Combo Counter] | [ALWAYS HIDE when combo resets or player is not attacking] | [Stale combo information is actively misleading] | [N/A — simply hidden] | +| [Element] | [MUST KEEP / SHOULD KEEP / CAN HIDE / ALWAYS HIDE] | [Why] | [Fallback channel or N/A] | --- ## 7. Visual Budget -> **Why this section exists**: Without explicit budget constraints, HUD elements -> accumulate until the game world is nearly invisible. These numbers are hard limits, -> not guidelines. Every element addition that would breach a limit requires explicit -> approval and must displace or reduce an existing element. +> These numbers are hard limits, not guidelines. Every element addition that +> would breach a limit requires explicit approval and must displace or reduce an +> existing element. See guide Section 7 for how to apply the budgets. | Budget Constraint | Limit | Measurement Method | Current Estimate | Status | |------------------|-------|--------------------|-----------------|--------| @@ -289,60 +168,37 @@ so it can be cited in design reviews.] | Maximum opacity for HUD background panels | [65%] | [Opacity of any panel behind HUD text — must preserve world visibility through panel] | [TBD] | [To verify] | | Minimum HUD element size at minimum supported resolution | [40px for icons, 18px for text] | [Measure at lowest target resolution] | [TBD] | [To verify] | -> **How to apply these budgets**: For every new HUD element proposed during -> production, require the proposer to state (1) which budget line it affects, -> (2) what the new total will be, and (3) what existing element will be reduced or -> made contextual to stay within budget. "It's a small icon" is not an analysis. - --- ## 8. Feedback & Notification Systems -> **Why this section exists**: Notifications are the most frequently-added and -> worst-controlled part of most HUDs. Every system wants to tell the player -> something. Without explicit rules about notification priority, stacking limits, -> and queue behavior, the notification zone becomes a firehose of overlapping -> toasts that players learn to ignore entirely. This section establishes the -> notification contract for all systems. +> One row per notification type. Every notification must have queue/priority +> behavior defined. See guide Section 8 for a worked example table and queue rules. | Notification Type | Trigger System | Screen Position | Duration (ms) | Animation In / Out | Max Simultaneous | Priority | Queue Behavior | Dismissible? | |------------------|---------------|-----------------|--------------|-------------------|-----------------|----------|---------------|-------------| -| [Item Pickup] | [InventorySystem] | [Bottom Right — toast] | [2000] | [Slide in from right 200ms / fade out 300ms] | [3 stacked] | [Low] | [FIFO queue; older toasts pushed up as new ones enter] | [No — auto-dismiss] | -| [XP Gain] | [ProgressionSystem] | [Bottom Right — toast, below item toasts] | [1500] | [Fade in 150ms / fade out 300ms] | [1 — XP messages merge: "XP +150"] | [Very Low — suppress during combat, queue for post-combat] | [Combat-aware queue] | [No] | -| [Level Up] | [ProgressionSystem] | [Center screen — persistent until dismissed] | [Persistent — requires input to dismiss] | [Scale up from 80% + fade in 400ms] | [1] | [High — interrupts normal toasts] | [Pauses all other notifications until dismissed] | [Yes — any input] | -| [Quest Update] | [QuestSystem] | [Top Center] | [4000] | [Slide down from top 250ms / fade out 400ms] | [1 — top center is single-message zone] | [Medium] | [If quest update arrives while previous is visible, extend duration by 2000ms; do not stack] | [No] | -| [Objective Complete] | [QuestSystem] | [Top Center] | [3000] | [Same as Quest Update but with additional completion sound] | [1] | [Medium-High — preempts Quest Update] | [Preempts any queued top-center message] | [No] | -| [Critical Warning (low health, hazard)] | [CombatSystem / EnvironmentSystem] | [Screen edge vignette + text at center-bottom] | [Persistent while condition active] | [Fade in 200ms; fades out 500ms when condition clears] | [1 per warning type] | [Critical — never suppressed] | [Renders immediately, bypasses all queues] | [No] | -| [Achievement Unlocked] | [AchievementSystem] | [Bottom Right — distinct from item toasts] | [4000] | [Slide in from right with icon expansion 300ms / fade out 400ms] | [1] | [Low] | [Queues behind item toasts; never more than one achievement toast at a time] | [No] | -| [Hint / Tutorial] | [TutorialSystem] | [Bottom Center] | [Persistent — until player performs the action or dismisses] | [Fade in 300ms] | [1] | [Medium] | [Only one tutorial hint at a time; queue others] | [Yes — B button / Esc] | +| [Type] | [System] | [Zone] | [ms] | [In / out animation + timing] | [N] | [Priority] | [Queue/merge/preempt behavior] | [Yes/No] | **Notification queue rules**: -1. Combat-aware queue: notifications tagged as Low priority are queued, not displayed, when the player is in combat state. The queue is flushed in a batch when the player exits combat, with a max of 3 items displayed in sequence. -2. Merge rule: identical notification types that fire within 500ms of each other are merged into a single notification with a combined value (e.g., "Item Pickup x3" rather than three separate toasts). -3. Critical notifications (health warning, environmental hazard) are never queued, never merged, and always displayed immediately regardless of combat state or existing notifications. +1. [Combat-aware queue rule — which priorities are held during combat and how the queue flushes] +2. [Merge rule — when identical notification types merge into one] +3. [Critical bypass rule — which notifications are never queued, never merged] --- ## 9. Platform Adaptation -> **Why this section exists**: A HUD designed at 1920x1080 on a monitor may be -> illegible on a 55-inch TV at 4K, broken at 1280x720 on Steam Deck, or hidden -> behind a notch on mobile. Platform adaptation is not optional post-ship work — -> it is a design requirement that must be specified before implementation so the -> architecture can support it from the start. Every platform listed here requires -> explicit layout testing before certification. +> Every target platform requires explicit layout testing before certification. +> See guide Section 9 for a worked example. | Platform | Safe Zone | Resolution Range | Input Method | HUD-Specific Notes | |----------|-----------|-----------------|-------------|-------------------| -| [PC — Windows, 1920x1080 reference] | [3% margin] | [1280x720 min to 3840x2160 max] | [Mouse + keyboard, controller optional] | [HUD must scale correctly at all resolutions. Test at 1280x720 — minimum before cert. Consider ultrawide (21:9) — minimap must not stretch.] | -| [PC — Steam Deck, 1280x800] | [5% margin] | [Fixed 1280x800] | [Controller + touchscreen] | [Smaller screen means minimum text sizes are critical. Test ALL elements at this resolution. Touch targets irrelevant (controller-only by default).] | -| [PlayStation 5 / Xbox Series X] | [10% margin] | [1080p to 4K] | [Controller] | [Console certification requires TV safe zone compliance. Action-safe is 90% of screen area. Test on a real TV, not a monitor — overscan behavior differs.] | -| [Mobile — iOS / Android] | [15% top, 10% other sides] | [360x640 min to 414x896 common] | [Touch] | [Notch/camera cutout avoidance at top. Bottom home indicator zone avoidance. Portrait and landscape layouts may differ significantly — specify both.] | +| [Platform] | [Margin] | [Min–max resolution] | [Input] | [Platform-specific HUD constraints] | **HUD repositionability requirement**: Players must be able to reposition at minimum the following elements using an in-game HUD layout editor (required for accessibility compliance on console): -- Health bar -- Minimap -- Ability bar (if present) +- [Health bar] +- [Minimap] +- [Ability bar (if present)] Repositioning saves to player profile, not to a single slot. Applies across play sessions. @@ -350,48 +206,30 @@ Repositioning saves to player profile, not to a single slot. Applies across play ## 10. Accessibility — HUD Specific -> **Why this section exists**: HUD accessibility failures are the most visible -> accessibility failures in games — players encounter the HUD in every session, -> in every gameplay moment. Color-blind failures, illegible text at minimum scale, -> and inability to disable distracting animations are among the top accessibility -> complaints in game reviews. This section defines HUD-specific requirements; refer -> to the project's `docs/accessibility-requirements.md` for the full project standard. +> HUD-specific requirements only — refer to the project's +> `design/accessibility-requirements.md` for the full project standard. ### 10.1 Colorblind Modes | Element | Color-Only Information Risk | Colorblind Mode Fix | |---------|----------------------------|---------------------| -| [Health bar fill] | [Red = low health uses red/green distinction] | [Add icon pulse + vignette as non-color indicators. Red fill is supplemental, not sole indicator.] | -| [Damage numbers] | [Red = taken, green = healed] | [Add minus (-) prefix for damage, plus (+) for healing. Symbols, not color.] | -| [Enemy health bars] | [If colored by faction or threat level] | [Add text label or icon badge for faction/threat level. Never color-only.] | -| [Status effect icons] | [If icon tint communicates status type] | [All status icons must have distinct shapes, not just distinct colors. Shape encodes meaning; color is secondary.] | -| [Minimap icons] | [If player vs. enemy vs. objective distinguished by color] | [Distinct icon shapes: circle = player, triangle = enemy, star = objective. Color supplements shape.] | +| [Element] | [Where color alone carries meaning] | [Non-color alternative: shape, icon, symbol, text] | ### 10.2 Text Scaling -[Describe what happens when the player sets the UI text scale to 150% (the maximum required for your Accessibility Tier). Which elements reflow? Which elements clip? Which elements are architecturally blocked from scaling (e.g., fixed-size canvases)? - -Example: "Health bar numerical label grows with text scale — bar expands slightly to accommodate. Quest objective text wraps at 150% scale — verify Top Center zone can accommodate two-line objectives. Damage numbers do not scale (they are world-space, not screen-space) — this is an accepted limitation documented here."] +[Describe what happens at 150% UI text scale: which elements reflow, clip, or are architecturally blocked from scaling — see guide Section 10 for an example narrative] **Text scaling test matrix**: | Element | 100% (baseline) | 125% | 150% | Overflow behavior | |---------|----------------|------|------|-------------------| -| [Health bar label] | [Pass] | [Pass] | [TBD] | [Bar expands; does not overlap stamina bar] | -| [Quest objective text] | [Pass] | [TBD] | [TBD] | [Wraps to second line; zone height expands] | -| [Notification toast text] | [Pass] | [TBD] | [TBD] | [Toast width expands to max 35% screen width, then wraps] | -| [Subtitle text] | [Pass] | [TBD] | [TBD] | [Dedicated subtitle zone — must accommodate scale] | +| [Element] | [Pass/Fail/TBD] | [Pass/Fail/TBD] | [Pass/Fail/TBD] | [What happens on overflow] | ### 10.3 Motion Sensitivity | Animation / Motion Element | Severity | Disabled by Reduced Motion Setting? | Replacement Behavior | |---------------------------|----------|-------------------------------------|---------------------| -| [Health bar low-HP pulse] | [Mild] | [Yes] | [Solid fill, no pulse. Vignette remains as it is less likely to trigger sensitivity.] | -| [Screen edge vignette] | [Moderate] | [Optional — separate toggle] | [Replace with static darkened corners at 30% opacity] | -| [Damage numbers float upward] | [Mild] | [Yes] | [Instant appear/disappear in place, no float] | -| [Notification toast slide-in] | [Mild] | [Yes] | [Instant appear at final position] | -| [Level up center animation] | [High] | [Yes — required] | [Static level up card, no scale animation, no particle effects] | -| [Combo counter scale pulse] | [Mild] | [Yes] | [Number increments without scale animation] | +| [Animated element] | [Mild/Moderate/High] | [Yes/No/Optional] | [Static replacement] | ### 10.4 Subtitles Specification @@ -400,15 +238,15 @@ Example: "Health bar numerical label grows with text scale — bar expands sligh > implementation discretion. - **Default setting**: [ON or OFF — document your game's default and the rationale. Industry standard is ON by default.] -- **Position**: Bottom Center zone, centered horizontally, above the bottom safe zone margin -- **Max characters per line**: [42 characters — the readable limit for subtitle lines at minimum text size on TV viewing distance] -- **Max simultaneous lines**: [2 lines before scrolling — do not display more than 2 lines at once] -- **Speaker identification**: [Speaker name displayed in color or above subtitle text — never rely on color alone; add colon prefix: "ARIA: The door is locked."] -- **Background**: [Semi-transparent black panel, 70% opacity, behind all subtitle text — ensures contrast against any game world background] -- **Font size minimum**: [24px at 1080p reference — scales with text scale setting] -- **Line break behavior**: [Break at natural language pause points — before conjunctions, after commas, never mid-word] -- **Subtitle persistence**: [Each subtitle line holds for the duration of the spoken line plus 300ms after it ends — never disappear while audio is still playing] -- **Non-dialogue captions**: [Document whether ambient sounds, music descriptions, and sound effects are captioned — e.g., "[tense music]", "[explosion in the distance]" — and where these appear if different from dialogue subtitles] +- **Position**: [Zone, alignment, relationship to safe zone margin] +- **Max characters per line**: [e.g., 42 characters — the readable limit at minimum text size on TV viewing distance] +- **Max simultaneous lines**: [e.g., 2 lines before scrolling] +- **Speaker identification**: [Name display method — never rely on color alone] +- **Background**: [Panel treatment ensuring contrast against any game world background] +- **Font size minimum**: [px at reference resolution — scales with text scale setting] +- **Line break behavior**: [Break at natural language pause points — never mid-word] +- **Subtitle persistence**: [Hold duration relative to spoken line — never disappear while audio is still playing] +- **Non-dialogue captions**: [Whether ambient sounds, music, and SFX are captioned, and where these appear] ### 10.5 HUD Opacity and Visibility Controls @@ -428,31 +266,20 @@ The following player-adjustable settings must be available from the Accessibilit ## 11. Tuning Knobs -> **Why this section exists**: HUD behavior should be data-driven to the same degree -> as gameplay systems. Values that are hardcoded are values that require an engineer -> to change. Values that are in config can be tuned by a designer or adjusted for -> player preferences. Document all tunable parameters before implementation so the -> programmer knows which values to externalize. +> Document all tunable parameters before implementation so the programmer knows +> which values to externalize. See guide Section 11 for a worked example. | Parameter | Current Value | Range | Effect of Increase | Effect of Decrease | Player Adjustable? | Notes | |-----------|-------------|-------|-------------------|-------------------|-------------------|-------| -| [Notification display duration (default)] | [2000ms] | [500ms – 5000ms] | [Toasts persist longer — less likely to be missed, more screen clutter] | [Toasts disappear faster — cleaner, higher miss risk] | [No — but player can adjust verbosity level] | [Per-type overrides in Section 8 take precedence] | -| [Notification queue max size] | [8] | [3 – 15] | [More messages preserved but queue takes longer to clear] | [Older messages dropped earlier] | [No] | [Expand if playtesting reveals important messages being lost] | -| [Health bar low-HP pulse frequency] | [1 Hz] | [0.5 – 2 Hz] | [More urgent feeling — can become fatiguing] | [Calmer — may fail to communicate urgency] | [No — but Reduced Motion disables it] | [Linked to accessibility setting] | -| [Combat HUD reveal duration] | [0ms (instant)] | [0 – 300ms] | [Softer reveal — feels less jarring] | [Instant — highest responsiveness] | [No] | [Keep at 0ms — combat information must be instant] | -| [Exploration HUD fade-out delay] | [10000ms (10s after last threat)] | [3000 – 30000ms] | [HUD fades sooner — cleaner exploration] | [HUD stays longer — more reassurance] | [No] | [Tune based on playtest; 10s is a starting estimate] | -| [Minimap range (world units visible)] | [80] | [40 – 200] | [More map context visible] | [Tighter local view] | [Yes — Small/Medium/Large preset] | [Exposed as S/M/L, not raw unit value] | -| [Minimap size (px radius at 1080p)] | [75] | [50 – 120] | [Larger map, more screen space consumed] | [Smaller, less intrusive] | [Yes — S/M/L preset] | [Three sizes exposed to player] | -| [Damage number duration (ms)] | [800] | [400 – 1500] | [Numbers linger longer — easier to read, more cluttered] | [Numbers clear faster — cleaner, harder to parse] | [No] | [Tune based on visual noise in dense combat] | -| [Global HUD opacity] | [100%] | [0 – 100%] | [Fully visible] | [Fully hidden] | [Yes — opacity slider in Accessibility settings] | [0% = full HUD off; some players prefer this] | +| [Parameter] | [Value] | [Min–max] | [Effect] | [Effect] | [Yes/No + how exposed] | [Tuning notes] | --- ## 12. Acceptance Criteria -> **Why this section exists**: These criteria are the certification checklist for the -> HUD. Every item must pass before the HUD can be marked Approved. QA must be able -> to verify each item independently. +> These criteria are the certification checklist for the HUD. Every item must +> pass before the HUD can be marked Approved. QA must be able to verify each +> item independently. **Layout & Visibility** - [ ] All HUD elements are within platform safe zone margins on all target platforms @@ -495,11 +322,9 @@ The following player-adjustable settings must be available from the Accessibilit ## 13. Open Questions > Track unresolved design questions here. All questions must be resolved before -> the HUD design document can be marked Approved. +> the HUD design document can be marked Approved. See guide Section 13 for +> example questions. | Question | Owner | Deadline | Resolution | |----------|-------|----------|-----------| -| [e.g., Should the minimap show enemy positions by default, or only after a detection skill is unlocked?] | [systems-designer + ui-designer] | [Sprint 5, Day 2] | [Pending — depends on progression GDD decision] | -| [e.g., Does the game have a boss health bar, or do bosses use the standard enemy health bar? Bosses need a visually distinct treatment if they are significantly more important than normal enemies.] | [game-designer] | [Sprint 5, Day 1] | [Pending] | -| [e.g., Damage numbers: diegetic (floating in world space, occluded by geometry) or screen space (always readable, overlaid on HUD layer)?] | [ui-designer + lead-programmer] | [Sprint 4, Day 5] | [Pending — architecture decision affects rendering layer choice] | -| [e.g., Mobile portrait vs. landscape: does the game support both orientations? If yes, each requires its own zone layout.] | [producer] | [Sprint 3, Day 3] | [Pending — platform scope decision required first] | +| [Add question] | [Owner] | [Deadline] | [Resolution] | diff --git a/.claude/docs/templates/interaction-pattern-library.md b/.claude/docs/templates/interaction-pattern-library.md index 355d9414..e8a927ad 100644 --- a/.claude/docs/templates/interaction-pattern-library.md +++ b/.claude/docs/templates/interaction-pattern-library.md @@ -1,5 +1,7 @@ # Interaction Pattern Library: [Game Title] +> Authoring guidance: .claude/docs/templates/guidance/interaction-pattern-library-guide.md (load per-section as you author — do not read entirely). + > **Status**: Draft | Stable | Under Revision > **Author**: [ux-designer] > **Last Updated**: [Date] @@ -8,7 +10,7 @@ > **UI Framework**: [Godot Control nodes / Unity UI Toolkit / Unreal UMG] > **Related Documents**: > - `design/art/art-bible.md` — visual standards (colors, typography, iconography) -> - `docs/accessibility-requirements.md` — accessibility commitments per feature +> - `design/accessibility-requirements.md` — accessibility commitments per feature > - `docs/ux/ux-spec-[screen].md` — individual screen specs that reference patterns > **Why this document exists**: Every UI screen spec should be able to say @@ -106,43 +108,29 @@ update this document before or simultaneously with any implementation change. ## Standard Control Patterns +> Full reference specifications for every pattern in this section (state tables +> with timing/audio values, accessibility requirements, implementation notes) +> are in `.claude/docs/templates/guidance/interaction-pattern-library-guide-standard-controls.md` — +> load only the pattern(s) currently being specified. + --- #### Button (Primary) **Category**: Input **Status**: Draft -**When to Use**: The single most important action on a screen. "Start Game," -"Confirm," "Accept," "Buy." There should be at most one Primary button visible -at a time. It is the answer to "what does the player most likely want to do here?" -**When NOT to Use**: Alternative or secondary actions; destructive actions that -require confirmation before the consequence is irreversible; any action that is -not the primary intent of the screen. +**When to Use**: [The single most important action on a screen — at most one visible at a time] +**When NOT to Use**: [Alternative/secondary actions; destructive actions; anything not the screen's primary intent] **Interaction Specification**: | State | Visual | Input | Response | Duration | Audio | |-------|--------|-------|----------|----------|-------| -| Default | Full-opacity fill, primary color from art-bible. Label centered. | — | — | — | — | -| Hovered (mouse) | Brightness +15%, subtle scale 1.03x, cursor changes to pointer | Mouse over element | Transition from Default | 80ms ease-out | [UI hover sound — see Sound Standards] | -| Focused (keyboard/gamepad) | Focus ring visible (2px, offset 3px, high contrast color). Same brightness as Hovered. | Tab / D-pad navigation | Transition from Default | 80ms ease-out | [UI focus sound — same as hover] | -| Pressed | Scale 0.97x, brightness -10% | Click / Enter / A (Xbox) / Cross (PS) | Action fires on press-up, not press-down. Scale on press-down. | 60ms ease-in for press; 80ms ease-out on release | [UI confirm sound] | -| Disabled | 40% opacity, no pointer cursor, no hover state | — | No response | — | — | -| Loading (post-press) | Replace label with spinner. Button remains at pressed scale, disabled state. | — | Prevents double-submission | Duration of async operation | — | +| [Default / Hovered / Focused / Pressed / Disabled / Loading] | [Visual treatment] | [Input trigger] | [Response] | [Duration + easing] | [Sound category] | -**Accessibility**: -- Keyboard: Tab to focus, Enter or Space to activate. Must be reachable from any other interactive element on screen via Tab sequence. -- Gamepad: D-pad or left stick to navigate focus to button. A (Xbox) / Cross (PS) to activate. Focus must be placed on Primary button by default when screen opens. -- Screen reader: Button must expose accessible name matching visible label. Role: "button." State: "dimmed" when disabled. Activation announcement: "[Label] button — [result of action, if known]." -- Colorblind: Do not rely on color alone to distinguish Primary from Secondary. Primary uses higher visual weight (fill vs. outline, or larger size) in addition to color differentiation. -- Minimum touch target: 44x44pt (iOS HIG) / 48x48dp (Android). Apply even on PC if touch support is possible. +**Accessibility**: [Keyboard, gamepad, screen reader, colorblind, and touch-target requirements] -**Implementation Notes**: -[Godot: Extend `Button` control. Override `_draw()` for custom states rather than -modifying themes mid-state. Use `focus_mode = FOCUS_ALL` to ensure keyboard -focusability. Set `mouse_default_cursor_shape = CURSOR_POINTING_HAND`. For the -scale animation, use a Tween on the `scale` property of the button's parent -Control — scaling the Button itself can clip children.] +**Implementation Notes**: [Engine-specific guidance] --- @@ -150,30 +138,18 @@ Control — scaling the Button itself can clip children.] **Category**: Input **Status**: Draft -**When to Use**: Alternative or cancel action. "Back," "Cancel," "Skip," "Maybe -Later." Lower visual weight than Primary — it should recede visually, not compete. -**When NOT to Use**: Destructive actions (use Button (Destructive)). The most -important action on the screen (use Button (Primary)). +**When to Use**: [Alternative or cancel action — lower visual weight than Primary] +**When NOT to Use**: [Destructive actions; the screen's most important action] **Interaction Specification**: | State | Visual | Input | Response | Duration | Audio | |-------|--------|-------|----------|----------|-------| -| Default | Outlined style (border only, transparent fill), secondary color. Slightly smaller or lower weight than Primary. | — | — | — | — | -| Hovered | Background fill appears at 15% opacity. Border brightens. Scale 1.02x. | Mouse over | Transition from Default | 80ms ease-out | [UI hover sound — softer variant than Primary] | -| Focused | Focus ring, same specification as Primary. | Tab / D-pad | Transition from Default | 80ms ease-out | [UI focus sound] | -| Pressed | Scale 0.97x, fill opacity increases to 30% | Click / Enter / B (Xbox) / Circle (PS) on focused state | Action fires on press-up | 60ms ease-in | [UI cancel/back sound] | -| Disabled | 40% opacity | — | No response | — | — | +| [State] | [Visual treatment] | [Input trigger] | [Response] | [Duration + easing] | [Sound category] | -**Accessibility**: Same requirements as Button (Primary). Accessible name must -match visible label. In a dialog with Primary and Secondary buttons, the Secondary -button typically maps to the platform "cancel" input (B / Circle / Escape) as well -as direct focus activation. +**Accessibility**: [Same baseline as Button (Primary); note cancel-input mapping in dialogs] -**Implementation Notes**: [Same as Button (Primary). Where a Primary and Secondary -appear together, ensure Secondary is always positioned consistently — right/bottom -of Primary on horizontal layouts, or below Primary on vertical layouts. Consistency -across screens is more important than per-screen aesthetic preference.] +**Implementation Notes**: [Engine-specific guidance; consistent Primary/Secondary positioning] --- @@ -181,32 +157,21 @@ across screens is more important than per-screen aesthetic preference.] **Category**: Input **Status**: Draft -**When to Use**: Any action that is irreversible and causes loss of player data or -significant progress: "Delete Save File," "Reset All Settings," "Leave Match," -"Discard Changes." The visual treatment signals danger before the player presses. -**When NOT to Use**: Actions that can be undone, or actions that are merely -consequential but reversible. +**When to Use**: [Irreversible actions causing loss of player data or progress] +**When NOT to Use**: [Actions that can be undone or are merely consequential] **Interaction Specification**: | State | Visual | Input | Response | Duration | Audio | |-------|--------|-------|----------|----------|-------| -| Default | Outlined or filled with destructive color (typically a desaturated red — confirm colorblind compatibility in accessibility-requirements). Label may include a warning icon. | — | — | — | — | -| Hovered / Focused | Same behavior as Button (Primary) hover/focus but with destructive color | — | — | 80ms | [UI hover sound] | -| Pressed (first press) | Does NOT execute the action. Instead, opens Confirmation Dialog pattern (see below). The button itself shows a brief pulse animation. | Click / Enter | Trigger Confirmation Dialog | 100ms pulse | [UI warning sound — distinct from standard confirm] | -| — | Confirmation Dialog handles the actual execution | — | — | — | — | -| Disabled | 40% opacity | — | No response | — | — | +| [State] | [Visual treatment] | [Input trigger] | [Response] | [Duration + easing] | [Sound category] | > **Critical rule**: A Button (Destructive) NEVER executes its action directly. -> It always triggers a Confirmation Dialog. There are no exceptions. A player -> who presses it by accident must always have one more opportunity to back out. -> Games that skip confirmation on destructive actions generate the most visible -> negative community sentiment of any UX failure type. See: every "accidentally -> deleted save file" complaint on any game forum. +> It always triggers a Confirmation Dialog. There are no exceptions. -**Accessibility**: Screen reader must announce the destructive nature: "[Label] button — this action cannot be undone." In addition to accessible name, use the `description` property if available to add the warning text. +**Accessibility**: [Screen reader must announce the destructive nature] -**Implementation Notes**: [Destructive button triggers a separate Confirmation Dialog scene. Pass the action callback to the dialog — the button itself does not hold the execution logic. This separation prevents accidental execution if the confirmation dialog has a bug.] +**Implementation Notes**: [Engine-specific guidance] --- @@ -214,35 +179,18 @@ consequential but reversible. **Category**: Input **Status**: Draft -**When to Use**: Binary on/off settings where both states are equally valid and -the current state must be visible at a glance. "Subtitles: On/Off," "Aim Assist: -On/Off," "Notifications: On/Off." -**When NOT to Use**: Selections from more than two options (use Dropdown). Actions -that happen once rather than representing a persistent state (use Button). Cases -where the consequence of toggling is complex enough to need explanation (show -a description field alongside). +**When to Use**: [Binary on/off settings where current state must be visible at a glance] +**When NOT to Use**: [More than two options; one-shot actions; choices needing explanation] **Interaction Specification**: | State | Visual | Input | Response | Duration | Audio | |-------|--------|-------|----------|----------|-------| -| Off / Default | Track: muted fill. Thumb: leftmost position. Label: "Off" or state label. | — | — | — | — | -| Hovered | Track brightens 10%. Cursor: pointer. | Mouse over | Transition | 60ms | [UI hover sound] | -| Focused | Focus ring around entire toggle element (track + thumb). | Tab / D-pad | — | 60ms | [UI focus sound] | -| Pressed / Activated | Thumb slides to right side. Track fill changes to active color. Label changes to "On" or active state label. State persists. | Click / Enter / A / Cross | Toggle state change. Fire onChange event. Persist value. | 150ms ease-in-out for slide | [Toggle ON sound] | -| Pressed / Deactivated | Thumb slides to left. Track reverts to muted fill. | Same inputs | Toggle state change | 150ms ease-in-out | [Toggle OFF sound — subtly different from ON] | -| Disabled | 40% opacity. No interaction. Current state still visible. | — | No response | — | — | +| [State] | [Visual treatment] | [Input trigger] | [Response] | [Duration + easing] | [Sound category] | -**Accessibility**: -- Keyboard/Gamepad: Space or Enter to toggle. Avoid requiring directional inputs (left/right) to toggle — some users cannot predict that behavior. -- Screen reader: Role: "switch." State: "on" or "off" — the accessible name should NOT include the state (the screen reader announces state separately). Correct: accessible name "Subtitles," state "on." Incorrect: accessible name "Subtitles On." -- The toggle label (not just the visual thumb position) must change to show current state for players who cannot reliably distinguish left from right positions. +**Accessibility**: [Activation inputs, screen reader switch role/state rules, state label requirements] -**Implementation Notes**: [Godot: Use a custom Control or a CheckButton. The -built-in CheckButton provides accessibility role but uses a checkbox-style visual; -a custom slide-toggle animation may be needed for the target art style. Ensure -the slide animation is skipped when motion reduction mode is active — in that -case, snap to final state instantly.] +**Implementation Notes**: [Engine-specific guidance; motion-reduction behavior] --- @@ -250,36 +198,18 @@ case, snap to final state instantly.] **Category**: Input **Status**: Draft -**When to Use**: Selecting a value from a continuous range where approximate values -are acceptable and the range and relative position matter. Volume (0–100%), brightness, -text size. The visual representation of position is itself useful information. -**When NOT to Use**: Precise value entry (use Input Field). Selection from a short -discrete list (use Dropdown). Binary state (use Toggle). +**When to Use**: [Continuous value selection where range and relative position matter] +**When NOT to Use**: [Precise value entry; short discrete lists; binary state] **Interaction Specification**: | State | Visual | Input | Response | Duration | Audio | |-------|--------|-------|----------|----------|-------| -| Default | Track (full width). Fill (left of thumb, shows current value). Thumb (draggable handle). Current value label (right of track or above thumb). | — | — | — | — | -| Hovered | Thumb enlarges slightly (1.2x). Track brightens. | Mouse over | — | 60ms | — | -| Focused | Focus ring on thumb. Track brightens. | Tab / D-pad | — | 60ms | [UI focus sound] | -| Dragging (mouse) | Thumb follows cursor. Fill updates in real time. Value label updates in real time. | Click + drag on thumb | Continuous value update. Fire onChange continuously. | Real time | [Slider adjust sound — subtle, loops while dragging] | -| Keyboard / D-pad adjust | Thumb moves one step (5% of range per press, or 1 discrete unit). | Left/Right arrows or Left/Right D-pad while focused | Step value change. Fire onChange per step. | Instant | [Slider step sound — one click per step] | -| Keyboard fast adjust | Larger step (25% of range). | Page Up / Page Down while focused | Large step value change | Instant | [Same step sound] | -| Released | Value locks. onChange fires final value. | Mouse release | — | — | — | -| Disabled | 40% opacity. No interaction. Value visible. | — | No response | — | — | +| [State] | [Visual treatment] | [Input trigger] | [Response] | [Duration + easing] | [Sound category] | -**Accessibility**: -- Keyboard: Left/Right arrows to adjust by small step. Page Up/Page Down for large step. Home/End to jump to min/max. -- Screen reader: Role: "slider." Accessible name: the label (e.g., "Music Volume"). Current value announced on every change: "Music Volume, 80 percent." Min/max values announced on first focus. -- All sliders must show a numeric value alongside the visual position. Relying only on track fill position excludes players who cannot perceive relative position. +**Accessibility**: [Keyboard step/large-step/min-max inputs, screen reader value announcements, numeric value display requirement] -**Implementation Notes**: [Godot `HSlider`: set `step` to appropriate increment. -Override keyboard input to add Page Up/Down support via `_input()`. Bind the -`value_changed` signal to update the displayed numeric label. When motion reduction -mode is enabled, ensure value label updates are the sole feedback — do not suppress -them. Rumble feedback on gamepad slider adjustment is a nice enhancement for -accessibility.] +**Implementation Notes**: [Engine-specific guidance] --- @@ -287,36 +217,18 @@ accessibility.] **Category**: Input **Status**: Draft -**When to Use**: Selection from a discrete list of 3-15 options where only the -selected value needs to be visible at rest. Display resolution, language, window -mode, input preset. The closed state shows only the current selection. -**When NOT to Use**: Binary choices (use Toggle). More than ~15 options (use a -full List pattern or a scrollable Select). When comparing options matters as much -as selecting one (show options visibly, e.g., as a horizontal selector or list). +**When to Use**: [Selection from a discrete list of 3-15 options; only the selection visible at rest] +**When NOT to Use**: [Binary choices; more than ~15 options; when comparing options matters] **Interaction Specification**: | State | Visual | Input | Response | Duration | Audio | |-------|--------|-------|----------|----------|-------| -| Closed / Default | Label (left). Current value (right). Chevron-down icon (far right). | — | — | — | — | -| Hovered | Row background fills at 10% opacity | Mouse over | — | 60ms | — | -| Focused (closed) | Focus ring on entire row. | Tab / D-pad | — | 60ms | [UI focus sound] | -| Opening | Dropdown list appears below (or above if near screen bottom). List items visible. Previously selected item highlighted. Focus moves to selected item inside list. | Click / Enter / A / Cross | Open list | 100ms ease-out (expand) | [UI expand sound] | -| List item hovered/focused | List item highlights | Mouse / D-pad | — | 60ms | [UI hover sound] | -| List item selected | List closes. Closed state shows new value. onChange event fires. | Click / Enter / A / Cross on item | Select value, close list | 80ms ease-in (collapse) | [UI confirm sound] | -| Dismissed without selecting | List closes. Value unchanged. | Escape / B / Circle / click outside | Dismiss | 80ms | [UI cancel sound] | -| Disabled | 40% opacity. No interaction. | — | — | — | — | +| [State] | [Visual treatment] | [Input trigger] | [Response] | [Duration + easing] | [Sound category] | -**Accessibility**: -- Keyboard: Up/Down arrows navigate list items while open. Enter selects. Escape dismisses. First letter of an option jumps focus to first matching item. -- Screen reader: Role: "combobox." Accessible name: the field label. Expanded/collapsed state announced. Current value announced when focused. Each list item announces its value and position: "English, 1 of 12." -- The dropdown list must never obscure the current item or the control that opened it — this is a common failure on small screens. +**Accessibility**: [List navigation, combobox role, expanded/collapsed announcements, positioning rules] -**Implementation Notes**: [Godot: Custom implementation using a `Button` (the -closed state) and a `PopupMenu` or a `VBoxContainer` revealed by animation. Native -`OptionButton` provides accessibility but limited visual customization. Ensure -the popup positions itself above the control if it would be clipped by the screen -bottom. Close the popup on `_input` detecting click outside its rect.] +**Implementation Notes**: [Engine-specific guidance] --- @@ -324,33 +236,18 @@ bottom. Close the popup on `_input` detecting click outside its rect.] **Category**: Layout / Input **Status**: Draft -**When to Use**: A single selectable row in a vertically scrollable list. Achievements, -quest log entries, settings categories, save file slots. The list is the container; -this is the row within it. -**When NOT to Use**: Grid layouts where items exist in two dimensions (use Grid Item). -Non-selectable content rows (remove hover/focus states and the pressed state). +**When to Use**: [A selectable row in a vertically scrollable list] +**When NOT to Use**: [Two-dimensional grids; non-selectable content rows] **Interaction Specification**: | State | Visual | Input | Response | Duration | Audio | |-------|--------|-------|----------|----------|-------| -| Default | Full-width row. Icon (optional, left). Primary label. Secondary label / metadata (right or below primary). Chevron (right, if navigates deeper). | — | — | — | — | -| Hovered | Row background at 12% opacity highlight. | Mouse over | — | 60ms | — | -| Focused | Focus ring on row OR row background at 20% opacity (consistent with platform convention). | D-pad / Tab | — | 60ms | [UI focus sound] | -| Selected (persistent) | Row background at 25% opacity. May show a selection indicator (left border, checkmark). Distinct from focused state — a row can be selected but not focused. | — | Rendered state | — | — | -| Pressed / Activated | Brief brightness flash, then navigates or performs action | Click / Enter / A / Cross | Navigation or action | 80ms flash | [UI confirm sound] | -| Disabled | 40% opacity. No interaction. | — | — | — | — | +| [State] | [Visual treatment] | [Input trigger] | [Response] | [Duration + easing] | [Sound category] | -**Accessibility**: -- Keyboard/Gamepad: Up/Down arrows or D-pad to move between list items. The list must handle focus cycling — reaching the bottom should stop (not wrap) unless wrapping is explicitly designed. -- Screen reader: Role: "listitem." Parent list role: "list." Accessible name: primary label content. Metadata (secondary label) is optionally included in the description. Position announced: "Quest Log, 3 of 12." -- Minimum row height: 44pt / 48dp for touch. For controller-primary platforms, 56px rows are more comfortable. +**Accessibility**: [Focus cycling rules, listitem role and position announcements, minimum row height] -**Implementation Notes**: [Godot: Use a `VBoxContainer` inside a `ScrollContainer`. -Each row is a custom `Control` or `PanelContainer` with a `_gui_input` override. -For keyboard navigation inside the scroll container, implement custom focus -traversal — Godot's default Tab navigation does not scroll the container to keep -focused items in view. Use `ensure_control_visible()` on the scroll container.] +**Implementation Notes**: [Engine-specific guidance; scroll-into-view on focus] --- @@ -358,36 +255,18 @@ focused items in view. Use `ensure_control_visible()` on the scroll container.] **Category**: Layout / Input **Status**: Draft -**When to Use**: A selectable cell in a two-dimensional grid. Inventory slots, -ability select, crafting ingredient selection, character portrait selection. The -grid is the container; this is the cell. -**When NOT to Use**: Single-column content (use List Item). Non-selectable display -cells (remove interactive states). +**When to Use**: [A selectable cell in a two-dimensional grid] +**When NOT to Use**: [Single-column content; non-selectable display cells] **Interaction Specification**: | State | Visual | Input | Response | Duration | Audio | |-------|--------|-------|----------|----------|-------| -| Empty | Empty slot visual (subtle border or dashed outline). Different from disabled. | — | — | — | — | -| Populated | Item icon fills cell. Stack count (bottom right, if applicable). Quality indicator (border color or icon overlay). | — | — | — | — | -| Hovered | Brightness +15%. Tooltip appears after 400ms delay. | Mouse over | — | 60ms | — | -| Focused | Focus ring (2px, offset 2px). Same brightness as hovered. Tooltip appears after 400ms delay or immediately on gamepad. | D-pad navigation | — | 60ms | [UI focus sound] | -| Selected (persistent) | Distinct border (thicker, contrasting color). May show selection checkmark. | Click / Enter / A / Cross | Select item. Can coexist with focused state on a different cell. | Instant | [UI select sound] | -| Pressed | Brief scale 0.95x, then executes action | Double-click / Enter / A / Cross | Action (equip, use, inspect — defined by context) | 80ms | [UI confirm sound] | -| Locked | Padlock overlay icon on populated content. No hover/focus states. | — | No interaction | — | — | -| Drag source | Cell dims (50% opacity), drag preview appears at cursor. | Click + drag (mouse only) | Begin drag operation | Instant | [UI grab sound] | -| Drop target (valid) | Cell brightens, accepting color indicator | Item dragged over | — | 60ms | — | -| Drop target (invalid) | Red tint or shake animation | Item dragged over invalid slot | — | 60ms | [UI error sound] | +| [Empty / Populated / Hovered / Focused / Selected / Pressed / Locked / Drag source / Drop target] | [Visual treatment] | [Input trigger] | [Response] | [Duration + easing] | [Sound category] | -**Accessibility**: -- Keyboard/Gamepad: D-pad or arrow keys navigate cells. The grid must communicate its dimensions to screen readers. Row/column position announced. -- Screen reader: Role: "gridcell." Parent role: "grid." Accessible name: item name (or "empty slot" for empty cells). State: "selected" when selected, "dimmed" when locked. Position: "row 2, column 3." -- Tooltips must be reachable by keyboard — they must appear when the cell is focused, not only when hovered. +**Accessibility**: [Grid dimensions and row/column position announcements, keyboard-reachable tooltips] -**Implementation Notes**: [Godot: `GridContainer` with fixed column count. Each -cell is a custom `Control`. Implement custom D-pad navigation by overriding -`_gui_input` and calculating the cell to the left/right/above/below based on -index and column count. `GridContainer` does not provide this natively.] +**Implementation Notes**: [Engine-specific guidance; custom D-pad cell navigation] --- @@ -395,41 +274,21 @@ index and column count. `GridContainer` does not provide this natively.] **Category**: Feedback / Layout **Status**: Draft -**When to Use**: A decision or acknowledgment that must be resolved before the -player can continue. The dialog is blocking — background content is dimmed and -non-interactive. "Are you sure?", "Your progress will be saved.", error states. -**When NOT to Use**: Non-blocking notifications (use Toast / Notification). Information -that can wait until the player is ready (add it to a persistent help system instead). -Dialogs that should allow the player to continue playing behind them. +**When to Use**: [A decision or acknowledgment that must be resolved before continuing] +**When NOT to Use**: [Non-blocking notifications; information that can wait; dialogs that should allow play behind them] **Interaction Specification**: | State | Visual | Input | Response | Duration | Audio | |-------|--------|-------|----------|----------|-------| -| Opening | Background overlay animates from 0 to 60% opacity. Dialog panel scales from 0.9 to 1.0. Dialog enters from center (not from an edge). | Triggered by code | Focus moves to first interactive element in dialog (or the Primary button) | 200ms ease-out | [UI modal open sound] | -| Active | Background non-interactive. Dialog has all input focus. Player cannot interact with background. | Keyboard / gamepad navigates within dialog only | — | — | — | -| Dismissing (confirmed) | Dialog panel scales to 1.1 then fades. Overlay fades to 0%. | Primary button pressed | Execute action, return focus to trigger element | 180ms | [UI confirm sound] | -| Dismissing (cancelled) | Dialog panel scales to 0.9 then fades. Overlay fades to 0%. | Secondary button / Escape / B / Circle | No action, return focus to trigger element | 150ms | [UI cancel sound] | -| Cannot dismiss | If the dialog represents a blocking error, do not provide a cancel path. Provide only resolution options. | — | — | — | — | +| [Opening / Active / Dismissing (confirmed) / Dismissing (cancelled) / Cannot dismiss] | [Visual treatment] | [Input trigger] | [Response] | [Duration + easing] | [Sound category] | -> **Focus trap rule**: While a modal dialog is open, Tab and D-pad navigation -> must cycle within the dialog's interactive elements only. It must not be possible -> to navigate focus outside the dialog to the background content. This is both -> an accessibility requirement (WCAG 2.1 SC 2.1.2) and a UX integrity requirement. -> When the dialog closes, focus must return to the element that triggered it, -> not to the top of the page. +> **Focus trap rule**: While a modal dialog is open, focus navigation must cycle +> within the dialog only, and focus must return to the trigger element on close. -**Accessibility**: -- Screen reader: Dialog container role: "dialog." Accessible name: dialog title (required — every dialog must have a title, even if visually hidden). On open, screen reader announces dialog title and first focusable element. Focus trap active. -- Keyboard: Escape key always maps to the cancel/dismiss action (same as Secondary button or close button). Enter always maps to the primary/confirm action. -- Motion reduction: Scale animation replaced with instant appear/disappear. Overlay fade retained at 100ms (faster). +**Accessibility**: [Dialog role and required title, Escape/Enter mappings, motion-reduction overrides] -**Implementation Notes**: [Godot: Implement as a `CanvasLayer` with a high layer -value (100+) to ensure it renders above all game content. The background overlay -is a full-screen `ColorRect` at 60% black opacity. Use `grab_focus()` on the -dialog's primary button after the open animation completes. Override `_input()` to -implement the focus trap — intercept Tab navigation and reroute to the dialog's -focusable elements.] +**Implementation Notes**: [Engine-specific guidance] --- @@ -437,33 +296,22 @@ focusable elements.] **Category**: Feedback / Layout **Status**: Draft -**When to Use**: The specific case of confirming a destructive action. Always -triggered by Button (Destructive). Always has exactly two options: confirm (labeled -with the specific action, not "OK") and cancel. -**When NOT to Use**: Non-destructive confirmations. Errors or notifications that -do not require a decision. Any dialog with more than two actions. +**When to Use**: [Confirming a destructive action — always triggered by Button (Destructive)] +**When NOT to Use**: [Non-destructive confirmations; dialogs with more than two actions] > **Label rule**: The confirm button must be labeled with the specific action, -> not a generic "OK" or "Yes." "Delete Save File" not "OK." "Leave Match" not -> "Yes." This reduces mistakes for players who have difficulty reading the dialog -> content quickly. The pattern comes from Apple HIG and is validated by decades -> of usability research. +> not a generic "OK" or "Yes." **Structure**: -- Title: Brief, action-describing. "Delete save file?" not "Are you sure?" -- Body: One sentence stating the consequence. "This cannot be undone." -- Confirm button: Button (Primary) — labeled with the specific action. "Delete Save File." -- Cancel button: Button (Secondary) — "Cancel." -- Default focus: Cancel (safer default — reduces accidental destructive actions). +- Title: [Brief, action-describing] +- Body: [One sentence stating the consequence] +- Confirm button: [Button (Primary) — labeled with the specific action] +- Cancel button: [Button (Secondary) — "Cancel"] +- Default focus: [Cancel — safer default] -**Accessibility**: Inherits all Modal Dialog accessibility. Additionally: screen -reader announces "Alert dialog, [title]" to signal destructive context. Default -focus on Cancel is a requirement, not a preference. +**Accessibility**: [Inherits Modal Dialog; alert-dialog announcement; default focus on Cancel is required] -**Implementation Notes**: [Confirmation Dialog is a specific instance of Modal -Dialog — implement it as a subclass or as a parameterized scene. The default -focus on Cancel is critical: set `grab_focus()` on the Cancel button, not the -Confirm button, after open animation completes.] +**Implementation Notes**: [Engine-specific guidance] --- @@ -471,35 +319,18 @@ Confirm button, after open animation completes.] **Category**: Feedback **Status**: Draft -**When to Use**: Brief, non-blocking information that does not require a player -decision. "Game saved." "Achievement unlocked." "Your inventory is full." The player -can continue playing; the notification disappears on its own. -**When NOT to Use**: Information that requires a decision (use Modal Dialog). -Errors that require the player to take action. Critical information that the player -must not miss. +**When to Use**: [Brief, non-blocking information requiring no player decision] +**When NOT to Use**: [Decisions; errors requiring action; critical information the player must not miss] **Interaction Specification**: | State | Visual | Input | Response | Duration | Audio | |-------|--------|-------|----------|----------|-------| -| Entering | Slides in from screen edge (typically bottom-right, away from primary action areas). Fades from 0 to 100% opacity. | Triggered by code | — | 200ms ease-out | [Sound matching notification type — see Sound Standards] | -| Displayed | Full opacity. Optional: icon (left), title, body text (optional), dismiss button (X, optional). | Pointer hover pauses auto-dismiss timer | Pause auto-dismiss | — | — | -| Auto-dismiss | Fades from 100 to 0% opacity, slides out | Timer expires (5 seconds default for one-line; 8 seconds for two-line) | Remove from queue | 200ms ease-in | — | -| Manual dismiss | Fades and slides out immediately | Click/tap X button or swipe on touch | Remove | 150ms | [UI cancel sound, quiet] | -| Queue overflow | New notification pushes oldest out early | New notification triggered while previous is displayed | FIFO queue, max 3 simultaneous | — | — | +| [Entering / Displayed / Auto-dismiss / Manual dismiss / Queue overflow] | [Visual treatment] | [Input trigger] | [Response] | [Duration + easing] | [Sound category] | -**Accessibility**: -- Screen reader: Toasts must be read aloud without requiring focus. In HTML, this uses `role="status"` or `role="alert"`. In game UI, this requires the engine's accessibility notification system. Verify engine support in engine-reference docs. -- Motion reduction: Slide animation replaced with fade only. -- Toasts must never be the sole communication channel for information the player needs to act on. If the information requires action, use a persistent UI element in addition to the toast. -- Auto-dismiss timer: 5 seconds is the minimum. Players with cognitive processing differences may need more time. Consider a setting to extend to 10 or 15 seconds. +**Accessibility**: [Announcement without focus, motion-reduction fallback, minimum auto-dismiss timing, never sole channel for actionable info] -**Implementation Notes**: [Godot: Manage a queue of `PanelContainer` scenes in a -`VBoxContainer` anchored to a screen corner. Each toast is instantiated, added to -the container, then auto-removed after a timer. The container should be on a high -`CanvasLayer` (50+) but below modal dialogs (100+). Animate using a `Tween` on -`modulate.a` and `position.x`. When motion reduction is active, skip the position -animation.] +**Implementation Notes**: [Engine-specific guidance; queue management and layer ordering] --- @@ -507,36 +338,18 @@ animation.] **Category**: Feedback **Status**: Draft -**When to Use**: Contextual information that supplements a visible label. Item -descriptions in inventory. Stat explanations on a character sheet. Setting -descriptions in accessibility options. The player must be able to access this -information or proceed without it. -**When NOT to Use**: Information the player MUST read to complete an action — put -that in the label or body text, not a tooltip. Tooltips are not discoverable -on mobile touch without a hover state. On touch-only platforms, use an info button -that opens a description modal instead. +**When to Use**: [Contextual information supplementing a visible label] +**When NOT to Use**: [Information required to complete an action; touch-only platforms without hover] **Interaction Specification**: | State | Visual | Input | Response | Duration | Audio | |-------|--------|-------|----------|----------|-------| -| Hidden | — | — | — | — | — | -| Hover trigger | — | Mouse enters element | Begin 400ms delay timer | — | — | -| Gamepad/keyboard trigger | — | Element receives focus | Begin 300ms delay timer (shorter because navigation is intentional) | — | — | -| Appearing | Tooltip panel fades in and scales from 0.95 to 1.0. Positioned near element (prefer above, adjust if near screen edge). | Timer expires | Show tooltip | 120ms ease-out | — | -| Displayed | Tooltip visible. Title (optional). Body text. Max width: 300px. Multiple lines allowed. | — | — | — | — | -| Hiding | Tooltip fades out | Mouse leaves element / focus moves away | Hide tooltip | 80ms ease-in | — | +| [Hidden / Hover trigger / Focus trigger / Appearing / Displayed / Hiding] | [Visual treatment] | [Input trigger] | [Response] | [Duration + easing] | [Sound category] | -**Accessibility**: -- Screen reader: Tooltip content must be accessible without hover. The accessible name of the parent element should include the most critical tooltip information. The full tooltip text is optionally in the `description` property. Screen reader reads tooltip content when element is focused. -- The delay (300-400ms) prevents accidental tooltip display and is required — instant tooltips are disruptive in gamepad navigation. -- Tooltip text must meet the same contrast requirements as body text (4.5:1 minimum). +**Accessibility**: [Content accessible without hover, required appearance delay, contrast requirements] -**Implementation Notes**: [Godot: Attach a custom `TooltipControl` scene as a -child of the trigger element. Show/hide with a `Timer` node. Position the tooltip -using a `CanvasLayer` to ensure it appears above all other UI. For screen edges, -detect if the tooltip rect extends beyond `get_viewport_rect()` and flip the -position to the opposite side.] +**Implementation Notes**: [Engine-specific guidance; screen-edge repositioning] --- @@ -544,34 +357,18 @@ position to the opposite side.] **Category**: Feedback / Layout **Status**: Draft -**When to Use**: Linear progress toward a defined endpoint. Loading screens (time -to completion), XP fill toward next level, quest objectives with countable progress -("3 of 10 enemies defeated"), download progress. -**When NOT to Use**: Circular or radial progress (use a separate Radial Progress -pattern if needed). Values that fluctuate up and down rapidly (use Health/Resource -Bar pattern). Values with no defined endpoint. +**When to Use**: [Linear progress toward a defined endpoint] +**When NOT to Use**: [Radial progress; rapidly fluctuating values; values with no endpoint] **Interaction Specification**: | State | Visual | Input | Response | Duration | Audio | |-------|--------|-------|----------|----------|-------| -| Default | Track (full width, background color). Fill (left to right, value color). Value label (percentage or N/M, outside or inside fill). | — | — | — | — | -| Value increasing | Fill width animates to new value | Value changes | Smooth fill animation | 300ms ease-out | [Context-dependent — XP gain has a sound; loading has none] | -| Value at maximum | Fill reaches full width. Optional: completion animation (pulse, glow). | Value reaches 100% | Completion event fires | 200ms | [Completion sound if appropriate] | -| Value at zero | Fill hidden (zero width). Track still visible. | — | — | — | — | -| Indeterminate (unknown duration) | Animated loop (fill segment moves left-to-right, repeat). Used for loading of unknown duration. | — | — | Infinite loop | — | +| [Default / Value increasing / At maximum / At zero / Indeterminate] | [Visual treatment] | [Input trigger] | [Response] | [Duration + easing] | [Sound category] | -**Accessibility**: -- Screen reader: Role: "progressbar." Accessible name: what is progressing (e.g., "Experience Points," "Loading"). Value: current numeric value AND percentage AND maximum. "Experience Points, 450 of 1000, 45 percent." Update on significant changes (not every pixel). -- Do not rely only on fill color to communicate value. Include a numeric label. -- Indeterminate progress bars: announce "Loading, in progress" — do not announce changes since the value is unknown. -- Motion reduction: Indeterminate animation is replaced with a static "loading" indicator. Smooth fill animation is replaced with instant jump to new value. +**Accessibility**: [Progressbar role, numeric value + percentage announcements, numeric label requirement, motion-reduction overrides] -**Implementation Notes**: [Godot: `ProgressBar` built-in with custom theming. -For indeterminate mode, `ProgressBar` does not have a native indeterminate state -in Godot 4.x — implement using a looping `Tween` on a fill element's position. -Ensure the Tween is paused when motion reduction mode is active and a static -indicator is shown instead.] +**Implementation Notes**: [Engine-specific guidance; indeterminate-mode implementation] --- @@ -579,39 +376,18 @@ indicator is shown instead.] **Category**: Input **Status**: Draft -**When to Use**: Text entry. Player name on a new save, search within a list, -remapping a key binding (special case — shows the key press, not typed text), -entering a numeric value precisely. -**When NOT to Use**: Selecting from known options (use Dropdown or List). On -console-primary platforms, minimize text entry — it requires a virtual keyboard, -which is high friction. +**When to Use**: [Text entry — player name, search, precise numeric values, key binding capture] +**When NOT to Use**: [Selecting from known options; console-primary flows where virtual keyboards add friction] **Interaction Specification**: | State | Visual | Input | Response | Duration | Audio | |-------|--------|-------|----------|----------|-------| -| Default | Field border, placeholder text (label-style, muted color), empty input area. | — | — | — | — | -| Hovered | Border brightens slightly | Mouse over | — | 60ms | — | -| Focused | Border brightens fully. Cursor (blinking, 530ms on/530ms off). Placeholder text hidden. | Tab / click | Open virtual keyboard on console/mobile | Instant | [UI focus sound] | -| Typing | Characters appear. Cursor advances. | Keyboard input | Update field value | Immediate | [Subtle keystroke sound, optional] | -| Value present | Field shows typed value. Placeholder hidden. Clear button appears (X, right of field) if value is non-empty. | — | — | — | — | -| Character limit reached | No further input accepted. Optional: brief shake animation and limit indicator changes color. | Input at limit | Reject further characters | 200ms shake | [UI error sound, subtle] | -| Clear | Field empties. Cursor returns. Clear button disappears. | Click X / gamepad clear input | Clear value | Instant | [UI cancel sound, subtle] | -| Validation error | Border turns error color (red — ensure colorblind safe). Error message appears below field. | On submit or on blur | Show error | Instant | [UI error sound] | -| Validated / correct | Border turns success color (green — ensure colorblind safe). Success icon optional. | On validation pass | — | Instant | — | -| Disabled | 40% opacity, no interaction. Value still visible. | — | — | — | — | +| [Default / Hovered / Focused / Typing / Value present / Limit reached / Clear / Validation error / Validated / Disabled] | [Visual treatment] | [Input trigger] | [Response] | [Duration + easing] | [Sound category] | -**Accessibility**: -- Keyboard: All standard text editing shortcuts (Home, End, Ctrl+A, Ctrl+C, Ctrl+V, Ctrl+Z). -- Screen reader: Role: "textbox." Accessible name: field label (not placeholder text). Current value announced. Character limit announced when reached. Validation errors announced immediately on occurrence. -- Placeholder text must not be used as the only label — a visible label above or beside the field is required. Placeholder text disappears when the player types, causing confusion for players with cognitive or memory impairments. +**Accessibility**: [Standard editing shortcuts, textbox role, visible label requirement (placeholder is not a label)] -**Implementation Notes**: [Godot `LineEdit`: set `placeholder_text` for the hint -but always include a visible `Label` node as the field's accessible name. Bind -`text_changed` signal for real-time validation. Bind `text_submitted` for form -submission on Enter. On console, `LineEdit.call("_popup_keyboard")` or use the OS -virtual keyboard API — verify against engine-reference/godot/ for Godot 4.6 -console keyboard API specifics.] +**Implementation Notes**: [Engine-specific guidance; virtual keyboard handling] --- @@ -619,37 +395,18 @@ console keyboard API specifics.] **Category**: Navigation **Status**: Draft -**When to Use**: Dividing a single screen's content into discrete sections where -only one section is visible at a time. Character sheet tabs (Stats / Equipment / -Skills), settings tabs (Gameplay / Graphics / Audio / Accessibility). Maximum -5-6 tabs before the pattern breaks down and a sidebar navigation should be -considered instead. -**When NOT to Use**: More than 6 tabs. Content that benefits from simultaneous -visibility (use a layout pattern instead). Navigation between different screens -(use Screen Push). +**When to Use**: [Dividing one screen's content into discrete sections, one visible at a time — max 5-6 tabs] +**When NOT to Use**: [More than 6 tabs; content needing simultaneous visibility; navigation between screens] **Interaction Specification**: | State | Visual | Input | Response | Duration | Audio | |-------|--------|-------|----------|----------|-------| -| Default (inactive tab) | Tab label. No active indicator. | — | — | — | — | -| Active tab | Tab label. Active indicator (underline, fill, or contrasting background). Content area shows this tab's content. | — | — | — | — | -| Hovered (inactive) | Tab background fills slightly | Mouse over | — | 60ms | — | -| Focused (keyboard/gamepad) | Focus ring on tab label. | Tab key (within tab bar) or D-pad left/right on tab row | — | 60ms | [UI focus sound] | -| Activated | Active indicator transitions to this tab. Content area transitions (fade or slide). | Click / Enter / A / Cross | Switch active tab. Content update. | 150ms ease | [UI tab switch sound] | -| Gamepad shoulder button | — | L1/R1 (PS) or LB/RB (Xbox) | Switch to previous/next tab (standard platform convention) | 150ms | [UI tab switch sound] | +| [Inactive / Active / Hovered / Focused / Activated / Shoulder-button switch] | [Visual treatment] | [Input trigger] | [Response] | [Duration + easing] | [Sound category] | -**Accessibility**: -- Keyboard: Arrow keys navigate between tabs within the tab bar (left/right). Tab key moves focus into the content area below. This follows the ARIA tab panel pattern. -- Screen reader: Role: "tab" for individual tabs. Role: "tablist" for the container. Role: "tabpanel" for the content area. Active tab state: "selected." Accessible name: tab label. Tabpanel is labeled by its corresponding tab. -- The active tab must be visually distinguishable by more than color alone (underline, fill pattern, or weight change in addition to color). +**Accessibility**: [ARIA tab panel pattern (tab/tablist/tabpanel), active-tab distinction beyond color] -**Implementation Notes**: [Godot: `TabContainer` built-in. For custom visual -styling, implement manually with a `HBoxContainer` of tab buttons and a -`MarginContainer` for content. The shoulder button shortcut (LB/RB) must be -implemented in the screen's `_input()` override — it is not built into Godot's -tab system. Check platform conventions: Xbox uses LB/RB; PlayStation uses L1/R1; -both are the same physical button, so a single binding works.] +**Implementation Notes**: [Engine-specific guidance; shoulder-button shortcut handling] --- @@ -657,67 +414,44 @@ both are the same physical button, so a single binding works.] **Category**: Layout **Status**: Draft -**When to Use**: Content that exceeds the visible area of its container. Inventory -lists, lore entry text, credits, long settings lists. The scroll indicator shows -the player that more content exists. -**When NOT to Use**: Content that can be paginated instead (pagination may be -clearer for dense list navigation). Infinite scroll (always provide a loading -state and an end state). +**When to Use**: [Content exceeding the visible area of its container] +**When NOT to Use**: [Content better paginated; infinite scroll without loading/end states] **Interaction Specification**: | State | Visual | Input | Response | Duration | Audio | |-------|--------|-------|----------|----------|-------| -| Content fits | No scrollbar visible (or always-visible scrollbar at full height, depending on art direction). | — | — | — | — | -| Scrollable | Scrollbar appears (right edge). Scrollbar thumb size represents viewport vs. content ratio. | — | — | — | — | -| Scrolling (mouse) | Content moves. Scrollbar thumb moves proportionally. | Mouse wheel | Scroll by 3 lines per wheel tick (configurable in OS) | Smooth | — | -| Scrollbar drag | Content moves. Thumb follows pointer. | Click + drag scrollbar thumb | Scroll proportionally | Real time | — | -| Keyboard scroll | Content moves one item height per keypress. | Up/Down arrows when container is focused and no child is focused | Scroll by one unit | Immediate | — | -| Gamepad scroll | Content moves to keep focused item in view. | D-pad navigation to items beyond visible area | Auto-scroll to keep focused item visible | Smooth 150ms | — | -| Scroll top / bottom | Content stops. Scrollbar thumb at end. | Content boundary reached | Stop scrolling | — | — | -| Focus follows scroll | When a child element receives focus, scroll container ensures it is fully visible. | Any child receives focus | Scroll to reveal focused element | 200ms ease | — | +| [Content fits / Scrollable / Scrolling / Scrollbar drag / Keyboard scroll / Gamepad scroll / Boundary / Focus follows scroll] | [Visual treatment] | [Input trigger] | [Response] | [Duration + easing] | [Sound category] | -**Accessibility**: -- Keyboard/Gamepad: The scroll container itself should not require explicit scrollbar interaction — navigating list items inside it should auto-scroll to keep focused items in view. -- Screen reader: The scroll container announces "scrollable" and the scroll position ("showing items 5 through 15 of 30"). This requires engine accessibility support — verify in engine-reference/godot/. -- Fade edges (content fading at scroll boundaries to indicate more content exists) are a helpful visual affordance but must not be the only indicator that content exists beyond the visible area. Include a scrollbar. +**Accessibility**: [Auto-scroll to keep focused items in view, scroll position announcements, scrollbar as required indicator] -**Implementation Notes**: [Godot `ScrollContainer`: call `ensure_control_visible()` -on the focused child whenever `gui_focus_changed` fires inside the container. -Bind this via a recursive `connect` on the container's `gui_focus_changed` signal. -For smooth scroll animation, use a `Tween` on `scroll_vertical` rather than -setting it directly.] +**Implementation Notes**: [Engine-specific guidance; ensure-visible on focus change] --- ## Game-Specific UI Patterns +> Full reference specifications for every pattern in this section are in +> `.claude/docs/templates/guidance/interaction-pattern-library-guide-game-specific.md` — load only the +> pattern(s) currently being specified. + --- #### Inventory Slot **Category**: Game-Specific **Status**: Draft -**When to Use**: Every item container in the inventory grid. Empty slots, populated -slots, equipped slots, locked slots. The slot is the frame; the item icon is the -content. +**When to Use**: [Every item container in the inventory grid — empty, populated, equipped, locked] **States**: | State | Visual | Notes | |-------|--------|-------| -| Empty | Subtle slot border, no content. Not the same as disabled. Empty slots are interactable (receive items). | Avoid fully invisible empty slots — players lose track of grid dimensions | -| Populated | Item icon fills 80% of slot area. Stack count bottom-right (if applicable). Quality border (colorblind-safe — icon + color). Equipped badge (top-right, if equipped). | | -| Focused | Focus ring. Tooltip appears after 300ms. | | -| Selected | Thicker or contrasting border. Used when multi-select is supported. | | -| Drag source | Slot dims, drag ghost follows pointer. | See Grid Item for full drag spec | -| Locked | Padlock icon overlay. No interaction. May show item at 50% opacity behind lock. | Used for locked loadout slots, DLC content, etc. | -| Highlighted | Animated border glow (pulsing). Used for quest-relevant items or newly acquired items. | Respect motion reduction — replace pulse with a static badge | -| Cooldown overlay | Radial fill overlay from 12 o'clock, clockwise, depleting as cooldown expires. | Only applicable if slots represent active items with cooldowns | +| [Empty / Populated / Focused / Selected / Drag source / Locked / Highlighted / Cooldown overlay] | [Visual treatment] | [Behavioral notes] | -**Accessibility**: Stack counts and quality tiers must have text or icon alternatives to color coding. Tooltip is the primary accessibility mechanism — ensure it is reachable by keyboard and screen reader. Locked slots must announce "locked" to screen readers. +**Accessibility**: [Non-color alternatives for stack counts and quality tiers; keyboard-reachable tooltip; locked-state announcement] -**Implementation Notes**: [Godot: Custom `Control` node. Quality border implemented as a `StyleBoxFlat` swapped based on rarity — avoid using `modulate` color for quality, as it affects the icon color. Drag and drop implemented via `get_drag_data()` and `can_drop_data()` / `drop_data()` override methods.] +**Implementation Notes**: [Engine-specific guidance] --- @@ -725,28 +459,17 @@ content. **Category**: Game-Specific **Status**: Draft -**When to Use**: Ability buttons in the HUD ability bar, skill tree nodes, and -any context where an ability must show availability state. +**When to Use**: [HUD ability bar buttons, skill tree nodes, any availability-state ability display] **States**: | State | Visual | Notes | |-------|--------|-------| -| Available | Full opacity icon. Keybinding label below. | | -| On cooldown | Radial overlay depleting clockwise from 12 o'clock. Remaining time shown as a number in the center when > 2 seconds remain. | | -| Charges remaining | Charge pip indicators below icon (e.g., 3 filled circles = 3 charges). Number alternative for screen readers. | | -| Out of resource | Icon desaturates to ~20%. Border dims. Keybinding label dims. Distinct from cooldown — resource-gated, not time-gated. | | -| Locked / not unlocked | Icon silhouette only (no full art visible). Padlock badge. May show unlock condition in tooltip. | | -| Active / channeling | Pulsing border. Radial fill shows channel duration remaining. | | -| Just activated | Brief scale 0.9x then spring to 1.0x (overshoot to 1.05x). | Example: Guild Wars 2 and Path of Exile both use press-depress animations on ability use to confirm activation. Respect motion reduction. | +| [Available / On cooldown / Charges remaining / Out of resource / Locked / Active / Just activated] | [Visual treatment] | [Behavioral notes] | -**Accessibility**: All cooldown/charge information must have a numeric value (screen reader cannot parse radial overlays). The cooldown timer number satisfies this. Ability names and descriptions must be exposed to screen readers via tooltip. +**Accessibility**: [Numeric alternatives for cooldown/charge information; names and descriptions exposed to screen readers] -**Implementation Notes**: [Godot: Custom `TextureButton` subclass with overlay -`Control` nodes for cooldown radial and charge pips. The cooldown radial uses a -custom shader on a `ColorRect` rotating a mask — or implement with a -`ProgressBar` styled as circular if engine supports it. Verify against -engine-reference/godot/ for Godot 4.6 shader support for this pattern.] +**Implementation Notes**: [Engine-specific guidance] --- @@ -754,26 +477,17 @@ engine-reference/godot/ for Godot 4.6 shader support for this pattern.] **Category**: Game-Specific **Status**: Draft -**When to Use**: Any continuously varying value in the HUD that represents a -critical player resource. Health, mana, stamina, shield, fuel. +**When to Use**: [Any continuously varying critical player resource in the HUD] **States and behaviors**: | Event | Visual | Audio | Duration | |-------|--------|-------|---------| -| Value decrease (damage) | Fill shrinks. Brief "damage flash" on the fill (white or red flash). Ghost bar lingers at previous value and drains to new value over 0.5s ("damage indicator"). | [Damage taken sound — varies by amount] | Instant decrease, 500ms ghost bar drain | -| Value increase (heal) | Fill grows. Brief heal color flash (green — ensure colorblind safe with icon/glow backup). | [Heal sound] | 300ms ease-in | -| Below 25% threshold | Fill changes color to warning state. Border pulses (or static badge in motion reduction mode). Optional: heartbeat audio cue (paired with visual if audio is sole signal). | [Low health sound — loops until above threshold] | Continuous | -| At zero | Bar empty. Optional: bar shakes briefly. Death/depletion event fires. | [Death/depletion sound] | 200ms shake | -| Maximum | Fill at 100%, brief glow. | — | 200ms | -| Overflow (shield) | A separate bar segment appears beyond the natural fill area, in shield color. | [Shield gain sound] | 200ms | +| [Decrease / Increase / Below threshold / At zero / Maximum / Overflow] | [Visual treatment] | [Sound category] | [Duration] | -**Accessibility**: The current value must be accessible as a number (tooltip or persistent display, or both). Color-coded threshold states must have non-color backups (icon, flashing, or audio visual warning). Warning state at 25% must have a visual signal independent of the color change. +**Accessibility**: [Numeric value access; non-color backups for threshold states] -**Implementation Notes**: [Godot: Two overlapping `ProgressBar` nodes for ghost -bar effect — back bar holds previous value (drains via Tween), front bar holds -current value (updates instantly). Threshold states trigger `StyleBoxFlat` swaps -on the front bar. Ghost bar Tween duration is tunable as a designer parameter.] +**Implementation Notes**: [Engine-specific guidance; ghost-bar technique] --- @@ -781,31 +495,19 @@ on the front bar. Ghost bar Tween duration is tunable as a designer parameter.] **Category**: Game-Specific **Status**: Draft -**When to Use**: NPC conversation, voiced narrative dialogue, tutorial text -delivered through a character. All dialogue that has a speaker. +**When to Use**: [All dialogue that has a speaker — NPC conversation, voiced narrative, character-delivered tutorial text] -**Structure**: Speaker portrait or name tag (top of box or left side). Dialogue text body. Continue/advance prompt (bottom right). Optional: skip-all button, voice acting indicator, subtitle indicator. +**Structure**: [Speaker identification, dialogue text body, advance prompt, optional skip/voice/subtitle indicators] **States and behaviors**: | State | Visual | Input | Response | Duration | |-------|--------|-------|----------|---------| -| Line entering | Text reveals character-by-character (typewriter effect). Or: text fades in at full speed if accessibility option set. | — | — | Speed: configurable in accessibility settings | -| Revealing | Text animating in. Continue prompt hidden or pulsing at slow opacity. | [Any advance input] | Skip to end of current line instantly (show full line, stop typewriter) | Immediate | -| Line complete | Full line shown. Continue prompt visible and animated. | — | — | — | -| Advancing to next line | Continue prompt hides. Text fades out or wipes. New line begins. | [Any advance input] — Enter / A / Cross / Space / mouse click | Advance | 100ms transition | -| Choices appearing | Choice buttons appear below dialogue text. Continue prompt hidden. Navigation focus moves to first choice. | D-pad / keyboard to select, Enter / A / Cross to confirm | Select choice | 150ms enter animation | -| Closing | Box fades out | Final line advanced | Return control to player | 200ms | -| Skipping all (if supported) | Brief confirmation prompt: "Skip dialogue?" | Dedicated skip button | Skip to post-dialogue state | — | +| [Line entering / Revealing / Line complete / Advancing / Choices appearing / Closing / Skipping all] | [Visual treatment] | [Input trigger] | [Response] | [Duration] | -**Accessibility**: Subtitles are always enabled by default for all voiced dialogue. Typewriter animation speed is a user setting (see accessibility-requirements.md). The dialogue box must not auto-advance — players must control pacing. Speaker name is always shown. All choice buttons must be navigable by keyboard and gamepad. Choices must be accessible to screen readers with position announced. +**Accessibility**: [Subtitles on by default, configurable typewriter speed, no auto-advance, navigable choices with position announcements] -**Implementation Notes**: [Godot: `RichTextLabel` with `bbcode_enabled` for -formatting. Typewriter effect via `visible_characters` property animated by a -`Timer`. Bind the advance input to a function that either skips typewriter -(sets `visible_characters = -1`) or advances the dialogue state. Speaker name -displayed in a separate `Label` above or beside the box. Dialogue data loaded from -JSON or a dedicated dialogue format (e.g., Dialogic, Yarn Spinner for Godot).] +**Implementation Notes**: [Engine-specific guidance; dialogue data format] --- @@ -813,23 +515,17 @@ JSON or a dedicated dialogue format (e.g., Dialogic, Yarn Spinner for Godot).] **Category**: Game-Specific **Status**: Draft -**When to Use**: A prompt that appears near an interactable game object indicating -what the player can do. "Press [A] to open chest." "Hold [E] to pick up." Appears -when the player enters the interaction zone, disappears when they leave. +**When to Use**: [A prompt near an interactable object indicating what the player can do] **States**: | State | Visual | Notes | |-------|--------|-------| -| Appearing | Fades in and rises 8px from object anchor point. | Respect motion reduction — fade only, no rise | -| Idle | Platform-correct button icon + action label. Icon matches current input method (updates if player switches). | Always show platform-correct icon — do not hardcode "Press A" for all platforms | -| Holding (for hold inputs) | Radial fill on the button icon shows hold progress. Label changes to active verb ("Opening..."). | | -| Cannot interact (blocked) | Icon dims. Label shows reason if known ("Too heavy", "Need key"). | Optional — only show blocked state if the reason is meaningful to the player | -| Disappearing | Fades out. | Triggered when player exits interaction zone | +| [Appearing / Idle / Holding / Cannot interact / Disappearing] | [Visual treatment] | [Behavioral notes] | -**Accessibility**: The button icon must be accompanied by a text label — do not rely on icon alone (some players use custom button labels or adaptive controllers with non-standard icons). The prompt must be positioned to not overlap character health or critical HUD information. +**Accessibility**: [Text label alongside button icon; positioning must not overlap critical HUD] -**Implementation Notes**: [Godot: Attach as a `Node3D` child (or `Node2D` child in 2D) of the interactable object. Use a `BillboardMesh` or a `SubViewport` with a UI scene for 3D games — this keeps the prompt facing the camera without code. Update the button icon texture based on `Input.get_joy_name()` or keyboard detection via `InputEventKey` vs `InputEventJoypadButton`. Hold progress implemented as an `AnimationPlayer` or `Tween` on a radial mask shader.] +**Implementation Notes**: [Engine-specific guidance; input-method-aware icon swapping] --- @@ -837,33 +533,28 @@ when the player enters the interaction zone, disappears when they leave. **Category**: Game-Specific **Status**: Draft -**When to Use**: Floating feedback numbers above combat participants. Normal -damage, critical damage, healing, miss. +**When to Use**: [Floating feedback numbers above combat participants] **Variants**: | Variant | Visual | Notes | |---------|--------|-------| -| Normal damage | White number, normal weight, medium size. | | -| Critical hit | Larger size (1.5x), bold weight, orange or yellow — verify colorblind safe. Brief scale impact (1.3x → 1.0x on appear). | Example: Path of Exile and Diablo IV both use scale-pop for crits to make them immediately recognizable by size alone, independent of color. | -| Healing | Green (verify colorblind safe — use + prefix and upward trajectory as non-color backups). | | -| Miss / Evade | "MISS" text, grey, italic. Floats at smaller size. | | -| Status damage (DoT) | Smaller size, distinct color matching the status effect. | | +| [Normal / Critical / Healing / Miss / Status damage] | [Visual treatment] | [Behavioral notes] | -**Behavior**: Numbers float upward from the hit location over 1.0 second. Numbers fade from 100% to 0% during the last 0.4 seconds. Multiple numbers from rapid hits stagger horizontally to avoid overlap. Maximum simultaneous damage numbers on screen: [define per game — typically 8-12 per character]. +**Behavior**: [Float trajectory, fade timing, overlap staggering, maximum simultaneous numbers] -**Accessibility**: Damage numbers are purely supplementary feedback — they must never be the only way to understand combat state. Health bars are the authoritative source. Provide an option to disable damage numbers entirely (some players find them visually overwhelming). When disabled, the game must remain fully playable. +**Accessibility**: [Supplementary-only feedback; disable option; game fully playable when disabled] -**Implementation Notes**: [Godot: Pool of `Label3D` (3D games) or `Label` (2D games) -instances recycled via an object pool. Each instance is given a random small -horizontal offset on spawn (±20px) to reduce overlap. Float animation via -`Tween` on `position.y` and `modulate.a`. Critical hit scale-pop via Tween -with `EASE_OUT` on scale followed by linear settle.] +**Implementation Notes**: [Engine-specific guidance; object pooling] --- ## Navigation Patterns +> Full reference specifications for this section and the next are in +> `.claude/docs/templates/guidance/interaction-pattern-library-guide-navigation-feedback.md` — load +> only the pattern(s) currently being specified. + --- #### Screen Push / Pop / Replace @@ -875,19 +566,13 @@ These three patterns define how screens enter and exit the navigation stack. | Pattern | Trigger | Animation | Stack Behavior | Focus Behavior | |---------|---------|-----------|---------------|----------------| -| Push | Navigate deeper (open submenu, open detail view) | New screen slides in from right. Previous screen slides left and dims. | Previous screen remains on stack | Focus moves to first interactive element on new screen | -| Pop (Back) | Back button / Escape / B / Circle | Current screen slides right and exits. Previous screen slides in from left and brightens. | Current screen removed from stack | Focus returns to the element that triggered the Push | -| Replace | Navigate to a peer screen (not child, not parent). Loading screen. | Fade out current, fade in new. No directional bias. | Current screen removed. New screen added. | Focus moves to first interactive element on new screen | +| [Push / Pop / Replace] | [Trigger] | [Animation] | [Stack behavior] | [Focus behavior] | -**Animation durations**: Push/Pop: 250ms ease-in-out. Replace: 200ms fade out + 200ms fade in. +**Animation durations**: [Push/Pop and Replace durations + easing] -**Motion reduction**: All slide animations become fades. Duration reduces to 100ms. +**Motion reduction**: [Fallback behavior] -**Implementation Notes**: [Godot: Implement as a `ScreenManager` singleton managing -a stack of `Control` scenes. `push(screen_scene)` instantiates and animates in. -`pop()` animates out and frees. `replace(screen_scene)` calls pop then push without -the intermediate stack state. Use `CanvasLayer` per screen to isolate input handling. -Store the "return focus" element reference before pushing so it can be restored on pop.] +**Implementation Notes**: [Engine-specific guidance; screen stack manager and return-focus storage] --- @@ -897,22 +582,11 @@ Store the "return focus" element reference before pushing so it can be restored **Status**: Draft > Focus management is the most common keyboard and gamepad accessibility failure -> in game UIs. These rules must be implemented consistently. A player should -> never be in a state where they cannot see which element is focused, or where -> Tab/D-pad produces no visible result. +> in game UIs. These rules must be implemented consistently. | Rule | Description | |------|-------------| -| Screen open | Focus is placed on the most logical interactive element — typically the Primary button, the first list item, or the last-focused element if the screen was previously visited. Never on a non-interactive element. | -| Screen close / pop | Focus returns to the element that triggered the navigation (the button that opened the screen, the list item that was selected). If that element no longer exists, focus goes to the nearest preceding interactive element. | -| Modal open | Focus is trapped inside the modal. See Modal Dialog pattern. | -| Modal close | Focus returns to the element that triggered the modal. | -| Element disabled | If the focused element becomes disabled, focus moves to the next available interactive element in the tab order. | -| Element destroyed | If the focused element is removed from the scene, focus moves to the nearest preceding element in the tab order. | -| Screen without interactive elements | Focus management is a no-op. Ensure back/cancel input still works. | -| Tab key (keyboard) | Moves focus forward through interactive elements in document order (left to right, top to bottom). Shift+Tab moves backward. | -| D-pad (gamepad) | Moves focus in the spatial direction pressed. Spatial navigation is preferred over strict tab order for gamepad. Never wrap focus between unrelated regions (e.g., Tab bar and content area should be separate navigation regions). | -| Focus is always visible | Focus ring or equivalent focus indicator must ALWAYS be visible when an element is focused via keyboard or gamepad. Never suppress focus indicators. | +| [Screen open / close, modal open / close, element disabled / destroyed, Tab order, D-pad spatial navigation, focus visibility] | [Rule description] | --- @@ -926,13 +600,9 @@ Store the "return focus" element reference before pushing so it can be restored | Platform | Input | Behavior | |----------|-------|---------| -| PC (keyboard) | Escape | Close top-most modal / go back one screen in stack / if at root screen (main menu), open "quit?" confirmation | -| PC (gamepad) | B (Xbox layout) / Circle (PS layout) | Same as Escape | -| Xbox | B button | Same as Escape | -| PlayStation | Circle button | Same as Escape | -| Nintendo Switch | B button | Same as Escape (NOTE: Nintendo uses B for confirm in some first-party titles — verify platform convention for this release and document the decision) | +| [Platform] | [Input] | [Behavior] | -**Rules**: This input must never be overridden to do something other than "go back / cancel." If a screen has no back action (e.g., the game is paused and the player must make a choice), Escape does nothing or shows a "you must choose" message — it does not navigate away. Every screen must define its Escape behavior explicitly in its UX spec. +**Rules**: [Never override "go back / cancel"; every screen defines its Escape behavior explicitly in its UX spec] --- @@ -947,12 +617,9 @@ Store the "return focus" element reference before pushing so it can be restored | Scope | Pattern | Notes | |-------|---------|-------| -| Full screen (initial load) | Full-screen loading screen with game art, progress bar (determinate if possible), tip text (optional). | Never use an empty black screen. Give the player something to read or look at. | -| Full screen (level transition) | Fade to black, loading screen, fade from black to new scene. | The fade removes the pop of the previous scene disappearing. | -| Component / inline | Spinner or skeleton placeholder replaces the loading component. Component does not shift layout when content loads. | Skeleton placeholder (grey boxes approximating content shape) is preferable to spinner for layout-heavy content — it prevents layout shift on load. | -| Background / async | No visual indication unless operation exceeds 2 seconds. After 2 seconds, show a small spinner or toast. | Do not show loading indicators for operations that complete in under 2 seconds — the flash of an indicator is more disruptive than waiting. | +| [Full screen (initial) / Full screen (transition) / Component inline / Background async] | [Pattern] | [Notes] | -**Accessibility**: Loading states must announce to screen readers: "[Context] loading, please wait." Completion must announce "[Context] loaded." For full-screen loading, ensure the loading screen itself is navigable to screen readers — the tips text and any UI elements must be exposed. +**Accessibility**: [Screen reader loading/loaded announcements; loading screens navigable] --- @@ -961,20 +628,14 @@ Store the "return focus" element reference before pushing so it can be restored **Category**: Feedback **Status**: Draft -> Empty states are consistently the least-designed parts of game UIs. They are -> the difference between a player feeling "this is where I'll store my items" -> and "why is nothing here? did something break?" Every empty list and grid must -> have a designed empty state. The empty state is not an error — it is a starting -> point. +> Every empty list and grid must have a designed empty state. The empty state +> is not an error — it is a starting point. | Location | Empty State Content | Notes | |----------|--------------------|----| -| Inventory (no items) | Icon (subtle, large, centered). Message: "Your inventory is empty." Sub-message: "Items you find on your journey will appear here." | Do not say "No items found" — "found" implies a failed search. | -| Quest Log (no active quests) | Icon. Message: "No active quests." Sub-message: "Talk to characters marked with [quest marker icon] to start a quest." | Give the player a clear action. | -| Achievements (none earned) | Icon. Message: "No achievements yet." List of hint achievements: "Try [Action] to earn your first achievement." | Gamified motivation, not just emptiness. | -| Search results (no matches) | Icon. Message: "No results for '[search term]'." Sub-message: "Try a different search or [browse all]." | Mirror the search term back at them. Give an alternative action. | +| [Location] | [Icon + message + sub-message or action] | [Notes] | -**Rule**: Every empty state must include an icon, a message, and either a sub-message or an action button. A blank container with no explanation is never acceptable. +**Rule**: Every empty state must include an icon, a message, and either a sub-message or an action button. --- @@ -985,15 +646,11 @@ Store the "return focus" element reference before pushing so it can be restored | Error Type | Pattern | Tone | |-----------|---------|------| -| Input validation (form field) | Inline error message below the field. Error icon left of message. Red border on field (colorblind-safe with icon). | Neutral and specific — "Username must be 3-20 characters." Not "Invalid input." | -| Operation failed (save error, network error) | Toast notification for non-critical failures. Modal Dialog for critical failures (save file cannot be written). | Calm and actionable — "Save failed. Check storage space." Not "FATAL ERROR." | -| System error (crash, data corruption) | Full-screen error screen with error code, recovery options ("Restart Game," "Load last save"), and support contact. | Reassuring — acknowledge the problem, give the player agency. Never blame the player. | -| Soft error (action cannot be performed) | Toast or inline message. | Explanatory — "Not enough gold" not "Action unavailable." | +| [Input validation / Operation failed / System error / Soft error] | [Pattern] | [Tone guidance] | -**Principle**: Error messages are never the player's fault. They are the game telling the player what happened and what to do next. Remove the word "invalid" from all error messages — replace with specific explanations. +**Principle**: Error messages are never the player's fault. They tell the player what happened and what to do next. --- - ## Animation Standards > These timing values apply to ALL patterns in this library. When a pattern says diff --git a/.claude/docs/templates/project-stage-report.md b/.claude/docs/templates/project-stage-report.md index c033dd69..0aa806a6 100644 --- a/.claude/docs/templates/project-stage-report.md +++ b/.claude/docs/templates/project-stage-report.md @@ -70,7 +70,7 @@ ### Prototypes - **Active Prototypes**: [N] in `prototypes/` - - ✅ [Prototype 1] — documented with README + - ✅ [Prototype 1] — documented with REPORT.md - ⚠️ [Prototype 2] — no README, unclear status - **Archived**: [N] (experiments completed) - **Key Gaps**: diff --git a/.claude/docs/templates/session-state.md b/.claude/docs/templates/session-state.md new file mode 100644 index 00000000..e8a790e6 --- /dev/null +++ b/.claude/docs/templates/session-state.md @@ -0,0 +1,74 @@ +# Active Session State — [project or branch] + +` is free-form narrative for humans + and for a returning agent that wants detail. No hook ever injects it, so + it may grow — but see Rotation below. + + Why the split exists: before it, `active.md` was one append-only file doing + both jobs. It reached 712 lines / 44 KB, and each consumer invented its own + slice of it — the status line parsed for a STATUS block nothing wrote, + pre-compact injected `head -100` (12.7 KB), and session-start previewed + `tail -20`, the opposite end. They disagreed because there was no contract to + agree on. Now every consumer reads the named region. + + ROTATION. When the narrative below grows past ~200 lines, move it to + `production/session-logs/` and start it fresh: + bash .claude/scripts/rotate-session-state.sh + Nothing is lost — and the commit history already records most of what the + narrative repeats. +--> + + +Epic: +Feature: +Task: + + + +**Updated:** [YYYY-MM-DD] +**Branch:** `[branch]` +**Current task:** [one line — what is actively being worked on] +**Next step:** [one line — the very next action, concrete enough to resume cold] +**Blocked on:** [one line, or `nothing`] +**Files in progress:** [paths, comma-separated, or `none`] +**Open questions:** [one line each, or `none`] + + +--- + +## Notes + +[Free-form. Decisions and their rationale, what was tried and rejected, context +that would be expensive to reconstruct. Not machine-read — write for the person +or agent picking this up cold.] + + diff --git a/.claude/docs/templates/skill-test-spec.md b/.claude/docs/templates/skill-test-spec.md deleted file mode 100644 index c39f7037..00000000 --- a/.claude/docs/templates/skill-test-spec.md +++ /dev/null @@ -1,96 +0,0 @@ -# Skill Test Spec: /[skill-name] - -## Skill Summary - -[One paragraph: what this skill does, when to use it, what it produces. Include -the primary output artifact, the verdict format it uses, and which pipeline stage -it belongs to.] - ---- - -## Static Assertions (Structural) - -Verified automatically by `/skill-test static` — no fixture needed. - -- [ ] Has required frontmatter fields: `name`, `description`, `argument-hint`, `user-invocable`, `allowed-tools` -- [ ] Has ≥2 phase headings (## Phase N or numbered ## sections) -- [ ] Contains verdict keywords: [list the ones expected, e.g., PASS, FAIL, CONCERNS] -- [ ] Contains "May I write" collaborative protocol language (if skill writes files) -- [ ] Has a next-step handoff at the end - ---- - -## Test Cases - -### Case 1: Happy Path — [short description] - -**Fixture:** [Describe the assumed project state. Which files exist? What do they -contain? E.g., "game-concept.md exists with all 8 required sections complete. -systems-index.md exists. All MVP GDDs are present and individually reviewed."] - -**Input:** `/[skill-name] [args]` - -**Expected behavior:** -1. [Phase 1 action — what the skill should read or check] -2. [Phase 2 action — what the skill should evaluate] -3. [Phase N action — what the skill should output] - -**Assertions:** -- [ ] Skill reads [specific file] before producing output -- [ ] Output includes verdict keyword [PASS/FAIL/etc.] -- [ ] Output lists [specific content] from the fixture -- [ ] Skill asks for approval before writing any file - ---- - -### Case 2: Failure Path — [short description, e.g., "Missing required artifact"] - -**Fixture:** [Describe the failure state. E.g., "game-concept.md is missing. -No files exist in design/gdd/."] - -**Input:** `/[skill-name] [args]` - -**Expected behavior:** -1. [Phase 1: skill detects missing file] -2. [Phase 2: skill surfaces the gap rather than assuming OK] -3. [Output: FAIL or BLOCKED verdict with specific blocker named] - -**Assertions:** -- [ ] Skill does NOT output PASS when the fixture is incomplete -- [ ] Skill names the specific missing artifact -- [ ] Skill suggests a remediation action (e.g., "Run /[other-skill]") -- [ ] Skill does not create files to fill in the gap without asking - ---- - -### Case 3: Edge Case — [short description, e.g., "No argument provided"] - -**Fixture:** [State of project files for this case] - -**Input:** `/[skill-name]` (no argument) - -**Expected behavior:** -1. [What the skill should do when invoked without arguments] - -**Assertions:** -- [ ] [assertion] - ---- - -## Protocol Compliance - -- [ ] Uses "May I write" before all file writes -- [ ] Presents findings or report before asking for write approval -- [ ] Ends with a recommended next step or follow-up skill -- [ ] Never auto-creates files without explicit user approval -- [ ] Does not skip phases or jump straight to a verdict without checking - ---- - -## Coverage Notes - -[Document what is intentionally NOT tested in this spec and why. Examples: -- "Case 3 (all-mode) is not covered because it runs too many checks to evaluate - in a single spec — test each sub-mode individually." -- "The database integration path is not covered as it requires a live environment." -- "Edge cases involving corrupted YAML files are deferred to a future spec."] diff --git a/.claude/docs/templates/test-evidence.md b/.claude/docs/templates/test-evidence.md index b9b52da9..7d76d872 100644 --- a/.claude/docs/templates/test-evidence.md +++ b/.claude/docs/templates/test-evidence.md @@ -32,6 +32,10 @@ the acceptance criteria numbers from the story that this evidence covers.] List all captured evidence below. Store files in the same directory as this document or in `production/qa/evidence/[story-slug]/`. +**The file must actually be on disk.** Visual/Feel and UI gates are BLOCKING by +default, and `/story-done` and `/test-evidence-review` glob for the image — a +filename listed here with no file beside it does not satisfy the gate. + | # | Filename | What It Shows | Acceptance Criterion | |---|----------|--------------|----------------------| | 1 | `[filename.png]` | [brief description of what is visible] | AC-1 | diff --git a/.claude/docs/templates/ux-spec.md b/.claude/docs/templates/ux-spec.md index 4a0c8505..01561616 100644 --- a/.claude/docs/templates/ux-spec.md +++ b/.claude/docs/templates/ux-spec.md @@ -1,5 +1,7 @@ # UX Specification: [Screen / Flow Name] +> Authoring guidance: .claude/docs/templates/guidance/ux-spec-guide.md (load per-section as you author — do not read entirely). + > **Status**: Draft | In Review | Approved | Implemented > **Author**: [Name or agent — e.g., ui-designer] > **Last Updated**: [Date] @@ -20,76 +22,39 @@ ## 1. Purpose & Player Need -> **Why this section exists**: Every screen must justify its existence from the -> player's perspective. Screens that are designed from a developer perspective ("display -> the save data") produce cluttered, confusing interfaces. Screens designed from the -> player's perspective ("let the player feel confident their progress is safe before they -> put the controller down") produce purposeful, calm interfaces. Write this section before -> touching any layout decisions — it is the filter through which every subsequent choice -> is evaluated. - **What player need does this screen serve?** -[One paragraph. Name the real human need, not the system function. Consider: what would -a player say they want when they open this screen? What would frustrate them if it did -not work? That frustration describes the need. - -Example — bad: "Displays the player's current items and equipment." -Example — good: "Lets the player understand what they're carrying and quickly decide what -to take into the next encounter, without breaking their mental model of the game world. -The inventory is the player's planning tool between moments of action."] +[One paragraph — the real human need from the player's perspective, not the system function. See guide Section 1 for good/bad examples.] **The player goal** (what the player wants to accomplish): -[One sentence. Specific enough that you could write an acceptance criterion for it. -Example: "Find the item they are looking for within three button presses and equip it -without navigating to a separate screen."] +[One sentence, specific enough to write an acceptance criterion for.] **The game goal** (what the game needs to communicate or capture): -[One sentence. This is what the system needs from this interaction. Example: "Record the -player's equipment choices and relay them to the combat system before the next encounter -loads." This section prevents UI that looks good but fails to serve the system it is -part of.] +[One sentence — what the system needs from this interaction.] --- ## 2. Player Context on Arrival -> **Why this section exists**: Screens do not exist in isolation. A player opening the -> inventory mid-combat is in a completely different cognitive and emotional state than -> a player opening it after clearing a dungeon. The same information architecture can -> feel oppressively complex in one context and trivially simple in another. Document the -> context so that design decisions — what to show first, what to hide, what to animate, -> what to simplify — are calibrated to the actual player arriving at this screen, not -> an abstract user. - | Question | Answer | |----------|--------| -| What was the player just doing? | [e.g., Completed a combat encounter / Pressed Esc from exploration / Triggered a story cutscene] | -| What is their emotional state? | [e.g., High tension — just narrowly survived / Calm — exploring between objectives] | -| What cognitive load are they carrying? | [e.g., High — actively tracking enemy positions / Low — no active threats] | -| What information do they already have? | [e.g., They know they just picked up an item but haven't seen its stats yet] | -| What are they most likely trying to do? | [e.g., Check if the new item is better than their current weapon — primary use case] | -| What are they likely afraid of? | [e.g., Missing something, making an irreversible mistake, losing track of where they were] | +| What was the player just doing? | [Prior activity / triggering state] | +| What is their emotional state? | [Tension / calm / urgency] | +| What cognitive load are they carrying? | [High / low + what they are tracking] | +| What information do they already have? | [Known context on arrival] | +| What are they most likely trying to do? | [Primary use case] | +| What are they likely afraid of? | [Risks and anxieties to design against] | **Emotional design target for this screen**: -[One sentence describing the feeling the player should have while using this screen. -Example: "Confident and in control — the player should feel like they have complete -information and complete authority over their choices, with no ambiguity about outcomes."] +[One sentence describing the feeling the player should have while using this screen.] --- ## 3. Navigation Position -> **Why this section exists**: A screen that does not know where it sits in the -> navigation hierarchy cannot define its entry/exit transitions, its back-button -> behavior, or its relationship to the game's pause state. Navigation position also -> reveals architectural problems early — if this screen is reachable from eight -> different places, that is a complexity flag that should be resolved in design, not -> implementation. - **Screen hierarchy** (use indentation to show parent-child relationships): ``` @@ -110,51 +75,31 @@ information and complete authority over their choices, with no ambiguity about o | Entry Point | Triggered By | Notes | |-------------|-------------|-------| -| [e.g., Main Menu → Play] | [Player selects "New Game"] | [Primary entry point] | -| [e.g., Pause Menu → Resume] | [Player presses Start from any gameplay state] | [Secondary entry] | -| [e.g., Game event] | [Tutorial system forces open first time only] | [Systemic entry — must not break if player dismisses] | +| [Entry point] | [Trigger] | [Notes] | --- ## 4. Entry & Exit Points -> **Why this section exists**: Entry and exit define the screen's contract with the -> rest of the navigation system. Every entry point must have a corresponding exit point. -> Transitions that are undefined become bugs — the player finds themselves stuck, or the -> game state becomes inconsistent. Fill this table completely before implementation -> begins. Empty cells are a sign that design work is unfinished. +> Every entry point must have a corresponding exit point. Empty cells are a sign +> that design work is unfinished. See guide Section 4 for worked examples. **Entry table**: | Trigger | Source Screen / State | Transition Type | Data Passed In | Notes | |---------|----------------------|-----------------|----------------|-------| -| [e.g., Player presses Inventory button] | [Gameplay / Exploration state] | [Overlay push — game pauses] | [Current player loadout, inventory contents] | [Works from any non-combat state] | -| [e.g., Item pickup prompt accepted] | [Gameplay / Item Pickup dialog] | [Replace dialog with full inventory] | [Newly acquired item pre-highlighted] | [The new item should be visually distinguished on open] | -| [e.g., Quest system directs player to inventory] | [Gameplay / Quest Update notification] | [Overlay push] | [Quest-relevant item ID for highlight] | [Screen should deep-link to the relevant item] | +| [Trigger] | [Source] | [Transition] | [Data in] | [Notes] | **Exit table**: | Exit Action | Destination | Transition Type | Data Returned / Saved | Notes | |-------------|------------|-----------------|----------------------|-------| -| [e.g., Player closes inventory (Back/B/Esc)] | [Previous state — Exploration] | [Overlay pop — game resumes] | [Updated equipment loadout committed] | [Changes must be committed before transition begins] | -| [e.g., Player selects "Equip" on item] | [Same screen, updated state] | [In-place state change] | [Loadout change event fired] | [No navigation, just a state refresh] | -| [e.g., Player navigates to Map from inventory shortcut] | [Map Screen] | [Replace] | [No data] | [Inventory state is preserved if player returns] | +| [Exit action] | [Destination] | [Transition] | [Data out / committed] | [Notes] | --- ## 5. Layout Specification -> **Why this section exists**: The layout specification is the handoff artifact between -> UX design and UI programming. It does not need to be pixel-perfect — it needs to -> communicate hierarchy (what is important), proximity (what belongs together), and -> proportion (what is big vs. small). ASCII wireframes achieve this without requiring -> design software. A programmer who reads this section should be able to build the -> correct structure without guessing. An artist who reads it should know where -> visual weight should be concentrated. -> -> Draw the layout at one standard resolution (e.g., 1920x1080). Note adaptations -> for other resolutions separately. - ### 5.1 Wireframe ``` @@ -166,37 +111,14 @@ information and complete authority over their choices, with no ambiguity about o ... for scrollable content ● for the focused element on open -Example: -┌──────────────────────────────────────────────┐ -│ [← Back] INVENTORY [Options] │ ← HEADER ZONE -├──────────────────────────────────────────────┤ -│ ┌──────────────┐ ┌─────────────────────────┐│ -│ │ CATEGORY NAV │ │ ITEM DETAIL PANEL ││ ← CONTENT ZONE -│ │ ● Weapons │ │ Item Name ││ -│ │ Armor │ │ {item icon} ││ -│ │ Consumable│ │ Stats comparison ││ -│ │ Key Items │ │ Description text... ││ -│ ├──────────────┤ └─────────────────────────┘│ -│ │ ITEM GRID │ │ -│ │ {□}{□}{□}{□} │ │ -│ │ {□}{□}{□}{□} │ │ -│ │ ... │ │ -│ └──────────────┘ │ -├──────────────────────────────────────────────┤ -│ [Equip] [Drop] [Compare] [Close] │ ← ACTION BAR -└──────────────────────────────────────────────┘ -] +See guide Section 5 for a completed example wireframe.] ``` ### 5.2 Zone Definitions | Zone Name | Description | Approximate Size | Scrollable? | Overflow Behavior | |-----------|-------------|-----------------|-------------|-------------------| -| [e.g., Header Zone] | [Top bar: navigation, screen title, global actions] | [Full width, ~10% height] | [No] | [Truncate long screen names with ellipsis] | -| [e.g., Category Nav] | [Left panel: item category tabs] | [~25% width, ~75% height] | [Yes — vertical if categories exceed panel] | [Scroll indicator appears at bottom of list] | -| [e.g., Item Grid] | [Center: grid of item icons for selected category] | [~45% width, ~75% height] | [Yes — vertical] | [Page-based: 4x4 grid, next page on overflow] | -| [e.g., Detail Panel] | [Right: stats and description for selected item] | [~30% width, ~75% height] | [Yes — vertical for long descriptions] | [Fade at bottom, scroll to reveal] | -| [e.g., Action Bar] | [Bottom: context-sensitive actions for selected item] | [Full width, ~15% height] | [No] | [Actions collapse to icon-only below 4] | +| [Zone] | [Contents and purpose] | [% width x % height] | [Yes/No] | [Overflow handling] | ### 5.3 Component Inventory @@ -205,96 +127,58 @@ Example: | Component Name | Type | Zone | Purpose | Required? | Reuses Existing Component? | |----------------|------|------|---------|-----------|---------------------------| -| [e.g., Back Button] | [Button] | [Header] | [Returns to previous screen] | [Yes] | [Yes — standard NavButton component] | -| [e.g., Screen Title Label] | [Text] | [Header] | [Displays "INVENTORY" or context name] | [Yes] | [Yes — ScreenTitle component] | -| [e.g., Category Tab] | [Toggle Button] | [Category Nav] | [Filters item grid by category] | [Yes] | [No — new component needed] | -| [e.g., Item Slot] | [Icon + Frame] | [Item Grid] | [Represents one inventory slot, empty or filled] | [Yes] | [No — new component] | -| [e.g., Item Name Label] | [Text] | [Detail Panel] | [Shows selected item's name] | [Yes] | [Yes — BodyText component] | -| [e.g., Stat Comparison Row] | [Compound — label + value + delta] | [Detail Panel] | [Shows stat value vs. currently equipped] | [Yes] | [No — new component] | -| [e.g., Equip Button] | [Primary Button] | [Action Bar] | [Equips selected item in appropriate slot] | [Yes] | [Yes — PrimaryAction component] | -| [e.g., Empty State Message] | [Text + Icon] | [Item Grid] | [Shown when category has no items] | [Yes] | [Yes — EmptyState component] | +| [Component] | [Type] | [Zone] | [Purpose] | [Yes/No] | [Yes — pattern/component name, or No — new] | -**Primary focus element on open**: [e.g., The first item in the Item Grid — or, if deep-linked, the highlighted item. If the grid is empty, focus lands on the first Category Tab.] +**Primary focus element on open**: [Element that receives focus when the screen opens, including deep-link and empty-state cases] --- ## 6. States & Variants -> **Why this section exists**: A screen is not a single picture — it is a set of -> states, each of which must look correct and behave correctly. Screens that are -> designed only in their "happy path" state ship with broken empty states, invisible -> loading indicators, and crashes when data is missing. Document every state before -> implementation. The states table is also the test matrix for QA. +> Document every state before implementation — at minimum: loading, empty, +> populated, and error. The states table is also the test matrix for QA. +> See guide Section 6 for a worked example. | State Name | Trigger | What Changes Visually | What Changes Behaviorally | Notes | |------------|---------|----------------------|--------------------------|-------| -| [Loading] | [Screen is opening, data not yet available] | [Item Grid shows skeleton/shimmer placeholders; Action Bar buttons disabled] | [No interactions possible except Close] | [Should not be visible >500ms under normal conditions; if it is, investigate data fetch performance] | -| [Empty — no items in category] | [Player switches to a category with zero items] | [Item Grid replaced by EmptyState component: icon + "Nothing here yet."] | [Action Bar shows no item actions; Drop/Equip/Compare all hidden] | [Do not show disabled buttons — remove them. Disabled buttons with no tooltip are confusing.] | -| [Populated — items present] | [Category has at least one item] | [Item Grid fills with item slots; first slot is auto-focused] | [All item actions available for selected item] | [Default and most common state] | -| [Item Selected] | [Player navigates to an item slot] | [Detail Panel populates; selected slot has focus ring; Action Bar updates to item's valid actions] | [Equip/Drop/Compare enabled based on item type] | [Equip is disabled if item is already equipped — show a "Equipped" badge instead] | -| [Confirmation Pending — Drop] | [Player selects Drop action] | [Confirmation dialog overlays the screen] | [All background interactions suspended until dialog resolves] | [Use a modal confirmation, not an inline toggle. Items cannot be recovered after dropping.] | -| [Error — data load failed] | [Inventory data could not be retrieved] | [Item Grid shows error state: icon + "Couldn't load items." + Retry button] | [Only Retry and Close are available] | [Log the error; do not expose technical details to player] | -| [Item Newly Acquired] | [Screen opened from item pickup deep-link] | [Newly acquired item has a visual "New" badge; Detail Panel pre-populated with that item] | [Same as Item Selected but with badge until player navigates away] | [Badge persists until the player manually navigates off that slot once] | +| [Loading / Empty / Populated / Selected / Confirmation / Error / other] | [Trigger] | [Visual changes] | [Behavioral changes] | [Notes] | --- ## 7. Interaction Map -> **Why this section exists**: This section is the source of truth for what every -> input does on this screen. It forces the designer to think through every input -> method (mouse, keyboard, gamepad, touch) and every interactive state (hover, focus, -> pressed, disabled). Gaps in this table are bugs waiting to happen. The -> interaction map is also the input for the accessibility audit — if an action is -> only reachable by mouse, it will fail the keyboard and gamepad columns. +> Cover every input method for the platform target (mouse, keyboard, gamepad, +> touch). Gaps in this table are bugs waiting to happen. See guide Section 7. ### 7.1 Navigation Inputs | Input | Platform | Action | Visual Response | Audio Cue | Notes | |-------|----------|--------|-----------------|-----------|-------| -| [Arrow keys / D-Pad] | [All] | [Move focus within active zone] | [Focus ring moves to adjacent element] | [Soft navigation tick] | [Wrap at edges within zone; do not cross zones with arrows alone] | -| [Tab / R1] | [KB / Gamepad] | [Move focus to next zone (Category → Grid → Detail → Action Bar)] | [Focus ring jumps to first element in next zone] | [Distinct zone-change tone] | [Shift+Tab / L1 goes backward] | -| [Mouse hover] | [PC] | [Show hover state on interactive elements] | [Highlight / underline / color shift] | [None] | [Hover does NOT move focus — only click does] | -| [Mouse click] | [PC] | [Select and focus the clicked element] | [Pressed state flash, then selected/focused] | [Soft click] | [Right-click opens context menu if applicable; otherwise no-op] | -| [Touch tap] | [Mobile] | [Select and activate in one gesture] | [Press ripple] | [Soft click] | [Treat tap as click + confirm for low-risk actions; require explicit confirm for destructive actions] | +| [Input] | [Platform] | [Action] | [Visual response] | [Audio] | [Notes] | ### 7.2 Action Inputs | Input | Platform | Context (What must be focused) | Action | Response | Animation | Audio Cue | Notes | |-------|----------|-------------------------------|--------|----------|-----------|-----------|-------| -| [Enter / A button / Left click] | [All] | [Item slot focused] | [Select item → populate Detail Panel] | [Detail panel slides in or updates in place] | [Panel fade/slide in, 120ms] | [Soft select tone] | [If item already selected: no-op] | -| [Enter / A button] | [All] | [Equip button focused] | [Equip selected item] | [Button animates press; item badge updates to "Equipped"; previously equipped item loses badge] | [Badge swap, 80ms] | [Equip success sound] | [Fires EquipItem event to Inventory system] | -| [Triangle / Y button / Right-click] | [All] | [Item slot focused] | [Open item context menu] | [Context menu appears adjacent to item slot] | [Popover, 80ms] | [Menu open sound] | [Context menu contains: Equip, Drop, Inspect, Compare] | -| [Square / X button] | [Gamepad] | [Item slot focused] | [Quick-equip without opening detail] | [Equip animation plays inline on slot] | [Slot flash, 80ms] | [Equip success sound] | [Convenience shortcut; does not change screen state] | -| [Esc / B button / Back] | [All] | [Any, screen level] | [Close screen and return to previous state] | [Screen exit transition plays] | [Slide out, 200ms] | [Back/close tone] | [Commits all changes before closing. No discard — inventory is not a draft.] | -| [F / L2] | [KB / Gamepad] | [Any] | [Toggle filter panel] | [Sort/filter overlay opens] | [Slide in from right, 200ms] | [Panel open tone] | [If no items in category, filter is disabled] | +| [Input] | [Platform] | [Focus context] | [Action] | [Response] | [Animation + timing] | [Audio] | [Notes] | ### 7.3 State-Specific Behaviors | State | Input Restriction | Reason | |-------|------------------|--------| -| [Loading] | [All item and action inputs disabled] | [No data to act on; prevent race conditions] | -| [Confirmation dialog open] | [Only Confirm and Cancel inputs active] | [Modal — background is locked] | -| [Error state] | [Only Retry and Close active] | [No data available to navigate] | +| [State] | [Which inputs are disabled] | [Why] | --- ## 8. Data Requirements -> **Why this section exists**: The separation between UI and game state is the most -> important architectural boundary in a game's UI system. UI reads data; it does not -> own it. UI fires events; it does not write state directly. This section defines -> exactly what data this screen needs to display, where it comes from, and how -> frequently it updates. Filling this table before implementation prevents two -> common failure modes: (1) UI developers reaching into systems they should not touch, -> and (2) systems not knowing they need to expose data until a UI is half-built. +> UI reads data; it does not own it. UI fires events; it does not write state +> directly. Every displayed data element needs a source system and owner. +> See guide Section 8 for a worked example. | Data Element | Source System | Update Frequency | Who Owns It | Format | Null / Missing Handling | |--------------|--------------|-----------------|-------------|--------|------------------------| -| [e.g., Item list] | [Inventory System] | [On screen open; on InventoryChanged event] | [InventorySystem] | [Array of ItemData structs: id, name, icon_path, category, stats, is_equipped] | [Empty array → show Empty State. Never null — system must return array.] | -| [e.g., Equipped loadout] | [Equipment System] | [On screen open; on EquipmentChanged event] | [EquipmentSystem] | [Dict mapping slot_id → item_id] | [Unequipped slot has null value — UI shows empty slot icon] | -| [e.g., Item stat comparisons] | [Stats System] | [On item selection change] | [StatsSystem] | [Dict mapping stat_name → {current, new, delta}] | [If no item selected, detail panel shows placeholder. Stats system must handle this gracefully.] | -| [e.g., Player currency] | [Economy System] | [On screen open only — inventory does not show live currency] | [EconomySystem] | [Int — gold pieces] | [If currency system not active for this game mode, hide the currency row entirely] | -| [e.g., Newly acquired item flag] | [Inventory System] | [On screen open] | [InventorySystem] | [Array of item_ids flagged as new] | [If empty array, no badges shown] | +| [Data element] | [System] | [When it updates — specific trigger, not "realtime"] | [Owning system — never the UI] | [Type/shape] | [What shows when data is unavailable] | > **Rule**: This screen must never write directly to any system listed above. All > player actions fire events (see Section 9). Systems update their own data and @@ -304,52 +188,30 @@ Example: ## 9. Events Fired -> **Why this section exists**: This is the other half of the UI/system boundary. -> Where Section 8 defines what the UI reads, this section defines what the UI -> communicates back to the game. Specifying events at design time prevents UI -> programmers from writing game logic, and prevents game programmers from being -> surprised by what the UI does. Every destructive or state-changing player action -> must appear in this table. +> Every destructive or state-changing player action must appear in this table. +> See guide Section 9 for a worked example. | Player Action | Event Fired | Payload | Receiver System | Notes | |---------------|-------------|---------|-----------------|-------| -| [Player equips an item] | [EquipItemRequested] | [{item_id: string, target_slot: string}] | [Equipment System] | [Equipment System validates the action and fires EquipmentChanged if successful; UI listens for EquipmentChanged to update its display] | -| [Player drops an item] | [DropItemRequested] | [{item_id: string, quantity: int}] | [Inventory System] | [Fires only after player confirms the drop dialog. Inventory System removes the item and fires InventoryChanged.] | -| [Player opens item compare] | [ItemCompareOpened] | [{item_a_id: string, item_b_id: string}] | [Analytics System] | [No game-state change — analytics event only. Compare view is purely local UI state.] | -| [Player closes screen] | [InventoryScreenClosed] | [{session_duration_ms: int}] | [Analytics System] | [Fires on every close regardless of reason. Used for engagement metrics.] | -| [Player navigates between categories] | [InventoryCategoryChanged] | [{category: string}] | [Analytics System] | [Analytics only. No game state change.] | +| [Action] | [EventName] | [{fields}] | [System] | [Validation/response flow, or "analytics only"] | --- ## 10. Transition & Animation -> **Why this section exists**: Transitions are not decoration — they communicate -> hierarchy and causality. A screen that slides in from the right implies the -> player has moved forward. A screen that fades implies a context break. Inconsistent -> transitions make navigation feel broken even when it is technically correct. -> This section ensures transitions are specified intentionally, not left to the -> developer's discretion, and that accessibility settings (reduced motion) are -> planned for from the start. +> Specify at least the screen enter and exit transitions, plus reduced-motion +> behavior for each. See guide Section 10 for a worked example. | Transition | Trigger | Direction / Type | Duration (ms) | Easing | Interruptible? | Skipped by Reduced Motion? | |------------|---------|-----------------|--------------|--------|----------------|---------------------------| -| [Screen enter] | [Screen pushed onto stack] | [Slide in from right] | [250] | [Ease out cubic] | [No — must complete before interaction is enabled] | [Yes — instant appear at 0ms] | -| [Screen exit — Back] | [Player presses Back] | [Slide out to right] | [200] | [Ease in cubic] | [No] | [Yes — instant disappear] | -| [Screen exit — Forward] | [Player navigates to child screen] | [Slide out to left] | [200] | [Ease in cubic] | [No] | [Yes — instant] | -| [Detail panel update] | [Player selects a new item] | [Cross-fade content] | [120] | [Linear] | [Yes — if player navigates quickly, previous animation cancels] | [Yes — instant swap] | -| [Loading → Populated] | [Data arrives after load] | [Skeleton shimmer fades out, content fades in] | [180] | [Ease out] | [No] | [Yes — instant reveal] | -| [Action Bar button press] | [Player activates a button] | [Scale down 95% on press, return on release] | [60 down / 60 up] | [Ease out / ease in] | [Yes — if released early, returns to normal] | [No — this is tactile feedback, not decorative motion] | -| [Confirmation dialog open] | [Player initiates destructive action] | [Background dims 60% opacity; dialog scales up from 95%] | [150] | [Ease out] | [No] | [Yes — instant appear, no scale] | -| [New item badge appear] | [Screen opens with newly acquired item] | [Badge pops from 0% to 110% to 100% scale] | [200 total] | [Ease out back] | [No] | [Yes — instant appear at 100% scale] | +| [Screen enter / exit / in-screen transition] | [Trigger] | [Type] | [ms] | [Easing] | [Yes/No] | [Yes/No + fallback] | --- ## 11. Input Method Completeness Checklist -> **Why this section exists**: Input completeness is not optional — it is a -> certification requirement for console platforms and a legal risk area for -> accessibility laws in multiple markets. Fill this checklist before marking -> the spec as Approved. Any unchecked item blocks implementation start. +> Fill this checklist before marking the spec as Approved. Any unchecked item +> blocks implementation start. **Keyboard** - [ ] All interactive elements are reachable using Tab and arrow keys alone @@ -382,11 +244,9 @@ Example: ## 12. Screen-Level Accessibility Requirements -> **Why this section exists**: Accessibility requirements must be specified at design -> time because retrofitting them is expensive and often architecturally impractical. -> This section documents requirements specific to this screen. Project-wide standards -> live in `docs/accessibility-requirements.md` — consult it before filling this -> section so you do not duplicate or contradict project-level commitments. +> Project-wide standards live in `design/accessibility-requirements.md` — consult it +> before filling this section so you do not duplicate or contradict project-level +> commitments. > > Accessibility Tiers in this project: > - Basic: WCAG 2.1 AA text contrast, keyboard navigable, no motion-only information @@ -398,71 +258,32 @@ Example: | Text Element | Background Context | Required Ratio | Current Ratio | Pass? | |--------------|-------------------|---------------|---------------|-------| -| [e.g., Item name in Detail Panel] | [Dark panel background ~#1a1a1a] | [4.5:1 (WCAG AA normal text)] | [TBD — verify in implementation] | [ ] | -| [e.g., Category tab label — inactive] | [Mid-grey tab background] | [4.5:1] | [TBD] | [ ] | -| [e.g., Category tab label — active] | [Accent color background] | [4.5:1] | [TBD] | [ ] | -| [e.g., Action button label] | [Button color (varies by state)] | [4.5:1] | [TBD] | [ ] | -| [e.g., Stat comparison delta (positive)] | [Detail panel] | [4.5:1 — do NOT rely on green color alone] | [TBD] | [ ] | +| [Text element] | [Background] | [Ratio — 4.5:1 WCAG AA baseline] | [TBD] | [ ] | **Colorblind-unsafe elements and mitigations**: | Element | Colorblind Risk | Mitigation | |---------|----------------|------------| -| [e.g., Stat delta indicators (red/green for worse/better)] | [Red-green colorblindness (Deuteranopia) — most common form] | [Add arrow icons (↑ / ↓) and +/- prefix in addition to color. Color is a redundant, not sole, indicator.] | -| [e.g., Item rarity color coding (grey/green/blue/purple/orange)] | [Multiple types — rarity color is a common industry failure] | [Add rarity name text label below icon. Color is supplemental only.] | +| [Element] | [Risk type] | [Non-color redundant indicator] | **Focus order** (Tab key sequence, numbered): -[e.g., -1. Back button (Header) -2. Options button (Header) -3. Category Tab 1 — Weapons -4. Category Tab 2 — Armor -5. Category Tab 3 — Consumables -6. Category Tab 4 — Key Items -7. Item Slot [0,0] -8. Item Slot [0,1] ... (grid traverses left-to-right, top-to-bottom) -9. Last item slot -10. Equip button (Action Bar) -11. Drop button (Action Bar) -12. Compare button (Action Bar) -13. Close button (Action Bar) -→ Cycles back to Back button - -Focus does not enter the Detail Panel — it is a display panel driven by item focus, not independently navigable.] +[Numbered focus sequence covering every interactive element, including cycle behavior and any panels focus does not enter — see guide Section 12 for an example] **Screen reader announcements for key state changes**: | State Change | Announcement Text | Announcement Timing | |--------------|------------------|---------------------| -| [Screen opens] | ["Inventory screen. [N] items. [Active category] selected."] | [On screen focus settle] | -| [Player focuses an item slot] | ["[Item name]. [Category]. [Rarity]. [Key stats summary]. [Equipped / Not equipped]."] | [On focus arrival] | -| [Player equips an item] | ["[Item name] equipped to [slot name]."] | [After EquipmentChanged event confirmed] | -| [Player drops an item] | ["[Item name] dropped."] | [After InventoryChanged event confirmed] | -| [Category changes] | ["[Category name]. [N] items."] | [On category tab focus] | -| [Empty state shown] | ["No items in [category name]."] | [When empty state renders] | +| [State change] | ["Announcement"] | [When it fires] | **Cognitive load assessment**: -[Estimate the number of information streams the player is simultaneously tracking while -using this screen. For this screen: (1) item grid position, (2) item detail stats, -(3) current equipment loadout for comparison, (4) available actions, (5) item category. -That is 5 concurrent streams — within the standard 7±2 limit, but at the higher end. -Mitigation: detail panel auto-updates on navigation so the player never needs to -manually retrieve item info. Reduce active decisions by surfacing stat comparison -automatically.] +[Count the concurrent information streams the player tracks on this screen; compare against the 7±2 limit and state mitigations — see guide Section 12 for an example] --- ## 13. Localization Considerations -> **Why this section exists**: UI built without localization in mind breaks on first -> translation. German text is typically 30–40% longer than English. Arabic and Hebrew -> require right-to-left layout mirroring. Japanese and Chinese text may be significantly -> shorter than English, creating awkward whitespace. These issues are cheap to plan for -> and expensive to fix after a layout is built and shipped. Every text element should -> have an explicit max-character count and a plan for overflow. - **General rules for this screen**: - All text elements must tolerate a minimum of 40% expansion from English baseline - RTL layout (Arabic, Hebrew): mirrored layout required — document which elements mirror and which do not @@ -471,27 +292,19 @@ automatically.] | Text Element | English Baseline Length | Max Characters | Expansion Budget | RTL Behavior | Overflow Behavior | Risk | |--------------|------------------------|----------------|-----------------|--------------|-------------------|------| -| [e.g., Screen title "INVENTORY"] | [9 chars] | [16 chars] | [78%] | [Mirror to right, or center — acceptable] | [Truncate with ellipsis — title is not critical content] | [Low] | -| [e.g., Item name] | [~15 chars avg, max ~35 "Enchanted Dragon Scale Gauntlets"] | [50 chars] | [43%] | [Right-align in RTL layouts] | [Truncate with tooltip showing full name on hover/focus] | [Medium — long fantasy item names are common] | -| [e.g., Item description] | [~80–120 chars] | [200 chars] | [67%] | [Right-align, wrap normally] | [Scroll within Detail Panel — no truncation] | [Low — panel is scrollable] | -| [e.g., Action button "Equip"] | [5 chars] | [14 chars] | [180%] | [Button layout mirrors; text right-aligns] | [Shrink font to 90% minimum, then truncate] | [Medium — "Ausrüsten" in German is 9 chars] | -| [e.g., Category tab "Consumables"] | [11 chars] | [18 chars] | [64%] | [Mirror tab position] | [Abbreviate: "Consum." — define abbreviations per language in loc file] | [High — long localized tab labels are a known problem] | +| [Text element] | [chars] | [max chars] | [%] | [Mirror/align behavior] | [Truncate/scroll/shrink strategy] | [Low/Medium/High] | --- ## 14. Acceptance Criteria -> **Why this section exists**: Acceptance criteria are the contractual definition of -> "done." Without them, implementation is complete when the developer says it is. -> With them, implementation is complete when a QA tester can verify every item on -> this list. Write criteria that a tester can verify independently, without asking the -> designer what they meant. Every criterion should be binary — pass or fail, not -> subjective. +> Write criteria a QA tester can verify independently, without asking the designer +> what they meant. Every criterion should be binary — pass or fail, not subjective. **Performance** -- [ ] Screen opens (first frame visible) within 200ms of trigger on minimum-spec hardware -- [ ] Screen is fully interactive (all data loaded) within 500ms of trigger on minimum-spec hardware -- [ ] Navigation between items produces no perceptible frame drop (maintain target framerate ±5fps) +- [ ] Screen opens (first frame visible) within [200]ms of trigger on minimum-spec hardware +- [ ] Screen is fully interactive (all data loaded) within [500]ms of trigger on minimum-spec hardware +- [ ] Navigation between elements produces no perceptible frame drop (maintain target framerate ±5fps) **Layout & Rendering** - [ ] Screen displays correctly (no overlap, no cutoff, no overflow) at minimum supported resolution [specify] @@ -499,8 +312,8 @@ automatically.] - [ ] Screen displays correctly at 4:3, 16:9, 16:10, and 21:9 aspect ratios if targeting PC - [ ] No text overflow or truncation in English within defined max-character bounds - [ ] No text overflow or truncation in the longest-translation language [specify — typically German] -- [ ] All states (Loading, Empty, Populated, Error, Confirmation) render correctly -- [ ] Item grid scrolls smoothly without frame drops when all item slots are populated +- [ ] All states documented in Section 6 render correctly +- [ ] Scrollable zones scroll smoothly without frame drops when fully populated **Input** - [ ] All interactive elements reachable by keyboard using Tab and arrow keys only @@ -513,13 +326,13 @@ automatically.] **Events & Data** - [ ] All events in Section 9 fire with correct payloads on all exit paths (verify with debug logging) - [ ] Screen does not write directly to any game system (verify: no direct state mutation calls) -- [ ] Inventory changes persist correctly after screen is closed and reopened -- [ ] Screen handles InventoryChanged events fired by other systems while it is open without crashing +- [ ] Changes made on this screen persist correctly after it is closed and reopened +- [ ] Screen handles data-change events fired by other systems while it is open without crashing **Accessibility** - [ ] All text passes minimum contrast ratios specified in Section 12 -- [ ] Stat comparison does not rely on color alone as the sole differentiator -- [ ] Screen reader announces item name and key stats on focus (verify with platform screen reader) +- [ ] No information relies on color alone as the sole differentiator +- [ ] Screen reader announces documented state changes (verify with platform screen reader) - [ ] Reduced motion setting results in instant transitions (no animated transitions) - [ ] High contrast mode (if applicable to Accessibility Tier) renders without visual breakage @@ -538,7 +351,4 @@ automatically.] | Question | Owner | Deadline | Resolution | |----------|-------|----------|-----------| -| [e.g., Should item comparison be automatic (always showing equipped stats) or player-triggered (press Compare)?] | [ui-designer] | [Sprint 4, Day 3] | [Pending] | -| [e.g., Do we support controller cursor (free aim) in the item grid, or d-pad-only grid navigation?] | [lead-programmer + ui-designer] | [Sprint 4, Day 3] | [Pending — depends on ADR-0015 input model decision] | -| [e.g., What is the game's item drop policy — permanent loss or drop-to-world?] | [systems-designer] | [Requires GDD update] | [Blocked on inventory GDD Edge Cases section] | -| [e.g., Maximum inventory size — does the grid have a hard cap or is it infinite-scroll?] | [economy-designer] | [Sprint 3, Day 5] | [Pending] | +| [Add question] | [Owner] | [Deadline] | [Resolution] | diff --git a/.claude/docs/workflow-catalog.yaml b/.claude/docs/workflow-catalog.yaml index d1efcec7..11b2dca8 100644 --- a/.claude/docs/workflow-catalog.yaml +++ b/.claude/docs/workflow-catalog.yaml @@ -6,6 +6,9 @@ # pattern: text pattern that must appear in the file (checked after glob) # min_count: minimum matching files required (default: 1) # note: human-readable fallback when completion can't be auto-detected +# any_of: list of alternative {glob, pattern} specs — the step is complete if +# ANY one of them matches. Use when a setting has migrated and may +# legitimately live in either the new or the legacy location. # # required: true → blocks progression to next phase (shown as REQUIRED) # required: false → optional enhancement (shown as OPTIONAL) @@ -31,9 +34,20 @@ phases: name: "Engine Setup" command: /setup-engine required: true + # v1.1: engine.name in project.yaml is the primary source; technical- + # preferences.md is the legacy fallback. Checking only the legacy file + # made a correctly-configured project fail this REQUIRED gate. artifact: - glob: ".claude/docs/technical-preferences.md" - pattern: "Engine: [^[]" + any_of: + - glob: "project.yaml" + pattern: "name:[[:space:]]*(Godot|Unity|Unreal)" + # technical-preferences.md writes "- **Engine**: Godot 4.6", so the + # pattern must clear the bold markers. The previous "Engine: [^[]" + # required a literal "Engine: " and therefore never matched any + # file /setup-engine produces. The [^[] guard rejects the + # "[TO BE CONFIGURED]" placeholder. + - glob: ".claude/docs/technical-preferences.md" + pattern: "\\*\\*Engine\\*\\*: [^[]" description: "Configure engine, pin version, set naming conventions and performance budgets" - id: game-concept @@ -85,7 +99,7 @@ phases: command: /design-review required: true repeatable: true - description: "Validate each GDD (8 required sections, no MAJOR REVISION verdict). Run per system." + description: "Validate each GDD (required sections per workflow tier, no MAJOR REVISION verdict). Run per system." - id: review-all-gdds name: "Cross-GDD Review" @@ -143,10 +157,16 @@ phases: - id: accessibility-doc name: "Accessibility Requirements" + # This step had no `command:` at all -- the only artifact entry in the + # catalog that named no producer. It was REQUIRED, so the Technical + # Setup -> Pre-Production gate could never pass, and the remediation + # every skill printed ("run /ux-design") wrote into design/ux/ where + # this glob does not look. /ux-design now has an `accessibility` mode. + command: /ux-design required: true artifact: glob: "design/accessibility-requirements.md" - description: "Commit accessibility tier (Basic/Standard/Comprehensive/Exemplary) and feature matrix. UX specs (Phase 4) reference this tier." + description: "Run `/ux-design accessibility`. Commit accessibility tier (Basic/Standard/Comprehensive/Exemplary) and feature matrix. UX specs (Phase 4) reference this tier." pre-production: label: "Pre-Production" @@ -191,7 +211,11 @@ phases: command: /prototype required: false artifact: - glob: "prototypes/*/README.md" + # /prototype writes prototypes/[name]-concept/REPORT.md -- it has never + # written a README. The `-concept` suffix is load-bearing: /vertical-slice + # writes the same REPORT.md basename under `-vertical-slice`, so a bare + # `prototypes/*/REPORT.md` here would let either step satisfy the other. + glob: "prototypes/*-concept/REPORT.md" min_count: 1 description: "Build a throwaway prototype to validate the core mechanic is fun before committing to full production. Recommended for first-time mechanics or high-risk design decisions. Solo devs with proven concepts may skip." @@ -237,7 +261,11 @@ phases: command: /vertical-slice required: false artifact: - glob: "prototypes/*/REPORT.md" + # Must stay scoped to `-vertical-slice`: the untethered form matched + # /prototype's concept report, so running only /prototype silently + # marked the vertical slice complete and skipped the validation the + # Pre-Production → Production gate exists to require. + glob: "prototypes/*-vertical-slice/REPORT.md" min_count: 1 description: "Build and playtest a vertical slice — a complete end-to-end pass through the core loop. Recommended before committing epics and stories to production. Skipping is a valid solo dev call but increases risk of late-stage design pivots. If built, must be played and documented before advancing." @@ -355,7 +383,10 @@ phases: command: /playtest-report required: true artifact: - glob: "production/playtests/*.md" + # /playtest-report writes production/qa/playtests/, not production/playtests/. + # The old glob matched nothing on every project, so this REQUIRED step + # read as 0/3 forever no matter how many playtests were actually run. + glob: "production/qa/playtests/*.md" min_count: 3 description: "Cover: new player experience, mid-game systems, difficulty curve" diff --git a/.claude/docs/workflow-modes.md b/.claude/docs/workflow-modes.md new file mode 100644 index 00000000..680ad24c --- /dev/null +++ b/.claude/docs/workflow-modes.md @@ -0,0 +1,310 @@ +# Workflow Modes — Shared Skill Pattern + +This document defines `modes.workflow` and `workflow_overrides` — how much +design documentation is REQUIRED before code can begin — for every skill that +authors, reviews, gates, or plans against project artifacts. Skills reference +this document instead of embedding the tier tables inline. + +**Companion spec**: `.claude/docs/effects-map.md` +sections `modes.workflow` and `workflow_overrides` are the source of +truth for behavior. This document is the implementation pattern. + +> **Do not read `effects-map.md` during a skill run.** It is ~2,150 lines +> (~110 KB) — about five times the size of everything CLAUDE.md loads per +> session combined — and it is a reference for *authoring and changing the +> spec*, not a runtime input. **This document is self-sufficient** for resolving +> the tier and applying it: the resolution chain, the three tiers, the required +> sections, and the override rules are all here. If you hit a case this document +> genuinely does not cover, read only the one `effects-map.md` section for the +> knob in question — never the file. + +**Related shared docs**: `.claude/docs/automation-modes.md` (how skills prompt), +`.claude/docs/director-gates.md` (which reviewers spawn). `workflow` is +orthogonal to both — it controls *what artifacts must exist*, not *how the +skill prompts* or *who reviews*. + +--- + +## How to Use This Document + +A skill that authors, reviews, or gates a design artifact resolves the +effective workflow tier at startup, then applies the required-section / +required-artifact set for that tier. + +**Resolution (per skill):** + +``` +Resolve the workflow tier (store the result; see scope note below): +1. If the resolution is for a named system AND + `workflow_overrides.system_overrides.` is set → use that tier +2. Else read `modes.workflow` from `project.yaml` → use that value +3. Else → use the tier implied by `modes.rigor` + (`minimal`→`minimal`, `standard`→`standard`, `full`→`full`; + `rigor` itself defaults to `minimal`) +``` + +`modes.workflow` has **no terminal default** — step 3 is the `rigor` expansion, +not a hardcoded `standard`. The resolved value is the same for an unconfigured +project, so nothing observable changed; but a skill that hardcodes `standard` as +its fallback will ignore `rigor: minimal` and `rigor: full` entirely. + +### What `` is — one derivation, everywhere + +**`` is the GDD filename stem**: `design/gdd/hammer-heat-system.md` → +`hammer-heat-system`. That is the key to write under +`workflow_overrides.system_overrides`, and every skill must derive it the same +way. + +A `TR-[system]-NNN` ID's `[system]` segment is **an alias, not the key**. Accept +it as a fallback match when the GDD stem yields nothing, but never derive the key +from it in preference to the stem. + +> **Why this is stated here rather than per skill.** Reading *"the `[system]` +> segment of its `TR-[system]-NNN` ID (else its `design/gdd/` path)"* — TR-ID +> **first** — is the wrong order. A system whose GDD is `hammer-heat-system.md` and +> whose requirements are `TR-heat-001` then had **two different override keys +> depending on whether a registry entry existed**, and the documented remedy for +> a missing GDD section — "set +> `workflow_overrides.system_overrides.hammer-heat-system: full`" — was looked up +> under `heat`, found nothing, and continued at the project tier **silently**. +> The escape hatch existed and could not be reached. + +**A key that matches no system is an error, not a no-op.** If +`workflow_overrides.system_overrides` contains a key that resolves to no GDD stem +in `design/gdd/`, say so — naming the unmatched key and the stems available. +Silently ignoring it is how the above went unnoticed. + +> **Checked where GDDs are expected to already exist — not by the skill that +> writes them.** `/gate-check` is the single implementing site (see +> `.claude/skills/gate-check/SKILL.md` §"Per-system overrides"), and it runs at a +> phase boundary, by which point every in-scope GDD should be on disk. An +> unmatched key there is genuinely suspicious. +> +> `/design-system` **creates** `design/gdd/.md`, so before its first run +> the override key matches no stem *by definition* — that is the healthy case, +> not an error. It must not report the key as orphaned, and must not refuse to +> apply the tier it names. The same holds for any other authoring skill running +> before the GDD exists. Applying this rule there would fire on exactly the +> configuration it was written to protect. + +--- + +Step 1 (the per-system override) applies to skills that act on a named system: +- **Single-system, resolve once per run**: `design-system`, `design-review`, + `dev-story`, `story-done`, `create-stories` (one story/epic per invocation). +- **Multi-system, resolve once per item**: `story-readiness` (per story across + `all`/`sprint` scope), `qa-plan` and `regression-suite` audit (per GDD/system). + These do NOT freeze a single tier for the run — different systems in one run + may resolve to different tiers. + +Skills that operate project-wide (`gate-check`, `create-epics`) use the +project-level `workflow` plus consider per-system overrides where the tier table +says so. + +> **"Critical ADR" (used in the tier tables):** an ADR governing a +> **Foundation-layer** system (scene management, event architecture, save/load, +> and the like — the systems whose decisions everything else depends on). At +> `standard`, only critical ADRs are required/blocking; non-critical ADRs are +> advisory. When in doubt about an ADR's layer, treat it as critical (fail safe +> toward requiring it). + +`modes.workflow` reads via **`resolve_config --keys workflow`** (or +`resolve_setting modes.workflow`), which applies the whole chain including the +`rigor` expansion. Do **not** use `get_effective_yaml_key modes.workflow`: it +reads the two YAML files only, so on a project that sets `modes.rigor` and not +`modes.workflow` it returns **empty** while the effective tier is whatever rigor +implies. (`project.local.yaml` is not allowed to override workflow — it is locked +to `project.yaml` per the whitelist — so the only sources are `project.yaml` and +the expansion.) Per-system overrides read via +`get_yaml_key project.yaml workflow_overrides.system_overrides.`. + +--- + +## The Three Tiers + +| Tier | Token/time cost | Required before code | Best for | +|------|----------------|---------------------|----------| +| `full` | High | All 8 GDD sections per system, full architecture, all ADRs, art bible, UX specs per screen | Teams, commercial titles, learning the full pipeline | +| `standard` | Balanced | 5 GDD sections per system, one architecture doc, critical ADRs, game concept, systems index | Projects that outgrew a brief - several interacting systems, or a design someone else implements | +| `minimal` | Low | One-page `design/game-brief.md` + engine choice (its build-order field is the plan — no separate sprint plan) | **Default.** Jam projects, small scope, design already in your head | + +**Default**: `minimal` (the rationale block above `_yaml_helper_defaults` in +`.claude/hooks/yaml-helper.sh` carries the reasoning and the ordering +constraint). **Set by**: `/start`, `/settings`. **Locked to +`project.yaml`** (not locally overridable — divergence would change which +artifacts must exist on disk). + +> **Permissive, not restrictive.** `workflow` controls what is REQUIRED, not +> what is ALLOWED. Any skill can run at any tier. The setting changes what +> `gate-check` enforces and what completeness checks validate — never what the +> user can invoke. + +> **`minimal` floor.** Even at minimal, engine choice and a filled +> **`design/game-brief.md`** are required before code starts — the brief's +> build-order field is the plan, so there is no separate `sprint-plan` step. +> Everything else can be skipped. + +> **"Game brief" is a real artifact at `minimal`: `design/game-brief.md`.** +> `/brainstorm` writes it from the one-page template +> (`.claude/docs/templates/game-brief.md`) when the effective tier is `minimal`, +> in place of the full-length `design/gdd/game-concept.md` it writes at +> `standard`/`full`. The two are distinct files for distinct tiers: any check, +> gate, or glob for the `minimal` design artifact must target +> **`design/game-brief.md`**; `game-concept.md` remains the `standard`/`full` +> concept doc. + +--- + +## GDD Required Sections per Tier + +The 8 GDD sections: Overview, Player Fantasy, Detailed Rules, Formulas, +Edge Cases, Dependencies, Tuning Knobs, Acceptance Criteria. + +> **Section-name alias.** "Detailed Rules" is the canonical name in the design +> standard, but the GDD template (`.claude/docs/templates/game-design-document.md`) +> and `/design-system` author this section under the heading `## Detailed Design` +> (with Core Rules / States / Interactions sub-headings). The two names denote the +> **same required section** — any skill that detects or validates section presence +> (`design-review`, `gate-check`, `adopt`, `project-stage-detect`) must accept +> either heading. Do not treat "Detailed Rules" as missing when `## Detailed +> Design` is present. + +| Tier | Required GDD sections | +|------|----------------------| +| `full` | All 8 | +| `standard` | **5 required**: Overview, Detailed Rules, Edge Cases, Dependencies, Acceptance Criteria. **Conditional**: Formulas — required when the system **defines numeric rules** (rates, curves, thresholds, costs, damage, drop weights — anything a balance pass would tune); optional only when it defines none. The `Category` column is a hint, not the test. **Skipped**: Player Fantasy, Tuning Knobs. | +| `minimal` | None — `design/game-brief.md` replaces GDDs. A GDD may still be authored voluntarily. | + +> **Edge Cases is required at `standard`** (it was promoted from optional — +> skipping it produced "what happens if X is null?" debt during implementation). + +> **Art bible is conditional at `standard`** — required only if visual asset +> stories exist in the project. Code-focused projects skip it. At `full`, all +> 9 art bible sections are required. (`workflow_overrides.art_bible_strict: +> true` forces all 9 regardless of tier.) + +--- + +## `workflow_overrides` + +This key holds two mechanisms with **different directional rules**. Read the one +you are using — conflating them is a known and easy mistake. + +```yaml +workflow_overrides: + # --- the three boolean FLAGS: additive, stricter-only --- + edge_cases: false # force Edge Cases required (no-op at standard/full; relevant at minimal) + tuning_knobs: false # force Tuning Knobs required (relevant at standard, no-op at full) + art_bible_strict: false # force all 9 art bible sections regardless of asset stories + + # --- system_overrides: replaces a tier, BOTH directions --- + system_overrides: {} # per-system tier overrides (see below) +``` + +**The three boolean flags are additive on top of the chosen `workflow` level — +they make requirements STRICTER, never looser.** Each one only ever forces a +requirement *on*; setting a flag cannot opt out of a requirement the tier +imposes. There is no value of `edge_cases`, `tuning_knobs` or `art_bible_strict` +that removes anything. + +**`system_overrides` is not a flag and is not additive.** It *replaces* the +effective tier for one named system, in **either** direction — see the next +section. A loosening override genuinely loosens: on a `full` project, +`system_overrides.inventory: minimal` drops that system's required sections to +zero. That is the intended design (it is what makes "one trivial system inside a +serious project" expressible), but it means an override is **not** a safe +no-op — do not leave one in place assuming the stricter-only rule protects you. + +### Per-system overrides + +```yaml +workflow_overrides: + system_overrides: + combat: full # combat GDD requires all 8 sections regardless of project workflow + inventory: minimal # inventory needs no GDD — the game brief covers it (minimal = no required sections) +``` + +`system_overrides` is a map of `system_name: workflow_level`. The named system +uses the override tier; all other systems use the project-level `workflow`. +A skill acting on a named system resolves its effective tier via step 1 of the +resolution chain above. + +--- + +## Effective-Tier Resolution Worked Example + +Project has `modes.workflow: standard`, `workflow_overrides.system_overrides.combat: full`. + +- `/design-system combat` → effective tier **full** → requires all 8 sections +- `/design-system inventory` → no override → effective tier **standard** → 5 sections + conditional Formulas +- `/gate-check` (Systems Design → Technical Setup) → requires the combat GDD at 8 sections AND every other MVP GDD at 5 sections before passing + +--- + +## Per-Skill Behavior (the working summary — sufficient at runtime) + +**Authoring** — branch on the effective tier for required sections (and, for +`brainstorm`, for which design artifact is produced — `design/game-brief.md` at +`minimal` vs `design/gdd/game-concept.md` at `standard`/`full`): +`brainstorm`, `design-system`, `art-bible`, `create-architecture`, `ux-design`, +`map-systems`, `architecture-decision`. + +**Review/validation** — validate against the effective tier; missing +*required* sections block, missing *optional* sections warn: +`design-review`, `review-all-gdds`, `architecture-review`, `consistency-check`, +`content-audit`. + +**Gate enforcement** — `gate-check` uses a different artifact checklist per +stage per tier (the highest-visibility impact). Each gate's own checklist and +tier reductions live in `.claude/skills/gate-check/references/gate-.md`, +and `/gate-check` loads only the one gate it is running — that is the right +place to look, not `effects-map.md`. + +**Planning/implementation** — adjust artifact prerequisites: +`create-epics`, `create-stories`, `dev-story`, `story-readiness`, `story-done`, +`qa-plan`, `regression-suite`. + +**Support** — adjust "what's missing" expectations so optional docs aren't +flagged as gaps: `project-stage-detect`, `adopt`, `reverse-document`, +`propagate-design-change`, `help`. + +--- + +## Workflow Change Mid-Project + +When `workflow` changes via `/settings`, emit an informational note: + +> "Workflow changed from [old] to [new]. Run `/content-audit` to see what's now +> required or extra. No automatic migration — existing files stay in place." + +No documents are auto-deleted, auto-generated, or blocked. The setting only +affects *new* enforcement going forward. + +--- + +## Companion Settings + +These are separate settings that compose with `workflow`. The summaries below +are what a skill needs at runtime; `effects-map.md` carries the exhaustive +per-skill tables for spec authors, and is not worth loading to apply a knob. + +> **All four below are fronted by `modes.rigor`** (`docs.density`, +> `modes.story_granularity`, `qa.level`, `team.size` — along with `workflow` itself, +> and `modes.review_mode`, for six fronted knobs total). One question at `/start` +> sets all of them; setting any explicitly overrides just that one and leaves its +> siblings on the rigor level. `team.size` and `modes.review_mode` are the two that +> also stay overridable from `project.local.yaml` (they are personal-experience +> knobs, not on-disk artifacts). + +- **`docs.density`** (`terse` | `balanced` | `thorough`) — `workflow` controls + *which sections exist*; `density` controls *how deep each section goes*. + `rigor` sets the two together; override `docs.density` alone to get + `full` + `terse` = "all 8 sections, each compact." +- **`modes.story_granularity`** (`coarse` | `balanced` | `fine`) — how big + each story is and how many per epic/sprint. +- **`qa.level`** (`minimal` | `standard` | `full`) — what test evidence is + required to mark stories Done. Composes with `testing.strict`: + `qa.level` = is evidence required; `testing.strict` = do failures block. +- **`team.size`** (`individual` | `small` | `studio`) — which agents are active by + default. Different axis from workflow: workflow controls *what docs are + required*, team.size controls *which agents exist to make them*. diff --git a/.claude/hooks/detect-gaps.sh b/.claude/hooks/detect-gaps.sh index 2a2c96da..fae822bf 100644 --- a/.claude/hooks/detect-gaps.sh +++ b/.claude/hooks/detect-gaps.sh @@ -1,4 +1,32 @@ #!/bin/bash + +# --- work from the project root ---------------------------------------------- +# Every path below is repo-relative, so a hook invoked with a working directory +# that is not the repo root would silently read and write the WRONG TREE -- +# returning a near-empty result instead of the session-recovery block, and +# creating stray trees such as docs/production/session-logs/ on write. +# +# PRECEDENCE IS LOAD-BEARING. A cwd that IS a project root carries real +# information and must win: a caller sitting inside another project means that +# project, not this one. Resolving to the script's own location first would +# override them. So, in order: +# 1. cwd holds project.yaml -> cwd (a project root) +# 2. cwd holds .claude/ -> cwd (a project root not yet configured) +# 3. CLAUDE_PROJECT_DIR -> that (populated in the hook environment) +# 4. this script's location -> /.claude/hooks/../.. by construction +# Rule 4 always works and needs no environment at all; rules 1-2 stop it from +# overriding a caller that legitimately means somewhere else. +# +# NOT an upward search: that resolves a nested project to its parent's config. +if [ -f "project.yaml" ] || [ -d ".claude" ]; then + CCGS_ROOT="$PWD" +elif [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -d "${CLAUDE_PROJECT_DIR}" ]; then + CCGS_ROOT="$CLAUDE_PROJECT_DIR" +else + CCGS_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." 2>/dev/null && pwd)" +fi +[ -n "$CCGS_ROOT" ] && cd "$CCGS_ROOT" 2>/dev/null || true + # Hook: detect-gaps.sh # Event: SessionStart # Purpose: Detect missing documentation when code/prototypes exist @@ -12,24 +40,78 @@ echo "=== Checking for Documentation Gaps ===" # --- Check 0: Fresh project detection (suggests /start) --- FRESH_PROJECT=true -# Check if engine is configured -if [ -f ".claude/docs/technical-preferences.md" ]; then - ENGINE_LINE=$(grep -E "^\- \*\*Engine\*\*:" .claude/docs/technical-preferences.md 2>/dev/null) +# Check if engine is configured (project.yaml first, fall back to technical-preferences.md) +if [ -f "project.yaml" ] && [ -f ".claude/hooks/yaml-helper.sh" ]; then + source .claude/hooks/yaml-helper.sh + ENGINE_NAME=$(get_yaml_key project.yaml engine.name 2>/dev/null) + if [ -n "$ENGINE_NAME" ]; then + FRESH_PROJECT=false + fi +fi +if [ "$FRESH_PROJECT" = true ] && [ -f ".claude/docs/technical-preferences.md" ]; then + ENGINE_LINE=$(grep -E "^[[:space:]]*-[[:space:]]+\*\*Engine\*\*:" .claude/docs/technical-preferences.md 2>/dev/null) if [ -n "$ENGINE_LINE" ] && ! echo "$ENGINE_LINE" | grep -q "TO BE CONFIGURED" 2>/dev/null; then FRESH_PROJECT=false fi fi -# Check if game concept exists -if [ -f "design/gdd/game-concept.md" ]; then +# Check if a design record exists. +# +# TWO FILES, NOT ONE. `game-concept.md` is the `standard`/`full` concept doc; +# at `minimal` the design record is the one-page `design/game-brief.md`, which +# `/brainstorm` writes in its place. workflow-modes.md states the rule this +# check has to satisfy: any check, gate or glob for the minimal design artifact +# must target `design/game-brief.md`. +# +# Testing only for `game-concept.md` means a minimal-tier project that has +# already run `/start` AND `/brainstorm` is greeted with "NEW PROJECT ... Run: +# /start" at every session start -- the banner wrong about the one artifact +# that tier produces. +if [ -f "design/gdd/game-concept.md" ] || [ -f "design/game-brief.md" ]; then FRESH_PROJECT=false fi -# Check if source code exists -if [ -d "src" ]; then - SRC_CHECK=$(find src -type f \( -name "*.gd" -o -name "*.cs" -o -name "*.cpp" -o -name "*.c" -o -name "*.h" -o -name "*.hpp" -o -name "*.rs" -o -name "*.py" -o -name "*.js" -o -name "*.ts" \) 2>/dev/null | head -1) +# Check if source code exists. +# +# All three code roots, not just `src/`. Per .claude/docs/directory-structure.md +# `src/` is the Godot row; Unity builds from `Assets/` and Unreal from +# `Source/`. Testing only `src/` meant a Unity project with a full codebase +# could still be greeted with "NEW PROJECT ... Run: /start". +# +# Tested directly rather than through resolve_code_root: this check runs before +# the config helper is guaranteed to be sourced, and "is there any code at all" +# does not need the engine to be known. +for _root in src Assets Source; do + [ -d "$_root" ] || continue + SRC_CHECK=$(find "$_root" -type f \( -name "*.gd" -o -name "*.cs" -o -name "*.cpp" -o -name "*.c" -o -name "*.h" -o -name "*.hpp" -o -name "*.rs" -o -name "*.py" -o -name "*.js" -o -name "*.ts" \) 2>/dev/null | head -1) if [ -n "$SRC_CHECK" ]; then FRESH_PROJECT=false + break + fi +done + +# --- Check 0b: v1.0 project — legacy config present, project.yaml absent --- +# The migration story depends on users discovering /adopt; UPGRADING.md only +# reaches people who read it. An unconfigured technical-preferences.md template +# does not count as legacy data (same rule as migrate-v1-config.sh), and legacy +# config also means the project is not fresh — the NEW PROJECT banner below +# would be wrong for it. +if [ ! -f "project.yaml" ]; then + LEGACY_FILES="" + [ -f "production/stage.txt" ] && LEGACY_FILES="production/stage.txt" + [ -f "production/review-mode.txt" ] && LEGACY_FILES="${LEGACY_FILES:+$LEGACY_FILES, }production/review-mode.txt" + if [ -f ".claude/docs/technical-preferences.md" ]; then + TP_ENGINE=$(grep -E "^[[:space:]]*-[[:space:]]+\*\*Engine\*\*:" .claude/docs/technical-preferences.md 2>/dev/null) + if [ -n "$TP_ENGINE" ] && ! echo "$TP_ENGINE" | grep -q "TO BE CONFIGURED" 2>/dev/null; then + LEGACY_FILES="${LEGACY_FILES:+$LEGACY_FILES, }.claude/docs/technical-preferences.md" + fi + fi + if [ -n "$LEGACY_FILES" ]; then + FRESH_PROJECT=false + echo "" + echo "⬆️ v1.0 PROJECT: legacy config ($LEGACY_FILES) but no project.yaml." + echo " Run: /adopt — it detects this and drives the migration (non-destructive)." + echo " Preview first: bash .claude/scripts/migrate-v1-config.sh --dry-run" fi fi @@ -43,10 +125,75 @@ if [ "$FRESH_PROJECT" = true ]; then exit 0 fi +# --- Resolve the workflow tier once, for the checks below --- +# +# Checks 1, 3 and 4 all ask "where is the design/architecture document for this +# code?". At `minimal` the answer is that there deliberately is none: +# workflow-modes.md requires only engine choice and a filled +# `design/game-brief.md` before code starts, and says everything else can be +# skipped. Reporting their absence as a GAP nags the user about work their own +# configuration told them to skip -- and a warning that fires when nothing is +# wrong trains the user to ignore the ones that matter. +# +# resolve_setting, NOT get_yaml_key: `modes.workflow` is rigor-fronted, so a +# project that set `rigor: minimal` and nothing else has no `modes.workflow` key +# to read. Only the full chain sees the expansion. Same pattern and same reason +# as validate-commit.sh. +# +# Resolved AFTER the fresh-project early-exit above, so a brand-new project +# never pays for the lookup. +WORKFLOW="standard" +if [ -f .claude/hooks/yaml-helper.sh ]; then + . .claude/hooks/yaml-helper.sh 2>/dev/null + W=$(resolve_setting modes.workflow 2>/dev/null | cut -f1) + [ -n "$W" ] && WORKFLOW="$W" +fi + +# Check 2 is NOT gated: an undocumented prototype is a gap about the prototype +# itself, not about the design pipeline the tier scales. +if [ "$WORKFLOW" = "minimal" ]; then + DOC_CHECKS=false +else + DOC_CHECKS=true +fi + +# --- Resolve the engine-specific code root for checks 1, 3 and 4 ------------ +# +# Checks 1, 3 and 4 were all hardcoded to `src/`, which .claude/docs/directory- +# structure.md defines as the GODOT row of the code-root table -- Unity uses +# `Assets/`, Unreal `Source/`. On those two engines every directory test below +# failed, the source-file count came back 0, and NONE of the three checks ran. +# A project with 200 undocumented Unity systems produced the same empty output +# as a fully documented one. +# +# Only the ROOT is engine-specific. The `core/`, `engine/` and `gameplay/` +# sub-layout is CCGS's own convention, unchanged by the engine, so it stays +# literal. +# +# Resolved AFTER the fresh-project early-exit, so a brand-new project never +# pays for the lookup. +if command -v resolve_code_root >/dev/null 2>&1; then + CODE_ROOT=$(resolve_code_root 2>/dev/null | cut -f1) +else + CODE_ROOT="" +fi + +# A doc check that cannot locate the code root has not established that there +# are no gaps -- it has established nothing (obligation 1 and 3 of +# .claude/rules/skill-authoring.md). Announce it, once, and only when the checks +# would otherwise have had somewhere to look. +if [ "$DOC_CHECKS" = true ] && [ -z "$CODE_ROOT" ]; then + if [ -d src ] || [ -d Assets ] || [ -d Source ]; then + echo "ℹ️ NOT CHECKED: code exists but the code root is ambiguous (engine.name unset, and the tree does not name one unambiguously)." + echo " Checks 1, 3 and 4 (design and architecture docs for existing code) did NOT run." + echo " Suggested action: /setup-engine, or set engine.name in project.yaml" + fi +fi + # --- Check 1: Substantial codebase but sparse design docs --- -if [ -d "src" ]; then +if [ -n "$CODE_ROOT" ] && [ -d "$CODE_ROOT" ]; then # Count source files (cross-platform, handles Windows paths) - SRC_FILES=$(find src -type f \( -name "*.gd" -o -name "*.cs" -o -name "*.cpp" -o -name "*.c" -o -name "*.h" -o -name "*.hpp" -o -name "*.rs" -o -name "*.py" -o -name "*.js" -o -name "*.ts" \) 2>/dev/null | wc -l) + SRC_FILES=$(find "$CODE_ROOT" -type f \( -name "*.gd" -o -name "*.cs" -o -name "*.cpp" -o -name "*.c" -o -name "*.h" -o -name "*.hpp" -o -name "*.rs" -o -name "*.py" -o -name "*.js" -o -name "*.ts" \) 2>/dev/null | wc -l) else SRC_FILES=0 fi @@ -61,9 +208,9 @@ fi SRC_FILES=$(echo "$SRC_FILES" | tr -d ' ') DESIGN_FILES=$(echo "$DESIGN_FILES" | tr -d ' ') -if [ "$SRC_FILES" -gt 50 ] && [ "$DESIGN_FILES" -lt 5 ]; then +if [ "$DOC_CHECKS" = true ] && [ "$SRC_FILES" -gt 50 ] && [ "$DESIGN_FILES" -lt 5 ]; then echo "⚠️ GAP: Substantial codebase ($SRC_FILES source files) but sparse design docs ($DESIGN_FILES files)" - echo " Suggested action: /reverse-document design src/[system]" + echo " Suggested action: /reverse-document design ${CODE_ROOT:-}/[system]" echo " Or run: /project-stage-detect to get full analysis" fi @@ -95,7 +242,7 @@ if [ -d "prototypes" ]; then fi # --- Check 3: Core systems without architecture docs --- -if [ -d "src/core" ] || [ -d "src/engine" ]; then +if [ "$DOC_CHECKS" = true ] && [ -n "$CODE_ROOT" ] && { [ -d "$CODE_ROOT/core" ] || [ -d "$CODE_ROOT/engine" ]; }; then if [ ! -d "docs/architecture" ]; then echo "⚠️ GAP: Core engine/systems exist but no docs/architecture/ directory" echo " Suggested action: Create docs/architecture/ and run /architecture-decision" @@ -105,15 +252,15 @@ if [ -d "src/core" ] || [ -d "src/engine" ]; then if [ "$ADR_COUNT" -lt 3 ]; then echo "⚠️ GAP: Core systems exist but only $ADR_COUNT ADR(s) documented" - echo " Suggested action: /reverse-document architecture src/core/[system]" + echo " Suggested action: /reverse-document architecture $CODE_ROOT/core/[system]" fi fi fi # --- Check 4: Gameplay systems without design docs --- -if [ -d "src/gameplay" ]; then +if [ "$DOC_CHECKS" = true ] && [ -n "$CODE_ROOT" ] && [ -d "$CODE_ROOT/gameplay" ]; then # Find major gameplay subdirectories (those with 5+ files) - GAMEPLAY_SYSTEMS=$(find src/gameplay -mindepth 1 -maxdepth 1 -type d 2>/dev/null) + GAMEPLAY_SYSTEMS=$(find "$CODE_ROOT/gameplay" -mindepth 1 -maxdepth 1 -type d 2>/dev/null) if [ -n "$GAMEPLAY_SYSTEMS" ]; then while IFS= read -r system_dir; do @@ -129,9 +276,9 @@ if [ -d "src/gameplay" ]; then design_doc_2="design/gdd/${system_name}.md" if [ ! -f "$design_doc_1" ] && [ ! -f "$design_doc_2" ]; then - echo "⚠️ GAP: Gameplay system 'src/gameplay/$system_name/' ($file_count files) has no design doc" + echo "⚠️ GAP: Gameplay system '$CODE_ROOT/gameplay/$system_name/' ($file_count files) has no design doc" echo " Expected: design/gdd/${system_name}-system.md or design/gdd/${system_name}.md" - echo " Suggested action: /reverse-document design src/gameplay/$system_name" + echo " Suggested action: /reverse-document design $CODE_ROOT/gameplay/$system_name" fi fi done <<< "$GAMEPLAY_SYSTEMS" @@ -147,6 +294,38 @@ if [ "$SRC_FILES" -gt 100 ]; then fi fi +# --- Check 6: project.stage is behind what is actually on disk --- +# +# On the `minimal` path nothing ever advances the stage. Only +# /gate-check writes it, and the four-step jam route never invokes one -- so a +# project with stories and running game code keeps reporting `Concept` to the +# status line, to /help, and to every gate checklist that branches on stage. +# +# This is an OBSERVATION, never a write. CLAUDE.md is explicit that the stage +# advances on a /gate-check PASS with the user confirming, so a hook that +# advanced it silently would be the worse bug. Say what is inconsistent and name +# the skill that resolves it. +STAGE="" +if [ -f .claude/hooks/yaml-helper.sh ]; then + STAGE=$(get_yaml_key project.stage 2>/dev/null) +fi +[ -z "$STAGE" ] && [ -f production/stage.txt ] && STAGE=$(head -1 production/stage.txt 2>/dev/null | tr -d ' +' | tr -d ' ') + +if [ -n "$STAGE" ]; then + STORY_COUNT=$(find production/epics -name "story-*.md" 2>/dev/null | wc -l | tr -d ' ') + case "$STAGE" in + Concept|concept|Pre-Production|"Pre-Production") + if [ "$STORY_COUNT" -gt 0 ] && [ "$SRC_FILES" -gt 0 ]; then + echo "⚠️ GAP: project.stage says '$STAGE', but $STORY_COUNT stories and $SRC_FILES source files exist" + echo " The stage only advances on a /gate-check PASS, and nothing on the" + echo " minimal path runs one -- so the status line can sit at '$STAGE' indefinitely." + echo " Suggested action: /gate-check (it asks before advancing; this hook never writes the stage)" + fi + ;; + esac +fi + # --- Summary --- echo "" echo "💡 To get a comprehensive project analysis, run: /project-stage-detect" diff --git a/.claude/hooks/log-agent-stop.sh b/.claude/hooks/log-agent-stop.sh index afbd2819..0f6c62e6 100644 --- a/.claude/hooks/log-agent-stop.sh +++ b/.claude/hooks/log-agent-stop.sh @@ -1,4 +1,32 @@ #!/bin/bash + +# --- work from the project root ---------------------------------------------- +# Every path below is repo-relative, so a hook invoked with a working directory +# that is not the repo root would silently read and write the WRONG TREE -- +# returning a near-empty result instead of the session-recovery block, and +# creating stray trees such as docs/production/session-logs/ on write. +# +# PRECEDENCE IS LOAD-BEARING. A cwd that IS a project root carries real +# information and must win: a caller sitting inside another project means that +# project, not this one. Resolving to the script's own location first would +# override them. So, in order: +# 1. cwd holds project.yaml -> cwd (a project root) +# 2. cwd holds .claude/ -> cwd (a project root not yet configured) +# 3. CLAUDE_PROJECT_DIR -> that (populated in the hook environment) +# 4. this script's location -> /.claude/hooks/../.. by construction +# Rule 4 always works and needs no environment at all; rules 1-2 stop it from +# overriding a caller that legitimately means somewhere else. +# +# NOT an upward search: that resolves a nested project to its parent's config. +if [ -f "project.yaml" ] || [ -d ".claude" ]; then + CCGS_ROOT="$PWD" +elif [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -d "${CLAUDE_PROJECT_DIR}" ]; then + CCGS_ROOT="$CLAUDE_PROJECT_DIR" +else + CCGS_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." 2>/dev/null && pwd)" +fi +[ -n "$CCGS_ROOT" ] && cd "$CCGS_ROOT" 2>/dev/null || true + # Claude Code SubagentStop hook: Log agent completion for audit trail # Tracks when agents finish and their outcome # @@ -10,21 +38,41 @@ # returns null on every invocation, so the fallback "unknown" is always used # and the audit trail captures nothing useful. +# features.session_state: off => no audit trail. Default `on`; see +# session_state_enabled() in yaml-helper.sh. +if [ -f .claude/hooks/yaml-helper.sh ]; then + . .claude/hooks/yaml-helper.sh + session_state_enabled || exit 0 +fi + INPUT=$(cat) # Parse agent name -- use jq if available, fall back to grep if command -v jq >/dev/null 2>&1; then AGENT_NAME=$(echo "$INPUT" | jq -r '.agent_type // "unknown"' 2>/dev/null) else - AGENT_NAME=$(echo "$INPUT" | grep -oE '"agent_type"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/"agent_type"[[:space:]]*:[[:space:]]*"//;s/"$//') - [ -z "$AGENT_NAME" ] && AGENT_NAME="unknown" + AGENT_NAME=$(echo "$INPUT" | grep -oE '"agent_type"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/"agent_type"[[:space:]]*:[[:space:]]*"//;s/"$//') fi +# OUTSIDE the branch, like SESSION_ID below. jq's `// "unknown"` substitutes for +# null and false but NOT for an empty string, so the jq path could emit a record +# with no agent name at all while the grep path guarded against it. One guard, +# both paths. +[ -z "$AGENT_NAME" ] && AGENT_NAME="unknown" + +# Parse session id -- keeps the completion record in the same three-field shape +# as log-agent.sh so both sides of a spawn belong to an identifiable session. +if command -v jq >/dev/null 2>&1; then + SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"' 2>/dev/null) +else + SESSION_ID=$(echo "$INPUT" | grep -oE '"session_id"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/"session_id"[[:space:]]*:[[:space:]]*"//;s/"$//') +fi +[ -z "$SESSION_ID" ] && SESSION_ID="unknown" TIMESTAMP=$(date +%Y%m%d_%H%M%S) SESSION_LOG_DIR="production/session-logs" mkdir -p "$SESSION_LOG_DIR" 2>/dev/null -echo "$TIMESTAMP | Agent completed: $AGENT_NAME" >> "$SESSION_LOG_DIR/agent-audit.log" 2>/dev/null +echo "$TIMESTAMP | $SESSION_ID | Agent completed: $AGENT_NAME" >> "$SESSION_LOG_DIR/agent-audit.log" 2>/dev/null exit 0 diff --git a/.claude/hooks/log-agent.sh b/.claude/hooks/log-agent.sh index e39a5e72..9f36e27d 100644 --- a/.claude/hooks/log-agent.sh +++ b/.claude/hooks/log-agent.sh @@ -1,4 +1,32 @@ #!/bin/bash + +# --- work from the project root ---------------------------------------------- +# Every path below is repo-relative, so a hook invoked with a working directory +# that is not the repo root would silently read and write the WRONG TREE -- +# returning a near-empty result instead of the session-recovery block, and +# creating stray trees such as docs/production/session-logs/ on write. +# +# PRECEDENCE IS LOAD-BEARING. A cwd that IS a project root carries real +# information and must win: a caller sitting inside another project means that +# project, not this one. Resolving to the script's own location first would +# override them. So, in order: +# 1. cwd holds project.yaml -> cwd (a project root) +# 2. cwd holds .claude/ -> cwd (a project root not yet configured) +# 3. CLAUDE_PROJECT_DIR -> that (populated in the hook environment) +# 4. this script's location -> /.claude/hooks/../.. by construction +# Rule 4 always works and needs no environment at all; rules 1-2 stop it from +# overriding a caller that legitimately means somewhere else. +# +# NOT an upward search: that resolves a nested project to its parent's config. +if [ -f "project.yaml" ] || [ -d ".claude" ]; then + CCGS_ROOT="$PWD" +elif [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -d "${CLAUDE_PROJECT_DIR}" ]; then + CCGS_ROOT="$CLAUDE_PROJECT_DIR" +else + CCGS_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." 2>/dev/null && pwd)" +fi +[ -n "$CCGS_ROOT" ] && cd "$CCGS_ROOT" 2>/dev/null || true + # Claude Code SubagentStart hook: Log agent invocations for audit trail # Tracks which agents are being used and when # @@ -9,21 +37,45 @@ # returns null on every invocation, so the fallback "unknown" is always used # and the audit trail captures nothing useful. +# features.session_state: off => no audit trail, no spawn tally. Default is +# `on`, so this changes nothing unless the user opted out. Checked before +# reading stdin: there is no point paying for the parse we are about to discard. +if [ -f .claude/hooks/yaml-helper.sh ]; then + . .claude/hooks/yaml-helper.sh + session_state_enabled || exit 0 +fi + INPUT=$(cat) # Parse agent name -- use jq if available, fall back to grep if command -v jq >/dev/null 2>&1; then AGENT_NAME=$(echo "$INPUT" | jq -r '.agent_type // "unknown"' 2>/dev/null) else - AGENT_NAME=$(echo "$INPUT" | grep -oE '"agent_type"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/"agent_type"[[:space:]]*:[[:space:]]*"//;s/"$//') - [ -z "$AGENT_NAME" ] && AGENT_NAME="unknown" + AGENT_NAME=$(echo "$INPUT" | grep -oE '"agent_type"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/"agent_type"[[:space:]]*:[[:space:]]*"//;s/"$//') fi +# OUTSIDE the branch, like SESSION_ID below. jq's `// "unknown"` substitutes for +# null and false but NOT for an empty string, so the jq path could emit a record +# with no agent name at all while the grep path guarded against it. One guard, +# both paths. +[ -z "$AGENT_NAME" ] && AGENT_NAME="unknown" + +# Parse session id -- session-stop.sh tallies spawns per session, so the record +# is useless without it. Same jq/grep fallback pattern as the agent name. +if command -v jq >/dev/null 2>&1; then + SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"' 2>/dev/null) +else + SESSION_ID=$(echo "$INPUT" | grep -oE '"session_id"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/"session_id"[[:space:]]*:[[:space:]]*"//;s/"$//') +fi +[ -z "$SESSION_ID" ] && SESSION_ID="unknown" TIMESTAMP=$(date +%Y%m%d_%H%M%S) SESSION_LOG_DIR="production/session-logs" mkdir -p "$SESSION_LOG_DIR" 2>/dev/null -echo "$TIMESTAMP | Agent invoked: $AGENT_NAME" >> "$SESSION_LOG_DIR/agent-audit.log" 2>/dev/null +# Field order is timestamp | session | event -- session-stop.sh greps the +# fixed string " | $SESSION_ID | Agent invoked: " to tally the current session. +# Changing this format silently breaks the spawn count (nothing else reads it). +echo "$TIMESTAMP | $SESSION_ID | Agent invoked: $AGENT_NAME" >> "$SESSION_LOG_DIR/agent-audit.log" 2>/dev/null exit 0 diff --git a/.claude/hooks/log-instructions.sh b/.claude/hooks/log-instructions.sh new file mode 100644 index 00000000..abc0ad09 --- /dev/null +++ b/.claude/hooks/log-instructions.sh @@ -0,0 +1,137 @@ +#!/bin/bash + +# --- work from the project root ---------------------------------------------- +# Every path below is repo-relative, so a hook invoked with a working directory +# that is not the repo root would silently write the WRONG TREE -- creating +# stray trees such as docs/production/session-logs/ on write. +# +# PRECEDENCE IS LOAD-BEARING. A cwd that IS a project root carries real +# information and must win: a caller sitting inside another project means that +# project, not this one. Resolving to the script's own location first would +# override them. So, in order: +# 1. cwd holds project.yaml -> cwd (a project root) +# 2. cwd holds .claude/ -> cwd (a project root not yet configured) +# 3. CLAUDE_PROJECT_DIR -> that (populated in the hook environment) +# 4. this script's location -> /.claude/hooks/../.. by construction +# Rule 4 always works and needs no environment at all; rules 1-2 stop it from +# overriding a caller that legitimately means somewhere else. +# +# NOT an upward search: that resolves a nested project to its parent's config. +if [ -f "project.yaml" ] || [ -d ".claude" ]; then + CCGS_ROOT="$PWD" +elif [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -d "${CLAUDE_PROJECT_DIR}" ]; then + CCGS_ROOT="$CLAUDE_PROJECT_DIR" +else + CCGS_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." 2>/dev/null && pwd)" +fi +[ -n "$CCGS_ROOT" ] && cd "$CCGS_ROOT" 2>/dev/null || true + +# Claude Code InstructionsLoaded hook: record WHICH instruction file loaded and +# WHY. +# +# WHAT THIS ANSWERS +# `.claude/rules/*.md` files carry `paths:` frontmatter, and a path-scoped rule +# loads only when Claude READS a file matching the pattern -- not on every tool +# use, and not when it CREATES one. This framework's agents predominantly create +# files: a new GDD, a new source file, a new test. So "does the rule for +# src/gameplay/** ever actually reach the model?" has a real answer, and before +# this hook the only way to get it was to infer it from behaviour afterwards. +# +# That inference was made once here, expensively: a data-file rule specified one +# key casing, agents wrote the other, and nothing surfaced the disagreement. +# Measurement replaces the inference. +# +# MEASURED, same repo, one session: +# session_start CLAUDE.md +# include its four @-imports, each carrying parent_file_path +# path_glob_match .claude/rules/skill-authoring.md, after READING a file +# under .claude/agents/** -- one of its declared paths +# (nothing) after CREATING a file under prototypes/** -- a declared +# path of .claude/rules/prototype-code.md +# Read fires, create does not. Keep this hook wired: the behaviour is upstream +# and can change in either direction, and this is the only thing here that would +# notice. +# +# SCHEMA CONFIRMED, NOT ASSUMED +# The common fields are documented; the event-specific ones are not. `pick()` +# tries the confirmed name first and keeps alternates behind it, so a schema +# change degrades to a fallback rather than to a blank column -- and the RAW +# line carries the answer regardless. That is how `load_reason` was found: the +# first guess (`reason`) missed, and the raw payload named the real field. +# +# NOT GATED ON features.session_state, deliberately. This is a diagnostic wired +# up on purpose; silencing it from an unrelated flag would make it do nothing +# while still looking installed -- the failure shape +# `.claude/rules/skill-authoring.md` exists to prevent. +# +# The exit code is ignored for this event and it cannot block a load. Fail +# silent, stay cheap: it fires once per instruction file per load. + +LOG_DIR="production/session-logs" +LOG="$LOG_DIR/instructions-loaded.log" + +# stdin may be absent; an unbounded read would then hang until the hook timeout. +[ -t 0 ] && exit 0 +if command -v timeout >/dev/null 2>&1; then + INPUT=$(timeout 2 cat 2>/dev/null) +else + INPUT=$(cat 2>/dev/null) +fi +[ -n "$INPUT" ] || exit 0 + +mkdir -p "$LOG_DIR" 2>/dev/null || exit 0 + +TIMESTAMP=$(date +%Y%m%d_%H%M%S) + +# `head -1` on every grep: an unanchored match can hit the same key nested +# deeper in the payload, and a multi-line value would break the one-record-per- +# line format this log depends on. +pick() { # ... + _j="$1"; shift + for _k in "$@"; do + _v=$(printf '%s' "$_j" \ + | grep -oE "\"$_k\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" \ + | head -1 \ + | sed "s/\"$_k\"[[:space:]]*:[[:space:]]*\"//;s/\"$//") + if [ -n "$_v" ]; then printf '%s' "$_v"; return 0; fi + done + printf '' +} + +FILE_VAL=$(pick "$INPUT" file_path path file source_path instruction_path filePath) +REASON_VAL=$(pick "$INPUT" load_reason reason loadReason matcher trigger source) +PARENT_VAL=$(pick "$INPUT" parent_file_path parentFilePath parent) + +# Paths arrive as absolute Windows paths with JSON-escaped separators. Left raw, +# the readable line is the least readable thing in the log. Unescape, normalise +# to forward slashes, strip the repo prefix -- so the column reads +# `.claude/rules/gameplay-code.md`, which is what anyone scanning this is +# looking for. The RAW line below keeps the original bytes untouched. +# TWO forms of the repo root, because the payload and the shell disagree about +# what a path looks like on Windows. Git Bash reports $PWD as `/c/Users/...` +# while the payload carries `C:\Users\...`; stripping only the shell's form +# silently strips nothing and the column stays absolute -- which is why this +# needs checking on real payloads rather than a fixture. `pwd -W` gives the Windows +# form where it exists and is empty elsewhere, so both are tried and neither is +# required. +_PWD_FWD=$(printf '%s' "$PWD" | sed 's|\\|/|g') +_PWD_WIN=$(pwd -W 2>/dev/null | sed 's|\\|/|g') +shorten() { + [ -n "$1" ] || return 0 + printf '%s' "$1" | sed 's|\\\\|/|g; s|\\|/|g' \ + | sed "s|^${_PWD_FWD}/||" \ + | { if [ -n "$_PWD_WIN" ]; then sed "s|^${_PWD_WIN}/||"; else cat; fi; } +} +FILE_SHORT=$(shorten "$FILE_VAL") +PARENT_SHORT=$(shorten "$PARENT_VAL") + +{ + printf '%s | %-16s | %s%s\n' \ + "$TIMESTAMP" "${REASON_VAL:-?}" "${FILE_SHORT:-?}" \ + "${PARENT_SHORT:+ (via $PARENT_SHORT)}" + # Raw payload on the following line, prefixed so the log stays greppable and + # so an unrecognised schema is still recoverable from the file itself. + printf '%s RAW %s\n' "$TIMESTAMP" "$INPUT" +} >> "$LOG" 2>/dev/null + +exit 0 diff --git a/.claude/hooks/notify.sh b/.claude/hooks/notify.sh index 661b0bd6..035e496f 100644 --- a/.claude/hooks/notify.sh +++ b/.claude/hooks/notify.sh @@ -1,4 +1,32 @@ #!/usr/bin/env bash + +# --- work from the project root ---------------------------------------------- +# Every path below is repo-relative, so a hook invoked with a working directory +# that is not the repo root would silently read and write the WRONG TREE -- +# returning a near-empty result instead of the session-recovery block, and +# creating stray trees such as docs/production/session-logs/ on write. +# +# PRECEDENCE IS LOAD-BEARING. A cwd that IS a project root carries real +# information and must win: a caller sitting inside another project means that +# project, not this one. Resolving to the script's own location first would +# override them. So, in order: +# 1. cwd holds project.yaml -> cwd (a project root) +# 2. cwd holds .claude/ -> cwd (a project root not yet configured) +# 3. CLAUDE_PROJECT_DIR -> that (populated in the hook environment) +# 4. this script's location -> /.claude/hooks/../.. by construction +# Rule 4 always works and needs no environment at all; rules 1-2 stop it from +# overriding a caller that legitimately means somewhere else. +# +# NOT an upward search: that resolves a nested project to its parent's config. +if [ -f "project.yaml" ] || [ -d ".claude" ]; then + CCGS_ROOT="$PWD" +elif [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -d "${CLAUDE_PROJECT_DIR}" ]; then + CCGS_ROOT="$CLAUDE_PROJECT_DIR" +else + CCGS_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." 2>/dev/null && pwd)" +fi +[ -n "$CCGS_ROOT" ] && cd "$CCGS_ROOT" 2>/dev/null || true + # Notification hook — fires when Claude Code sends a notification # Shows a Windows toast via PowerShell @@ -9,15 +37,24 @@ INPUT=$(cat) if command -v jq &>/dev/null; then MESSAGE=$(echo "$INPUT" | jq -r '.message // empty' 2>/dev/null) fi -if [ -z "$MESSAGE" ]; then - MESSAGE=$(echo "$INPUT" | grep -oE '"message":"[^"]*"' | sed 's/"message":"//;s/"//') +if [ -z "${MESSAGE:-}" ]; then + # `[[:space:]]*` around the colon, like every other hook's fallback. This one + # required `"message":"` with no space, so a pretty-printed or space-separated + # payload fell through to the generic text below and the real notification was + # lost. Nothing reported that -- the hook still fired, just saying nothing. + MESSAGE=$(echo "$INPUT" | grep -oE '"message"[[:space:]]*:[[:space:]]*"[^"]*"' \ + | head -1 | sed 's/"message"[[:space:]]*:[[:space:]]*"//;s/"$//') fi if [ -z "$MESSAGE" ]; then MESSAGE="Claude Code needs your attention" fi -# Sanitize message for PowerShell string embedding (escape single quotes) -MESSAGE_SAFE=$(echo "$MESSAGE" | sed "s/'/''/g" | head -c 200) +# TRUNCATE FIRST, then escape. Escaping doubles each `'` into `''`, so cutting +# at a fixed byte count afterwards can land between the two halves of a pair and +# hand PowerShell an unbalanced quote. The command then fails to parse, and +# because it is backgrounded with stderr discarded the only symptom is a +# notification that never appears. +MESSAGE_SAFE=$(printf '%s' "$MESSAGE" | head -c 200 | sed "s/'/''/g") # Show Windows balloon tip notification (works on all Windows 10/11 without extra modules) powershell.exe -NonInteractive -WindowStyle Hidden -Command " diff --git a/.claude/hooks/post-compact.sh b/.claude/hooks/post-compact.sh index 92d57fa9..9a949a1a 100644 --- a/.claude/hooks/post-compact.sh +++ b/.claude/hooks/post-compact.sh @@ -1,7 +1,42 @@ #!/usr/bin/env bash + +# --- work from the project root ---------------------------------------------- +# Every path below is repo-relative, so a hook invoked with a working directory +# that is not the repo root would silently read and write the WRONG TREE -- +# returning a near-empty result instead of the session-recovery block, and +# creating stray trees such as docs/production/session-logs/ on write. +# +# PRECEDENCE IS LOAD-BEARING. A cwd that IS a project root carries real +# information and must win: a caller sitting inside another project means that +# project, not this one. Resolving to the script's own location first would +# override them. So, in order: +# 1. cwd holds project.yaml -> cwd (a project root) +# 2. cwd holds .claude/ -> cwd (a project root not yet configured) +# 3. CLAUDE_PROJECT_DIR -> that (populated in the hook environment) +# 4. this script's location -> /.claude/hooks/../.. by construction +# Rule 4 always works and needs no environment at all; rules 1-2 stop it from +# overriding a caller that legitimately means somewhere else. +# +# NOT an upward search: that resolves a nested project to its parent's config. +if [ -f "project.yaml" ] || [ -d ".claude" ]; then + CCGS_ROOT="$PWD" +elif [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -d "${CLAUDE_PROJECT_DIR}" ]; then + CCGS_ROOT="$CLAUDE_PROJECT_DIR" +else + CCGS_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." 2>/dev/null && pwd)" +fi +[ -n "$CCGS_ROOT" ] && cd "$CCGS_ROOT" 2>/dev/null || true + # post-compact.sh — fires after conversation compaction # Reminds Claude to restore session state from the file-backed checkpoint. +# features.session_state: off => this hook is a no-op. Default `on`; see +# session_state_enabled() in yaml-helper.sh. +if [ -f .claude/hooks/yaml-helper.sh ]; then + . .claude/hooks/yaml-helper.sh + session_state_enabled || exit 0 +fi + ACTIVE="production/session-state/active.md" echo "=== Context Restored After Compaction ===" diff --git a/.claude/hooks/pre-compact.sh b/.claude/hooks/pre-compact.sh index 4d4842f5..efb02162 100644 --- a/.claude/hooks/pre-compact.sh +++ b/.claude/hooks/pre-compact.sh @@ -1,49 +1,126 @@ #!/bin/bash + +# --- work from the project root ---------------------------------------------- +# Every path below is repo-relative, so a hook invoked with a working directory +# that is not the repo root would silently read and write the WRONG TREE -- +# returning a near-empty result instead of the session-recovery block, and +# creating stray trees such as docs/production/session-logs/ on write. +# +# PRECEDENCE IS LOAD-BEARING. A cwd that IS a project root carries real +# information and must win: a caller sitting inside another project means that +# project, not this one. Resolving to the script's own location first would +# override them. So, in order: +# 1. cwd holds project.yaml -> cwd (a project root) +# 2. cwd holds .claude/ -> cwd (a project root not yet configured) +# 3. CLAUDE_PROJECT_DIR -> that (populated in the hook environment) +# 4. this script's location -> /.claude/hooks/../.. by construction +# Rule 4 always works and needs no environment at all; rules 1-2 stop it from +# overriding a caller that legitimately means somewhere else. +# +# NOT an upward search: that resolves a nested project to its parent's config. +if [ -f "project.yaml" ] || [ -d ".claude" ]; then + CCGS_ROOT="$PWD" +elif [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -d "${CLAUDE_PROJECT_DIR}" ]; then + CCGS_ROOT="$CLAUDE_PROJECT_DIR" +else + CCGS_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." 2>/dev/null && pwd)" +fi +[ -n "$CCGS_ROOT" ] && cd "$CCGS_ROOT" 2>/dev/null || true + # Claude Code PreCompact hook: Dump session state before context compression # This output appears in the conversation right before compaction, ensuring # critical state survives the summarization process. +# features.session_state: off => this hook is a no-op. Default `on`; see +# session_state_enabled() in yaml-helper.sh. +if [ -f .claude/hooks/yaml-helper.sh ]; then + . .claude/hooks/yaml-helper.sh + session_state_enabled || exit 0 +fi + echo "=== SESSION STATE BEFORE COMPACTION ===" echo "Timestamp: $(date)" -# --- Active session state file --- +# --- Active session state: the CHECKPOINT region, BY REFERENCE --- +# +# Injecting `head -n 100` of active.md here would be wrong twice over. +# +# First, the cap bounds the wrong dimension. active.md's lines are long, so 100 +# of them was 12.7 KB (~3,200 tokens) -- injected at the one moment context is +# scarcest, and growing with the file. +# +# Second, and worse: content emitted here goes INTO the context that is about to +# be compacted, so it is summarised like everything else rather than preserved. +# We were paying full price for a lossy copy of a file that is already on disk +# -- while post-compact.sh, which fires after, already tells the agent to read +# that file. Pointer + a bounded checkpoint beats a dump on both cost and +# fidelity. +# +# So: emit only the region between the CHECKPOINT markers (bounded by the schema +# in .claude/docs/templates/session-state.md) plus a pointer to the whole file. STATE_FILE="production/session-state/active.md" if [ -f "$STATE_FILE" ]; then echo "" - echo "## Active Session State (from $STATE_FILE)" - STATE_LINES=$(wc -l < "$STATE_FILE" 2>/dev/null | tr -d ' ') - if [ "$STATE_LINES" -gt 100 ] 2>/dev/null; then - head -n 100 "$STATE_FILE" - echo "... (truncated — $STATE_LINES total lines, showing first 100)" + echo "## Active Session State — checkpoint from $STATE_FILE" + CHECKPOINT=$(sed -n '//,//p' "$STATE_FILE" 2>/dev/null \ + | grep -v '') + STATUS_BLOCK=$(sed -n '//,//p' "$STATE_FILE" 2>/dev/null \ + | grep -v '' | grep -E '^(Epic|Feature|Task):[[:space:]]*[^[:space:]]') + [ -n "$STATUS_BLOCK" ] && printf '%s\n' "$STATUS_BLOCK" + if [ -n "$CHECKPOINT" ]; then + printf '%s\n' "$CHECKPOINT" else - cat "$STATE_FILE" + # No markers: an unmigrated or hand-written file. Fall back to a small + # head slice rather than nothing -- but say so, because a missing + # checkpoint is a real problem the user should fix, not absorb silently. + echo "(no CHECKPOINT block — showing the first 20 lines instead;" + echo " re-create this file from .claude/docs/templates/session-state.md)" + head -n 20 "$STATE_FILE" fi + echo "" + echo "Full detail (NOT reproduced here — read the file if you need it): $STATE_FILE" else echo "" echo "## No active session state file found" echo "Consider maintaining production/session-state/active.md for better recovery." + echo "Template: .claude/docs/templates/session-state.md" fi # --- Files modified this session (unstaged + staged + untracked) --- echo "" echo "## Files Modified (git working tree)" +# BOUNDED. These three lists must never be emitted in full, one line per +# file, with no cap -- and this output goes INTO the context that is about to be +# compacted. The checkpoint above was redesigned to be by-reference for exactly +# that reason (see the note at the top of this file); the lists below were left +# unbounded, so the hook still dumped whatever the working tree happened to +# contain at the moment context was scarcest. Measured on a tree with ~5k +# untracked files: 181,125 bytes, roughly 45,000 tokens, of which ~4,984 lines +# were this section. A fresh asset import or un-gitignored build output is +# enough to trigger it. +# +# A count plus a sample is what this section is actually for -- orienting the +# agent after compaction -- and the full list is one `git status` away. +_PC_CAP=20 +_pc_list() { # $1=label $2=newline-separated paths + [ -n "$2" ] || return 0 + _n=$(printf '%s\n' "$2" | grep -c .) + echo "$1 ($_n):" + printf '%s\n' "$2" | head -n "$_PC_CAP" | while read -r f; do [ -n "$f" ] && echo " - $f"; done + if [ "$_n" -gt "$_PC_CAP" ]; then + echo " ... and $((_n - _PC_CAP)) more (run: git status)" + fi +} + CHANGED=$(git diff --name-only 2>/dev/null) STAGED=$(git diff --staged --name-only 2>/dev/null) UNTRACKED=$(git ls-files --others --exclude-standard 2>/dev/null) -if [ -n "$CHANGED" ]; then - echo "Unstaged changes:" - echo "$CHANGED" | while read -r f; do echo " - $f"; done -fi -if [ -n "$STAGED" ]; then - echo "Staged changes:" - echo "$STAGED" | while read -r f; do echo " - $f"; done -fi -if [ -n "$UNTRACKED" ]; then - echo "New untracked files:" - echo "$UNTRACKED" | while read -r f; do echo " - $f"; done -fi +_pc_list "Unstaged changes" "$CHANGED" +_pc_list "Staged changes" "$STAGED" +_pc_list "New untracked files" "$UNTRACKED" + if [ -z "$CHANGED" ] && [ -z "$STAGED" ] && [ -z "$UNTRACKED" ]; then echo " (no uncommitted changes)" fi @@ -53,15 +130,30 @@ echo "" echo "## Design Docs — Work In Progress" WIP_FOUND=false -for f in design/gdd/*.md; do - [ -f "$f" ] || continue - INCOMPLETE=$(grep -n -E "TODO|WIP|PLACEHOLDER|\[TO BE|\[TBD\]" "$f" 2>/dev/null) - if [ -n "$INCOMPLETE" ]; then - WIP_FOUND=true - echo " $f:" - echo "$INCOMPLETE" | while read -r line; do echo " $line"; done +# BOUNDED and BATCHED. One `grep -n` per design doc, printing every matching +# line from every file, fails twice over -- the same two ways as above: +# unbounded OUTPUT into a compacting context, and unbounded +# COST -- ~85ms per file, crossing this hook's 10s budget at roughly 90 GDDs. +# Measured: 220 docs 18262ms, 420 docs killed at the timeout. +# +# `xargs -0` bounds the spawn count by ARG_MAX rather than by file count, and +# the NUL delimiter keeps it portable: `-d` is GNU-only, BSD xargs rejects it, +# and the pipeline swallows that error silently. The +# report is capped. What the agent needs after compaction is "these docs are +# unfinished", not every TODO line in the project. +_WIP_HITS=$(printf '%s\n' design/gdd/*.md | tr '\n' '\0' \ + | xargs -0 grep -lE "TODO|WIP|PLACEHOLDER|\[TO BE|\[TBD\]" 2>/dev/null || true) +if [ -n "$_WIP_HITS" ]; then + WIP_FOUND=true + _wn=$(printf '%s\n' "$_WIP_HITS" | grep -c .) + echo "$_wn design doc(s) contain TODO/WIP/PLACEHOLDER markers:" + printf '%s\n' "$_WIP_HITS" | head -n "$_PC_CAP" | while read -r f; do + [ -n "$f" ] && echo " - $f" + done + if [ "$_wn" -gt "$_PC_CAP" ]; then + echo " ... and $((_wn - _PC_CAP)) more" fi -done +fi if [ "$WIP_FOUND" = false ]; then echo " (no WIP markers found in design docs)" diff --git a/.claude/hooks/session-start.sh b/.claude/hooks/session-start.sh index 05687d19..59f17906 100644 --- a/.claude/hooks/session-start.sh +++ b/.claude/hooks/session-start.sh @@ -1,8 +1,53 @@ #!/bin/bash + +# --- work from the project root ---------------------------------------------- +# Every path below is repo-relative, so a hook invoked with a working directory +# that is not the repo root would silently read and write the WRONG TREE -- +# returning a near-empty result instead of the session-recovery block, and +# creating stray trees such as docs/production/session-logs/ on write. +# +# PRECEDENCE IS LOAD-BEARING. A cwd that IS a project root carries real +# information and must win: a caller sitting inside another project means that +# project, not this one. Resolving to the script's own location first would +# override them. So, in order: +# 1. cwd holds project.yaml -> cwd (a project root) +# 2. cwd holds .claude/ -> cwd (a project root not yet configured) +# 3. CLAUDE_PROJECT_DIR -> that (populated in the hook environment) +# 4. this script's location -> /.claude/hooks/../.. by construction +# Rule 4 always works and needs no environment at all; rules 1-2 stop it from +# overriding a caller that legitimately means somewhere else. +# +# NOT an upward search: that resolves a nested project to its parent's config. +if [ -f "project.yaml" ] || [ -d ".claude" ]; then + CCGS_ROOT="$PWD" +elif [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -d "${CLAUDE_PROJECT_DIR}" ]; then + CCGS_ROOT="$CLAUDE_PROJECT_DIR" +else + CCGS_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." 2>/dev/null && pwd)" +fi +[ -n "$CCGS_ROOT" ] && cd "$CCGS_ROOT" 2>/dev/null || true + # Claude Code SessionStart hook: Load project context at session start # Outputs context information that Claude sees when a session begins # -# Input schema (SessionStart): No stdin input +# Input schema (SessionStart): stdin IS supplied -- VERIFIED, not asserted. +# Observed on real harness events as a single line of JSON with session_id, +# transcript_path, cwd, hook_event_name and `source` ("clear", "compact"). +# Do NOT read stdin here unboundedly -- the harness may leave it open, and a +# blocking read costs this hook its whole 10s budget on every session start. +# +# THIS HOOK MUST FINISH INSIDE ITS BUDGET. If it is killed partway, the +# session-state block never reaches context and the session starts blind -- +# and nothing visible from inside the session says so. The early-exit gating +# and bounded reads below are what keep it inside 2s; treat them as load- +# bearing, not as optimisation. +# +# If that ever recurs, the diagnostic that works is a FIRE/DONE pair appended +# to a gitignored log at the top and bottom of this script -- "did it run at +# all" and "did it get here" are opposite failures with opposite fixes, and +# nothing else distinguishes them. /compact in particular CANNOT be used as +# evidence: PostCompact runs post-compact.sh, which prints the checkpoint +# independently, so seeing the checkpoint proves nothing about this script. echo "=== Claude Code Game Studios — Session Context ===" @@ -19,6 +64,84 @@ if [ -n "$BRANCH" ]; then done fi +# Resolve review_mode the same way skills do: resolve_setting applies the FULL +# chain (project.local.yaml -> project.yaml -> legacy review-mode.txt -> modes.rigor +# expansion -> default). modes.review_mode is rigor-fronted and locally overridable, +# so only resolve_setting reflects both a rigor-derived value (a project that set +# only `rigor: minimal` shows `solo`) and a local override. get_effective_yaml_key +# returns empty for a rigor-only project, which would leave the banner showing a +# stale `lean`. +REVIEW_MODE="" +if [ -f "project.yaml" ] && [ -f ".claude/hooks/yaml-helper.sh" ]; then + source .claude/hooks/yaml-helper.sh + REVIEW_MODE=$(resolve_setting modes.review_mode 2>/dev/null | cut -f1) +fi +if [ -z "$REVIEW_MODE" ] && [ -f "production/review-mode.txt" ]; then + REVIEW_MODE=$(head -1 production/review-mode.txt 2>/dev/null | tr -d '[:space:]') +fi +if [ -z "$REVIEW_MODE" ]; then + REVIEW_MODE="lean" +fi +echo "" +echo "Review mode: $REVIEW_MODE" + +# Schema validation — surface invalid enum values in +# project.yaml and project.local.yaml so typos like `automation: chaotic` +# surface at session start rather than failing inside a skill. +if [ -f ".claude/hooks/yaml-helper.sh" ]; then + if ! type validate_yaml_enum >/dev/null 2>&1; then + source .claude/hooks/yaml-helper.sh + fi + # Hard-error guard: project.local.yaml requires a project.yaml base + if ! BASE_ERR=$(validate_local_yaml_base 2>&1); then + echo "" + echo "[!] $BASE_ERR" + fi + if [ -f "project.yaml" ]; then + SCHEMA_ERRORS=$(validate_yaml_enum project.yaml 2>&1) + if [ -n "$SCHEMA_ERRORS" ]; then + echo "" + echo "[!] project.yaml schema errors:" + echo "$SCHEMA_ERRORS" | sed 's/^/ /' + fi + fi + if [ -f "project.local.yaml" ]; then + SCHEMA_ERRORS=$(validate_yaml_enum project.local.yaml 2>&1) + if [ -n "$SCHEMA_ERRORS" ]; then + echo "" + echo "[!] project.local.yaml schema errors:" + echo "$SCHEMA_ERRORS" | sed 's/^/ /' + fi + fi +fi + +# --- Stage source agreement --- +# `project.stage` in project.yaml is authoritative; production/stage.txt is a +# legacy mirror kept for unmigrated hooks. Twelve consumers read the mirror -- +# including detect-gaps.sh and yaml-helper.sh -- so when the two disagree, part +# of the system acts on one stage and part on the other, silently. +# +# Warn, never reconcile. Only /gate-check on a PASS may change a stage, so a +# hook that "helpfully" rewrote the mirror would be advancing a phase gate +# nobody passed. Helpers emit observations, never verdicts (CLAUDE.md). +STAGE_YAML="" +STAGE_TXT="" +if [ -f "project.yaml" ] && command -v get_yaml_key >/dev/null 2>&1; then + STAGE_YAML=$(get_yaml_key project.yaml project.stage 2>/dev/null) +fi +if [ -f "production/stage.txt" ]; then + STAGE_TXT=$(head -1 production/stage.txt 2>/dev/null | tr -d '\r' | sed 's/[[:space:]]*$//') +fi +if [ -n "$STAGE_YAML" ] && [ -n "$STAGE_TXT" ] && [ "$STAGE_YAML" != "$STAGE_TXT" ]; then + echo "" + echo "[!] Stage sources disagree:" + echo " project.yaml project.stage : $STAGE_YAML (authoritative)" + echo " production/stage.txt : $STAGE_TXT (legacy mirror)" + echo " Twelve consumers read the mirror, so part of the system is acting on" + echo " each value. Only /gate-check on a PASS should change a stage — do not" + echo " hand-edit either file to silence this." +fi + # Current sprint (find most recent sprint file) LATEST_SPRINT=$(ls -t production/sprints/sprint-*.md 2>/dev/null | head -1) if [ -n "$LATEST_SPRINT" ]; then @@ -44,32 +167,122 @@ if [ "$BUG_COUNT" -gt 0 ]; then echo "Open bugs: $BUG_COUNT" fi -# Code health quick check -if [ -d "src" ]; then - TODO_COUNT=$(grep -r "TODO" src/ 2>/dev/null | wc -l) - FIXME_COUNT=$(grep -r "FIXME" src/ 2>/dev/null | wc -l) +# Code health quick check. +# +# Reads the ENGINE-SPECIFIC code root, not a literal `src/`. Hardcoding `src/` +# made this line Godot-only: on a Unity or Unreal project the directory test +# failed, the banner printed no code-health line at all, and a clean project and +# an unreadable one looked identical. See resolve_code_root in yaml-helper.sh. +# +# Sourcing here is defensive: the two source sites above are both conditional, +# so by this point the function may or may not exist. +if [ -f .claude/hooks/yaml-helper.sh ] && ! command -v resolve_code_root >/dev/null 2>&1; then + . .claude/hooks/yaml-helper.sh 2>/dev/null +fi +if command -v resolve_code_root >/dev/null 2>&1; then + CODE_ROOT=$(resolve_code_root 2>/dev/null | cut -f1) +else + CODE_ROOT="" +fi +if [ -n "$CODE_ROOT" ] && [ -d "$CODE_ROOT" ]; then + TODO_COUNT=$(grep -r "TODO" "$CODE_ROOT/" 2>/dev/null | wc -l) + FIXME_COUNT=$(grep -r "FIXME" "$CODE_ROOT/" 2>/dev/null | wc -l) if [ "$TODO_COUNT" -gt 0 ] || [ "$FIXME_COUNT" -gt 0 ]; then echo "" - echo "Code health: ${TODO_COUNT} TODOs, ${FIXME_COUNT} FIXMEs in src/" + echo "Code health: ${TODO_COUNT} TODOs, ${FIXME_COUNT} FIXMEs in ${CODE_ROOT}/" fi fi # --- Active session state recovery --- +# Only THIS block is gated by features.session_state, not the whole hook: the +# sprint/milestone/git context above is not part of the session-state pipeline +# and a user who turns that pipeline off still wants it. Gating the whole hook +# (as the original spec's "exit early at the top" wording implies) would take +# the branch and stage context away with it. +# +# Fail OPEN: this hook only sources yaml-helper.sh conditionally, so +# session_state_enabled may be undefined. An undefined function is falsey, which +# would silently suppress the recovery checkpoint -- the one piece of output +# whose absence the user cannot notice. Show the state unless we positively +# determined the flag is off. STATE_FILE="production/session-state/active.md" -if [ -f "$STATE_FILE" ]; then +if [ -f .claude/hooks/yaml-helper.sh ] && ! command -v session_state_enabled >/dev/null 2>&1; then + . .claude/hooks/yaml-helper.sh +fi +if [ -f "$STATE_FILE" ] && { ! command -v session_state_enabled >/dev/null 2>&1 || session_state_enabled; }; then echo "" echo "=== ACTIVE SESSION STATE DETECTED ===" echo "A previous session left state at: $STATE_FILE" echo "Read this file to recover context and continue where you left off." echo "" - echo "Quick summary (last 20 lines):" - tail -20 "$STATE_FILE" 2>/dev/null - TOTAL_LINES=$(wc -l < "$STATE_FILE" 2>/dev/null) - if [ "$TOTAL_LINES" -gt 20 ]; then + # The CHECKPOINT region -- the same region pre-compact.sh injects. + # + # Previewing `tail -20` here while pre-compact takes `head -100` would put two + # consumers on opposite ends of one file, so which slice you got would + # depend on which hook happened to fire. Neither was wrong, because + # nothing defined where the recoverable state lived. The schema in + # .claude/docs/templates/session-state.md defines it; both read it now. + CHECKPOINT=$(sed -n '//,//p' "$STATE_FILE" 2>/dev/null \ + | grep -v '') + TOTAL_LINES=$(wc -l < "$STATE_FILE" 2>/dev/null | tr -d ' ') + # A sed range whose END address never matches runs to EOF. So a file with an + # opening marker and NO closing one produced a non-empty capture of the whole + # remaining file and took the healthy branch below -- silently previewing + # narrative under a "Checkpoint:" heading, with no warning and no template + # named. Emptiness cannot distinguish "no block" from + # "unterminated block"; only the markers can, so test them directly. + # rotate-session-state.sh already checks the CLOSING marker and refuses. + # Two consumers of one region must agree on what malformed means. + # `` cannot match `` -- the slash sits + # where the space would be -- so these two counts are independent. + CP_OPEN=$(grep -c '' "$STATE_FILE" 2>/dev/null | tr -d ' ') + CP_CLOSE=$(grep -c '' "$STATE_FILE" 2>/dev/null | tr -d ' ') + if [ "${CP_OPEN:-0}" -gt 0 ] && [ "${CP_CLOSE:-0}" -eq 0 ]; then + echo " [!] CHECKPOINT block is not terminated — no marker." + echo " Not previewing it: without the closing marker the checkpoint" + echo " cannot be told from the narrative, and everything to the end" + echo " of the file would be shown as if it were recoverable state." + echo " Re-create from .claude/docs/templates/session-state.md." echo " ... ($TOTAL_LINES total lines — read the full file to continue)" + elif [ -n "$CHECKPOINT" ]; then + echo "Checkpoint:" + printf '%s\n' "$CHECKPOINT" + echo " ... ($TOTAL_LINES total lines — read the full file for detail)" + else + echo "Quick summary (first 20 lines — no CHECKPOINT block in this file):" + head -20 "$STATE_FILE" 2>/dev/null + echo " ... ($TOTAL_LINES total lines — read the full file to continue)" + echo " NOTE: re-create from .claude/docs/templates/session-state.md so" + echo " recovery reads a bounded checkpoint instead of a slice." + fi + # Rotation is an OBSERVATION, never an action: helpers in .claude/scripts/ + # emit observations, never verdicts (CLAUDE.md). The user decides. + if [ "${TOTAL_LINES:-0}" -gt 200 ] 2>/dev/null; then + echo " Note: $TOTAL_LINES lines. Narrative can be rotated into" + echo " production/session-logs/ — bash .claude/scripts/rotate-session-state.sh" fi echo "=== END SESSION STATE PREVIEW ===" fi +# --- engine reference vs configured engine ----------------------------------- +# /setup-engine is the only thing that rewrites CLAUDE.md's ENGINE-REFERENCE-IMPORT +# line. Edit engine.name in project.yaml by hand and the import keeps loading the +# previous engine's reference as project instructions -- every session, silently. +# An OBSERVATION, never an action (CLAUDE.md): it reports, the user decides. +if [ -f project.yaml ] && [ -f CLAUDE.md ]; then + ERI=$(grep -m1 -E '^@docs/engine-reference/[a-z]+/VERSION\.md' CLAUDE.md 2>/dev/null \ + | sed -n 's|^@docs/engine-reference/\([a-z]*\)/VERSION\.md.*|\1|p') + CFG=$(sed -n 's/^[[:space:]]*name:[[:space:]]*"\{0,1\}\([A-Za-z]*\)"\{0,1\}[[:space:]]*$/\1/p' \ + project.yaml 2>/dev/null | head -1 | tr '[:upper:]' '[:lower:]') + if [ -n "$ERI" ] && [ -n "$CFG" ] && [ "$ERI" != "$CFG" ]; then + echo "" + echo "!! ENGINE REFERENCE MISMATCH" + echo " project.yaml engine.name : $CFG" + echo " CLAUDE.md imports : docs/engine-reference/$ERI/VERSION.md" + echo " Every session is loading the $ERI reference on a $CFG project." + echo " Fix: re-run /setup-engine, or edit the ENGINE-REFERENCE-IMPORT line." + fi +fi + echo "===================================" exit 0 diff --git a/.claude/hooks/session-stop.sh b/.claude/hooks/session-stop.sh index 2b78ac25..d681a60f 100644 --- a/.claude/hooks/session-stop.sh +++ b/.claude/hooks/session-stop.sh @@ -1,7 +1,42 @@ #!/bin/bash + +# --- work from the project root ---------------------------------------------- +# Every path below is repo-relative, so a hook invoked with a working directory +# that is not the repo root would silently read and write the WRONG TREE -- +# returning a near-empty result instead of the session-recovery block, and +# creating stray trees such as docs/production/session-logs/ on write. +# +# PRECEDENCE IS LOAD-BEARING. A cwd that IS a project root carries real +# information and must win: a caller sitting inside another project means that +# project, not this one. Resolving to the script's own location first would +# override them. So, in order: +# 1. cwd holds project.yaml -> cwd (a project root) +# 2. cwd holds .claude/ -> cwd (a project root not yet configured) +# 3. CLAUDE_PROJECT_DIR -> that (populated in the hook environment) +# 4. this script's location -> /.claude/hooks/../.. by construction +# Rule 4 always works and needs no environment at all; rules 1-2 stop it from +# overriding a caller that legitimately means somewhere else. +# +# NOT an upward search: that resolves a nested project to its parent's config. +if [ -f "project.yaml" ] || [ -d ".claude" ]; then + CCGS_ROOT="$PWD" +elif [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -d "${CLAUDE_PROJECT_DIR}" ]; then + CCGS_ROOT="$CLAUDE_PROJECT_DIR" +else + CCGS_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." 2>/dev/null && pwd)" +fi +[ -n "$CCGS_ROOT" ] && cd "$CCGS_ROOT" 2>/dev/null || true + # Claude Code Stop hook: Log session summary when Claude finishes # Records what was worked on for audit trail and sprint tracking +# features.session_state: off => this hook is a no-op. Default `on`; see +# session_state_enabled() in yaml-helper.sh. +if [ -f .claude/hooks/yaml-helper.sh ]; then + . .claude/hooks/yaml-helper.sh + session_state_enabled || exit 0 +fi + TIMESTAMP=$(date +%Y%m%d_%H%M%S) SESSION_LOG_DIR="production/session-logs" @@ -14,30 +49,123 @@ MODIFIED_FILES=$(git diff --name-only 2>/dev/null) # --- Archive active session state on shutdown (do NOT delete) --- # active.md persists across clean exits so multi-session recovery works. # It is only valid to delete active.md manually or when explicitly superseded. +# APPEND ONLY WHEN THE CONTENT CHANGED. +# +# The Stop hook fires once per RESPONSE, not once per session. Both blocks below +# appended unconditionally, so a long session wrote the whole of active.md on +# every turn. Measured on this repository before this guard: +# +# 3,187 Stop firings, 665 archived copies, session-log.md at 17.5 MB +# +# and session-logs/ is gitignored, so nothing ever surfaced it. The growth +# compounds: the larger active.md gets, the more each firing appends -- and +# rotate-session-state.sh caps active.md precisely because it grows, while the +# log accumulating full copies of it had no cap at all. +# +# Content hash, not mtime: an editor that rewrites a file unchanged would +# otherwise re-archive it. Same reasoning and same tool as review-receipts.sh -- +# git hash-object is content-addressed and git is already called above. +# +# A hash that cannot be computed archives ANYWAY. For an audit trail the safe +# failure direction is a duplicate entry, never a lost one. STATE_FILE="production/session-state/active.md" +STATE_HASH_FILE="$SESSION_LOG_DIR/.active-state.hash" if [ -f "$STATE_FILE" ]; then - { - echo "## Archived Session State: $TIMESTAMP" - cat "$STATE_FILE" - echo "---" - echo "" - } >> "$SESSION_LOG_DIR/session-log.md" 2>/dev/null + STATE_HASH=$(git hash-object -- "$STATE_FILE" 2>/dev/null) + if [ -z "$STATE_HASH" ] || [ "$STATE_HASH" != "$(cat "$STATE_HASH_FILE" 2>/dev/null)" ]; then + { + echo "## Archived Session State: $TIMESTAMP" + cat "$STATE_FILE" + echo "---" + echo "" + } >> "$SESSION_LOG_DIR/session-log.md" 2>/dev/null + [ -n "$STATE_HASH" ] && printf '%s\n' "$STATE_HASH" > "$STATE_HASH_FILE" 2>/dev/null + fi fi +SUMMARY_HASH_FILE="$SESSION_LOG_DIR/.session-end.hash" if [ -n "$RECENT_COMMITS" ] || [ -n "$MODIFIED_FILES" ]; then - { - echo "## Session End: $TIMESTAMP" - if [ -n "$RECENT_COMMITS" ]; then - echo "### Commits" - echo "$RECENT_COMMITS" + # Hash the CONTENT, not the rendered block: the block carries $TIMESTAMP, + # which differs every firing and would defeat the comparison entirely. + SUMMARY_HASH=$(printf '%s\n---\n%s' "$RECENT_COMMITS" "$MODIFIED_FILES" \ + | git hash-object --stdin 2>/dev/null) + if [ -z "$SUMMARY_HASH" ] || [ "$SUMMARY_HASH" != "$(cat "$SUMMARY_HASH_FILE" 2>/dev/null)" ]; then + { + echo "## Session End: $TIMESTAMP" + if [ -n "$RECENT_COMMITS" ]; then + echo "### Commits" + echo "$RECENT_COMMITS" + fi + if [ -n "$MODIFIED_FILES" ]; then + echo "### Uncommitted Changes" + echo "$MODIFIED_FILES" + fi + echo "---" + echo "" + } >> "$SESSION_LOG_DIR/session-log.md" 2>/dev/null + [ -n "$SUMMARY_HASH" ] && printf '%s\n' "$SUMMARY_HASH" > "$SUMMARY_HASH_FILE" 2>/dev/null + fi +fi + +# --- Subagent spawn tally (cost visibility) --- +# log-agent.sh has always written per-spawn records that nothing read, so the +# framework's dominant cost was invisible to the user. This turns those records +# into a running count. +# +# Ordering matters: everything above runs BEFORE stdin is touched. Stop hooks +# are piped JSON, but if stdin were ever absent the read would block until the +# 10s timeout killed the hook -- and the state archive above must not be lost +# to that. Read last, fail silent. +AUDIT_LOG="$SESSION_LOG_DIR/agent-audit.log" + +if [ ! -t 0 ] && [ -f "$AUDIT_LOG" ]; then + if command -v timeout >/dev/null 2>&1; then + INPUT=$(timeout 2 cat 2>/dev/null) + else + INPUT=$(cat 2>/dev/null) + fi + + if command -v jq >/dev/null 2>&1; then + SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // ""' 2>/dev/null) + else + SESSION_ID=$(echo "$INPUT" | grep -oE '"session_id"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/"session_id"[[:space:]]*:[[:space:]]*"//;s/"$//') + fi + + # No session id means no honest per-session number. Report nothing rather + # than a total that silently spans every session in the log. + if [ -n "$SESSION_ID" ] && [ "$SESSION_ID" != "unknown" ] && [ "$SESSION_ID" != "null" ]; then + SPAWN_LINES=$(grep -F " | $SESSION_ID | Agent invoked: " "$AUDIT_LOG" 2>/dev/null) + SPAWN_COUNT=$(printf '%s' "$SPAWN_LINES" | grep -c . 2>/dev/null) + [ -z "$SPAWN_COUNT" ] && SPAWN_COUNT=0 + + if [ "$SPAWN_COUNT" -gt 0 ]; then + BREAKDOWN=$(printf '%s\n' "$SPAWN_LINES" \ + | sed 's/.*Agent invoked: //' \ + | sort | uniq -c | sort -rn) + + { + echo "# Session Subagent Cost" + echo "" + echo "**Session:** \`$SESSION_ID\` " + echo "**Updated:** $TIMESTAMP" + echo "" + echo "Subagent spawns this session: **$SPAWN_COUNT**" + echo "" + echo "| Agent | Spawns |" + echo "|-------|--------|" + printf '%s\n' "$BREAKDOWN" | while read -r count name; do + [ -z "$name" ] && continue + echo "| $name | $count |" + done + echo "" + echo "Each spawn is a fresh context window that re-reads its own inputs." + echo "To reduce this: lower \`modes.review_mode\` (\`/settings modes.review_mode=solo\`)" + echo "or \`modes.rigor\` (\`/settings modes.rigor=minimal\`)." + } > "$SESSION_LOG_DIR/session-cost.md" 2>/dev/null + + echo "Subagent spawns this session: $SPAWN_COUNT (see production/session-logs/session-cost.md)" fi - if [ -n "$MODIFIED_FILES" ]; then - echo "### Uncommitted Changes" - echo "$MODIFIED_FILES" - fi - echo "---" - echo "" - } >> "$SESSION_LOG_DIR/session-log.md" 2>/dev/null + fi fi exit 0 diff --git a/.claude/hooks/validate-assets.sh b/.claude/hooks/validate-assets.sh index 44685241..c4525ef4 100644 --- a/.claude/hooks/validate-assets.sh +++ b/.claude/hooks/validate-assets.sh @@ -1,10 +1,48 @@ #!/bin/bash + +# --- work from the project root ---------------------------------------------- +# Every path below is repo-relative, so a hook invoked with a working directory +# that is not the repo root would silently read and write the WRONG TREE -- +# returning a near-empty result instead of the session-recovery block, and +# creating stray trees such as docs/production/session-logs/ on write. +# +# PRECEDENCE IS LOAD-BEARING. A cwd that IS a project root carries real +# information and must win: a caller sitting inside another project means that +# project, not this one. Resolving to the script's own location first would +# override them. So, in order: +# 1. cwd holds project.yaml -> cwd (a project root) +# 2. cwd holds .claude/ -> cwd (a project root not yet configured) +# 3. CLAUDE_PROJECT_DIR -> that (populated in the hook environment) +# 4. this script's location -> /.claude/hooks/../.. by construction +# Rule 4 always works and needs no environment at all; rules 1-2 stop it from +# overriding a caller that legitimately means somewhere else. +# +# NOT an upward search: that resolves a nested project to its parent's config. +if [ -f "project.yaml" ] || [ -d ".claude" ]; then + CCGS_ROOT="$PWD" +elif [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -d "${CLAUDE_PROJECT_DIR}" ]; then + CCGS_ROOT="$CLAUDE_PROJECT_DIR" +else + CCGS_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." 2>/dev/null && pwd)" +fi +[ -n "$CCGS_ROOT" ] && cd "$CCGS_ROOT" 2>/dev/null || true + # Claude Code PostToolUse hook: Validates asset files after Write/Edit # Checks naming conventions for files in assets/ directory # # Exit behavior: -# exit 0 = success or advisory warnings only (non-blocking) -# exit 1 = blocking error (build-breaking issues: invalid JSON, missing required fields) +# exit 0 = success or advisory warnings only +# exit 2 = build-breaking issue (invalid JSON, missing required fields) +# +# A PostToolUse hook CANNOT block -- the write has already happened by the time +# this runs (see .claude/docs/hooks-reference/hook-input-schemas.md). The only +# question is who hears about it, and that is what the exit code selects: +# exit 1 -> stderr goes to the USER only. Claude never sees it, so a JSON file +# it just corrupted looks like it wrote cleanly and it moves on. +# exit 2 -> stderr is fed back to CLAUDE as actionable feedback, so it can fix +# the file it just wrote. +# Exiting 1 while printing "ERRORS (Blocking) ... fix before proceeding" would be +# a promise the hook cannot keep, delivered to the one party who cannot act on it. # # Input schema (PostToolUse for Write/Edit): # { "tool_name": "Write", "tool_input": { "file_path": "assets/data/foo.json", "content": "..." } } @@ -15,11 +53,16 @@ INPUT=$(cat) if command -v jq >/dev/null 2>&1; then FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty') else - FILE_PATH=$(echo "$INPUT" | grep -oE '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//') + FILE_PATH=$(echo "$INPUT" | grep -oE '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//') fi -# Normalize path separators (Windows backslash to forward slash) -FILE_PATH=$(echo "$FILE_PATH" | sed 's|\\|/|g') +# Normalize path separators (Windows backslash to forward slash). +# Two rules, order significant -- see the long note in validate-skill-change.sh. +# Short version: hook input is JSON, so a Windows path arrives as `\\`. Without +# jq the fallback cannot unescape it, and a single-rule sed turns each escaped +# pair into two slashes, so `assets/` never matches and this hook silently +# validates nothing on Windows. +FILE_PATH=$(printf '%s' "$FILE_PATH" | sed 's|\\\\|/|g; s|\\|/|g') # Only check files in assets/ if ! echo "$FILE_PATH" | grep -qE '(^|/)assets/'; then @@ -54,6 +97,13 @@ if echo "$FILE_PATH" | grep -qE '(^|/)assets/data/.*\.json$'; then if ! "$PYTHON_CMD" -m json.tool "$FILE_PATH" > /dev/null 2>&1; then ERRORS="$ERRORS\n FORMAT: $FILE_PATH is not valid JSON — fix syntax errors before continuing" fi + else + # Say so rather than skipping in silence. This is the only BLOCKING + # check in this hook, so without an interpreter the hook exits 0 on + # a file it never opened -- indistinguishable from valid JSON. The + # sibling validate-commit.sh already warns in exactly this case; + # this half was the one that did not. + echo "WARNING: Cannot validate JSON (python not found) — $FILE_PATH unchecked" >&2 fi fi fi @@ -63,10 +113,11 @@ if [ -n "$WARNINGS" ]; then echo -e "=== Asset Validation: Warnings ===$WARNINGS\n==================================\n(Warnings are advisory. Fix before final commit.)" >&2 fi -# Report errors and block if any build-breaking issues found +# Report build-breaking issues. exit 2 routes this to Claude, not just the user, +# so the file that was already written can actually be corrected. if [ -n "$ERRORS" ]; then - echo -e "=== Asset Validation: ERRORS (Blocking) ===$ERRORS\n===========================================\nFix these errors before proceeding." >&2 - exit 1 + echo -e "=== Asset Validation: ERRORS ===$ERRORS\n================================\nThe file was written but is broken. Fix it now, before continuing." >&2 + exit 2 fi exit 0 diff --git a/.claude/hooks/validate-commit.sh b/.claude/hooks/validate-commit.sh index 530b4337..b7a2abc9 100644 --- a/.claude/hooks/validate-commit.sh +++ b/.claude/hooks/validate-commit.sh @@ -1,4 +1,32 @@ #!/bin/bash + +# --- work from the project root ---------------------------------------------- +# Every path below is repo-relative, so a hook invoked with a working directory +# that is not the repo root would silently read and write the WRONG TREE -- +# returning a near-empty result instead of the session-recovery block, and +# creating stray trees such as docs/production/session-logs/ on write. +# +# PRECEDENCE IS LOAD-BEARING. A cwd that IS a project root carries real +# information and must win: a caller sitting inside another project means that +# project, not this one. Resolving to the script's own location first would +# override them. So, in order: +# 1. cwd holds project.yaml -> cwd (a project root) +# 2. cwd holds .claude/ -> cwd (a project root not yet configured) +# 3. CLAUDE_PROJECT_DIR -> that (populated in the hook environment) +# 4. this script's location -> /.claude/hooks/../.. by construction +# Rule 4 always works and needs no environment at all; rules 1-2 stop it from +# overriding a caller that legitimately means somewhere else. +# +# NOT an upward search: that resolves a nested project to its parent's config. +if [ -f "project.yaml" ] || [ -d ".claude" ]; then + CCGS_ROOT="$PWD" +elif [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -d "${CLAUDE_PROJECT_DIR}" ]; then + CCGS_ROOT="$CLAUDE_PROJECT_DIR" +else + CCGS_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." 2>/dev/null && pwd)" +fi +[ -n "$CCGS_ROOT" ] && cd "$CCGS_ROOT" 2>/dev/null || true + # Claude Code PreToolUse hook: Validates git commit commands # Receives JSON on stdin with tool_input.command # Exit 0 = allow, Exit 2 = block (stderr shown to Claude) @@ -12,7 +40,7 @@ INPUT=$(cat) if command -v jq >/dev/null 2>&1; then COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty') else - COMMAND=$(echo "$INPUT" | grep -oE '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/"command"[[:space:]]*:[[:space:]]*"//;s/"$//') + COMMAND=$(echo "$INPUT" | grep -oE '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/"command"[[:space:]]*:[[:space:]]*"//;s/"$//') fi # Only process git commit commands @@ -20,6 +48,14 @@ if ! echo "$COMMAND" | grep -qE '^git[[:space:]]+commit'; then exit 0 fi +# Resolve a Python interpreter ONCE for every batched check below. Both the +# blocking JSON scan and the advisory design scan must not spawn one process per +# item; they spawn one each, and share this lookup. +_VC_PY="" +for _c in python python3 py; do + if command -v "$_c" >/dev/null 2>&1; then _VC_PY="$_c"; break; fi +done + # Get staged files STAGED=$(git diff --cached --name-only 2>/dev/null) if [ -z "$STAGED" ]; then @@ -28,69 +64,266 @@ fi WARNINGS="" -# Check design documents for required sections -DESIGN_FILES=$(echo "$STAGED" | grep -E '^design/gdd/') -if [ -n "$DESIGN_FILES" ]; then - while IFS= read -r file; do - if [[ "$file" == *.md ]] && [ -f "$file" ]; then - for section in "Overview" "Player Fantasy" "Detailed" "Formulas" "Edge Cases" "Dependencies" "Tuning Knobs" "Acceptance Criteria"; do - if ! grep -qi "$section" "$file"; then - WARNINGS="$WARNINGS\nDESIGN: $file missing required section: $section" - fi - done - fi - done <<< "$DESIGN_FILES" -fi +# Section findings are collected through a file, not a variable: the loops below +# run inside pipelines, and a subshell cannot assign to WARNINGS in the parent. +# The scratch file must actually be WRITABLE, and that has to be tested rather +# than assumed. `mktemp` and the `${TMPDIR:-/tmp}` fallback beside it both aim +# at /tmp, which does not exist or is not writable in several environments this +# hook runs in -- notably sandboxed Windows shells. When the redirect failed, +# `[ -s "$TMP_DESIGN" ]` was false, the design-section scan emitted nothing, and +# the hook exited 0: a check that could not run, reporting clean. +# +# So: try mktemp, then TMPDIR, then the repo own .git directory (writable by +# definition -- `git diff --cached` has already succeeded against it), and PROVE +# each candidate by writing to it. If none works, TMP_DESIGN stays empty and the +# scan announces that it did not run instead of skipping in silence. +TMP_DESIGN="" +for _cand in "$(mktemp 2>/dev/null)" "${TMPDIR:+$TMPDIR/ccgs-commit-$$}" \ + "$(git rev-parse --git-dir 2>/dev/null)/ccgs-commit-$$"; do + [ -z "$_cand" ] && continue + if : > "$_cand" 2>/dev/null; then TMP_DESIGN="$_cand"; break; fi +done +trap '[ -n "$TMP_DESIGN" ] && rm -f "$TMP_DESIGN"' EXIT -# Validate JSON data files -- block invalid JSON +# --------------------------------------------------------------------------- +# BLOCKING CHECK FIRST. Everything below this block is advisory: +# it appends to $WARNINGS and the hook still exits 0. This block is the only one +# that can exit 2, and exit 2 is the entire reason the hook is registered. +# +# Running it THIRD, after the design-section scan, and validating one file +# per `python -m json.tool` invocation -- one interpreter spawn each, ~110ms on +# Windows -- gets the hook killed by its 15s timeout on a large commit before +# it reaches the corrupt file, so whether a bad file is caught depends on where +# its NAME SORTED. Measured with 201 staged files and one corrupt: +# +# corrupt sorts FIRST -> 730ms, rc=2, BLOCKED emitted +# corrupt sorts LAST -> 15217ms, rc=124, nothing emitted, commit not blocked +# +# Two independent fixes, because either alone is a mitigation rather than a +# guarantee: +# (a) ORDER. The blocking check runs before any advisory work, so a starved +# hook has already done the part that matters. +# (b) COST. One interpreter validates every staged JSON file, so the check is +# constant in file count instead of linear. The one-subprocess-per-item +# shape has caused this same starvation more than once; batching is the +# only fix that holds. +# +# Making it merely faster is not enough -- the cost stays linear and the hook +# starves again at a larger file count. Batch it, or it returns. +# --------------------------------------------------------------------------- DATA_FILES=$(echo "$STAGED" | grep -E '^assets/data/.*\.json$') if [ -n "$DATA_FILES" ]; then - # Find a working Python command - PYTHON_CMD="" - for cmd in python python3 py; do - if command -v "$cmd" >/dev/null 2>&1; then - PYTHON_CMD="$cmd" - break + PYTHON_CMD="$_VC_PY" + if [ -n "$PYTHON_CMD" ]; then + # One spawn, all files. The file list arrives on stdin so an unbounded + # number of paths cannot overflow the argument limit. + BAD_JSON=$(printf '%s\n' "$DATA_FILES" | "$PYTHON_CMD" -c ' +import json, sys, os +bad = [] +for line in sys.stdin.read().splitlines(): + path = line.strip() + if not path or not os.path.isfile(path): + continue + try: + with open(path, "r", encoding="utf-8-sig") as fh: + json.load(fh) + except Exception: + bad.append(path) +# Bytes, not text. Python text-mode stdout on Windows rewrites \n as \r\n, +# putting a CR inside every path but the last. Invisible in a terminal and in +# a line-by-line diff -- only a byte comparison against the pre-batch +# implementation surfaced it. get_yaml_key writes bytes for the same reason. +sys.stdout.buffer.write("\n".join(bad).encode("utf-8")) +' 2>/dev/null) + if [ -n "$BAD_JSON" ]; then + # Every offender at once. The per-file loop reported only the first + # and exited, so a commit with several bad files took several + # round-trips to clean up. + echo "$BAD_JSON" | while IFS= read -r bad; do + [ -n "$bad" ] && echo "BLOCKED: $bad is not valid JSON" >&2 + done + exit 2 fi - done + else + echo "WARNING: Cannot validate JSON (python not found)" >&2 + fi +fi - while IFS= read -r file; do - if [ -f "$file" ]; then - if [ -n "$PYTHON_CMD" ]; then - if ! "$PYTHON_CMD" -m json.tool "$file" > /dev/null 2>&1; then - echo "BLOCKED: $file is not valid JSON" >&2 - exit 2 - fi - else - echo "WARNING: Cannot validate JSON (python not found): $file" >&2 - fi +# Check design documents for the sections REQUIRED AT THIS PROJECT'S TIER. +# +# Demanding all 8 sections unconditionally would contradict +# .claude/docs/coding-standards.md and workflow-modes.md: the required count is +# a function of `modes.workflow` -- 8 at `full`, 5 (+ conditional Formulas) at +# `standard`, and no GDD requirement at all at `minimal`. A jam project on the +# recommended `minimal` tier was being warned about six sections its own +# configuration says it does not need, which trains the user to ignore the hook. +# Source the config helper ONCE, ahead of every consumer below. It was +# previously sourced inside the design-doc branch, so the code scans further +# down -- which now need resolve_code_root -- could not reach it on a commit +# that staged no GDD. +# Detect success by asking whether the function EXISTS, not by the source's exit +# status. A sourced file returns the status of its last statement, which here is +# incidental -- gating on it left _VC_HELPER at 0 on a perfectly good source, the +# workflow tier silently stayed at its "standard" fallback, and the GDD section +# check stopped scaling with the tier (caught by test LL.9). +_VC_HELPER=0 +if [ -f .claude/hooks/yaml-helper.sh ]; then + . .claude/hooks/yaml-helper.sh 2>/dev/null || true + command -v resolve_setting >/dev/null 2>&1 && _VC_HELPER=1 +fi + +DESIGN_FILES=$(echo "$STAGED" | grep -E '^design/gdd/') +if [ -n "$DESIGN_FILES" ]; then + WORKFLOW="standard" + if [ "$_VC_HELPER" = 1 ]; then + W=$(resolve_setting modes.workflow 2>/dev/null | cut -f1) + [ -n "$W" ] && WORKFLOW="$W" + fi + + # "Detailed" (not "Detailed Rules") because the canonical heading has a + # documented alias, "Detailed Design" -- workflow-modes.md says not to treat + # one as missing when the other is present. + case "$WORKFLOW" in + minimal) REQUIRED="" ;; + full) REQUIRED="Overview|Player Fantasy|Detailed|Formulas|Edge Cases|Dependencies|Tuning Knobs|Acceptance Criteria" ;; + *) REQUIRED="Overview|Detailed|Edge Cases|Dependencies|Acceptance Criteria" ;; + esac + + if [ -n "$REQUIRED" ]; then + # One pass for every file x section pair. + # + # A nested shell loop -- for each staged design doc, `echo | + # tr | while read` then one `grep -qi` per required section -- does not + # scale. At + # workflow=full that is 8 greps per file, and each is a process. 50 + # design docs measured 14644ms -- 97% of this hook's 15s budget, from + # the ADVISORY half alone. 50 GDDs is an ordinary project, not a + # stress case. + # + # Matching is unchanged on purpose: case-insensitive SUBSTRING anywhere + # in the file, exactly what `grep -qi "$section"` did. It is looser than + # a heading check, and tightening it here would silently change which + # documents warn -- a behaviour change smuggled inside a performance + # fix. If that wants tightening it should be its own change with its + # own test. + printf '%s\n' "$DESIGN_FILES" | "${_VC_PY:-python}" -c ' +import sys, os +argv = sys.argv +required = [x for x in argv[1].split("|") if x] +workflow = argv[2] +out = [] +for line in sys.stdin.read().splitlines(): + path = line.strip() + if not path or not path.endswith(".md") or not os.path.isfile(path): + continue + try: + with open(path, "r", encoding="utf-8", errors="replace") as fh: + body = fh.read().lower() + except OSError: + continue + for section in required: + if section.lower() not in body: + out.append("DESIGN: %s missing section required at workflow=%s: %s" + % (path, workflow, section)) +if out: + # Bytes, not text -- see the CRLF note in the JSON scan above. + sys.stdout.buffer.write(("\n".join(out) + "\n").encode("utf-8")) +' "$REQUIRED" "$WORKFLOW" > "$TMP_DESIGN" 2>/dev/null + if [ -z "$TMP_DESIGN" ]; then + # Obligation 3 of .claude/rules/skill-authoring.md: a skipped step + # announces itself. Silence here is indistinguishable from a GDD + # that already has every section its tier requires. + WARNINGS="$WARNINGS\nSKIPPED: no writable scratch location, so the GDD section check did NOT run on the staged design doc(s)." + elif [ -s "$TMP_DESIGN" ]; then + WARNINGS="$WARNINGS\n$(cat "$TMP_DESIGN")" fi - done <<< "$DATA_FILES" + fi fi # Check for hardcoded gameplay values in gameplay code # Uses grep -E (POSIX extended) instead of grep -P (Perl) for cross-platform compatibility -CODE_FILES=$(echo "$STAGED" | grep -E '^src/gameplay/') +# +# Batched through xargs. Run one `grep` per staged file -- a process each, +# ~60ms on Windows -- and 400 staged gameplay files blow a 15s budget on their +# own. `xargs` packs as many +# paths into each grep as ARG_MAX allows, so the spawn count is bounded by total +# path length rather than by file count. +# +# `-l` not `-q`: with many files per invocation the exit status can no longer +# say WHICH matched, and the warning names the file. It stays a test, not a +# report -- `-l` prints only names, never matched lines, which is what the -q +# comment here was guarding against. +# +# NUL-delimited, NOT `-d '\n'`: both `-d` and `-r` are GNU-only extensions. +# BSD xargs (macOS) rejects `-d`, and because this pipeline ends in +# `2>/dev/null || true` the rejection is SILENT -- the check returns empty and +# reports nothing found, which is indistinguishable from a clean scan. README +# claims these hooks run on macOS, so a silent no-op there is a false pass. +# `-0` exists in both GNU and BSD xargs. `-r` is dropped, not replaced: both +# callers below reach xargs only inside `if [ -n ... ]`, so the empty-input case +# it guarded cannot occur. `|| true` stays -- grep exits 1 on no match, which is +# the normal case and not an error. +# --- resolve the code root before either scan below ------------------------ +# +# Both scans used to be hardcoded to `^src/gameplay/` and `^src/`. Per +# .claude/docs/directory-structure.md, `src/` is the GODOT row of the code-root +# table -- Unity compiles only `Assets/`, Unreal builds only from `Source/`. So +# on two of the three supported engines both greps matched nothing on every +# commit, both checks silently did not run, and the hook exited 0. A scan that +# cannot run and a scan that found nothing produced the identical artifact. +# +# The `/gameplay/` narrowing is dropped rather than translated: it describes a +# Godot source layout and has no Unity or Unreal equivalent, so keeping it would +# leave the check Godot-only by a second route. The hardcoded-value scan instead +# widens to the whole code root and is bounded by source-code EXTENSION, which +# is what actually keeps it precise -- without it, a Unity commit would drag +# `.meta`, `.asset` and `.prefab` YAML (all full of `speed: 5`) into a scan +# meant for code, and a hook that cries wolf gets muted rather than fixed. +CODE_ROOT=""; CODE_ROOT_SRC="unset" +if [ "$_VC_HELPER" = 1 ]; then + _CR=$(resolve_code_root 2>/dev/null) + CODE_ROOT=$(printf '%s' "$_CR" | cut -f1) + CODE_ROOT_SRC=$(printf '%s' "$_CR" | cut -f2) +fi + +# Obligation 3 of .claude/rules/skill-authoring.md -- a skipped step announces +# itself. Only fires when source code is actually being committed, so a +# docs-only or config-only commit stays quiet and the notice keeps its meaning. +if [ -z "$CODE_ROOT" ]; then + if echo "$STAGED" | grep -qiE '\.(gd|cs|cpp|cc|hpp|h|c|py|js|ts|rs|java|kt|lua|gdshader|shader|hlsl)$'; then + WARNINGS="$WARNINGS\nSKIPPED: source files are staged but no code root could be resolved (engine.name unset, and the tree does not name one unambiguously). The hardcoded-value and TODO-owner scans did NOT run. Run /setup-engine, or set engine.name in project.yaml." + fi +fi + +CODE_FILES="" +[ -n "$CODE_ROOT" ] && CODE_FILES=$(echo "$STAGED" \ + | grep -E "^${CODE_ROOT}/" \ + | grep -iE '\.(gd|cs|cpp|cc|hpp|h|c|py|js|ts|rs|java|kt|lua)$') if [ -n "$CODE_FILES" ]; then - while IFS= read -r file; do - if [ -f "$file" ]; then - if grep -nE '(damage|health|speed|rate|chance|cost|duration)[[:space:]]*[:=][[:space:]]*[0-9]+' "$file" 2>/dev/null; then - WARNINGS="$WARNINGS\nCODE: $file may contain hardcoded gameplay values. Use data files." - fi - fi - done <<< "$CODE_FILES" + HARDCODED=$(printf '%s\n' "$CODE_FILES" | tr '\n' '\0' \ + | xargs -0 grep -lE '(damage|health|speed|rate|chance|cost|duration)[[:space:]]*[:=][[:space:]]*[0-9]+' 2>/dev/null || true) + if [ -n "$HARDCODED" ]; then + while IFS= read -r file; do + [ -n "$file" ] && WARNINGS="$WARNINGS\nCODE: $file may contain hardcoded gameplay values. Use data files." + done <<< "$HARDCODED" + fi fi # Check for TODO/FIXME without assignee -- uses grep -E instead of grep -P -SRC_FILES=$(echo "$STAGED" | grep -E '^src/') +# Same code root as the scan above. Not extension-bounded: an unowned TODO is +# worth flagging in a shader or a build script too, and this scan greps for a +# literal marker rather than a numeric pattern, so it does not misfire on +# serialized asset YAML the way the hardcoded-value scan would. +SRC_FILES="" +[ -n "$CODE_ROOT" ] && SRC_FILES=$(echo "$STAGED" | grep -E "^${CODE_ROOT}/") if [ -n "$SRC_FILES" ]; then - while IFS= read -r file; do - if [ -f "$file" ]; then - if grep -nE '(TODO|FIXME|HACK)[^(]' "$file" 2>/dev/null; then - WARNINGS="$WARNINGS\nSTYLE: $file has TODO/FIXME without owner tag. Use TODO(name) format." - fi - fi - done <<< "$SRC_FILES" + UNOWNED=$(printf '%s\n' "$SRC_FILES" | tr '\n' '\0' \ + | xargs -0 grep -lE '(TODO|FIXME|HACK)[^(]' 2>/dev/null || true) + if [ -n "$UNOWNED" ]; then + while IFS= read -r file; do + [ -n "$file" ] && WARNINGS="$WARNINGS\nSTYLE: $file has TODO/FIXME without owner tag. Use TODO(name) format." + done <<< "$UNOWNED" + fi fi # Print warnings (non-blocking) and allow commit diff --git a/.claude/hooks/validate-push.sh b/.claude/hooks/validate-push.sh index e9aff2eb..b20d17df 100644 --- a/.claude/hooks/validate-push.sh +++ b/.claude/hooks/validate-push.sh @@ -1,4 +1,32 @@ #!/bin/bash + +# --- work from the project root ---------------------------------------------- +# Every path below is repo-relative, so a hook invoked with a working directory +# that is not the repo root would silently read and write the WRONG TREE -- +# returning a near-empty result instead of the session-recovery block, and +# creating stray trees such as docs/production/session-logs/ on write. +# +# PRECEDENCE IS LOAD-BEARING. A cwd that IS a project root carries real +# information and must win: a caller sitting inside another project means that +# project, not this one. Resolving to the script's own location first would +# override them. So, in order: +# 1. cwd holds project.yaml -> cwd (a project root) +# 2. cwd holds .claude/ -> cwd (a project root not yet configured) +# 3. CLAUDE_PROJECT_DIR -> that (populated in the hook environment) +# 4. this script's location -> /.claude/hooks/../.. by construction +# Rule 4 always works and needs no environment at all; rules 1-2 stop it from +# overriding a caller that legitimately means somewhere else. +# +# NOT an upward search: that resolves a nested project to its parent's config. +if [ -f "project.yaml" ] || [ -d ".claude" ]; then + CCGS_ROOT="$PWD" +elif [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -d "${CLAUDE_PROJECT_DIR}" ]; then + CCGS_ROOT="$CLAUDE_PROJECT_DIR" +else + CCGS_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." 2>/dev/null && pwd)" +fi +[ -n "$CCGS_ROOT" ] && cd "$CCGS_ROOT" 2>/dev/null || true + # Claude Code PreToolUse hook: Validates git push commands # Warns on pushes to protected branches # Exit 0 = allow, Exit 2 = block @@ -12,7 +40,7 @@ INPUT=$(cat) if command -v jq >/dev/null 2>&1; then COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty') else - COMMAND=$(echo "$INPUT" | grep -oE '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/"command"[[:space:]]*:[[:space:]]*"//;s/"$//') + COMMAND=$(echo "$INPUT" | grep -oE '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/"command"[[:space:]]*:[[:space:]]*"//;s/"$//') fi # Only process git push commands diff --git a/.claude/hooks/validate-skill-change.sh b/.claude/hooks/validate-skill-change.sh index 23d657db..2ded3957 100644 --- a/.claude/hooks/validate-skill-change.sh +++ b/.claude/hooks/validate-skill-change.sh @@ -1,4 +1,32 @@ #!/bin/bash + +# --- work from the project root ---------------------------------------------- +# Every path below is repo-relative, so a hook invoked with a working directory +# that is not the repo root would silently read and write the WRONG TREE -- +# returning a near-empty result instead of the session-recovery block, and +# creating stray trees such as docs/production/session-logs/ on write. +# +# PRECEDENCE IS LOAD-BEARING. A cwd that IS a project root carries real +# information and must win: a caller sitting inside another project means that +# project, not this one. Resolving to the script's own location first would +# override them. So, in order: +# 1. cwd holds project.yaml -> cwd (a project root) +# 2. cwd holds .claude/ -> cwd (a project root not yet configured) +# 3. CLAUDE_PROJECT_DIR -> that (populated in the hook environment) +# 4. this script's location -> /.claude/hooks/../.. by construction +# Rule 4 always works and needs no environment at all; rules 1-2 stop it from +# overriding a caller that legitimately means somewhere else. +# +# NOT an upward search: that resolves a nested project to its parent's config. +if [ -f "project.yaml" ] || [ -d ".claude" ]; then + CCGS_ROOT="$PWD" +elif [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -d "${CLAUDE_PROJECT_DIR}" ]; then + CCGS_ROOT="$CLAUDE_PROJECT_DIR" +else + CCGS_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." 2>/dev/null && pwd)" +fi +[ -n "$CCGS_ROOT" ] && cd "$CCGS_ROOT" 2>/dev/null || true + # Claude Code PostToolUse hook: Advises running skill-test after skill file changes # Fires when any file inside .claude/skills/ is written or edited. # @@ -14,11 +42,23 @@ INPUT=$(cat) if command -v jq >/dev/null 2>&1; then FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty') else - FILE_PATH=$(echo "$INPUT" | grep -oE '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//') + FILE_PATH=$(echo "$INPUT" | grep -oE '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//') fi -# Normalize path separators (Windows backslash to forward slash) -FILE_PATH=$(echo "$FILE_PATH" | sed 's|\\|/|g') +# Normalize path separators (Windows backslash to forward slash). +# +# TWO rules, and the order matters. The hook input is JSON, so a Windows path +# arrives escaped: "C:\\Users\\x". jq unescapes it to a single backslash, but +# the grep fallback CANNOT -- it hands back the raw two-character `\\`. A +# single `s|\\|/|g` then turns each of those into its own slash, producing +# `.claude//skills//help//SKILL.md`, which no path test below matches. The hook +# went silent on every Windows edit whenever jq was absent -- and jq is absent +# on a stock Windows Git Bash, which is this project's primary platform. +# +# Rule 1 collapses the escaped pair to one slash; rule 2 handles an already +# unescaped separator (the jq path, or a POSIX caller). Running both is safe +# for either input because rule 1 finds nothing to do on unescaped text. +FILE_PATH=$(printf '%s' "$FILE_PATH" | sed 's|\\\\|/|g; s|\\|/|g') # Only act on files inside .claude/skills/ if ! echo "$FILE_PATH" | grep -qE '(^|/)\.claude/skills/'; then diff --git a/.claude/hooks/yaml-helper.sh b/.claude/hooks/yaml-helper.sh new file mode 100644 index 00000000..9ba8d595 --- /dev/null +++ b/.claude/hooks/yaml-helper.sh @@ -0,0 +1,1547 @@ +#!/bin/bash +# Hook helper: yaml-helper.sh +# Purpose: Shared YAML reading helper for CCGS hooks and skill orchestrators. +# Cross-platform: Windows Git Bash compatible. +# +# Provides: +# get_yaml_key +# Prints the scalar value at in , or empty string if: +# - the file does not exist or is unreadable +# - the path does not exist in the YAML +# - the path resolves to a sub-map or array (caller wanted a scalar) +# - no Python interpreter is available +# Always exits 0 — callers can safely use it under `set -e`. +# +# get_yaml_array +# Prints array values one per line. Handles both inline [a, b, c] +# and block-dash forms (- a / - b on subsequent indented lines). +# Empty output if not an array, not found, or file missing. +# +# get_effective_yaml_key +# get_effective_yaml_array +# Convenience wrappers that read project.local.yaml first, then +# fall back to project.yaml. Per-leaf override (local wins) — +# sibling keys are read independently from their own files, so the +# effective config has deep-merge semantics across leaves. +# +# validate_yaml_enum +# Validates known enum-typed keys in against their allowed +# value sets (12 enums hardcoded below). Prints one error line per +# invalid value to stderr. Returns 1 if any invalid value found, +# 0 otherwise. Unknown-key (typo) detection is a future sub-phase — +# enum errors are the critical-path block per the spec. +# +# validate_local_yaml_base +# Hard-error guard per spec: project.local.yaml requires a project.yaml +# base. Returns 1 with error on stderr if local exists without base. +# Uses CWD-relative paths. +# +# is_locally_overridable +# Whitelist check for the /settings --local flag. Returns 0 if the +# setting is on the personal-experience whitelist (12 settings per +# effects-map.md), 1 otherwise. Locked settings cannot be locally +# overridden because they affect on-disk artifacts. +# +# validate_enum_value +# Single-key version of validate_yaml_enum — for /settings to check +# a pending write before committing it to disk. Returns 0 if the +# key has no enum constraint OR the value is in the enum set. +# Returns 1 (with error on stderr) if the key is enum-typed and the +# value is out of range. Skills call this before any Write/Edit. +# +# is_always_ask_category +# Membership check on modes.automation_always_ask. Returns 0 if the +# category is in the configured list (or in the default list when +# unset), 1 otherwise. Skills use this in autonomous mode to know +# when to prompt anyway. Default list: scope_changes, file_deletions, +# schema_changes. +# +# log_decision +# Append a decision-log entry to production/session-logs/decision-log.md. +# Creates the file and directory if absent. Used by skills in +# autonomous mode to record decisions that would otherwise have +# been AskUserQuestion prompts. +# +# Python fallback chain: python -> python3 -> py +# Parses a YAML subset: nested maps + scalar leaves + arrays + comments +# + quoted strings. PyYAML is NOT required. +# +# Usage: +# source .claude/hooks/yaml-helper.sh +# review_mode=$(get_yaml_key "$_YH_ROOT/project.yaml" modes.review_mode) +# if [ -z "$review_mode" ]; then +# # fall back to legacy .txt +# review_mode=$(cat production/review-mode.txt 2>/dev/null || echo lean) +# fi + +# Resolve a working Python interpreter once per shell, cache the result. +_yaml_helper_python="" +_yaml_helper_resolve_python() { + if [ -n "$_yaml_helper_python" ]; then return 0; fi + for candidate in python python3 py; do + if command -v "$candidate" >/dev/null 2>&1; then + # Require Python 3 — the parser uses py3-only open(encoding=) kwargs. + if "$candidate" -c "import sys; sys.exit(0 if sys.version_info[0] >= 3 else 1)" >/dev/null 2>&1; then + _yaml_helper_python="$candidate" + return 0 + fi + fi + done + return 1 +} + +get_yaml_key() { + local file="$1" + local path="$2" + if [ -z "$file" ] || [ -z "$path" ]; then return 0; fi + if [ ! -f "$file" ]; then return 0; fi + if ! _yaml_helper_resolve_python; then + echo "yaml-helper: no python interpreter found (tried python, python3, py)" >&2 + return 0 + fi + "$_yaml_helper_python" - "$file" "$path" <<'PYEOF' +import sys, re + +path_file, dotted = sys.argv[1], sys.argv[2] +keys = dotted.split('.') + +def parse(lines): + root = {} + stack = [(-1, root)] # (indent, mapping) + for raw in lines: + line = raw.rstrip('\n').rstrip('\r') + stripped = line.lstrip(' ') + if not stripped.strip() or stripped.lstrip().startswith('#'): + continue + indent = len(line) - len(stripped) + while stack and stack[-1][0] >= indent: + stack.pop() + if not stack: + return root + parent = stack[-1][1] + m = re.match(r'([^:#\s][^:]*?)\s*:\s*(.*)$', stripped) + if not m: + continue + key = m.group(1).strip() + value = m.group(2).strip() + # Resolve the scalar: handle comment-only, quoted, and inline-comment forms. + if value.startswith('#'): + value = '' + elif value[:1] in ('"', "'"): + quote = value[0] + end = value.find(quote, 1) + if end >= 0: + value = value[1:end] # quoted content; any trailing comment ignored + else: + value = value[1:] # unterminated quote — take the remainder + else: + hash_pos = value.find(' #') + if hash_pos >= 0: + value = value[:hash_pos].strip() + if value == '': + new_map = {} + parent[key] = new_map + stack.append((indent, new_map)) + else: + parent[key] = value + return root + +try: + with open(path_file, 'r', encoding='utf-8-sig', errors='replace') as f: + data = parse(f.readlines()) +except OSError: + sys.exit(0) + +cur = data +for k in keys: + if isinstance(cur, dict) and k in cur: + cur = cur[k] + else: + sys.exit(0) + +if isinstance(cur, dict): + sys.exit(0) + +# Write raw UTF-8 bytes — bypasses the platform stdout encoding (cp1252 on +# Windows) so unicode values round-trip intact. +sys.stdout.buffer.write(str(cur).encode('utf-8')) +PYEOF +} + +# ----------------------------------------------------------------------------- +# Array reads, effective (deep-merged) reads, enum validation +# ----------------------------------------------------------------------------- + +get_yaml_array() { + local file="$1" + local path="$2" + if [ -z "$file" ] || [ -z "$path" ]; then return 0; fi + if [ ! -f "$file" ]; then return 0; fi + if ! _yaml_helper_resolve_python; then + echo "yaml-helper: no python interpreter found (tried python, python3, py)" >&2 + return 0 + fi + "$_yaml_helper_python" - "$file" "$path" <<'PYEOF' +import sys, re + +path_file, dotted = sys.argv[1], sys.argv[2] +keys = dotted.split('.') + +# Parser extended to recognize two YAML array forms: +# key: [a, b, c] (inline) +# key: (block-dash) +# - a +# - b +# Block-dash detection peeks ahead at indented child lines after a key +# whose own value is empty; if those children start with "- ", they form +# an array, otherwise they form a sub-map (existing behavior). +def parse(lines): + root = {} + stack = [(-1, root)] + i = 0 + while i < len(lines): + raw = lines[i] + line = raw.rstrip('\n').rstrip('\r') + stripped = line.lstrip(' ') + if not stripped.strip() or stripped.lstrip().startswith('#'): + i += 1 + continue + indent = len(line) - len(stripped) + while stack and stack[-1][0] >= indent: + stack.pop() + if not stack: + return root + parent = stack[-1][1] + m = re.match(r'([^:#\s][^:]*?)\s*:\s*(.*)$', stripped) + if not m: + i += 1 + continue + key = m.group(1).strip() + value = m.group(2).strip() + if value.startswith('#'): + value = '' + elif value[:1] == '[': + # Inline array, scanned with QUOTE AWARENESS. + # + # A comma or a closing bracket inside a quoted element is DATA, not + # a delimiter. Splitting on every comma turns one quoted entry that + # contains commas into several bogus ones -- and because each shard + # is a plausible-looking string, nothing downstream can tell that it + # happened. Values written from hand-authored prose hit this + # routinely. + parts = [] + buf = '' + quote = None + idx = 1 + while idx < len(value): + ch = value[idx] + if quote is not None: + if ch == quote: + quote = None + else: + buf += ch + elif ch in ('"', "'"): + quote = ch + elif ch == ']': + break + elif ch == ',': + parts.append(buf.strip()) + buf = '' + else: + buf += ch + idx += 1 + if buf.strip(): + parts.append(buf.strip()) + parent[key] = [p for p in parts if p != ''] + i += 1 + continue + elif value[:1] in ('"', "'"): + quote = value[0] + end = value.find(quote, 1) + if end >= 0: + value = value[1:end] + else: + value = value[1:] + else: + hash_pos = value.find(' #') + if hash_pos >= 0: + value = value[:hash_pos].strip() + if value == '': + # Peek: block-dash list or sub-map? + j = i + 1 + items = [] + while j < len(lines): + pl = lines[j].rstrip('\n').rstrip('\r') + pstripped = pl.lstrip(' ') + if not pstripped.strip() or pstripped.lstrip().startswith('#'): + j += 1 + continue + pindent = len(pl) - len(pstripped) + if pindent <= indent: + break + if pstripped.startswith('- '): + # INDENT IS NOT ENFORCED between items, deliberately. + # + # Requiring every item to sit at the first item's indent + # made ONE misindented entry end the list there and return + # the prefix, silently: three configured always-ask + # categories came back as one. `is_always_ask_category` + # compares exactly, so the dropped entries lost their + # prompt with nothing written anywhere -- and a partial + # list is non-empty, so the caller's "fall back to the + # defaults when unset" branch does not fire either. It + # failed toward LESS confirmation, which is the wrong + # direction for the setting that decides when to ask. + # + # Safe because this schema holds only flat scalar lists: + # any deeper `- ` line under the key is an item of it. A + # non-dash line still ends the list via the break below, + # so a sub-map after a list is unaffected. + item = pstripped[2:].strip() + if item[:1] in ('"', "'") and item[-1:] == item[:1] and len(item) >= 2: + item = item[1:-1] + else: + # Unquoted items take the same trailing-comment rule + # scalars take. Without it `- scope_changes # ask` + # parsed as the literal string INCLUDING the comment + # and matched no category -- the same silent loss as + # above, reached a different way. Quoted items are + # left alone: a `#` inside quotes is data. + hash_pos = item.find(' #') + if hash_pos >= 0: + item = item[:hash_pos].strip() + items.append(item) + j += 1 + continue + break + if items: + parent[key] = items + i = j + continue + else: + new_map = {} + parent[key] = new_map + stack.append((indent, new_map)) + else: + parent[key] = value + i += 1 + return root + +try: + with open(path_file, 'r', encoding='utf-8-sig', errors='replace') as f: + data = parse(f.readlines()) +except OSError: + sys.exit(0) + +cur = data +for k in keys: + if isinstance(cur, dict) and k in cur: + cur = cur[k] + else: + sys.exit(0) + +if not isinstance(cur, list): + sys.exit(0) + +for item in cur: + sys.stdout.buffer.write((str(item) + '\n').encode('utf-8')) +PYEOF +} + +# Guard: project.local.yaml requires a project.yaml base. +# Per spec (effects-map.md "Creation and error handling"), the local file +# has no meaning without a base — it only overrides values from project.yaml. +# Prints a hard-error message to stderr and returns 1 if the orphan case +# is detected. Returns 0 otherwise (including when neither file exists). +# Uses CWD-relative paths — callers cd into the project root first. +# --- project root resolution ------------------------------------------------- +# +# Config paths must NOT be resolved against the bare current working directory. +# Run a skill or hook from a subdirectory that way and project.yaml is +# unfindable, so EVERY setting resolves to empty -- silently, because the guard +# that reports a missing base cannot find its files either. From the repo root +# `modes.rigor` returns `full`; from `src/` it would return nothing. +# +# Being loadable from anywhere is only half the problem: the helper must also be +# able to do its job from anywhere. Fixing one without the other leaves it +# sourceable everywhere and functional only at the root. +# +# PRECEDENCE IS LOAD-BEARING, in this order: +# 1. cwd holds project.yaml -> cwd +# 2. cwd holds project.local.yaml -> cwd +# 3. CLAUDE_PROJECT_DIR has a base -> that directory +# 4. nothing found -> cwd (unchanged behaviour) +# +# AN UPWARD WALK WAS IMPLEMENTED AND REMOVED, and the reason is worth keeping. +# The intent was a fallback needing no environment variable: climb until an +# ancestor has project.yaml. It resolves the wrong tree. A project that has a +# legacy production/review-mode.txt and no project.yaml, sitting anywhere below +# another project, reads the OUTER project's project.local.yaml one directory +# up instead of its own legacy file -- the wrong answer, returned silently. +# +# That is not a test-harness artifact. Any real project nested inside another +# repo that happens to carry a project.yaml would silently inherit the outer +# project's settings, which is the same silent-wrong-answer class this whole fix +# exists to remove. A fallback that guesses is worse than one that is absent: +# rule 4 leaves an unconfigured directory reading its own legacy files, which is +# exactly what a project without project.yaml should do. +# +# CLAUDE_PROJECT_DIR is populated in both the hook environment and the +# skill-bootstrap environment, so rule 3 covers every way this framework +# actually invokes the helper. +# +# 1 and 2 come FIRST deliberately. A project nested inside another one must read +# ITSELF, and an ambient CLAUDE_PROJECT_DIR would otherwise point it at the +# outer tree. Rule 2 covers the case that is easiest to lose: a directory with a +# project.local.yaml and deliberately NO base must still raise the hard error +# for a missing base. Without it, an upward walk finds an ancestor's +# project.yaml and that error silently stops firing. +# +# Sets a variable rather than echoing a path: a $( ) per lookup would add a +# subshell to every one of validate_yaml_enum's 23 calls, and per-lookup cost of +# that kind is what pushes a hook past its 10s timeout. +_yaml_helper_set_root() { + _YH_ROOT="." + [ -f "$_YH_ROOT/project.yaml" ] && return 0 + [ -f "$_YH_ROOT/project.local.yaml" ] && return 0 + if [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -f "$CLAUDE_PROJECT_DIR/project.yaml" ]; then + _YH_ROOT="$CLAUDE_PROJECT_DIR"; return 0 + fi + return 0 +} + +validate_local_yaml_base() { + _yaml_helper_set_root + if [ -f "$_YH_ROOT/project.local.yaml" ] && [ ! -f "$_YH_ROOT/project.yaml" ]; then + echo "project.local.yaml exists but project.yaml is missing — run /start to create one. Local overrides require a base." >&2 + return 1 + fi + return 0 +} + +# Per-leaf deep-merge: read project.local.yaml first (if present), +# fall back to project.yaml. Each leaf is resolved independently from +# its own file, so sibling keys at the same nesting level naturally +# come through from project.yaml when project.local.yaml only overrides +# specific leaves. +get_effective_yaml_key() { + _yaml_helper_set_root + local path="$1" + if [ -z "$path" ]; then return 0; fi + local val="" + if [ -f "$_YH_ROOT/project.local.yaml" ]; then + val=$(get_yaml_key "$_YH_ROOT/project.local.yaml" "$path") + fi + if [ -z "$val" ] && [ -f "$_YH_ROOT/project.yaml" ]; then + val=$(get_yaml_key "$_YH_ROOT/project.yaml" "$path") + fi + printf '%s' "$val" +} + +get_effective_yaml_array() { + _yaml_helper_set_root + local path="$1" + if [ -z "$path" ]; then return 0; fi + local val="" + if [ -f "$_YH_ROOT/project.local.yaml" ]; then + val=$(get_yaml_array "$_YH_ROOT/project.local.yaml" "$path") + fi + if [ -z "$val" ] && [ -f "$_YH_ROOT/project.yaml" ]; then + val=$(get_yaml_array "$_YH_ROOT/project.yaml" "$path") + fi + printf '%s' "$val" +} + +# Enum constants — the 12 settings whose values are constrained to a +# fixed set per effects-map.md. Format: dotted.path::value1|value2|... +# Validation: walk this list; for each key present in the file, verify +# value is in the allowed set. Out-of-set values are hard errors per +# spec (effects-map.md line 145-153). +_yaml_helper_enums="\ +modes.review_mode::full|lean|solo +modes.rigor::minimal|standard|full +modes.workflow::minimal|standard|full +modes.automation::collaborative|guided|autonomous +modes.story_granularity::coarse|balanced|fine +docs.density::terse|balanced|thorough +qa.level::minimal|standard|full +team.size::individual|small|studio +performance.enforce::warn|block|off +platform.cert_tier::none|itch|steam|console +accessibility.target::none|standard|aaa +engine.name::Godot|Unity|Unreal +project.stage::Concept|Systems Design|Technical Setup|Pre-Production|Production|Polish|Release +testing.strict.logic::true|false +testing.strict.integration::true|false +testing.strict.visual::true|false +testing.strict.ui::true|false +testing.strict.config::true|false +features.session_state::on|off +platform.online::true|false +workflow_overrides.edge_cases::true|false +workflow_overrides.tuning_knobs::true|false +workflow_overrides.art_bible_strict::true|false" +# The boolean keys above are enumerated for the same reason the string keys are: +# `resolve_setting` validates every hop and DROPS a value that fails, so a key +# absent from this table accepts anything. Before they were listed, +# `testing.strict.logic: maybe` in project.local.yaml passed validation and won +# over an explicit `true` in project.yaml -- and `/story-done` treats the gate as +# BLOCKING only when the value is `true`, so a typo silently downgraded a +# blocking test gate to advisory with no error. `performance.enforce` was +# already enumerated and correctly rejected garbage, which is exactly the +# asymmetry that hides this. Any NEW boolean setting must be added here too. +# +# Two keys deliberately NOT listed as `true|false`, both of which the config +# test suite catches if they are -- add nothing here without checking it: +# - `features.session_state` is an ON/OFF enum, not a boolean. It is listed +# above as `on|off` because `session_state_enabled()` disables only on the +# literal `off`, and effects-map documents `on`/`off`. Writing `false` there +# means ENABLED, which is why validating it matters. +# - `testing.strict` (the parent) is absent because its primary shape is a MAP +# of the five type keys; the bare scalar is only a back-compat form. Its +# effects-map `**Values:**` line documents the map, so enumerating the parent +# as a scalar would force that line to misdescribe the real shape. The five +# leaves below carry the validation, which is where /story-done reads. +# +# validate_local_scope [] +# The location check the value checks above cannot do: names keys in +# project.local.yaml that are NOT locally overridable, which resolution +# silently ignores. One line per key on stderr; returns 1 if any found. +# Warns, never reconciles. Defined below is_locally_overridable. + +# Whitelist of locally-overridable settings (per effects-map.md +# "Whitelist — which settings can be locally overridden"). Personal-experience +# settings only — divergence between developers is safe because they don't +# affect what artifacts exist on disk. +_yaml_helper_locally_overridable="\ +modes.review_mode +modes.automation +modes.automation_always_ask +team.size +testing.strict.logic +testing.strict.integration +testing.strict.visual +testing.strict.ui +testing.strict.config +performance.enforce +features.session_state +features.token_budget_warn_at" + +is_locally_overridable() { + local path="$1" + [ -z "$path" ] && return 1 + local whitelisted + while IFS= read -r whitelisted; do + [ -z "$whitelisted" ] && continue + if [ "$path" = "$whitelisted" ]; then return 0; fi + done <] +# Names the keys in project.local.yaml that resolution will NEVER read, +# because they are not on the whitelist above. One line per key on stderr; +# returns 1 if any were found, 0 otherwise. +# +# Why this needs its own check, separate from validate_yaml_enum: that +# function validates VALUES, and a locked key hand-written into the local +# file has a perfectly legal one. `modes.rigor: strict` there passes every +# validation in the chain — correct key, correct value — and then +# resolve_setting never consults the local file for that path, so the +# setting is discarded in total silence and the user sees the default they +# were trying to override. Nothing else checks the key's LOCATION. +# /settings --local already refuses these; only the hand-edited file is +# unguarded, and hand-editing is what the file is for. +# +# WARNS, NEVER RECONCILES — the same shape as the stage-mirror check. +# It does not edit project.local.yaml, does not begin honouring +# the key, and does not alter resolution. A locked setting is locked +# because it changes which artifacts exist on disk, so a helper that +# "helpfully" applied one would let two developers' checkouts diverge — +# precisely what the whitelist exists to prevent. The fix belongs to the +# user: move the key to project.yaml, or delete it. +# +# The parser below is a third copy of the one in get_yaml_key and +# validate_yaml_enum. That duplication is deliberate and matches the +# established shape of this file: these helpers are sourced individually +# and must agree on YAML edge cases exactly, and a shared copy that drifted +# would make two functions disagree about what a file says. +validate_local_scope() { + local file="${1:-project.local.yaml}" + [ -f "$file" ] || return 0 + if ! _yaml_helper_resolve_python; then return 0; fi + local found key rc=0 + found=$("$_yaml_helper_python" - "$file" "$_yaml_helper_locally_overridable" <<'PYEOF' +import sys, re + +path_file, allowed_table = sys.argv[1], sys.argv[2] +allowed = set(l.strip() for l in allowed_table.splitlines() if l.strip()) + +# File metadata, not settings -- effects-map.md lists `schema_version` and +# `framework.*` under "Locked to project.yaml" with the reason "File metadata, +# not preferences". They are not preferences, so "you cannot override this +# preference locally" is the wrong complaint about them: nothing was dropped +# and nothing needs moving. This repo's own project.local.yaml carries +# `schema_version: 1`, which is how the false positive was caught -- the check +# was written against a hand-made fixture and only run against the real file +# afterwards, where it immediately flagged a correct file. +EXEMPT_EXACT = {'schema_version'} +EXEMPT_PREFIX = ('framework.',) + +def parse(lines): + root = {} + stack = [(-1, root)] + for raw in lines: + line = raw.rstrip('\n').rstrip('\r') + stripped = line.lstrip(' ') + if not stripped.strip() or stripped.lstrip().startswith('#'): + continue + indent = len(line) - len(stripped) + while stack and stack[-1][0] >= indent: + stack.pop() + if not stack: + return root + parent = stack[-1][1] + m = re.match(r'([^:#\s][^:]*?)\s*:\s*(.*)$', stripped) + if not m: + continue + key = m.group(1).strip() + value = m.group(2).strip() + if value.startswith('#'): + value = '' + elif value[:1] in ('"', "'"): + quote = value[0] + end = value.find(quote, 1) + value = value[1:end] if end >= 0 else value[1:] + else: + hash_pos = value.find(' #') + if hash_pos >= 0: + value = value[:hash_pos].strip() + if value == '': + new_map = {} + parent[key] = new_map + stack.append((indent, new_map)) + else: + parent[key] = value + return root + +try: + with open(path_file, 'r', encoding='utf-8-sig', errors='replace') as f: + data = parse(f.readlines()) +except OSError: + sys.exit(0) + +# An EMPTY map counts as a leaf. Array-valued keys parse that way -- the +# `- item` lines carry no colon and are skipped -- so treating an empty map as +# a branch would silently exempt every list-valued setting from this check. +def walk(node, prefix): + for k, v in node.items(): + dotted = prefix + k if not prefix else prefix + '.' + k + if isinstance(v, dict) and v: + walk(v, dotted) + elif dotted in allowed or dotted in EXEMPT_EXACT: + continue + elif dotted.startswith(EXEMPT_PREFIX): + continue + else: + print(dotted) + +walk(data, '') +PYEOF +) + [ -n "$found" ] || return 0 + while IFS= read -r key; do + [ -z "$key" ] && continue + echo "$file: \`$key\` is not locally overridable — ignored, not applied. Move it to project.yaml (or delete it); see the whitelist in yaml-helper.sh." >&2 + rc=1 + done </dev/null | tr -d '\r' | tr -d ' ') + if [ -n "$v" ]; then + [ "$v" = "off" ] && return 1 + return 0 + fi + done + return 0 # unset anywhere => on +} + +validate_yaml_enum() { + local file="$1" + if [ -z "$file" ] || [ ! -f "$file" ]; then return 0; fi + if ! _yaml_helper_resolve_python; then + echo "yaml-helper: no python interpreter found (tried python, python3, py)" >&2 + return 0 + fi + # ONE interpreter spawn for the whole enum table. + # + # Calling `get_yaml_key` once per enum -- 23 calls -- spawns a Python + # interpreter each time (see get_yaml_key). At ~214ms of process startup on + # Windows that is ~4.9s per config file, and with project.yaml plus + # project.local.yaml it puts session-start.sh over its 10s hook timeout, + # killing it mid-write so the recovery checkpoint never reaches context. + # + # A bash pre-filter that skips lookups for keys ABSENT from the file is + # correct but insufficient, and the distinction is the lesson: it only removes + # spawns for keys you do not have. A sparse config skips nearly all of them + # and looks like a 35x win. A fully configured project sets all of them, every + # lookup survives the filter, and the cost returns in full -- ~4683ms filtered + # versus ~4576ms unfiltered, i.e. no improvement on the population that + # matters. + # The count of spawns was never the invariant worth fixing; the cost of one + # was. Parsing the document once and answering every enum from that parse is + # constant in the number of keys, so a denser config no longer costs more. + # + # Behaviour is unchanged by construction: the parser below is the same one + # get_yaml_key uses, the enum table is walked in the same order, absent keys + # are skipped exactly as `[ -z "$actual" ] && continue` did, and the error + # text is byte-identical. + "$_yaml_helper_python" - "$file" "$_yaml_helper_enums" <<'PYEOF' +import sys, re + +path_file, enum_table = sys.argv[1], sys.argv[2] + +def parse(lines): + root = {} + stack = [(-1, root)] # (indent, mapping) + for raw in lines: + line = raw.rstrip('\n').rstrip('\r') + stripped = line.lstrip(' ') + if not stripped.strip() or stripped.lstrip().startswith('#'): + continue + indent = len(line) - len(stripped) + while stack and stack[-1][0] >= indent: + stack.pop() + if not stack: + return root + parent = stack[-1][1] + m = re.match(r'([^:#\s][^:]*?)\s*:\s*(.*)$', stripped) + if not m: + continue + key = m.group(1).strip() + value = m.group(2).strip() + if value.startswith('#'): + value = '' + elif value[:1] in ('"', "'"): + quote = value[0] + end = value.find(quote, 1) + if end >= 0: + value = value[1:end] + else: + value = value[1:] + else: + hash_pos = value.find(' #') + if hash_pos >= 0: + value = value[:hash_pos].strip() + if value == '': + new_map = {} + parent[key] = new_map + stack.append((indent, new_map)) + else: + parent[key] = value + return root + +try: + with open(path_file, 'r', encoding='utf-8-sig', errors='replace') as f: + raw_lines = f.readlines() +except OSError: + sys.exit(0) + +data = parse(raw_lines) + +# A TAB IN THE INDENTATION IS AN ERROR, not something to normalise away. +# +# The parser stripped leading SPACES only, so a tab-indented line never matched +# the key regex and vanished -- the setting then silently resolved to its +# default. Every enum check below passed on such a file, because a key that +# cannot be read cannot hold an invalid value. That is rc=0 on a config that +# configures nothing, byte-identical in outcome to a genuinely clean file. +# Measured before this check: the same file with tabs instead of spaces +# reported no errors while `modes.review_mode: BOGUS_VALUE` sat in it unread. +# +# This is the validator's own version of the rule the skills are held to -- +# absence of evidence is not evidence of absence. It cannot report "no invalid +# values" when the real answer is "no values". +# +# YAML forbids tabs for indentation, so reporting rather than repairing is +# correct; doing neither was the defect. Comment lines are exempt: a tab in +# front of a `#` hides nothing. +tab_lines = [] +for _n, _raw in enumerate(raw_lines, 1): + _line = _raw.rstrip('\n').rstrip('\r') + if not _line.strip() or _line.lstrip(' \t').startswith('#'): + continue + _lead = _line[:len(_line) - len(_line.lstrip(' \t'))] + if '\t' in _lead: + tab_lines.append(_n) + +def lookup(dotted): + cur = data + for k in dotted.split('.'): + if isinstance(cur, dict) and k in cur: + cur = cur[k] + else: + return '' + if isinstance(cur, dict): + return '' + return str(cur) + +errors = 0 +out = [] +if tab_lines: + _shown = ', '.join(str(n) for n in tab_lines[:5]) + if len(tab_lines) > 5: + _shown += ' (and %d more)' % (len(tab_lines) - 5) + out.append("line %s: indented with a TAB. YAML forbids tabs for indentation, " + "so these lines are invisible to the config parser and their " + "settings silently fall back to defaults. Replace leading tabs " + "with spaces." % _shown) + errors += 1 +for line in enum_table.splitlines(): + line = line.strip() + if not line or '::' not in line: + continue + enum_path, _, enum_values = line.partition('::') + actual = lookup(enum_path) + if actual == '': + continue + if actual not in enum_values.split('|'): + out.append("%s: '%s' is not a valid value (expected: %s)" + % (enum_path, actual, enum_values)) + errors += 1 + +if out: + sys.stderr.buffer.write(('\n'.join(out) + '\n').encode('utf-8')) +sys.exit(1 if errors else 0) +PYEOF +} + +# Validate a single pending value for /settings writes. Looks up +# the key in _yaml_helper_enums; if found, checks value membership. If +# the key has no enum constraint, returns 0 (no validation applies). +# Returns 1 with stderr error on invalid value. +validate_enum_value() { + local key="$1" + local value="$2" + [ -z "$key" ] && return 0 + local line enum_path enum_values v + while IFS= read -r line; do + [ -z "$line" ] && continue + enum_path="${line%::*}" + enum_values="${line##*::}" + if [ "$key" = "$enum_path" ]; then + local oldIFS="$IFS" + IFS='|' + for v in $enum_values; do + if [ "$value" = "$v" ]; then IFS="$oldIFS"; return 0; fi + done + IFS="$oldIFS" + echo "Invalid value '$value' for '$key'. Allowed: $enum_values" >&2 + return 1 + fi + done < "$logfile" + fi + local timestamp + timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u) + cat >> "$logfile" < use that; 2. Else read +# modes.review_mode from project.yaml; 3. Else production/review-mode.txt; +# 4. Else lean"), re-interpreted by a model on every invocation at roughly +# +1,360 tokens per skill. resolve_config computes the same answer once, in +# ~150 tokens, deterministically -- and testably, which prose never was. +# +# CONTRACT BOUNDARY +# This layer resolves SOURCES. It does not own per-skill policy. Anything +# whose default legitimately differs between skills (testing.strict.*) is +# reported as configured-or-unset and left to the skill to default. +# ============================================================================= + +# Terminal defaults. ONLY knobs whose default is uniform across every consumer. +# +# testing.strict.* is deliberately ABSENT: /smoke-check defaults it to blocking +# (build-health gate) while /story-done and /dev-story default per story type. +# Emitting one value here would silently pick a winner between them. +# +# The six knobs `rigor` fronts (modes.workflow, docs.density, qa.level, +# modes.story_granularity, modes.review_mode, team.size) are ALSO absent: their +# value comes from the rigor expansion below, which sits between the legacy step +# and this table. Leaving a terminal default here as well would shadow the +# expansion and make `rigor` a no-op for anyone who had not also set the sub-knob. +# modes.review_mode is fronted by rigor so `rigor: minimal` resolves +# review_mode to `solo` (skipping the ~44k-token director/specialist gates), +# `standard` yields `lean` and `full` yields `full`. team.size joined the fronted +# set the same way: `full` yields `studio` (the whole roster) while +# `minimal`/`standard` yield `individual`. Both stay locally overridable (they +# are personal-experience knobs, not on-disk artifacts): that source sits ABOVE +# the expansion, so only the terminal fallback moved. +# +# --- WHY modes.rigor DEFAULTS TO `minimal` ----------------------------------- +# +# It defaulted to `standard`. Building the same project both ways showed the +# heavier tier costing several times as much to reach working code without +# producing a better result, and giving nothing back when a fresh developer +# picked the project up. A default that costs more and does not repay is the +# wrong default, and it was what every user who never opened /settings received. +# +# Raising rigor stays one question in /start and one `/settings` call, and +# settings-guidance.md's upward triggers ("system-GDD count crosses ~9", +# "gate-check PASS into Production while rigor is minimal") are written to fire +# from exactly this starting state. The cost of under-running is one prompt. +# +# ORDER MATTERS: this default is only safe while /gate-check keeps its floors. +# `rigor: minimal` expands to `workflow: minimal` + `qa.level: minimal`, which +# together once left the Production->Polish gate with ZERO required artifacts -- +# a vacuous PASS. That is fixed (the smoke-report floor and the "nothing +# required -> NOT ASSESSED, never PASS" rule). Do not move this default again +# without re-checking that the gates below it still require something. +_yaml_helper_defaults="\ +modes.automation::collaborative +modes.rigor::minimal +performance.enforce::warn" + +# Rigor expansion — one asked-at-/start knob that supplies four. +# +# WHY: /start has never asked about workflow, docs.density, qa.level or +# story_granularity, so in practice every project ran all four at their defaults. +# Between them the latter three drive ONE behaviour each (prose verbosity, is- +# evidence-required, story size) restated across 19 skills with 17 "we are +# orthogonal" disclaimers. `rigor` makes the common case reachable in one +# question while each knob stays individually settable. +# +# modes.workflow is fronted, NOT replaced: it drives ~5 distinct behaviours and +# owns the only per-system override mechanism (workflow_overrides.system_ +# overrides), which continues to win over the rigor-derived value. +_yaml_helper_rigor_expansion="\ +minimal::modes.workflow=minimal,docs.density=terse,qa.level=minimal,modes.story_granularity=coarse,modes.review_mode=solo,team.size=individual +standard::modes.workflow=standard,docs.density=balanced,qa.level=standard,modes.story_granularity=balanced,modes.review_mode=lean,team.size=individual +full::modes.workflow=full,docs.density=thorough,qa.level=full,modes.story_granularity=fine,modes.review_mode=full,team.size=studio" + +# Value for implied by the project's rigor level, or '' if rigor does not +# front that key. Never consulted for modes.rigor itself — that would recurse. +_yaml_helper_rigor_value() { + _yaml_helper_set_root + local path="$1" level="" line pair + [ -z "$path" ] && return 0 + [ "$path" = "modes.rigor" ] && return 0 + + # Resolve rigor WITHOUT resolve_setting, to keep the recursion impossible. + [ -f "$_YH_ROOT/project.yaml" ] && level=$(get_yaml_key "$_YH_ROOT/project.yaml" modes.rigor) + if [ -n "$level" ] && ! validate_enum_value modes.rigor "$level" 2>/dev/null; then level=""; fi + [ -z "$level" ] && level=$(get_yaml_default modes.rigor) + [ -z "$level" ] && return 0 + + while IFS= read -r line; do + [ -z "$line" ] && continue + [ "${line%%::*}" = "$level" ] || continue + local oldIFS="$IFS"; IFS=',' + for pair in ${line##*::}; do + if [ "${pair%%=*}" = "$path" ]; then IFS="$oldIFS"; printf '%s' "${pair#*=}"; return 0; fi + done + IFS="$oldIFS" + done </dev/null; then level=""; fi + [ -z "$level" ] && level=$(get_yaml_default modes.rigor) + printf '%s' "$level" +} + +# Plain-text legacy mirrors — the read side of the dual-write contract. +_yaml_helper_legacy_files="\ +modes.review_mode::production/review-mode.txt +project.stage::production/stage.txt" + +# Which keys consult project.local.yaml DURING RESOLUTION. +# +# This list MUST equal the `/settings --local` write whitelist. If resolution +# consults project.local.yaml for a narrower set than /settings will write to +# it, `/settings --local modes.review_mode=solo` writes a value that /settings +# accepts and displays, and that every skill then ignores. A write +# list and a read list that disagree is not a design; it is a bug with a +# plausible-looking symptom. +# +# Kept as its own variable rather than inlining the whitelist: "may be written +# locally" and "is read during resolution" are distinct concepts that merely +# coincide today. A future setting could legitimately be one and not the other. +_yaml_helper_local_read_scope="$_yaml_helper_locally_overridable" + +_yaml_helper_in_local_read_scope() { + local path="$1" entry + [ -z "$path" ] && return 1 + while IFS= read -r entry; do + [ -z "$entry" ] && continue + [ "$path" = "$entry" ] && return 0 + done < 0) { print; exit } }' "$file" +} + +# Resolve one setting through the full chain. +# prints: "\t" +# source: project.local.yaml | project.yaml | | default | unset +# exit: always 0 +# +# Enum-invalid values FALL THROUGH rather than winning — a typo degrades to the +# next source and ultimately the documented default, instead of propagating a +# nonsense mode into every skill. resolve_config surfaces what was rejected. +resolve_setting() { + _yaml_helper_set_root + local path="$1" + if [ -z "$path" ]; then printf '\tunset'; return 0; fi + local val="" src="" legacy="" + + if _yaml_helper_in_local_read_scope "$path" && [ -f "$_YH_ROOT/project.local.yaml" ]; then + val=$(get_yaml_key "$_YH_ROOT/project.local.yaml" "$path") + if [ -n "$val" ] && ! validate_enum_value "$path" "$val" 2>/dev/null; then val=""; fi + [ -n "$val" ] && src="project.local.yaml" + fi + + if [ -z "$val" ] && [ -f "$_YH_ROOT/project.yaml" ]; then + val=$(get_yaml_key "$_YH_ROOT/project.yaml" "$path") + if [ -n "$val" ] && ! validate_enum_value "$path" "$val" 2>/dev/null; then val=""; fi + [ -n "$val" ] && src="project.yaml" + fi + + if [ -z "$val" ]; then + legacy=$(_yaml_helper_legacy_file_for "$path") + if [ -n "$legacy" ] && [ -f "$legacy" ]; then + val=$(get_legacy_key "$path") + if [ -n "$val" ] && ! validate_enum_value "$path" "$val" 2>/dev/null; then val=""; fi + [ -n "$val" ] && src="$legacy" + fi + fi + + # Rigor expansion sits BELOW every explicit source and ABOVE the terminal + # default. That ordering is the back-compat guarantee: a project.yaml that + # already sets docs.density resolves exactly as it did before rigor existed. + if [ -z "$val" ]; then + val=$(_yaml_helper_rigor_value "$path") + [ -n "$val" ] && src="rigor:$(_yaml_helper_rigor_level)" + fi + + if [ -z "$val" ]; then + val=$(get_yaml_default "$path") + [ -n "$val" ] && src="default" + fi + + [ -z "$src" ] && src="unset" + printf '%s\t%s' "$val" "$src" +} + +# Resolve the ENGINE-SPECIFIC code root. +# prints: "\t" +# source: engine.name | | detected | unset +# exit: always 0 +# +# WHY THIS EXISTS. `.claude/docs/directory-structure.md` is explicit that the +# code root is not a style preference but a hard toolchain constraint: Unity +# compiles only `Assets/` and `Packages/`, and UnrealBuildTool discovers modules +# under `Source/`. `src/` is that table's GODOT row, not a universal path. +# +# Every hook that filtered staged or on-disk files with a literal `^src/` +# therefore matched NOTHING on a Unity or Unreal project — and matching nothing +# is indistinguishable from scanning cleanly. That is the exact row +# `.claude/rules/skill-authoring.md` records for `/security-audit` +# ("Godot-only greps returned zero hits on Unity/Unreal, and zero hits read as +# clean"), reproduced in four more places. +# +# UNSET DOES NOT DEFAULT TO `src`. Obligation 2 of skill-authoring.md: an absent +# value may not default to the permissive one. Defaulting here would re-create +# the Godot bias this function exists to remove, and would do it invisibly. An +# unresolvable root returns empty, and the CALLER must announce that its check +# did not run — see the `_no_code_root` reporting in validate-commit.sh. +# +# Step 3 (detect) is derivation, not enumeration (obligation 5): it believes the +# tree over the absence of config, but only when the tree is UNAMBIGUOUS. Two +# roots present means a genuinely undecidable project, and guessing there would +# scan half of it and report a pass. +resolve_code_root() { + _yaml_helper_set_root + local ename="" src="" root="" legacy="" found="" d n=0 + + # 1. The configured engine, through the normal chain. + if [ -f "$_YH_ROOT/project.local.yaml" ]; then + ename=$(get_yaml_key "$_YH_ROOT/project.local.yaml" engine.name 2>/dev/null) + [ -n "$ename" ] && src="project.local.yaml" + fi + if [ -z "$ename" ] && [ -f "$_YH_ROOT/project.yaml" ]; then + ename=$(get_yaml_key "$_YH_ROOT/project.yaml" engine.name 2>/dev/null) + [ -n "$ename" ] && src="engine.name" + fi + + # 2. The legacy mirror. `/setup-engine` wrote an `Engine:` line here before + # project.yaml existed, and workflow-catalog's `engine-setup` step still + # accepts it as an `any_of` alternative — so a v1.0 project that never + # migrated resolves rather than falling through to "unset". + legacy="$_YH_ROOT/.claude/docs/technical-preferences.md" + if [ -z "$ename" ] && [ -f "$legacy" ]; then + ename=$(grep -iE '^[[:space:]]*[-*]?[[:space:]]*\*{0,2}Engine\*{0,2}[[:space:]]*:' "$legacy" 2>/dev/null \ + | head -1 | sed 's/.*://' | tr -d '\r' \ + | sed 's/^[[:space:]]*//; s/[[:space:]].*$//; s/\*//g') + case "$ename" in + *"[TO BE CONFIGURED]"*|"["*) ename="" ;; + esac + [ -n "$ename" ] && src=".claude/docs/technical-preferences.md" + fi + + case "$(printf '%s' "$ename" | tr '[:upper:]' '[:lower:]')" in + godot) root="src" ;; + unity) root="Assets" ;; + unreal*) root="Source" ;; + *) root="" ;; + esac + + # 3. No usable engine value — believe the tree, but only if it is unambiguous. + if [ -z "$root" ]; then + for d in src Assets Source; do + if [ -d "$_YH_ROOT/$d" ]; then found="$d"; n=$((n + 1)); fi + done + if [ "$n" = 1 ]; then root="$found"; src="detected"; else root=""; src=""; fi + fi + + [ -z "$src" ] && src="unset" + printf '%s\t%s' "$root" "$src" +} + +# List the child key names of a mapping node (one per line). get_yaml_key +# deliberately returns nothing for a mapping, so this is how the whole +# workflow_overrides.system_overrides map gets enumerated in one shot. +get_yaml_child_keys() { + local file="$1" path="$2" + if [ -z "$file" ] || [ -z "$path" ] || [ ! -f "$file" ]; then return 0; fi + _yaml_helper_resolve_python || return 0 + "$_yaml_helper_python" - "$file" "$path" <<'PYEOF' +import sys, re +path_file, dotted = sys.argv[1], sys.argv[2] +keys = dotted.split('.') + +def parse(lines): + root = {} + stack = [(-1, root)] + for raw in lines: + line = raw.rstrip('\n').rstrip('\r') + stripped = line.lstrip(' ') + if not stripped.strip() or stripped.lstrip().startswith('#'): + continue + indent = len(line) - len(stripped) + while stack and stack[-1][0] >= indent: + stack.pop() + if not stack: + return root + parent = stack[-1][1] + m = re.match(r'([^:#\s][^:]*?)\s*:\s*(.*)$', stripped) + if not m: + continue + key, value = m.group(1).strip(), m.group(2).strip() + if value.startswith('#'): + value = '' + elif value[:1] in ('"', "'"): + q = value[0]; end = value.find(q, 1) + value = value[1:end] if end >= 0 else value[1:] + else: + hp = value.find(' #') + if hp >= 0: + value = value[:hp].strip() + if value == '': + nm = {}; parent[key] = nm; stack.append((indent, nm)) + else: + parent[key] = value + return root + +try: + with open(path_file, 'r', encoding='utf-8-sig', errors='replace') as f: + data = parse(f.readlines()) +except OSError: + sys.exit(0) + +cur = data +for k in keys: + if isinstance(cur, dict) and k in cur: + cur = cur[k] + else: + sys.exit(0) +if not isinstance(cur, dict): + sys.exit(0) +sys.stdout.buffer.write("\n".join(cur.keys()).encode('utf-8')) +PYEOF +} + +# Emit the resolved-config block a skill reads instead of re-deriving anything. +# +# resolve_config [] +# +# ALWAYS exits 0 and ALWAYS emits a complete block — a missing project.yaml, +# malformed YAML, an orphan project.local.yaml or a missing Python interpreter +# each degrade to defaults and surface on the notes: line rather than producing +# a truncated block a skill might half-read. +# resolve_config [--keys k1,k2,...] [] +# +# --keys restricts output to the named knobs. USE IT. Emitting all 12 lines costs +# ~188 tokens, while the resolution prose it replaces averages only ~101 tokens +# per skill -- so a full block is a NET LOSS for any skill that reads 2-3 knobs. +# A 3-knob block costs ~45 tokens, which is the actual win. Measured, not +# estimated. +# Valid key names are the output labels: rigor, review_mode, automation, workflow, +# docs.density, story_granularity, qa.level, team.size, project.stage, +# automation_always_ask, engine, testing.strict, system_overrides. +resolve_config() { + _yaml_helper_set_root + local want="" + if [ "${1:-}" = "--keys" ]; then want=",${2},"; shift 2; fi + local system="${1:-}" + local notes="" v s pair + # emit