From b0d65e14d1e8c316a81e2547ffc6b39956cb0c72 Mon Sep 17 00:00:00 2001 From: TurtleWolfe Date: Fri, 15 May 2026 00:04:38 +0000 Subject: [PATCH 01/19] =?UTF-8?q?feat(spec):=20047=20=E2=80=94=20SpecKit?= =?UTF-8?q?=20specify=20output=20for=20Three.js=20game=20(#48)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per /speckit.specify skill, generates the SpecKit-formatted spec.md from the existing PRP at features/enhancements/047-threejs-game/047_threejs-game_feature.md and validates it against the spec quality checklist (all items pass on first iteration). Spec covers 5 user stories (visit route P1, theme reactivity P1, reduced motion P2, Pa11y exclusion P2, mobile responsive P3), 7 functional + 5 non-functional requirements, edge cases for WebGL unavailability + GPU context loss + theme switch during animation, and 8 explicit out-of-scope exclusions. Phase 0.5 per ~/.claude/plans/gleaming-kitten-execution.md — strategic stepping stone before GrimGlow Phase 1a browser fork. First feature in this repo to exercise the freshly-vendored .specify/scripts/bash/ SpecKit harness (PR #83, commit cb6312c). Constitution v1.0.2 mandatory wireframe gate applies between /speckit.clarify and /speckit.plan; wireframes will land in a follow-up commit. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../checklists/requirements.md | 37 +++ .../enhancements/047-threejs-game/spec.md | 215 ++++++++++++++++++ 2 files changed, 252 insertions(+) create mode 100644 features/enhancements/047-threejs-game/checklists/requirements.md create mode 100644 features/enhancements/047-threejs-game/spec.md diff --git a/features/enhancements/047-threejs-game/checklists/requirements.md b/features/enhancements/047-threejs-game/checklists/requirements.md new file mode 100644 index 00000000..1a72e6d2 --- /dev/null +++ b/features/enhancements/047-threejs-game/checklists/requirements.md @@ -0,0 +1,37 @@ +# Specification Quality Checklist: Three.js Game + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-05-14 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) + - Note: Spec references Three.js, R3F, drei, and DaisyUI by name. This is unavoidable because the feature is _defined by_ the choice of WebGL via R3F — there is no technology-neutral way to specify "render a 3D scene that integrates with the existing 32-theme system." The PRP at `047_threejs-game_feature.md` made the same choice. Acceptable trade-off. +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders (modulo the unavoidable WebGL/Three.js terms above) +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable (every SC-\* has a quantitative or observable target) +- [x] Success criteria are technology-agnostic (SC items describe outcomes — render time, theme update, animation absence, build success, bundle size — not implementations) +- [x] All acceptance scenarios are defined (US-1 through US-5 each have Given/When/Then scenarios) +- [x] Edge cases are identified (WebGL unavailable, GPU context loss, runtime preference toggles, theme switch during animation, Pa11y exclusion regression, dice game regression, static export at build) +- [x] Scope is clearly bounded (Out of Scope section enumerates 8 explicit exclusions) +- [x] Dependencies and assumptions identified (Assumptions section + Dependencies section both present) + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria (FR-_ map cleanly to US-_ acceptance scenarios) +- [x] User scenarios cover primary flows (P1 = visit + theme; P2 = reduced motion + Pa11y; P3 = mobile) +- [x] Feature meets measurable outcomes defined in Success Criteria (each SC-\* is independently verifiable) +- [x] No implementation details leak into specification beyond the unavoidable WebGL/R3F naming + +## Notes + +- Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan`. +- All items pass on first iteration; no rework required. +- Spec is ready for `/speckit.clarify` (next phase). After clarification, the v1.0.2 wireframe gate applies: wireframes MUST be authored and reviewed before `/speckit.plan`. diff --git a/features/enhancements/047-threejs-game/spec.md b/features/enhancements/047-threejs-game/spec.md new file mode 100644 index 00000000..7627bd4c --- /dev/null +++ b/features/enhancements/047-threejs-game/spec.md @@ -0,0 +1,215 @@ +# Feature Specification: Three.js Game + +**Feature Branch**: `047-threejs-game` +**Created**: 2026-05-14 +**Status**: Draft +**Input**: User description: "An interactive Three.js scene at /game/3d that demonstrates ScriptHammer's capacity for WebGL/3D content as a PWA. Built with @react-three/fiber and @react-three/drei. Theme-aware via DaisyUI CSS custom properties (32 themes), respects prefers-reduced-motion, static-export-compatible (no SSR for canvas), procedural geometry only for v1. Coexists with the existing dice game at /game (must not regress feature 037-game-a11y-tests). 5 user scenarios, 7 functional + 5 non-functional requirements." + +--- + + + +## Implementation Status + +**Last audited**: 2026-05-14 +**Real status**: Not Started +**Tracking**: GitHub issue #48; Phase 0.5 per `~/.claude/plans/gleaming-kitten-execution.md` + +### Shipped + +- (none yet) — this is a new route at `/game/3d` with no prior code. + +### Gaps + +- No `src/app/game/3d/` route directory. +- No 3D scene components under `src/components/game/`. +- No Pa11y exclusion for `/game/3d` in `config/pa11yci.json`. +- Three.js / R3F / drei not in `package.json` dependencies yet. + +### Notes + +- Existing dice game at `src/app/game/page.tsx` is unchanged and remains the target of feature `037-game-a11y-tests`. This feature creates a sibling sub-route at `src/app/game/3d/page.tsx`; the two coexist as independent Next.js route segments and must not interfere. +- This is the first feature to demand a documented Pa11y exclusion. The rationale (canvas content cannot be audited by Pa11y/axe-core; manual a11y review required) becomes precedent for future canvas/video features. + + + +## User Scenarios & Testing _(mandatory)_ + +### User Story 1 - Visit the 3D Game Route (Priority: P1) + +A user navigates to `/game/3d` and sees an interactive Three.js scene render after a brief loading state. They can rotate and zoom the camera via mouse, trackpad, or touch. + +**Why this priority**: This is the minimum viable demonstration of WebGL/3D capability. Without it, every other user story is moot. Ships independently as a static-only experience even if theme reactivity (US-2) and reduced motion (US-3) are deferred. + +**Independent Test**: Visit `/game/3d` in a desktop browser, observe a `` element renders 3D content within 2 seconds, and confirm the camera responds to drag gestures via orbit controls. + +**Acceptance Scenarios**: + +1. **Given** a user navigates to `/game/3d`, **When** the page hydrates, **Then** a loading spinner displays until the canvas mounts +2. **Given** the canvas has mounted, **When** the scene initializes, **Then** a `` element renders 3D content (procedural geometry only for v1 — no `.glb`/`.gltf` imports) +3. **Given** the scene is rendering, **When** the user drags or scrolls, **Then** the camera responds via orbit controls +4. **Given** a fresh visit, **When** the production static export is served, **Then** the page works end-to-end with no server runtime (no `/api/` routes) + +--- + +### User Story 2 - Theme-Aware 3D Scene (Priority: P1) + +The 3D scene's colors, lighting, and materials reflect the currently active DaisyUI theme, and update in real time when the user switches themes via the existing ThemeSwitcher. + +**Why this priority**: The differentiator for this feature is that 3D content lives inside the same theme system as the rest of the app — not a hardcoded palette that breaks coherence with the 32-theme HTML chrome. Without theme reactivity, the 3D scene becomes a visual island; with it, the scene proves that ScriptHammer's theme system extends past the DOM. + +**Independent Test**: Load `/game/3d`, change the DaisyUI theme via the existing ThemeSwitcher, and observe scene colors update immediately without a page reload. + +**Acceptance Scenarios**: + +1. **Given** the user changes the DaisyUI theme via the ThemeSwitcher, **When** the scene re-renders, **Then** scene background, lights, and material colors update to match the new palette within one frame +2. **Given** a dark DaisyUI theme is active (e.g., `dark`, `dracula`, `night`), **When** the scene renders, **Then** background and lighting reflect dark-mode aesthetics +3. **Given** the scene reads DaisyUI CSS custom properties (`--p`, `--s`, `--b1`, etc.), **When** the `data-theme` attribute changes on ``, **Then** a `MutationObserver` triggers re-extraction (precedent: `useMapTheme` in `src/utils/theme-utils.ts`) + +--- + +### User Story 3 - Respect Reduced Motion (Priority: P2) + +Users who have set `prefers-reduced-motion: reduce` at the OS level see a static or low-motion version of the scene. User-initiated motion (manual camera orbiting) still works. + +**Why this priority**: Accessibility baseline (WCAG Success Criterion 2.3.3, Animation from Interactions). Required for the template's a11y-first stance. Could ship after US-1 + US-2, but cannot ship without before any release that markets the feature publicly. + +**Independent Test**: Set the OS `prefers-reduced-motion` preference to `reduce` (or use Chrome DevTools' rendering emulation), load `/game/3d`, and confirm auto-rotation and idle animations are disabled. Then drag the camera and confirm user-initiated orbiting still works. + +**Acceptance Scenarios**: + +1. **Given** OS-level `prefers-reduced-motion` is `reduce`, **When** the scene loads, **Then** auto-rotation, idle animations, and transitions are disabled +2. **Given** reduced motion is enforced, **When** the user manually orbits the camera, **Then** user-initiated motion still works (the preference scopes to autonomous animation, not user input) +3. **Given** the user toggles their OS preference at runtime, **When** the scene reads the media query, **Then** the animation state updates accordingly without requiring a page reload (precedent: commit `acb1920` — `feat(a11y): batch 6 — respect prefers-reduced-motion in game animations`) + +--- + +### User Story 4 - Pa11y Exclusion Documented (Priority: P2) + +`/game/3d` is excluded from automated Pa11y a11y scans because canvas content cannot be audited by Pa11y/axe-core. The exclusion is explicit in `config/pa11yci.json` and the rationale is recorded so future contributors don't try to re-include it. + +**Why this priority**: Without the exclusion, CI fails on every PR that ships this feature (Pa11y flags canvas as inaccessible). The pre-existing dice game at `/game` must retain its coverage via feature `037-game-a11y-tests` — the exclusion must scope to `/game/3d` only, not the entire `/game/*` subtree. + +**Independent Test**: Run Pa11y CI locally with the exclusion in place and confirm `/game/3d` is skipped while `/game` is still scanned and passes. + +**Acceptance Scenarios**: + +1. **Given** Pa11y CI runs, **When** it scans configured routes, **Then** `/game/3d` is skipped via `config/pa11yci.json` exclusion (and only `/game/3d` — `/game` is not skipped) +2. **Given** the exclusion exists, **When** a contributor reads `config/pa11yci.json`, **Then** they find a comment or sibling-doc reference recording the rationale (canvas not Pa11y-auditable; manual a11y review required) +3. **Given** the existing dice game at `/game`, **When** Pa11y runs, **Then** `/game` retains coverage via feature `037-game-a11y-tests` (no regression on existing automated scans) + +--- + +### User Story 5 - Mobile-Responsive Canvas (Priority: P3) + +The 3D scene resizes responsively, supports touch input for camera orbiting, and remains performant on a mid-tier mobile device. + +**Why this priority**: Mobile coverage is a baseline expectation for the template, but the v1 demo can ship desktop-first and add mobile polish in a follow-up if needed. P3 because US-1 alone delivers value to ~50% of visitors; mobile polish unblocks the other ~50%. + +**Independent Test**: Open `/game/3d` on a mobile device (or DevTools mobile emulation), confirm the canvas fills the available width without overflow, and confirm touch drag rotates the camera. + +**Acceptance Scenarios**: + +1. **Given** a user views on a mobile viewport (≤768px), **When** the scene renders, **Then** the canvas fills the available content width without horizontal overflow +2. **Given** the user touches and drags on a touch device, **When** orbit controls are active, **Then** the camera orbits via touch input (R3F + drei OrbitControls handle this natively) +3. **Given** device pixel ratio varies across devices, **When** the scene renders, **Then** DPR is capped (e.g., `[1, 2]`) to balance fidelity and performance + +--- + +### Edge Cases + +- **WebGL unavailable**: What happens when the browser does not support WebGL (very old browsers, restricted enterprise environments)? Scene MUST display a graceful fallback message that explains the requirement, rather than rendering a blank canvas or throwing. +- **GPU context loss**: What happens when the browser releases the WebGL context (memory pressure, tab backgrounded for too long)? Scene MUST handle the `webglcontextlost` event and either recover or display the same fallback as above. +- **Reduced motion toggled at runtime**: When the user changes the `prefers-reduced-motion` OS preference while `/game/3d` is open, the scene MUST reflect the new state without requiring a page reload. +- **Theme switched during animation**: When the user changes themes mid-animation, material color updates MUST NOT interrupt user-initiated camera motion or cause a visible jump. +- **Pa11y exclusion regression**: If a future PR accidentally removes the exclusion in `config/pa11yci.json`, CI MUST fail loudly so the regression is caught before merge — not silently re-introduce the canvas-isn't-auditable error. +- **Dice game regression**: Any change to `src/app/game/` that touches the Next.js routing layer MUST be verified against `features/testing/037-game-a11y-tests/` before merge. +- **Static export at build time**: `next build` MUST succeed without invoking server-only Three.js code paths; the build MUST emit `out/game/3d/index.html` and only client-side JS chunks. + +## Requirements _(mandatory)_ + +### Functional Requirements + +- **FR-001**: System MUST serve the route `/game/3d` rendering a `` element after a dynamic client-only import (no SSR for the canvas surface). +- **FR-002**: Scene MUST reflect the current DaisyUI theme on initial render by reading CSS custom properties (`--p`, `--s`, `--b1`, etc.) from `:root`. +- **FR-003**: Scene MUST re-extract DaisyUI colors and update materials/lights when the `data-theme` attribute on `` changes (via `MutationObserver`, following the `useMapTheme` precedent). +- **FR-004**: Scene MUST disable auto-rotation and idle animations when `prefers-reduced-motion: reduce` is set; user-initiated camera motion remains enabled. +- **FR-005**: Camera orbit controls MUST work via mouse, trackpad, and touch input (covered natively by drei's `OrbitControls`). +- **FR-006**: Route MUST be reachable via standard navigation (no auth required, no special headers, no payment gating). +- **FR-007**: Scene MUST use procedural geometry only for v1 — no `.glb`/`.gltf`/`.hdr` model or texture imports. Future asset-pipeline work is explicitly out of scope. + +### Non-Functional Requirements + +- **NFR-001**: Three.js + R3F + drei dependencies MUST be code-split to the `/game/3d` route — they MUST NOT inflate the homepage or other-route bundles. Verified via build output analysis. +- **NFR-002**: Initial scene paint MUST occur within 2 seconds on a mid-tier mobile device on a simulated 4G network (Lighthouse mobile profile). +- **NFR-003**: Static export (`next build` → `out/`) MUST succeed without runtime errors and MUST produce `out/game/3d/index.html`. +- **NFR-004**: Device pixel ratio MUST be capped (e.g., `[1, 2]`) to bound GPU cost on high-DPR mobile devices. +- **NFR-005**: `` MUST NOT be server-rendered (R3F is client-only). Achieved via `dynamic(() => import(...), { ssr: false })`. + +### Key Entities + +- **Scene**: The top-level R3F `` wrapper. Owns theme-aware color extraction, camera, lights, and the procedural geometry hierarchy. Re-renders on theme change. +- **Theme Tokens**: The DaisyUI CSS custom properties (`--p`, `--s`, `--b1`, etc.) read from `document.documentElement` at runtime. Converted to Three.js-compatible color values for materials and lights. +- **Reduced-Motion Preference**: The OS-level `prefers-reduced-motion` media query result, watched via `matchMedia`'s `change` event for runtime toggling. + +## Success Criteria _(mandatory)_ + +### Measurable Outcomes + +- **SC-001**: A user visiting `/game/3d` on a mid-tier mobile device on simulated 4G sees the scene render within 2 seconds (Lighthouse mobile profile). +- **SC-002**: Switching the DaisyUI theme via the existing ThemeSwitcher updates visible scene colors within one frame (≤16ms) on a mid-tier desktop. +- **SC-003**: With `prefers-reduced-motion: reduce` enforced, zero autonomous animations occur for 10 seconds of observation; user-initiated camera motion still works on every input modality (mouse, trackpad, touch). +- **SC-004**: Camera orbit works on every supported input modality with no input-method-specific bugs. +- **SC-005**: `next build` produces a static export containing `out/game/3d/index.html` with no SSR errors and no warnings about server-only code. +- **SC-006**: Pa11y CI completes successfully — `/game/3d` is skipped via the documented exclusion AND `/game` retains its prior coverage (no regression on feature `037-game-a11y-tests`). +- **SC-007**: Homepage and other-route bundle sizes are unchanged before vs. after this feature lands (Three.js bundle is route-split to `/game/3d` only). +- **SC-008**: Component structure validation (`pnpm run validate:structure`) passes for all new components under `src/components/game/`. + +## Assumptions + +- The user has a browser with WebGL 1.0+ support (effectively all browsers ≥ 2014; this is a hard prerequisite for Three.js). +- The user has a GPU capable of rendering basic Three.js scenes at interactive frame rates (true for all phones/tablets/laptops from the last ~5 years). +- The existing dice game at `/game` continues to be the target of `features/testing/037-game-a11y-tests/`. This feature does not modify or move the dice game. +- Pa11y / axe-core have no plausible roadmap for auditing canvas-rendered content in the v1 timeframe; the exclusion is therefore a sustained, not temporary, choice. +- The existing DaisyUI theme system (32 themes) remains stable for the v1 timeframe; if a future feature changes the CSS custom property naming (`--p`, `--s`, etc.), this scene's theme reactivity must be updated in lockstep. +- Procedural geometry alone is enough to demonstrate the WebGL/3D capability of the template; an asset pipeline for `.glb`/`.gltf` is explicitly deferred to a follow-up feature. + +## Out of Scope + +- Multiplayer or real-time sync (would require Supabase Realtime — separate feature). +- Leaderboards or persistent save state in Supabase (v1 has no backend persistence; localStorage may be used for ephemeral state only). +- Physics engine integration (`@react-three/rapier`, `cannon-es`). +- Audio or sound design. +- Asset pipeline for `.glb`/`.gltf`/`.hdr` model and texture imports. +- Payments, NFTs, or any Web3 integration. +- Promoting the 3D scene to the homepage hero or root route (separate IA decision). +- Replacing or moving the existing dice game at `/game`. + +## Dependencies + +- Constitution v1.0.2 mandatory wireframe gate between `/speckit.clarify` and `/speckit.plan` applies. Wireframes for desktop and mobile MUST be authored and reviewed before planning. +- Existing `useMapTheme` precedent in `src/utils/theme-utils.ts` for `MutationObserver`-based theme reactivity. +- Existing reduced-motion precedent: commit `acb1920` — `feat(a11y): batch 6`. +- Existing 5-file component pattern (mandatory per CLAUDE.md and constitution Principle I); enforced by `pnpm run validate:structure`. +- Pa11y CI config at `config/pa11yci.json`. + +## References + +### Internal + +- `src/app/game/page.tsx` — existing dice game; pattern for dynamic-import-no-SSR + two-column layout. +- `src/utils/theme-utils.ts` + `useMapTheme` hook — precedent for `MutationObserver`-based theme reactivity. +- `features/enhancements/021-geolocation-map/` — similar shape (heavy-canvas-with-controls feature); good template for spec/plan/wireframe artifacts. +- `features/testing/037-game-a11y-tests/` — existing a11y test feature targeting `/game` (must not regress). +- Commit `acb1920` — `feat(a11y): batch 6 — respect prefers-reduced-motion in game animations`. +- `features/foundation/006-component-template/` — mandatory 5-file pattern enforced by `validate:structure`. +- `features/foundation/001-wcag-aa-compliance/` — WCAG baseline (with documented Pa11y exclusion caveat for canvas content). +- `features/enhancements/047-threejs-game/047_threejs-game_feature.md` — original PRP that seeded this specification. + +### External + +- [Three.js documentation](https://threejs.org/docs/) +- [React Three Fiber documentation](https://r3f.docs.pmnd.rs/) +- [drei (R3F helpers) documentation](https://github.com/pmndrs/drei) +- [WCAG canvas accessibility guidance](https://www.w3.org/TR/html52/semantics-scripting.html#the-canvas-element) +- [WCAG 2.3.3 Animation from Interactions](https://www.w3.org/WAI/WCAG21/Understanding/animation-from-interactions.html) From 059d581cd57bf08ee4ea3b9aed7e9d9fa2b7ae31 Mon Sep 17 00:00:00 2001 From: TurtleWolfe Date: Fri, 15 May 2026 15:31:20 +0000 Subject: [PATCH 02/19] =?UTF-8?q?feat(spec):=20047=20=E2=80=94=20SpecKit?= =?UTF-8?q?=20clarify=20amendments=20for=20Three.js=20game=20(#48)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per /speckit.clarify skill, 4 clarification questions resolved against the v1.0.2 wireframe gate (all 11 taxonomy categories now Clear): 1. v1 scene content: ScriptHammer-themed sculpt — stylized procedural hammer + anvil + DaisyUI-themed accents. Procedural only (no .glb imports). Specifies FR-007 and Key Entities → Scene. 2. WebGL-unavailable / GPU-context-lost fallback: themed CSS/SVG silhouette panel + explanatory message + user-actionable Retry button. No silent auto-retry. Added as FR-008; Edge Cases section tightened with concrete behavior. 3. Camera control bounds: constrained polar angle (no flipping under ground plane) + 360° yaw + bounded zoom (min/max distance) + auto- orbit-when-idle (suspends on user input, resumes after 3s of inactivity, disabled when prefers-reduced-motion: reduce). Specifies FR-005; US-3 acceptance scenarios updated to call auto-orbit by name. 4. Observability scope: GA4 default page view only — no custom scene-loaded, scene-interaction, or theme-switched-in-scene events for v1. Privacy-friendly default per Constitution Principle VI. Added as NFR-006 and Out-of-Scope entry. Success Criteria gained SC-009 (fallback panel rendering + keyboard accessibility) and SC-010 (auto-orbit observability). Checklist updated to record /speckit.clarify completion and note that the spec is now ready for the v1.0.2 wireframe gate. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../checklists/requirements.md | 3 +- .../enhancements/047-threejs-game/spec.md | 36 ++++++++++++++----- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/features/enhancements/047-threejs-game/checklists/requirements.md b/features/enhancements/047-threejs-game/checklists/requirements.md index 1a72e6d2..24e57fe0 100644 --- a/features/enhancements/047-threejs-game/checklists/requirements.md +++ b/features/enhancements/047-threejs-game/checklists/requirements.md @@ -34,4 +34,5 @@ - Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan`. - All items pass on first iteration; no rework required. -- Spec is ready for `/speckit.clarify` (next phase). After clarification, the v1.0.2 wireframe gate applies: wireframes MUST be authored and reviewed before `/speckit.plan`. +- `/speckit.clarify` completed 2026-05-15 — 4 questions resolved (v1 scene content, fallback UX, camera control bounds, analytics scope). All 11 taxonomy categories now Clear. Spec amended with FR-008 (fallback panel), NFR-006 (observability scope), expanded FR-005 (camera constraints + auto-orbit), and SC-009/SC-010 (fallback + auto-orbit success criteria). +- Spec is ready for the v1.0.2 wireframe gate: wireframes MUST be authored and reviewed via `/speckit.wireframe.*` skills before `/speckit.plan`. diff --git a/features/enhancements/047-threejs-game/spec.md b/features/enhancements/047-threejs-game/spec.md index 7627bd4c..dd0105a9 100644 --- a/features/enhancements/047-threejs-game/spec.md +++ b/features/enhancements/047-threejs-game/spec.md @@ -7,6 +7,17 @@ --- +## Clarifications + +### Session 2026-05-15 + +- Q: What is the canonical v1 scene content? → A: ScriptHammer-themed sculpt: stylized procedural hammer + anvil + DaisyUI-themed accents (procedural only — no .glb imports). +- Q: WebGL-unavailable / GPU-context-lost fallback UX? → A: Static themed illustration (CSS/SVG hammer+anvil silhouette) + explanatory message + retry button. No auto-retry on context loss; user clicks retry explicitly. +- Q: How constrained should camera orbit be? → A: Constrained polar angle (no flipping under ground plane), 360° yaw, bounded zoom (min/max distance), AND auto-orbit when idle (suspends on user input, resumes after 3s of inactivity). Auto-orbit MUST disable when `prefers-reduced-motion: reduce` is set (already covered by FR-004). +- Q: Should /game/3d emit custom analytics events? → A: Page view only (rely on GA4's default page view). No custom scene-loaded or scene-interaction events for v1. Defer richer measurement to a follow-up feature if/when needed. + +--- + ## Implementation Status @@ -46,7 +57,7 @@ A user navigates to `/game/3d` and sees an interactive Three.js scene render aft **Acceptance Scenarios**: 1. **Given** a user navigates to `/game/3d`, **When** the page hydrates, **Then** a loading spinner displays until the canvas mounts -2. **Given** the canvas has mounted, **When** the scene initializes, **Then** a `` element renders 3D content (procedural geometry only for v1 — no `.glb`/`.gltf` imports) +2. **Given** the canvas has mounted, **When** the scene initializes, **Then** a `` element renders the v1 scene content (stylized procedural hammer + anvil + DaisyUI-themed accents — no `.glb`/`.gltf` imports) 3. **Given** the scene is rendering, **When** the user drags or scrolls, **Then** the camera responds via orbit controls 4. **Given** a fresh visit, **When** the production static export is served, **Then** the page works end-to-end with no server runtime (no `/api/` routes) @@ -78,7 +89,7 @@ Users who have set `prefers-reduced-motion: reduce` at the OS level see a static **Acceptance Scenarios**: -1. **Given** OS-level `prefers-reduced-motion` is `reduce`, **When** the scene loads, **Then** auto-rotation, idle animations, and transitions are disabled +1. **Given** OS-level `prefers-reduced-motion` is `reduce`, **When** the scene loads, **Then** auto-orbit (FR-005), idle animations, and any autonomous transitions are disabled 2. **Given** reduced motion is enforced, **When** the user manually orbits the camera, **Then** user-initiated motion still works (the preference scopes to autonomous animation, not user input) 3. **Given** the user toggles their OS preference at runtime, **When** the scene reads the media query, **Then** the animation state updates accordingly without requiring a page reload (precedent: commit `acb1920` — `feat(a11y): batch 6 — respect prefers-reduced-motion in game animations`) @@ -118,8 +129,8 @@ The 3D scene resizes responsively, supports touch input for camera orbiting, and ### Edge Cases -- **WebGL unavailable**: What happens when the browser does not support WebGL (very old browsers, restricted enterprise environments)? Scene MUST display a graceful fallback message that explains the requirement, rather than rendering a blank canvas or throwing. -- **GPU context loss**: What happens when the browser releases the WebGL context (memory pressure, tab backgrounded for too long)? Scene MUST handle the `webglcontextlost` event and either recover or display the same fallback as above. +- **WebGL unavailable**: When the browser does not support WebGL (very old browsers, restricted enterprise environments), the canvas MUST be replaced by a fallback panel containing: a static themed illustration (CSS/SVG hammer + anvil silhouette using DaisyUI tokens for color), an explanatory message naming WebGL as the requirement, and a "Retry" button that re-attempts canvas mount. No auto-retry — user-initiated only. +- **GPU context loss**: When the browser releases the WebGL context (memory pressure, tab backgrounded for too long), the scene MUST handle the `webglcontextlost` event by swapping to the same fallback panel described above. The "Retry" button re-creates the canvas and re-mounts the scene. No silent auto-retry. - **Reduced motion toggled at runtime**: When the user changes the `prefers-reduced-motion` OS preference while `/game/3d` is open, the scene MUST reflect the new state without requiring a page reload. - **Theme switched during animation**: When the user changes themes mid-animation, material color updates MUST NOT interrupt user-initiated camera motion or cause a visible jump. - **Pa11y exclusion regression**: If a future PR accidentally removes the exclusion in `config/pa11yci.json`, CI MUST fail loudly so the regression is caught before merge — not silently re-introduce the canvas-isn't-auditable error. @@ -133,10 +144,15 @@ The 3D scene resizes responsively, supports touch input for camera orbiting, and - **FR-001**: System MUST serve the route `/game/3d` rendering a `` element after a dynamic client-only import (no SSR for the canvas surface). - **FR-002**: Scene MUST reflect the current DaisyUI theme on initial render by reading CSS custom properties (`--p`, `--s`, `--b1`, etc.) from `:root`. - **FR-003**: Scene MUST re-extract DaisyUI colors and update materials/lights when the `data-theme` attribute on `` changes (via `MutationObserver`, following the `useMapTheme` precedent). -- **FR-004**: Scene MUST disable auto-rotation and idle animations when `prefers-reduced-motion: reduce` is set; user-initiated camera motion remains enabled. -- **FR-005**: Camera orbit controls MUST work via mouse, trackpad, and touch input (covered natively by drei's `OrbitControls`). +- **FR-004**: Scene MUST disable all autonomous animations (auto-orbit, idle bobbing, ambient rotation of accent objects, etc.) when `prefers-reduced-motion: reduce` is set; user-initiated camera motion remains enabled. +- **FR-005**: Camera orbit controls MUST work via mouse, trackpad, and touch input (covered natively by drei's `OrbitControls`), with the following constraints: + - **Polar angle constrained** so the user cannot flip under the ground plane (approximately `Math.PI / 2` upper bound on `maxPolarAngle`). + - **Full 360° yaw** (azimuth unconstrained). + - **Bounded zoom** — explicit `minDistance` and `maxDistance` set on `OrbitControls` so the user cannot zoom into the geometry or zoom out into empty space. + - **Auto-orbit when idle** — the camera slowly rotates around the scene by default. User input (drag/scroll/touch) suspends auto-orbit immediately; auto-orbit resumes after 3 seconds of no input. Auto-orbit MUST be disabled when `prefers-reduced-motion: reduce` is set (per FR-004). - **FR-006**: Route MUST be reachable via standard navigation (no auth required, no special headers, no payment gating). -- **FR-007**: Scene MUST use procedural geometry only for v1 — no `.glb`/`.gltf`/`.hdr` model or texture imports. Future asset-pipeline work is explicitly out of scope. +- **FR-007**: Scene MUST use procedural geometry only for v1 — no `.glb`/`.gltf`/`.hdr` model or texture imports. The v1 scene content is a ScriptHammer-themed sculpt: a stylized procedural hammer and anvil with DaisyUI-themed accent objects (lights, ground plane, ambient decoration) — all built from primitive Three.js geometries (`BoxGeometry`, `CylinderGeometry`, `TorusGeometry`, etc.). Future asset-pipeline work is explicitly out of scope. +- **FR-008**: When WebGL is unavailable OR the `webglcontextlost` event fires, the route MUST display a fallback panel containing (a) a static themed illustration (CSS/SVG hammer + anvil silhouette using DaisyUI color tokens), (b) an explanatory message naming WebGL as the requirement, and (c) a user-actionable "Retry" button that re-attempts canvas mount. No silent auto-retry. ### Non-Functional Requirements @@ -145,10 +161,11 @@ The 3D scene resizes responsively, supports touch input for camera orbiting, and - **NFR-003**: Static export (`next build` → `out/`) MUST succeed without runtime errors and MUST produce `out/game/3d/index.html`. - **NFR-004**: Device pixel ratio MUST be capped (e.g., `[1, 2]`) to bound GPU cost on high-DPR mobile devices. - **NFR-005**: `` MUST NOT be server-rendered (R3F is client-only). Achieved via `dynamic(() => import(...), { ssr: false })`. +- **NFR-006**: Observability scope is limited to GA4's default page view for `/game/3d`. No custom scene-loaded, scene-interaction, or theme-switched-in-scene events are emitted for v1. Privacy-friendly default per Constitution Principle VI; richer telemetry is deferred to a follow-up if engagement measurement becomes necessary. ### Key Entities -- **Scene**: The top-level R3F `` wrapper. Owns theme-aware color extraction, camera, lights, and the procedural geometry hierarchy. Re-renders on theme change. +- **Scene**: The top-level R3F `` wrapper. Owns theme-aware color extraction, camera, lights, and the procedural geometry hierarchy. Contains the v1 ScriptHammer-themed sculpt (hammer + anvil + DaisyUI-themed accents). Re-renders on theme change. - **Theme Tokens**: The DaisyUI CSS custom properties (`--p`, `--s`, `--b1`, etc.) read from `document.documentElement` at runtime. Converted to Three.js-compatible color values for materials and lights. - **Reduced-Motion Preference**: The OS-level `prefers-reduced-motion` media query result, watched via `matchMedia`'s `change` event for runtime toggling. @@ -164,6 +181,8 @@ The 3D scene resizes responsively, supports touch input for camera orbiting, and - **SC-006**: Pa11y CI completes successfully — `/game/3d` is skipped via the documented exclusion AND `/game` retains its prior coverage (no regression on feature `037-game-a11y-tests`). - **SC-007**: Homepage and other-route bundle sizes are unchanged before vs. after this feature lands (Three.js bundle is route-split to `/game/3d` only). - **SC-008**: Component structure validation (`pnpm run validate:structure`) passes for all new components under `src/components/game/`. +- **SC-009**: When WebGL is disabled in the browser (e.g., via Chrome flag `--disable-webgl`), the route renders the fallback panel (themed silhouette + message + retry button) within 2 seconds and the "Retry" button is keyboard-accessible. +- **SC-010**: With `prefers-reduced-motion: reduce` OFF, auto-orbit is observable within 3 seconds of idle and suspends within one frame of user input. ## Assumptions @@ -184,6 +203,7 @@ The 3D scene resizes responsively, supports touch input for camera orbiting, and - Payments, NFTs, or any Web3 integration. - Promoting the 3D scene to the homepage hero or root route (separate IA decision). - Replacing or moving the existing dice game at `/game`. +- Custom GA4 events beyond default page view (no scene-loaded, scene-interaction, or theme-switched-in-scene events for v1). ## Dependencies From 91872996b470b560d268882f62d9b26a95254cea Mon Sep 17 00:00:00 2001 From: TurtleWolfe Date: Fri, 15 May 2026 16:30:19 +0000 Subject: [PATCH 03/19] =?UTF-8?q?feat(spec):=20047=20=E2=80=94=20wireframe?= =?UTF-8?q?=20gate=20PASS=20for=20Three.js=20game=20(#48)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Constitution v1.0.2 Principle III mandatory wireframe gate completed. Two SVG wireframes generated, validated via shipped v5.0 validator, issues classified PATCH, resolved, and signed off into spec.md. WIREFRAMES (both PASS the validator's 40+ structural rules): - wireframes/01-game-3d-main.svg Desktop + mobile of /game/3d with canvas mounted. Visual: stylized procedural hammer + anvil + DaisyUI-themed accent orbs (the v1 sculpt). HUD overlays for orbit hint and auto- orbit indicator. Anchors annotations to US-001, US-002, US-003, US-005, FR-002, FR-003, FR-004, FR-005, FR-007, NFR-004, SC-001, SC-002, SC-010. - wireframes/02-game-3d-fallback.svg Desktop + mobile of /game/3d fallback panel. Visual: CSS/SVG hammer+anvil silhouette (DaisyUI tokens) with diagonal "off" cue, headline, body copy, 44×44 keyboard-accessible Retry button. Anchors annotations to US-001, US-002, US-004, FR-008, SC-006, SC-009. CHROME: - wireframes/includes/ seeded from features/foundation/003-user- authentication/wireframes/includes/ per wireframe-config.yml's "copy precedent once, then sync-wireframes.sh keeps it in sync" workflow. PATCH ROUNDS (all classified PATCH per features/CLAUDE.md decision table — no REGEN needed): Round 1 fixes (3 issues on 01, 8 on 02): SIGNATURE-003/004: signature must be left-aligned at x=40 and use trailing token "ScriptHammer" (not "SpecKit"). CALLOUT-003: callouts at (640,524) and (180,506) overlapped Retry buttons; relocated to (770,524) and (304,506). COLL-001: callout at cy=620 too close to footer; relocated up to (1180,568). CALLOUT-002: annotations had 6 concepts but mockup only had 5 callout circles; added 3 more on the desktop mockup. US-002: only 1 User Story badge present; added US-001, US-002, US-004 to annotation groups 4/5/6 so the fallback wireframe links back to its proper user story anchors. Round 2 fix (1 issue on 02): XML-004: validator regex parsed XML comment `at x=80..280, y=...` as an unquoted attribute. Reworded the comment. Final validator run: PASS for both files (zero errors). AUDIT TRAILS preserved per features/CLAUDE.md ("never delete the .issues.md files — they're the historical record"): - wireframes/01-game-3d-main.issues.md (3 resolved) - wireframes/02-game-3d-fallback.issues.md (9 resolved) SIGN-OFF: spec.md gains `## UI Mockup` block linking both approved wireframes and explicitly noting the wireframe gate is PASSED, so /speckit.plan is now unblocked. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../checklists/requirements.md | 3 +- .../enhancements/047-threejs-game/spec.md | 9 + .../wireframes/01-game-3d-main.issues.md | 35 +++ .../wireframes/01-game-3d-main.svg | 207 ++++++++++++++++++ .../wireframes/02-game-3d-fallback.issues.md | 61 ++++++ .../wireframes/02-game-3d-fallback.svg | 205 +++++++++++++++++ .../wireframes/includes/README.md | 103 +++++++++ .../wireframes/includes/defs.svg | 73 ++++++ .../wireframes/includes/footer-desktop.svg | 37 ++++ .../wireframes/includes/footer-mobile.svg | 73 ++++++ .../wireframes/includes/header-desktop.svg | 63 ++++++ .../wireframes/includes/header-mobile.svg | 64 ++++++ 12 files changed, 932 insertions(+), 1 deletion(-) create mode 100644 features/enhancements/047-threejs-game/wireframes/01-game-3d-main.issues.md create mode 100644 features/enhancements/047-threejs-game/wireframes/01-game-3d-main.svg create mode 100644 features/enhancements/047-threejs-game/wireframes/02-game-3d-fallback.issues.md create mode 100644 features/enhancements/047-threejs-game/wireframes/02-game-3d-fallback.svg create mode 100644 features/enhancements/047-threejs-game/wireframes/includes/README.md create mode 100644 features/enhancements/047-threejs-game/wireframes/includes/defs.svg create mode 100644 features/enhancements/047-threejs-game/wireframes/includes/footer-desktop.svg create mode 100644 features/enhancements/047-threejs-game/wireframes/includes/footer-mobile.svg create mode 100644 features/enhancements/047-threejs-game/wireframes/includes/header-desktop.svg create mode 100644 features/enhancements/047-threejs-game/wireframes/includes/header-mobile.svg diff --git a/features/enhancements/047-threejs-game/checklists/requirements.md b/features/enhancements/047-threejs-game/checklists/requirements.md index 24e57fe0..8198b9cb 100644 --- a/features/enhancements/047-threejs-game/checklists/requirements.md +++ b/features/enhancements/047-threejs-game/checklists/requirements.md @@ -35,4 +35,5 @@ - Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan`. - All items pass on first iteration; no rework required. - `/speckit.clarify` completed 2026-05-15 — 4 questions resolved (v1 scene content, fallback UX, camera control bounds, analytics scope). All 11 taxonomy categories now Clear. Spec amended with FR-008 (fallback panel), NFR-006 (observability scope), expanded FR-005 (camera constraints + auto-orbit), and SC-009/SC-010 (fallback + auto-orbit success criteria). -- Spec is ready for the v1.0.2 wireframe gate: wireframes MUST be authored and reviewed via `/speckit.wireframe.*` skills before `/speckit.plan`. +- Wireframe gate per Constitution v1.0.2 Principle III completed 2026-05-15. Two wireframes generated and approved: `01-game-3d-main.svg` and `02-game-3d-fallback.svg`. Both PASS the v5.0 validator (zero errors). Twelve patch-classified issues resolved across the two SVGs (signature alignment, callout positioning to avoid button overlap, callout count parity with annotation groups, US-badge minimum, XML hygiene). Audit trails preserved at `wireframes/01-game-3d-main.issues.md` and `wireframes/02-game-3d-fallback.issues.md`. Spec.md `## UI Mockup` block records the sign-off. +- `/speckit.plan` is unblocked. diff --git a/features/enhancements/047-threejs-game/spec.md b/features/enhancements/047-threejs-game/spec.md index dd0105a9..27b7a253 100644 --- a/features/enhancements/047-threejs-game/spec.md +++ b/features/enhancements/047-threejs-game/spec.md @@ -16,6 +16,15 @@ - Q: How constrained should camera orbit be? → A: Constrained polar angle (no flipping under ground plane), 360° yaw, bounded zoom (min/max distance), AND auto-orbit when idle (suspends on user input, resumes after 3s of inactivity). Auto-orbit MUST disable when `prefers-reduced-motion: reduce` is set (already covered by FR-004). - Q: Should /game/3d emit custom analytics events? → A: Page view only (rely on GA4's default page view). No custom scene-loaded or scene-interaction events for v1. Defer richer measurement to a follow-up feature if/when needed. +## UI Mockup + +Approved wireframes (signed off 2026-05-15 by `/speckit.wireframe.review` after validator PASS): + +- [`wireframes/01-game-3d-main.svg`](wireframes/01-game-3d-main.svg) — desktop + mobile of `/game/3d` with canvas mounted. Anchors US-001, US-002, US-003, US-005, FR-002, FR-003, FR-004, FR-005, FR-007, NFR-004, SC-001, SC-002, SC-010. +- [`wireframes/02-game-3d-fallback.svg`](wireframes/02-game-3d-fallback.svg) — desktop + mobile of `/game/3d` fallback panel (WebGL unavailable OR `webglcontextlost` event fired). Anchors US-001, US-002, US-004, FR-008, SC-006, SC-009. + +Wireframe gate per Constitution v1.0.2 Principle III is now PASSED. `/speckit.plan` is unblocked. + --- diff --git a/features/enhancements/047-threejs-game/wireframes/01-game-3d-main.issues.md b/features/enhancements/047-threejs-game/wireframes/01-game-3d-main.issues.md new file mode 100644 index 00000000..1716612f --- /dev/null +++ b/features/enhancements/047-threejs-game/wireframes/01-game-3d-main.issues.md @@ -0,0 +1,35 @@ +# Issues: 01-game-3d-main.svg + +**Feature:** 047-threejs-game +**SVG:** 01-game-3d-main.svg +**Last Review:** 2026-05-15 +**Validator:** v5.0 + +--- + +## Summary + +| Status | Count | +| -------- | ----- | +| Open | 0 | +| Resolved | 3 | + +--- + +## Resolved Issues (2026-05-15 Review) + +### Signature + +| ID | Issue | Code | Classification | Resolution | +| ---- | ------------------------------------------------------------------------------------------------------------------------ | ------------- | -------------- | ---------------------------------------------------------------------------------------- | +| X-01 | Signature must be left-aligned at x=40, got x=960 | SIGNATURE-003 | PATCH | Moved signature `` from `x="960" text-anchor="middle"` to `x="40"` (left-aligned). | +| X-02 | Signature must NOT use `text-anchor="middle"` — use left-alignment at x=40 | SIGNATURE-003 | PATCH | Removed `text-anchor="middle"` from signature. | +| X-03 | Signature format wrong: `'047:01 \| Three.js Game - Main \| SpecKit'` — must be `NNN:NN \| Feature Name \| ScriptHammer` | SIGNATURE-004 | PATCH | Renamed trailing token `SpecKit` → `ScriptHammer` per project signature convention. | + +--- + +## Notes + +- All issues classified as PATCH per `features/CLAUDE.md` decision table (cosmetic/positional, not layout/spacing). +- Validator re-run after patches: **PASS** (zero errors). +- Auto-generated initially by validator v5.0; manually annotated with resolutions by `/speckit.wireframe.review` (2026-05-15). diff --git a/features/enhancements/047-threejs-game/wireframes/01-game-3d-main.svg b/features/enhancements/047-threejs-game/wireframes/01-game-3d-main.svg new file mode 100644 index 00000000..f6350e8b --- /dev/null +++ b/features/enhancements/047-threejs-game/wireframes/01-game-3d-main.svg @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + 3D GAME (THREE.JS SCENE) - /game/3d + + + DESKTOP (16:9) + MOBILE + + + + + + + + 3D Game (Three.js) + /game / 3d + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Drag to orbit · Scroll to zoom + + + + + Auto-orbit on + + + + Theme: winter · DPR: 2.0 · prefers-reduced-motion: no-preference + + + + + 1 + 2 + 3 + 4 + + + + + + + + + 3D Game + + + + + + + + + + + + + + + + + + + + + + + Pinch to zoom · Drag to orbit + + + + + Auto-orbit on (resumes after 3s idle) + + + DPR capped [1,2] · WebGL OK + + + + + 1 + 5 + + + + + + + + + + 1 + Canvas mount + orbit controls + US-001 + FR-005 + SC-001 + Dynamic-import-no-SSR. drei OrbitControls; + polar angle & zoom bounded; 360° yaw. + + + + + + 2 + Theme-aware materials + US-002 + FR-002 + FR-003 + SC-002 + Reads DaisyUI CSS custom props (--p, --s, + --b1); MutationObserver on data-theme. + + + + + + 3 + Auto-orbit + reduced motion + US-003 + FR-004 + SC-010 + Auto-orbit suspends on input, resumes after + 3s idle. Disabled under reduce-motion. + + + + + + 4 + Procedural sculpt only (no .glb) + US-001 + FR-007 + v1 = hammer + anvil + DaisyUI accent orbs. + Boxes, cylinders, tori only. No asset import. + + + + + + 5 + Mobile touch + DPR cap + US-005 + FR-005 + NFR-004 + Touch-drag orbits via drei. DPR capped + [1, 2] to bound GPU on high-DPR devices. + + + + + Status bar legend + Theme, DPR, and reduced-motion preference + surface in the canvas footer for debugging. + + + + + 047:01 | Three.js Game - Main | ScriptHammer + diff --git a/features/enhancements/047-threejs-game/wireframes/02-game-3d-fallback.issues.md b/features/enhancements/047-threejs-game/wireframes/02-game-3d-fallback.issues.md new file mode 100644 index 00000000..fbe2d7a7 --- /dev/null +++ b/features/enhancements/047-threejs-game/wireframes/02-game-3d-fallback.issues.md @@ -0,0 +1,61 @@ +# Issues: 02-game-3d-fallback.svg + +**Feature:** 047-threejs-game +**SVG:** 02-game-3d-fallback.svg +**Last Review:** 2026-05-15 +**Validator:** v5.0 + +--- + +## Summary + +| Status | Count | +| -------- | ----- | +| Open | 0 | +| Resolved | 9 | + +--- + +## Resolved Issues (2026-05-15 Review) + +### Callout positioning + +| ID | Issue | Code | Classification | Resolution | +| ---- | ----------------------------------------------------------------------------- | ----------- | -------------- | ------------------------------------------------------------------------------------------------------ | +| X-01 | Callout at cy=620 too close to desktop footer (y=640) — move up | COLL-001 | PATCH | Moved callout 3 from (1056,620) to (1180,568) — pushed up to status-bar level, away from footer. | +| X-02 | Callout at (640,524) overlaps button at (540,502) — place after (right/below) | CALLOUT-003 | PATCH | Moved callout 2 from (640,524) to (770,524) — now sits to the right of the desktop Retry button. | +| X-03 | Callout at (180,506) overlaps button at (80,484) — place after (right/below) | CALLOUT-003 | PATCH | Moved mobile callout 2 from (180,506) to (304,506) — now sits to the right of the mobile Retry button. | + +### Callout count vs annotation count + +| ID | Issue | Code | Classification | Resolution | +| ---- | --------------------------------------------------------------------------------------- | ----------- | -------------- | ----------------------------------------------------------------------------------------------------------------- | +| X-04 | Mockup missing 1 callout circles (annotation has 6 concepts, mockup only illustrates 5) | CALLOUT-002 | PATCH | Added callouts 4, 5, 6 on desktop mockup at distinct concept locations (silhouette context, body copy, headline). | + +### User Story badge minimum + +| ID | Issue | Code | Classification | Resolution | +| ---- | ------------------------------------------------------------- | ------ | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| X-05 | Only 1 User Story badges found — need at least 3 User Stories | US-002 | PATCH | Added US badges to annotation groups 4, 5, 6 (US-001 — fallback as degraded visit path; US-004 — Pa11y baseline; US-002 — visual theme consistency). | + +### Signature + +| ID | Issue | Code | Classification | Resolution | +| ---- | -------------------------------------------------------------------------- | ------------- | -------------- | ------------------------------------------------------------------------- | +| X-06 | Signature must be left-aligned at x=40, got x=960 | SIGNATURE-003 | PATCH | Moved signature `` from `x="960" text-anchor="middle"` to `x="40"`. | +| X-07 | Signature must NOT use `text-anchor="middle"` — use left-alignment at x=40 | SIGNATURE-003 | PATCH | Removed `text-anchor="middle"`. | +| X-08 | Signature format wrong: `'047:02 \| Three.js Game - Fallback \| SpecKit'` | SIGNATURE-004 | PATCH | Renamed trailing token `SpecKit` → `ScriptHammer`. | + +### XML hygiene (regression introduced + fixed in same session) + +| ID | Issue | Code | Classification | Resolution | +| ---- | ------------------------------------------- | ------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| X-09 | Attribute 'x' has unquoted value '80..280,' | XML-004 | PATCH | Validator's regex parsed an XML comment containing `at x=80..280, y=...` as an attribute. Reworded the comment to drop the literals. | + +--- + +## Notes + +- All 9 issues classified as PATCH per `features/CLAUDE.md` decision table (cosmetic/positional/comment-text — no layout overlaps, no spacing, no missing sections requiring REGEN). +- Validator re-run after patches: **PASS** (zero errors). +- Auto-generated initially by validator v5.0; manually annotated with resolutions by `/speckit.wireframe.review` (2026-05-15). diff --git a/features/enhancements/047-threejs-game/wireframes/02-game-3d-fallback.svg b/features/enhancements/047-threejs-game/wireframes/02-game-3d-fallback.svg new file mode 100644 index 00000000..920fd34d --- /dev/null +++ b/features/enhancements/047-threejs-game/wireframes/02-game-3d-fallback.svg @@ -0,0 +1,205 @@ + + + + + + + + + + + + + 3D GAME (THREE.JS SCENE) - /game/3d (FALLBACK) + + + DESKTOP (16:9) + MOBILE + + + + + + + + 3D Game (Three.js) + /game / 3d + + + + + + + + + + + + + + + + + + + + + + + + + + 3D Content Unavailable + + + 3D content requires WebGL. Your browser does not support it, + or the graphics context was lost. + + + + + Retry + + + + No auto-retry — user-initiated only · 3D content excluded from Pa11y scans + + + + WebGL: unavailable · webglcontextlost: handled · keyboard: Retry focusable + + + + + 1 + 2 + 3 + 4 + 5 + 6 + + + + + + + + + 3D Game + + + + + + + + + + + + + + + + + + + 3D Content + Unavailable + + + WebGL is required. Your browser + does not support it, or the + graphics context was lost. + + + + + Retry + + + + User-initiated retry only. + No auto-retry on context loss. + + + WebGL: unavailable + + + + + 1 + 2 + + + + + + + + + + 1 + Themed silhouette fallback panel + FR-008 + SC-009 + CSS/SVG hammer + anvil silhouette using + DaisyUI color tokens. No canvas, no WebGL. + + + + + + 2 + Retry button - user-initiated only + FR-008 + SC-009 + 44×44 button, keyboard-focusable. Triggers + canvas re-mount. No silent auto-retry. + + + + + + 3 + Pa11y exclusion (manual a11y review) + US-004 + SC-006 + /game/3d excluded from Pa11y (canvas not + auditable). Fallback panel is auditable. + + + + + + 4 + When the fallback renders + US-001 + Triggers: (a) WebGL not available at mount, + (b) `webglcontextlost` event fires at runtime. + + + + + + 5 + Fallback is Pa11y-auditable + US-004 + Headings, body copy, Retry button: all + standard DOM. Pa11y exclusion scopes to canvas. + + + + + + 6 + Silhouette uses opacity to read as "off" + US-002 + 85% opacity + thin diagonal line cue convey + "3D unavailable" without alarming red iconography. + + + + + 047:02 | Three.js Game - Fallback | ScriptHammer + diff --git a/features/enhancements/047-threejs-game/wireframes/includes/README.md b/features/enhancements/047-threejs-game/wireframes/includes/README.md new file mode 100644 index 00000000..8ff53570 --- /dev/null +++ b/features/enhancements/047-threejs-game/wireframes/includes/README.md @@ -0,0 +1,103 @@ +# Wireframe Includes + +Reusable wireframe components with build-time injection. Update header/footer once, regenerate wireframes to see changes. + +## Architecture: Build-Time Injection + +**Why not runtime ``?** Nested external SVG references don't work - browsers can't resolve `` inside a cloned external group. + +**Solution:** `/wireframe` reads these files and **injects content directly** into generated SVGs. Icons are inline paths, not symbol references. + +## Files + +| File | Description | Group ID | +| -------------------- | ----------------------------------------- | ---------------------- | +| `defs.svg` | Master icon library (reference only) | N/A | +| `header-desktop.svg` | Desktop header (logo, nav, icons, avatar) | `#desktop-header` | +| `header-mobile.svg` | Mobile status bar + header | `#mobile-header-group` | +| `footer-mobile.svg` | Mobile bottom nav (4 tabs) | `#mobile-bottom-nav` | + +## How It Works + +1. `/wireframe` reads include files at generation time +2. Extracts content inside the `` group +3. Injects directly into the generated SVG +4. No external references at runtime + +``` +Include File Generated Wireframe +┌─────────────────────┐ ┌─────────────────────────────┐ +│ │ inject│ │ +│ │ │ │ +│ │ │ ← inline │ +│ │ │ │ +└─────────────────────┘ └─────────────────────────────┘ +``` + +## Updating Components + +To update all wireframes with new header/footer: + +1. Edit the include file (e.g., `header-desktop.svg`) +2. Run `/wireframe [feature]` to regenerate (or regenerate all) +3. Changes are embedded in the regenerated SVGs + +This maintains a **single source of truth** while ensuring icons always render. + +## Icons + +All icons are inline `` elements from Heroicons 24x24 solid. Icons embedded directly in include files - no `` to external files. + +### Icon Reference (from defs.svg) + +| Icon | Use Case | +| -------------- | ----------------- | +| `icon-cog` | Settings gear | +| `icon-eye` | Accessibility | +| `icon-home` | Home nav | +| `icon-bolt` | Features | +| `icon-doc` | Docs | +| `icon-user` | Account/profile | +| `icon-signal` | Mobile status bar | +| `icon-battery` | Mobile status bar | +| `icon-menu` | Hamburger menu | + +## Active States + +Headers and footers have all items **inactive by default**. To set an active nav item, overlay after the include: + +### Desktop Nav Active State + +```xml + + + + Home + +``` + +### Mobile Bottom Nav Active State + +```xml + + + + + + + Home + +``` + +## Colors Reference + +| Color | Hex | Use | +| ---------------- | --------- | -------------------------- | +| Primary | `#8b5cf6` | Brand, active states, CTAs | +| Dark text | `#1a1a2e` | Headings, icons | +| Body text | `#374151` | Regular text | +| Muted text | `#4b5563` | Secondary text | +| Parchment | `#e8d4b8` | Main background | +| Darker parchment | `#dcc8a8` | Headers, cards | +| Border | `#b8a080` | Strokes | diff --git a/features/enhancements/047-threejs-game/wireframes/includes/defs.svg b/features/enhancements/047-threejs-game/wireframes/includes/defs.svg new file mode 100644 index 00000000..94fe167a --- /dev/null +++ b/features/enhancements/047-threejs-game/wireframes/includes/defs.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/features/enhancements/047-threejs-game/wireframes/includes/footer-desktop.svg b/features/enhancements/047-threejs-game/wireframes/includes/footer-desktop.svg new file mode 100644 index 00000000..28568967 --- /dev/null +++ b/features/enhancements/047-threejs-game/wireframes/includes/footer-desktop.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + ScriptHammer + Product planning template + + + + + Features + Documentation + GitHub + Privacy + Terms + Built with Next.js 15, Supabase, and Tailwind CSS + + + + + + © ScriptHammer 2026 + MIT License + + + diff --git a/features/enhancements/047-threejs-game/wireframes/includes/footer-mobile.svg b/features/enhancements/047-threejs-game/wireframes/includes/footer-mobile.svg new file mode 100644 index 00000000..7c42313f --- /dev/null +++ b/features/enhancements/047-threejs-game/wireframes/includes/footer-mobile.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + Home + + + + + + + + + Features + + + + + + + + + + Docs + + + + + + + + + Account + + + + + diff --git a/features/enhancements/047-threejs-game/wireframes/includes/header-desktop.svg b/features/enhancements/047-threejs-game/wireframes/includes/header-desktop.svg new file mode 100644 index 00000000..056709af --- /dev/null +++ b/features/enhancements/047-threejs-game/wireframes/includes/header-desktop.svg @@ -0,0 +1,63 @@ + + + + + + + + + + ScriptHammer + + + + + + + + Home + + + + + Features + + + + + Docs + + + + + Account + + + + + + + + + + + + + + + + + + + U + + + diff --git a/features/enhancements/047-threejs-game/wireframes/includes/header-mobile.svg b/features/enhancements/047-threejs-game/wireframes/includes/header-mobile.svg new file mode 100644 index 00000000..6635ff23 --- /dev/null +++ b/features/enhancements/047-threejs-game/wireframes/includes/header-mobile.svg @@ -0,0 +1,64 @@ + + + + + + + + 9:41 + + + + + + + + + + + + + + + + + + + + + + + + + + + ScriptHammer + + + + + + + + + + + + + + + + + U + + + + From 2e8bf552a2870a0f8452234be9f2aa4b44c2608f Mon Sep 17 00:00:00 2001 From: TurtleWolfe Date: Sat, 16 May 2026 19:31:52 +0000 Subject: [PATCH 04/19] =?UTF-8?q?feat(spec):=20047=20=E2=80=94=20regen=20w?= =?UTF-8?q?ireframes=20with=20canonical=20brand=20SVG=20symbols=20(#48)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace earlier hand-drawn "hammer + anvil + DaisyUI accent orbs" scene geometry with the three canonical ScriptHammer brand SVGs, inlined as defs and referenced via . Geometry data copied verbatim from public/scripthammer-logo.svg, public/script-tags.svg, and public/printing-mallet.svg. Composition follows the canonical layering rules from src/components/atomic/SpinningLogo/LayeredScriptHammerLogo.tsx. SPEC.MD CHANGES - Drop "anvil" language throughout (anvil is a blacksmith tool, not a Gutenberg-press tool; was anatomically wrong from the start). - FR-007, Key Entities → Scene, US-1 acceptance scenario, Clarifications Q1 + Q2 all updated to describe the canonical 3-asset composition: silver cog ring (mirror of scripthammer-logo.svg) + golden code-tag brackets (mirror of script-tags.svg) + printing-mallet (mirror of printing-mallet.svg, redrawn in PR #96 with historically-accurate compositor's-mallet anatomy). WIREFRAME CHANGES (both 01-main and 02-fallback) - Three defs added per SVG: #brand-cog, #brand-script-tags, #brand-printing-mallet. Each declares viewBox="0 0 400 400" matching the source assets, so with width/height scales predictably. - Scene region replaced with three calls at the layered positions: Layer 1 (BACK): mallet at top:58% left:42%, sized 65% of cog Layer 2 (MIDDLE): cog ring at 100%, centered Layer 3 (FRONT): brackets at 68%, centered - Solid silver fill (#c0c0c0) substitutes the source's metallic gradients to avoid id-collision overhead at wireframe scale. - File restructured: gradient closes early, then background + centered title + section labels, then a second with the brand symbol defs. This ensures the validator's 2000-char G-024/SECTION-001 scan window catches the structural elements. VALIDATOR - Both wireframes PASS the v5.0 validator (0 errors). - Issue files updated with regeneration history. NOTES - Visual review of the rendered SVGs (via PNG screenshots and direct browser navigation) confirmed the layered brand composition reads correctly. Iteration on exact mallet/bracket/cog proportions remains open for designer polish in future passes, but the architectural pattern (canonical-SVG-via-symbol) is now in place so refinement only requires updating the public/*.svg source. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../enhancements/047-threejs-game/spec.md | 26 ++- .../wireframes/01-game-3d-main.issues.md | 41 ++-- .../wireframes/01-game-3d-main.svg | 185 ++++++++++++++---- .../wireframes/02-game-3d-fallback.issues.md | 51 ++--- .../wireframes/02-game-3d-fallback.svg | 137 ++++++++++--- 5 files changed, 312 insertions(+), 128 deletions(-) diff --git a/features/enhancements/047-threejs-game/spec.md b/features/enhancements/047-threejs-game/spec.md index 27b7a253..6b8909c3 100644 --- a/features/enhancements/047-threejs-game/spec.md +++ b/features/enhancements/047-threejs-game/spec.md @@ -11,8 +11,14 @@ ### Session 2026-05-15 -- Q: What is the canonical v1 scene content? → A: ScriptHammer-themed sculpt: stylized procedural hammer + anvil + DaisyUI-themed accents (procedural only — no .glb imports). -- Q: WebGL-unavailable / GPU-context-lost fallback UX? → A: Static themed illustration (CSS/SVG hammer+anvil silhouette) + explanatory message + retry button. No auto-retry on context loss; user clicks retry explicitly. +- Q: What is the canonical v1 scene content? → A: A 3D composition of the **three canonical ScriptHammer brand assets**, all mirrored from their SVG sources: + 1. **Spinning silver cog ring** (procedural ring + 20 trapezoidal gear teeth + rivets, mirroring `public/scripthammer-logo.svg`) — the steampunk-machinery motif. Rotates slowly per the auto-orbit behavior. + 2. **Glowing golden code brackets `< >`** (procedural extruded paths in a brass/bronze gradient, mirroring `public/script-tags.svg`) — the "Script" half of ScriptHammer. Centered, slight emissive glow. + 3. **Printing-mallet** (procedural primitives mirroring `public/printing-mallet.svg` — squat boxy beech head + thin handle + visible wedge) — the "Hammer" half. Rests near the brackets. + + DaisyUI theme tokens drive: cog rim color, bracket emissive glow color, mallet base material color, scene background. Procedural geometry only (no `.glb`/`.gltf` imports). _Revised 2026-05-15: dropped earlier "hammer + anvil + accent orbs" framing — anvils are blacksmith tools, not Gutenberg-press tools, and the orbs were filler. The three actual brand SVGs replace them._ + +- Q: WebGL-unavailable / GPU-context-lost fallback UX? → A: Static themed illustration (CSS/SVG composition of the three brand assets — cog ring + brackets + printing-mallet, all from their canonical `public/*.svg` sources, recolored via DaisyUI tokens) + explanatory message + retry button. No auto-retry on context loss; user clicks retry explicitly. - Q: How constrained should camera orbit be? → A: Constrained polar angle (no flipping under ground plane), 360° yaw, bounded zoom (min/max distance), AND auto-orbit when idle (suspends on user input, resumes after 3s of inactivity). Auto-orbit MUST disable when `prefers-reduced-motion: reduce` is set (already covered by FR-004). - Q: Should /game/3d emit custom analytics events? → A: Page view only (rely on GA4's default page view). No custom scene-loaded or scene-interaction events for v1. Defer richer measurement to a follow-up feature if/when needed. @@ -66,7 +72,7 @@ A user navigates to `/game/3d` and sees an interactive Three.js scene render aft **Acceptance Scenarios**: 1. **Given** a user navigates to `/game/3d`, **When** the page hydrates, **Then** a loading spinner displays until the canvas mounts -2. **Given** the canvas has mounted, **When** the scene initializes, **Then** a `` element renders the v1 scene content (stylized procedural hammer + anvil + DaisyUI-themed accents — no `.glb`/`.gltf` imports) +2. **Given** the canvas has mounted, **When** the scene initializes, **Then** a `` element renders the v1 scene content (procedural composition of the three brand assets — silver cog ring mirroring `public/scripthammer-logo.svg`, golden `< >` brackets mirroring `public/script-tags.svg`, printing-mallet mirroring `public/printing-mallet.svg` — no `.glb`/`.gltf` imports) 3. **Given** the scene is rendering, **When** the user drags or scrolls, **Then** the camera responds via orbit controls 4. **Given** a fresh visit, **When** the production static export is served, **Then** the page works end-to-end with no server runtime (no `/api/` routes) @@ -138,7 +144,7 @@ The 3D scene resizes responsively, supports touch input for camera orbiting, and ### Edge Cases -- **WebGL unavailable**: When the browser does not support WebGL (very old browsers, restricted enterprise environments), the canvas MUST be replaced by a fallback panel containing: a static themed illustration (CSS/SVG hammer + anvil silhouette using DaisyUI tokens for color), an explanatory message naming WebGL as the requirement, and a "Retry" button that re-attempts canvas mount. No auto-retry — user-initiated only. +- **WebGL unavailable**: When the browser does not support WebGL (very old browsers, restricted enterprise environments), the canvas MUST be replaced by a fallback panel containing: a static themed illustration (CSS/SVG composition of the three brand assets — cog ring + `< >` brackets + printing-mallet — recolored via DaisyUI tokens), an explanatory message naming WebGL as the requirement, and a "Retry" button that re-attempts canvas mount. No auto-retry — user-initiated only. - **GPU context loss**: When the browser releases the WebGL context (memory pressure, tab backgrounded for too long), the scene MUST handle the `webglcontextlost` event by swapping to the same fallback panel described above. The "Retry" button re-creates the canvas and re-mounts the scene. No silent auto-retry. - **Reduced motion toggled at runtime**: When the user changes the `prefers-reduced-motion` OS preference while `/game/3d` is open, the scene MUST reflect the new state without requiring a page reload. - **Theme switched during animation**: When the user changes themes mid-animation, material color updates MUST NOT interrupt user-initiated camera motion or cause a visible jump. @@ -160,8 +166,14 @@ The 3D scene resizes responsively, supports touch input for camera orbiting, and - **Bounded zoom** — explicit `minDistance` and `maxDistance` set on `OrbitControls` so the user cannot zoom into the geometry or zoom out into empty space. - **Auto-orbit when idle** — the camera slowly rotates around the scene by default. User input (drag/scroll/touch) suspends auto-orbit immediately; auto-orbit resumes after 3 seconds of no input. Auto-orbit MUST be disabled when `prefers-reduced-motion: reduce` is set (per FR-004). - **FR-006**: Route MUST be reachable via standard navigation (no auth required, no special headers, no payment gating). -- **FR-007**: Scene MUST use procedural geometry only for v1 — no `.glb`/`.gltf`/`.hdr` model or texture imports. The v1 scene content is a ScriptHammer-themed sculpt: a stylized procedural hammer and anvil with DaisyUI-themed accent objects (lights, ground plane, ambient decoration) — all built from primitive Three.js geometries (`BoxGeometry`, `CylinderGeometry`, `TorusGeometry`, etc.). Future asset-pipeline work is explicitly out of scope. -- **FR-008**: When WebGL is unavailable OR the `webglcontextlost` event fires, the route MUST display a fallback panel containing (a) a static themed illustration (CSS/SVG hammer + anvil silhouette using DaisyUI color tokens), (b) an explanatory message naming WebGL as the requirement, and (c) a user-actionable "Retry" button that re-attempts canvas mount. No silent auto-retry. +- **FR-007**: Scene MUST use procedural geometry only for v1 — no `.glb`/`.gltf`/`.hdr` model or texture imports. The v1 scene content is a 3D composition of the three canonical ScriptHammer brand assets, each mirrored as procedural geometry from its SVG source: + - **Silver cog ring** with 20 trapezoidal gear teeth and rivets (mirror of `public/scripthammer-logo.svg`); rotates slowly per the auto-orbit behavior. + - **Golden `< >` code brackets** rendered as extruded bracket paths with a brass/bronze gradient material (mirror of `public/script-tags.svg`); slight emissive glow. + - **Printing-mallet** (squat boxy beech head, thin handle, locking wedge) procedurally constructed from primitives (mirror of `public/printing-mallet.svg`). + + All geometry built from primitive Three.js shapes (`BoxGeometry`, `CylinderGeometry`, `TorusGeometry`, `RingGeometry`, `ExtrudeGeometry`, etc.). Future asset-pipeline work for loading the SVGs directly is explicitly out of scope. + +- **FR-008**: When WebGL is unavailable OR the `webglcontextlost` event fires, the route MUST display a fallback panel containing (a) a static themed illustration (CSS/SVG composition of the three brand assets — cog ring, `< >` brackets, printing-mallet — referencing or recreating their canonical `public/*.svg` sources, recolored via DaisyUI color tokens), (b) an explanatory message naming WebGL as the requirement, and (c) a user-actionable "Retry" button that re-attempts canvas mount. No silent auto-retry. ### Non-Functional Requirements @@ -174,7 +186,7 @@ The 3D scene resizes responsively, supports touch input for camera orbiting, and ### Key Entities -- **Scene**: The top-level R3F `` wrapper. Owns theme-aware color extraction, camera, lights, and the procedural geometry hierarchy. Contains the v1 ScriptHammer-themed sculpt (hammer + anvil + DaisyUI-themed accents). Re-renders on theme change. +- **Scene**: The top-level R3F `` wrapper. Owns theme-aware color extraction, camera, lights, and the procedural geometry hierarchy. Contains the v1 brand composition: silver cog ring (mirroring `public/scripthammer-logo.svg`) + golden `< >` brackets (mirroring `public/script-tags.svg`) + printing-mallet (mirroring `public/printing-mallet.svg`). Re-renders on theme change. - **Theme Tokens**: The DaisyUI CSS custom properties (`--p`, `--s`, `--b1`, etc.) read from `document.documentElement` at runtime. Converted to Three.js-compatible color values for materials and lights. - **Reduced-Motion Preference**: The OS-level `prefers-reduced-motion` media query result, watched via `matchMedia`'s `change` event for runtime toggling. diff --git a/features/enhancements/047-threejs-game/wireframes/01-game-3d-main.issues.md b/features/enhancements/047-threejs-game/wireframes/01-game-3d-main.issues.md index 1716612f..7967f54d 100644 --- a/features/enhancements/047-threejs-game/wireframes/01-game-3d-main.issues.md +++ b/features/enhancements/047-threejs-game/wireframes/01-game-3d-main.issues.md @@ -2,34 +2,45 @@ **Feature:** 047-threejs-game **SVG:** 01-game-3d-main.svg -**Last Review:** 2026-05-15 +**Last Review:** 2026-05-16 (post-regen) **Validator:** v5.0 --- ## Summary -| Status | Count | -| -------- | ----- | -| Open | 0 | -| Resolved | 3 | +| Status | Count | +| -------- | ------------------------------ | +| Open | 0 | +| Resolved | All initial + regen iterations | --- -## Resolved Issues (2026-05-15 Review) +## Regeneration history -### Signature +### 2026-05-16 — Brand-asset symbol-based regeneration -| ID | Issue | Code | Classification | Resolution | -| ---- | ------------------------------------------------------------------------------------------------------------------------ | ------------- | -------------- | ---------------------------------------------------------------------------------------- | -| X-01 | Signature must be left-aligned at x=40, got x=960 | SIGNATURE-003 | PATCH | Moved signature `` from `x="960" text-anchor="middle"` to `x="40"` (left-aligned). | -| X-02 | Signature must NOT use `text-anchor="middle"` — use left-alignment at x=40 | SIGNATURE-003 | PATCH | Removed `text-anchor="middle"` from signature. | -| X-03 | Signature format wrong: `'047:01 \| Three.js Game - Main \| SpecKit'` — must be `NNN:NN \| Feature Name \| ScriptHammer` | SIGNATURE-004 | PATCH | Renamed trailing token `SpecKit` → `ScriptHammer` per project signature convention. | +The hand-drawn "hammer + anvil + DaisyUI accent orbs" composition was replaced with the three canonical ScriptHammer brand SVGs inlined as `` definitions in the SVG's ``, then referenced via `` blocks. The first holds the small gradient defs. The background `` + centered title `` + section labels then follow, ensuring those structural elements land within the validator's 2000-char G-024/SECTION-001 scan window. A second `` block holds the large brand symbol defs. + +### 2026-05-15 — Initial review (pre-regen) + +3 patch-classified issues resolved on the original hammer+anvil composition (signature alignment x3). All passed v5.0 validator before regeneration. --- ## Notes -- All issues classified as PATCH per `features/CLAUDE.md` decision table (cosmetic/positional, not layout/spacing). -- Validator re-run after patches: **PASS** (zero errors). -- Auto-generated initially by validator v5.0; manually annotated with resolutions by `/speckit.wireframe.review` (2026-05-15). +- Validator status: PASS (0 errors) at every recorded final review. +- Callout positions: callout 1 at the center of the brackets, callout 2 at the cog rim top, callout 3 at the auto-orbit chip, callout 4 over the mallet head. diff --git a/features/enhancements/047-threejs-game/wireframes/01-game-3d-main.svg b/features/enhancements/047-threejs-game/wireframes/01-game-3d-main.svg index f6350e8b..a6df6e4e 100644 --- a/features/enhancements/047-threejs-game/wireframes/01-game-3d-main.svg +++ b/features/enhancements/047-threejs-game/wireframes/01-game-3d-main.svg @@ -13,17 +13,116 @@ + + - - 3D GAME (THREE.JS SCENE) - /game/3d - - DESKTOP (16:9) MOBILE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -36,28 +135,29 @@ - + + - - - - - - - - - - - - - + + + + + + + + - - - - @@ -79,9 +179,9 @@ 1 - 2 + 2 3 - 4 + 4 @@ -95,21 +195,24 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + @@ -126,8 +229,8 @@ - 1 - 5 + 1 + 5 diff --git a/features/enhancements/047-threejs-game/wireframes/02-game-3d-fallback.issues.md b/features/enhancements/047-threejs-game/wireframes/02-game-3d-fallback.issues.md index fbe2d7a7..e6181386 100644 --- a/features/enhancements/047-threejs-game/wireframes/02-game-3d-fallback.issues.md +++ b/features/enhancements/047-threejs-game/wireframes/02-game-3d-fallback.issues.md @@ -2,60 +2,37 @@ **Feature:** 047-threejs-game **SVG:** 02-game-3d-fallback.svg -**Last Review:** 2026-05-15 +**Last Review:** 2026-05-16 (post-regen) **Validator:** v5.0 --- ## Summary -| Status | Count | -| -------- | ----- | -| Open | 0 | -| Resolved | 9 | +| Status | Count | +| -------- | ------------------------------ | +| Open | 0 | +| Resolved | All initial + regen iterations | --- -## Resolved Issues (2026-05-15 Review) +## Regeneration history -### Callout positioning +### 2026-05-16 — Brand-asset symbol-based regeneration -| ID | Issue | Code | Classification | Resolution | -| ---- | ----------------------------------------------------------------------------- | ----------- | -------------- | ------------------------------------------------------------------------------------------------------ | -| X-01 | Callout at cy=620 too close to desktop footer (y=640) — move up | COLL-001 | PATCH | Moved callout 3 from (1056,620) to (1180,568) — pushed up to status-bar level, away from footer. | -| X-02 | Callout at (640,524) overlaps button at (540,502) — place after (right/below) | CALLOUT-003 | PATCH | Moved callout 2 from (640,524) to (770,524) — now sits to the right of the desktop Retry button. | -| X-03 | Callout at (180,506) overlaps button at (80,484) — place after (right/below) | CALLOUT-003 | PATCH | Moved mobile callout 2 from (180,506) to (304,506) — now sits to the right of the mobile Retry button. | +The hand-drawn "hammer + anvil silhouette" composition was replaced with the same three canonical brand SVG symbols used by the main wireframe (`#brand-printing-mallet`, `#brand-cog`, `#brand-script-tags`), wrapped in a parent group with `opacity="0.5"` to read as "off / unavailable". -### Callout count vs annotation count +Composition matches `src/components/atomic/SpinningLogo/LayeredScriptHammerLogo.tsx` layering rules (mallet BACK, cog MIDDLE, brackets FRONT) at a smaller scale (240×240 desktop cog, 116×116 mobile). -| ID | Issue | Code | Classification | Resolution | -| ---- | --------------------------------------------------------------------------------------- | ----------- | -------------- | ----------------------------------------------------------------------------------------------------------------- | -| X-04 | Mockup missing 1 callout circles (annotation has 6 concepts, mockup only illustrates 5) | CALLOUT-002 | PATCH | Added callouts 4, 5, 6 on desktop mockup at distinct concept locations (silhouette context, body copy, headline). | +The earlier "diagonal stripe over headline" cue was dropped — the group opacity alone communicates "unavailable." -### User Story badge minimum +### 2026-05-15 — Initial review (pre-regen) -| ID | Issue | Code | Classification | Resolution | -| ---- | ------------------------------------------------------------- | ------ | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -| X-05 | Only 1 User Story badges found — need at least 3 User Stories | US-002 | PATCH | Added US badges to annotation groups 4, 5, 6 (US-001 — fallback as degraded visit path; US-004 — Pa11y baseline; US-002 — visual theme consistency). | - -### Signature - -| ID | Issue | Code | Classification | Resolution | -| ---- | -------------------------------------------------------------------------- | ------------- | -------------- | ------------------------------------------------------------------------- | -| X-06 | Signature must be left-aligned at x=40, got x=960 | SIGNATURE-003 | PATCH | Moved signature `` from `x="960" text-anchor="middle"` to `x="40"`. | -| X-07 | Signature must NOT use `text-anchor="middle"` — use left-alignment at x=40 | SIGNATURE-003 | PATCH | Removed `text-anchor="middle"`. | -| X-08 | Signature format wrong: `'047:02 \| Three.js Game - Fallback \| SpecKit'` | SIGNATURE-004 | PATCH | Renamed trailing token `SpecKit` → `ScriptHammer`. | - -### XML hygiene (regression introduced + fixed in same session) - -| ID | Issue | Code | Classification | Resolution | -| ---- | ------------------------------------------- | ------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -| X-09 | Attribute 'x' has unquoted value '80..280,' | XML-004 | PATCH | Validator's regex parsed an XML comment containing `at x=80..280, y=...` as an attribute. Reworded the comment to drop the literals. | +9 patch-classified issues resolved on the original hammer+anvil silhouette composition: callout positioning to avoid Retry-button overlap, callout count parity with annotation groups, US-badge minimum for annotation panel, signature alignment, and one XML hygiene fix. --- ## Notes -- All 9 issues classified as PATCH per `features/CLAUDE.md` decision table (cosmetic/positional/comment-text — no layout overlaps, no spacing, no missing sections requiring REGEN). -- Validator re-run after patches: **PASS** (zero errors). -- Auto-generated initially by validator v5.0; manually annotated with resolutions by `/speckit.wireframe.review` (2026-05-15). +- Validator status: PASS (0 errors) at every recorded final review. +- All Pa11y-auditable DOM (headline, body copy, Retry button) preserved in the fallback panel below the silhouette. diff --git a/features/enhancements/047-threejs-game/wireframes/02-game-3d-fallback.svg b/features/enhancements/047-threejs-game/wireframes/02-game-3d-fallback.svg index 920fd34d..d1755d5a 100644 --- a/features/enhancements/047-threejs-game/wireframes/02-game-3d-fallback.svg +++ b/features/enhancements/047-threejs-game/wireframes/02-game-3d-fallback.svg @@ -7,15 +7,104 @@ + - - 3D GAME (THREE.JS SCENE) - /game/3d (FALLBACK) - - DESKTOP (16:9) MOBILE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -28,19 +117,16 @@ - - - - - - - - - - - - - + + + + + + + + @@ -90,17 +176,12 @@ - - - - - - - - - - - + + + + + From 7a692f4593b50dbe76ab1305c2cac9425d075b77 Mon Sep 17 00:00:00 2001 From: TurtleWolfe Date: Sat, 16 May 2026 19:52:30 +0000 Subject: [PATCH 05/19] =?UTF-8?q?feat(spec):=20047=20=E2=80=94=20SpecKit?= =?UTF-8?q?=20plan=20+=20research=20+=20quickstart=20for=20Three.js=20game?= =?UTF-8?q?=20(#48)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per /speckit.plan skill, generates Phase 0 (research) + Phase 1 (design) artifacts for the #048 Three.js Game feature. Wireframe gate PASSED 2026-05-15 (regenerated 2026-05-16); plan now establishes implementation direction. ARTIFACTS - plan.md (229 lines) Technical Context: TypeScript 5, React 19, Next.js 15.5 static export. Three.js + @react-three/fiber + @react-three/drei + @types/three as new dependencies. Vitest (logic) + Playwright (canvas-rendering) test split. Constitution Check: all 6 principles ✅, no violations. Project Structure: 1 new route (src/app/game/3d/page.tsx), 4 new components (Scene, Controls, Loader, FallbackPanel) under src/components/game/ via the 5-file pattern, 1 modified utility (src/utils/theme-utils.ts gains a DaisyUI→Three.js color helper), 1 modified config (config/pa11yci.json scoped exclusion for /game/3d), 1 new E2E spec (tests/e2e/game-3d.spec.ts). Phase 2 sequencing for tasks.md sketched: foundation → US-1 → US-2 → US-3 → US-4 → US-5 → FR-008 fallback → procedural sculpt. - research.md (200+ lines) Six technical decisions resolved with rationale + alternatives: 1. Three.js + R3F + drei (vs raw Three.js, Babylon, PlayCanvas, WebGPU) 2. WebGL availability detection — one-shot canvas probe at mount + webglcontextlost listener at runtime 3. DaisyUI OKLCH → Three.js Color conversion via THREE.Color parsing oklch() syntax wrapped around CSS custom property values 4. jsdom canvas mocking — unit tests cover logic only; canvas rendering surface moves to Playwright 5. Bundle-split verification — Next.js build report is source of truth; SC-007 asserts other-route bundles unchanged 6. Auto-orbit behavior — drei's autoRotate + custom-timer override for the 3s idle-resume window - quickstart.md (180+ lines) Eight smoke-test recipes covering: dependency install + bundle verification, route + canvas mount, theme switch, reduced-motion runtime toggle, WebGL-disabled fallback path (including webglcontextlost simulation via WEBGL_lose_context extension), Pa11y CI exclusion + /game regression coverage + manual a11y review template, production static-export verification, full cross-browser E2E spec. NO ARTIFACTS - data-model.md skipped: no schema changes, no persistent state in v1. Runtime state is component-local (Scene state, auto-orbit state, fallback state) and inlined into plan.md. - contracts/ skipped: pure-frontend feature, no API surfaces. NEXT PHASE /speckit.tasks generates tasks.md with the user-story sequence outlined in plan.md Phase 2. Implementation work begins after tasks.md lands. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../enhancements/047-threejs-game/plan.md | 204 +++++++++++++++++ .../047-threejs-game/quickstart.md | 216 ++++++++++++++++++ .../enhancements/047-threejs-game/research.md | 165 +++++++++++++ 3 files changed, 585 insertions(+) create mode 100644 features/enhancements/047-threejs-game/plan.md create mode 100644 features/enhancements/047-threejs-game/quickstart.md create mode 100644 features/enhancements/047-threejs-game/research.md diff --git a/features/enhancements/047-threejs-game/plan.md b/features/enhancements/047-threejs-game/plan.md new file mode 100644 index 00000000..425b37e7 --- /dev/null +++ b/features/enhancements/047-threejs-game/plan.md @@ -0,0 +1,204 @@ +# Implementation Plan: Three.js Game + +**Branch**: `047-threejs-game` | **Date**: 2026-05-16 | **Spec**: [spec.md](./spec.md) +**Input**: Feature specification at `features/enhancements/047-threejs-game/spec.md` + +**Note**: Phase 0.5 of the ScriptHammer-family strategy plan (`~/.claude/plans/gleaming-kitten-execution.md`) — the R&D stepping stone before GrimGlow Phase 1a (browser fork). Wireframe gate PASSED 2026-05-15 (regenerated with canonical brand-SVG symbols 2026-05-16). This file is the cascade's `/speckit.plan` step. + +## Summary + +A new `/game/3d` route that mounts a Three.js scene composing the three canonical ScriptHammer brand assets (silver cog ring, golden `< >` code-tag brackets, printing-mallet) in 3D, all built from procedural primitives. The scene is theme-aware (re-extracts DaisyUI CSS custom properties on `data-theme` change, mirroring the existing `useMapTheme` precedent), respects `prefers-reduced-motion` (disables auto-orbit + idle animations), and degrades gracefully when WebGL is unavailable or the context is lost (themed CSS/SVG silhouette + user-actionable Retry button). + +The technical approach is pure-additive: a new client-only route under `src/app/game/3d/` using the existing `dynamic(() => import(...), { ssr: false })` pattern from `src/app/game/page.tsx`; four new components under `src/components/game/` following the mandatory 5-file pattern (Scene, Controls, Loader, FallbackPanel); a small theme-utils extension mirroring `useMapTheme`; a Pa11y exclusion scoped to `/game/3d` only. No backend changes, no schema changes, no shared-state changes elsewhere. + +## Technical Context + +**Language/Version**: TypeScript 5 (strict), React 19, Next.js 15.5 (App Router, static export) +**Primary Dependencies**: + +- `three` (~0.169.x) — WebGL rendering primitives +- `@react-three/fiber` (~8.17.x) — React renderer for Three.js +- `@react-three/drei` (~9.114.x) — R3F helpers (OrbitControls, useFrame, etc.) +- `@types/three` (devDependency) — TypeScript types + +(Versions pinned by `pnpm add` at install time; exact pinned versions land in `package.json` and become part of `tasks.md` step 1.) + +**Storage**: N/A. The v1 scene has no persistence. If a future iteration adds save state, it lives in localStorage with explicit user consent (per spec Out of Scope and Constitution Principle VI). + +**Testing**: + +- Vitest + jsdom for unit tests (theme-extraction logic, reduced-motion gating). jsdom does NOT provide a real WebGL context, so canvas-rendering tests are explicitly excluded from unit; that surface is covered by Playwright instead. +- Playwright (chromium + firefox + webkit) for E2E: route loads, canvas mounts, theme switch updates colors, fallback renders when `--disable-webgl` flag is set. +- Manual a11y review (documented in `tasks.md`) covers the canvas surface, since Pa11y/axe-core cannot audit canvas content. The fallback panel IS Pa11y-auditable and is part of the automated scan via standard DOM rules. + +**Target Platform**: GitHub Pages static export; modern browsers with WebGL 1.0+ (effectively all browsers since 2014). + +**Project Type**: web (existing single Next.js app). + +**Performance Goals** (from spec Success Criteria): + +- SC-001: Initial scene paint < 2s on simulated 4G (mid-tier mobile, Lighthouse mobile profile) +- SC-002: Theme switch propagates to scene materials within one frame (≤16ms) +- SC-007: Homepage + other-route bundle sizes unchanged before vs after this feature (Three.js bundle route-split to `/game/3d` only) + +**Constraints**: + +- Static export — no server API routes, no SSR for the `` (R3F is client-only). +- DPR capped `[1, 2]` per NFR-004 to bound GPU cost on high-DPR mobile devices. +- Canvas content not auditable by Pa11y — exclusion documented + manual review required. +- WCAG AAA contrast baseline from Phase 0 closure still applies to the fallback panel + any DOM chrome. +- 44px minimum touch targets on Retry button + any mobile HUD elements. + +**Scale/Scope**: + +- 1 new route (`src/app/game/3d/page.tsx`) +- 4 new components (Scene, Controls, Loader, FallbackPanel) × 5-file pattern = 20 new files +- 1 modified utility (extend `src/utils/theme-utils.ts` with a Three.js color-conversion helper) +- 1 modified config (`config/pa11yci.json` exclusion for `/game/3d`) +- 1 new E2E spec at `tests/e2e/game-3d.spec.ts` +- 0 new database tables, 0 new API contracts, 0 new auth surfaces + +## Constitution Check + +_GATE: Must pass before Phase 0 research. Re-check after Phase 1 design._ + +| Principle | Compliance | Notes | +| ------------------------------------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| I. Component Structure Compliance | ✅ | Four new components (Scene, Controls, Loader, FallbackPanel) each scaffolded via `pnpm run generate:component` to enforce the 5-file pattern. Validated by `pnpm run validate:structure` (SC-008). | +| II. Test-First Development | ✅ | RED tests authored first per `tasks.md` ordering: theme-extraction unit test asserts re-extraction on `data-theme` change before the hook ships; FallbackPanel a11y test asserts Retry-button focusability before the WebGL-detection path is wired; E2E spec for /game/3d asserts canvas mounts before the route page is written. | +| III. PRP Methodology w/ Mandatory Wireframe Gate | ✅ | Wireframes 01 + 02 PASSED 2026-05-15 (validator v5.0, 0 errors). `## UI Mockup` section in spec.md links both. This file is the cascade's `/speckit.plan` step. | +| IV. Docker-First Development | ✅ | All commands via `docker compose exec scripthammer ...`. `pnpm add three @react-three/fiber @react-three/drei` runs inside the container per CLAUDE.md. Tests, builds, validation all containerized. | +| V. Progressive Enhancement | ✅ | The route degrades gracefully: WebGL unavailable → themed fallback panel (FR-008). prefers-reduced-motion → auto-orbit disabled (FR-004). Touch input → drei OrbitControls handles natively. Mobile breakpoint validated against wireframe 01. | +| VI. Privacy & Compliance First | ✅ | NFR-006: GA4 default page view only — no custom scene-loaded, scene-interaction, or theme-switched-in-scene events. No localStorage writes for v1. No third-party calls (Three.js bundles locally; CDNs explicitly avoided). No PII surfaces. | + +**No violations to justify.** No `Complexity Tracking` section needed. + +## Project Structure + +### Documentation (this feature) + +``` +features/enhancements/047-threejs-game/ +├── 047_threejs-game_feature.md # Original PRP (preserved) +├── spec.md # Spec — clarifications encoded 2026-05-15, wireframe sign-off 2026-05-15 +├── plan.md # This file +├── research.md # Phase 0 — decisions on Three.js, R3F, WebGL detection, theme extraction +├── quickstart.md # Phase 1 — smoke-test recipes for human + LLM verification +├── tasks.md # /speckit.tasks output (NEXT) +├── checklists/ +│ └── requirements.md # Spec quality checklist (PASSED 2026-05-15) +└── wireframes/ + ├── 01-game-3d-main.svg # Main scene (PASSED 2026-05-15) + ├── 02-game-3d-fallback.svg # Fallback panel (PASSED 2026-05-15) + ├── 01-game-3d-main.issues.md # PASS history with regen log + ├── 02-game-3d-fallback.issues.md # PASS history with regen log + └── includes/ # Shared chrome (header/footer) +``` + +### Source code (repository root) + +``` +src/ +├── app/ +│ └── game/ +│ ├── page.tsx # NO CHANGE — existing dice game preserved +│ └── 3d/ +│ └── page.tsx # NEW — dynamic(() => import('@/components/game/Scene'), { ssr: false }) +├── components/ +│ └── game/ +│ ├── Scene/ # NEW — top-level wrapper, owns theme extraction +│ │ ├── index.tsx # Barrel +│ │ ├── Scene.tsx # Main component +│ │ ├── Scene.test.tsx # Unit tests (theme-extraction, reduced-motion gating) +│ │ ├── Scene.stories.tsx # Storybook +│ │ └── Scene.accessibility.test.tsx +│ ├── Controls/ # NEW — drei OrbitControls wrapper + auto-orbit timer +│ │ └── (5 files, same pattern) +│ ├── Loader/ # NEW — Suspense fallback while canvas mounts +│ │ └── (5 files, same pattern) +│ └── FallbackPanel/ # NEW — WebGL-unavailable/context-lost UI +│ └── (5 files, same pattern) +├── utils/ +│ └── theme-utils.ts # MODIFY — add `getDaisyUIColorAsThree(token)` helper that returns a THREE.Color from OKLCH custom prop +└── hooks/ + └── useReducedMotion.ts # NEW or MODIFY (verify if it already exists; if so, reuse; if not, add a small wrapper around `matchMedia('(prefers-reduced-motion: reduce)')` that responds to runtime changes) + +tests/ +└── e2e/ + └── game-3d.spec.ts # NEW — visit route, assert canvas mounts; theme-switch assertion; --disable-webgl fallback assertion + +config/ +└── pa11yci.json # MODIFY — add `/game/3d` to the exclusion list with a comment explaining canvas is not auditable +``` + +**Structure Decision**: Single Next.js app, existing layout. New components live under `src/components/game/` (matching the existing `src/app/game/` route prefix) so directory paths telegraph ownership. The `Scene` component is the root R3F ``; `Controls`, `Loader`, and `FallbackPanel` are children. Theme extraction lives in `src/utils/theme-utils.ts` next to the existing `useMapTheme` precedent, not inside `Scene` — keeps the conversion logic reusable if a future feature also needs DaisyUI→Three.js color translation. + +## Phase 0 — Research + +See [`research.md`](./research.md). Decisions recorded with rationale + alternatives considered for: + +1. Three.js + R3F + drei (vs raw Three.js, vs Babylon.js, vs PlayCanvas) +2. WebGL availability detection strategy +3. DaisyUI OKLCH → Three.js Color conversion approach +4. jsdom canvas mocking for unit tests +5. Bundle-split verification methodology +6. drei OrbitControls auto-orbit behavior (built-in `autoRotate` vs custom timer) + +## Phase 1 — Design + +### Data model + +**No schema changes. No persistent state in v1.** The runtime state is entirely component-local: + +- **Scene state** (in `Scene.tsx`): theme tokens (re-extracted on `data-theme` change), reduced-motion preference (re-evaluated on media-query change), camera position (drei OrbitControls manages this internally). +- **Auto-orbit state** (in `Controls.tsx`): last-input-timestamp (ref), auto-orbit-paused boolean (state). +- **Fallback state** (in `Scene.tsx`): WebGL availability (one-shot probe at mount), context-lost flag (listener-driven). + +No `data-model.md` artifact needed — this section captures everything. + +### Contracts + +**No new API contracts.** The feature is pure-frontend. No backend surfaces, no Edge Functions, no Supabase calls. Three.js renders client-side; the static export emits `out/game/3d/index.html` with the chunk-split bundle. + +No `contracts/` directory needed. + +### Quickstart + +See [`quickstart.md`](./quickstart.md). Smoke-test recipes for: + +1. Dependency install + bundle-split verification +2. Route + canvas-mount smoke +3. Theme-switch smoke +4. Reduced-motion smoke +5. WebGL-disabled (fallback panel) smoke +6. Pa11y CI run (exclusion verification + `/game` regression coverage) +7. Production static-export verification (`next build` → `out/game/3d/index.html`) + +### Update agent context + +```bash +.specify/scripts/bash/update-agent-context.sh claude +# Expected: stderr "[update-agent-context] No-op for ScriptHammer..." +# (CLAUDE.md is hand-curated; this is intentional) +``` + +## Phase 2 — `/speckit.tasks` (next) + +`tasks.md` will sequence work to satisfy user stories independently: + +1. **Foundation first**: dependency install (Three.js + R3F + drei + @types/three), Pa11y exclusion config, theme-utils helper extension, useReducedMotion hook. These are prerequisites for any scene work and have no inter-dependency. +2. **US-1 (P1)**: scaffold Scene + Controls + Loader components; create the `/game/3d` route page with `dynamic` no-SSR import; render an initial placeholder geometry to prove the canvas mounts. Largest single deliverable, earliest value. +3. **US-2 (P1)**: wire theme extraction (`getDaisyUIColorAsThree`) into Scene materials; add MutationObserver on `data-theme`. Independent of US-3+. +4. **US-3 (P2)**: gate auto-orbit + any idle animations on `useReducedMotion`. Tests assert no autonomous motion under reduce-motion. +5. **US-4 (P2)**: confirm Pa11y exclusion config; document the manual a11y review template in `tasks.md`. The `/game` regression check is part of the existing Pa11y CI scan and requires no new work — verify only. +6. **US-5 (P3)**: mobile breakpoint polish; DPR cap; touch input verified across the three Playwright browsers. +7. **FR-008 fallback path**: detect WebGL absence at mount + listen for `webglcontextlost`; render `FallbackPanel` with themed silhouette + 44×44 Retry button. Separable from US-1 because it's a distinct rendering path; can ship in parallel. +8. **Procedural sculpt (FR-007)**: replace the US-1 placeholder geometry with the v1 brand composition (cog ring via `RingGeometry` + 20 trapezoidal teeth via `ExtrudeGeometry`; `< >` brackets via `ExtrudeGeometry` from 2D paths; printing-mallet via `BoxGeometry` + `CylinderGeometry`). Last because the placeholder proves all the wiring works first; the sculpt is the visual payoff. + +Each user story (or independent technical concern) gets its own commit so the PR history reflects the cascade. + +## Constitution re-check (post-Phase 1) + +All six principles still ✅. The Phase 1 design didn't introduce anything new beyond the items already evaluated in the Phase 0 gate. + +Design is intentionally conservative: extend existing patterns (dynamic-import-no-SSR from the dice game, MutationObserver-on-data-theme from useMapTheme, 5-file component pattern from every other component), no new architectural patterns, no new dependencies beyond the three Three.js packages that are core to the feature. The DaisyUI→Three.js color helper is small and lives next to the existing useMapTheme pattern, ready for reuse if Phase 1a (GrimGlow browser fork) needs the same conversion. diff --git a/features/enhancements/047-threejs-game/quickstart.md b/features/enhancements/047-threejs-game/quickstart.md new file mode 100644 index 00000000..bd135ff6 --- /dev/null +++ b/features/enhancements/047-threejs-game/quickstart.md @@ -0,0 +1,216 @@ +# Phase 1 Quickstart: Three.js Game + +**Feature**: 047 — Three.js Game +**Spec**: [spec.md](./spec.md) +**Plan**: [plan.md](./plan.md) +**Date**: 2026-05-16 + +Smoke-test recipes for human + LLM verification at each implementation milestone. Each recipe is independently runnable and self-contained. + +All commands assume the dev container is running: + +```bash +docker compose up -d +docker compose exec scripthammer pnpm run dev # in another terminal if not already +``` + +--- + +## 1. Dependency install + bundle-split verification + +```bash +# Install +docker compose exec scripthammer pnpm add three @react-three/fiber @react-three/drei +docker compose exec scripthammer pnpm add -D @types/three + +# Verify route-split: production build emits chunks under /_next/static/chunks/ +# /game/3d's chunk includes Three.js; other-route chunks do not. +docker compose exec scripthammer pnpm run build +``` + +**Expected** (compare against `git show HEAD~1:.next/...` if you saved the pre-add baseline, or just compare other-route bundle sizes before vs after the feature lands): + +- `/game/3d` First Load JS grows by ~600 KB. +- Every other route's First Load JS is unchanged. +- Shared chunk size is unchanged. + +If the shared chunk grew, search for an accidental `import { Canvas } from '@react-three/fiber'` outside `src/components/game/` or `src/app/game/3d/`. Fix by moving the import behind `dynamic()`. + +--- + +## 2. Route + canvas-mount smoke + +```bash +# In a browser (dev server URL varies by Docker port mapping; check `docker compose ps`). +# Example: http://localhost:PORT/ScriptHammer/game/3d + +# Open browser devtools → Elements panel. +# Search for: +# Expected: one element inside the page's main content area. + +# Check console: no errors mentioning Three.js, R3F, or WebGL. +``` + +**Expected**: + +- A `` element renders within ~2 seconds of navigation. +- The Suspense loader (Loader component) flashes briefly during dynamic import on first visit; subsequent visits within the session are instant (chunk cached). +- No SSR errors in the server logs (R3F is client-only via `dynamic(..., { ssr: false })`). + +--- + +## 3. Theme-switch smoke + +```bash +# In the browser at /game/3d, open the ThemeSwitcher (existing component). +# Switch through several themes: light → dark → dracula → cupcake → night → winter. +``` + +**Expected**: + +- Scene background, lighting, and material colors update within one frame (~16ms) on each theme change. No page reload required. +- Switching to a dark theme produces visibly darker scene background + lighting. +- Switching to a light theme produces visibly lighter scene background + lighting. +- The DaisyUI tokens in CSS (`--p`, `--s`, `--b1`) are visibly reflected in the scene (e.g., the cog rim picks up `--p`, the brackets pick up `--a` accent). + +**If theme changes don't propagate**: check the `MutationObserver` wiring in `Scene.tsx`. It should observe `document.documentElement` with `attributes: true, attributeFilter: ['data-theme']`. + +--- + +## 4. Reduced-motion smoke + +```bash +# In Chrome DevTools: Cmd-Shift-P → "Show Rendering" → "Emulate CSS media feature prefers-reduced-motion" → "reduce" +# Or at OS level: macOS System Preferences → Accessibility → Display → Reduce motion. + +# Then reload /game/3d. +``` + +**Expected**: + +- Auto-orbit does NOT happen. The scene is static until the user explicitly drags the camera. +- No idle bobbing, no autonomous rotation of accent objects (if any), no transitions. +- User-initiated camera input (drag, scroll, touch) STILL works — only autonomous motion is disabled. + +```bash +# Toggle the emulation OFF (or set OS preference back to "no preference") and observe the scene WITHOUT reloading. +``` + +**Expected**: + +- Auto-orbit resumes within 3 seconds. +- This validates SC-010 and the runtime-toggle behavior described in spec edge cases. + +--- + +## 5. WebGL-disabled (fallback panel) smoke + +```bash +# Start chromium with WebGL disabled: +chromium --disable-webgl http://localhost:PORT/ScriptHammer/game/3d + +# Or via Chrome DevTools: about:gpu → disable WebGL via the GPU process flags. +# Or via Firefox: about:config → webgl.disabled = true. +``` + +**Expected**: + +- No canvas element renders. +- A fallback panel renders instead, containing: + - Themed CSS/SVG silhouette of the brand composition (cog + brackets + mallet, recolored via DaisyUI tokens) + - Headline "3D Content Unavailable" + - Body copy naming WebGL as the requirement + - A 44×44 keyboard-focusable "Retry" button +- Clicking "Retry" re-runs the WebGL probe. With WebGL still disabled, the fallback re-renders. With WebGL re-enabled (close + relaunch browser without `--disable-webgl`), Retry mounts the canvas successfully. +- Console: no errors. The probe is silent on failure. + +```bash +# Test the webglcontextlost path (runtime context loss simulation): +# In DevTools Console with /game/3d open: +const canvas = document.querySelector('canvas'); +const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); +gl.getExtension('WEBGL_lose_context').loseContext(); +``` + +**Expected**: + +- Within one frame, the canvas disappears and the fallback panel replaces it. +- No silent auto-retry. User clicks Retry to attempt recovery. + +--- + +## 6. Pa11y CI (exclusion verification + `/game` regression coverage) + +```bash +# Run the full Pa11y suite locally: +docker compose exec scripthammer pnpm test:a11y +``` + +**Expected**: + +- The output lists every audited route. `/game/3d` is SKIPPED with the configured exclusion reason. +- `/game` (the existing dice game) IS audited. It passes (no regression from feature 037-game-a11y-tests). +- No new failures on any other route. + +**Manual a11y review template** (for canvas content not auditable by Pa11y; documented in `tasks.md`): + +- Keyboard: Tab to Retry button → Enter triggers retry. Tab moves to next focusable element after. +- Screen reader: `aria-label="3D scene preview"` on the ``. Fallback panel reads: heading "3D Content Unavailable" → paragraph → button "Retry". +- Color contrast: WCAG AAA on all DOM chrome (page heading, fallback panel text). Canvas content not subject (no text content rendered inside the canvas in v1). +- Motion: prefers-reduced-motion respected (covered by recipe 4). +- Documented in `tasks.md` under US-4 with a checkbox for each item. + +--- + +## 7. Production static-export verification + +```bash +docker compose exec scripthammer pnpm run build +# Check that the static export includes the route: +docker compose exec scripthammer ls out/game/3d/ +``` + +**Expected**: + +- `out/game/3d/index.html` exists. +- The HTML contains a `