diff --git a/.agents/rules/10-architecture.md b/.agents/rules/10-architecture.md index a37194e2..b0ef410a 100644 --- a/.agents/rules/10-architecture.md +++ b/.agents/rules/10-architecture.md @@ -22,7 +22,13 @@ Use these checks before adding modules, wrappers, interfaces, or cross-folder se 4. **Count adapters honestly.** One adapter is a hypothetical seam; two adapters are evidence that the seam may be real. 5. **Prefer locality.** Keep change, bugs, knowledge, and verification concentrated unless separation gives clear leverage. 6. **Reject knowledge-leaking interfaces.** Do not add interfaces that require callers to understand more lifecycle, ordering, or implementation detail than before. -7. **Keep Ridge spatial truth in Ridge Map Language and compiled facts.** Follow ADR-0001 instead of recreating parent/route/spatial catalogs in runtime code. +7. **Keep Ridge gameplay truth in Ridge Console Core.** Follow ADR-0005 + (`src/game/core/ridge/`) instead of recreating route/spatial catalogs in + Phaser scene code. Art stays behind VisualProvider. +8. **Keep full-game headless play in Game Console Core.** Follow ADR-0006 + (`src/game/core/console/`, `pnpm game:console`) for Overworld / Basement / + discrete Potassium / Ridge orchestration. Do not invent a second headless + store or browser-only smoke path when the console session can answer. ## Folder Ownership diff --git a/.fallowrc.jsonc b/.fallowrc.jsonc index 208cc6f8..582defe9 100644 --- a/.fallowrc.jsonc +++ b/.fallowrc.jsonc @@ -19,10 +19,13 @@ }, // Phaser scenes and lazy React overlays load through dynamic import() strings. + // Headless CLI scripts are real entrypoints for GameConsoleSession public methods. "dynamicallyLoaded": [ "src/game/scenes/**/runtime/*Scene.ts", "src/game/overlays/**/*.tsx", - "src/dev/**/*.tsx" + "src/dev/**/*.tsx", + "scripts/game-console.ts", + "scripts/**/*.ts" ], "health": { @@ -98,6 +101,21 @@ { "file": "src/game/scenes/potassiumSlip/runtime/waves.ts", "exports": ["*"] + }, + { + // Shared street/room spot constants intentionally re-exported by scene layout modules. + "file": "src/game/core/console/content/overworldSpots.ts", + "exports": [ + "OVERWORLD_WIDTH", + "OVERWORLD_PLAYER_START", + "OVERWORLD_BASEMENT_HOLE", + "OVERWORLD_INTERACT_DISTANCE_X", + "OVERWORLD_INTERACT_MIN_PLAYER_Y" + ] + }, + { + "file": "src/game/core/console/content/basementSpots.ts", + "exports": ["BASEMENT_FLOOR_Y", "BASEMENT_PLAYER_START"] } ] } diff --git a/CONTEXT.md b/CONTEXT.md index 9dfe95cd..0007e1f5 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -35,30 +35,59 @@ final assets are placed. The current blockout compiles into typed facts, geometry, connectors, and presentation inputs. _Avoid_: final map, finished level +**Ridge Console Core**: +Pure TypeScript Ridge gameplay under `src/game/core/ridge/`. It owns progress, +beats, nearby interactables, conversation halt, and command results for both +`pnpm ridge:console` and the Phaser Ridge scene. Phaser/React are presentation +adapters only. +_Avoid_: debugger-as-gameplay-authority, duplicating route logic in Phaser + +**Ridge Visual Provider**: +Replaceable presentation seam that renders a Ridge view model. The default is +mathematical stick-figure art; future iPad/Procreate art should plug in here +without rewriting console/route logic. +_Avoid_: baking final art assumptions into core gameplay modules + +**Character Conversation Panel**: +Persona-style Scene UI that halts explore mode while talking: speaker name, +portrait slot, line text, advance, and optional choices. +_Avoid_: timed Phaser toast dialogue, non-halting talk overlays + **Ridge Stage Debugger**: -A development QA surface for Walkable Sketchbook Stages. It runs the live Ridge -stage preview and exposes route beats, Walk Rail readouts, Stage Spot movement, -Rail Perspective Cues, and debug overlays so Danilo and AI agents can tune a -Ridge Stage Composition Source. It is not the legacy Ridge Blockout Viewer or a -final art map editor. -_Avoid_: Ridge Blockout Viewer, final art editor, generic map editor +Removed development QA surface for the old Stage Composition / Walk Rail +workflow. Superseded by Ridge Console Core playtest and the stick Bridge scene. +_Avoid_: reviving `?mode=ridge-debugger` without a new ADR **Stage Authoring Draft**: Ephemeral in-debugger adjustments to Ridge Stage Composition Source spatial -data. A draft updates the live preview immediately, shows copyable typed-source -snippets for review, and is applied to git only when Danilo or an agent pastes -the change into the Ridge Stage Composition Source. It is not a second persisted -editor format or runtime data store. -_Avoid_: map editor save file, localStorage canonical source, auto-patched source file +data. A draft updates the live preview immediately while Danilo or an agent +tunes placement. It is not a second persisted editor format or runtime data +store until committed. +_Avoid_: map editor save file, localStorage canonical source, parallel JSON stage source + +**Stage Authoring Commit**: +The dev-only save action that writes the current Stage Authoring Draft back +into the owning Ridge Stage Composition Source file. Danilo should not need to +hunt for changed fields or paste per-target snippets by hand. +_Avoid_: manual snippet archaeology, delta-only export as the primary workflow **Stage Authoring Mode**: An explicit Ridge Stage Debugger state for spatial tweaking. While active, player -movement is frozen, all source-backed Walk Rail points, Stage Spots, and Stage -Object contact markers are shown regardless of route-beat visibility, preview -clicks select draft targets, and the sidebar exposes nudge controls plus -copyable typed-source replacement snippets. Outside this mode, the debugger -keeps its normal live-preview QA behavior. -_Avoid_: always-on pick mode, modifier-only pick mode, beat-filtered edit targets, separate map editor app +movement is frozen, all source-backed Walk Rail points, Stage Spots, Stage +Plates, and Stage Object contact markers are shown regardless of route-beat +visibility, preview clicks select draft targets, and the sidebar exposes nudge +controls plus a dev-only Stage Authoring Commit action. Stage Plates are chosen +from the authoring target list first; the preview highlights the active plate +with a bounding box. Canvas click may select a plate when visible pixels are +hit, but obscured plates are not expected to be reachable by click alone. +Outside this mode, the debugger keeps its normal live-preview QA behavior. +_Avoid_: always-on pick mode, modifier-only pick mode, beat-filtered edit targets, separate map editor app, canvas-only plate picking + +**Ridge Debugger Route**: +The dev-only app route that opens the Ridge Stage Debugger without entering +Interactive mode first. It is reached from Pick Your Path, the dev scene +switcher, or a short `?mode=` URL alias. +_Avoid_: treating the debugger as a Phaser scene **Stage Authoring Camera**: The preview camera behavior used during Stage Authoring Mode. It stops following @@ -124,8 +153,85 @@ _Avoid_: separate chapter, required dungeon, second mini-map **Bridge Area**: The first required Ridge Area. It contains the Blueprint Bridge Resident Beat -and teaches that helping a resident can visibly change the route. -_Avoid_: calling the whole area Blueprint Bridge, bridge hub +and teaches that helping a resident can visibly change the route. It should read +as a sunny **Farm River** lowland with a **Cornfield Section** leading into a +very **Simple Bridge Crossing** over the river, plus childlike cheerful staging +rather than a mountain-top or moody forest start. +_Avoid_: calling the whole area Blueprint Bridge, bridge hub, summit entry, +generic mountain vista without destination reads, moody-only forest gorge as +the accepted Bridge identity, complicated truss bridge as the v0 read + +**Cornfield Section**: +The left Bridge approach where Cicka plays with the toy car inside tall corn. +It uses a simple background without the full **Route Landmark Backdrop**, and +ends with foreground corn stalks that mark the player leaving the field before +the bridge work site opens. +_Avoid_: full-route promise shot in the cornfield, making corn a traversal maze, +required hide-and-seek mechanic + +**Cornfield Exit**: +The foreground corn stalks or screen at the end of the **Cornfield Section** +that makes leaving the field feel readable and explains why the Bridge +Draftsperson could not see Cicka with the toy car. +_Avoid_: invisible sightline cheat, separate scene load, mandatory stealth beat + +**Simple Bridge Crossing**: +The accepted v0 Bridge crossing read: a very simple unfinished gap and an equally +simple completed bridge state, drawn and animated easily by hand rather than as +a detailed engineering structure. +_Avoid_: truss blueprint porn, multi-span engineering set, physics-simulation bridge + +**Childlike Sunny Register**: +The accepted Bridge Area visual tone: rounder, simpler ink shapes, brighter +off-white paper mood, sunflowers and playful pastoral props, and a gentler +hand-drawn line quality than the denser adult cross-hatch used elsewhere on the +route. +_Avoid_: dark forest-only Bridge, hyper-detailed realism, gray-box placeholder +tone, making the whole Ridge route childlike + +**Route Landmark Backdrop**: +The far-background silhouette layer on a Ridge stage that hints where the route +is heading: Concert as a distant city read, Dance Festival as a foothill plaza +or setup glow, Relay as a coastal hill crest with sea horizon and bench read. +These are environmental promise shots, not labels, minimap icons, or playable +geography. +_Avoid_: busy landmark collage, literal UI arrows, one repeated mountain layer, +unreadable same-y peaks, lighthouse as required destination read + +**Ridge Geography Arc**: +The accepted felt progression across required Ridge Areas: childlike sunny +**Farm River** lowland at Bridge, valley-town night at Concert, foothill +festival setup at Dance Festival, and a bare **Relay Summit Overlook** at Relay. +Literal altitude can stay loose because areas are Compact Ridge Stages, but each +area needs a distinct register. The full **Route Landmark Backdrop** opens at +the Bridge crossing vista, not in the **Cornfield Section**. +_Avoid_: four mountain stages, unexplained biome jumps, overworld street geography, +landmark backdrop in every Bridge sub-section + +**Relay Summit Overlook**: +The accepted Relay Ending visual register: a hilltop or ridge crest with a +bench, open sea/ocean view, sunset farewell framing, Cicka present, and at most +one optional quiet inspect prop such as a star-watcher instrument. It is a +threshold overlook, not a tower climb, lighthouse fantasy, or busy final hub. +_Avoid_: lighthouse as focal landmark, required beacon climb, interior lighthouse, +crowded summit town, generic twin-peak mountain finale + +**Hand-Drawable Sketchbook**: +The accepted Ridge art-production target: rough, goofy, human-drawable assets +that a skilled artist can make and animate quickly in Procreate or similar tools. +Prefer simple shapes, readable silhouettes, and light shading over dense +cross-hatch polish; stick-figure-adjacent simplicity is allowed when it stays +on-style and readable on mobile. +_Avoid_: hyper-real shading passes, production asset packs that are hard to revise +by hand, over-modeled detail that fights fast iteration + +**Unified Hand-Drawn Lane**: +The accepted Ridge visual rule: one Hand-Drawable Sketchbook line system across +Bridge, Concert, Dance Festival, and Relay. Areas mature emotionally through +place, props, crowd density, and time-of-day rather than switching to a more +polished or more childlike ink system mid-route. +_Avoid_: per-area line-quality cliffs, Bridge-only stick-figure style while later +areas become hyper-shaded, separate Ridge art bibles per area **Blueprint Bridge**: The first required Resident Beat where the player helps the Bridge Draftsperson @@ -279,17 +385,68 @@ _Avoid_: calling the whole area Opening Dance Shuttle, final hub A high-level route graph showing room beats, locks, shortcuts, and return paths. _Avoid_: blockout map, minimap +**Overworld**: +The default portfolio-first visitor layer: a walkable street where building +stops surface CV sections, hobbies, contact, and other low-friction about-me +content for visitors who may never play the full Ridge route. +_Avoid_: Exploration Map, Ridge route, Sketchbook Neighborhood, modal menu site + +**Portfolio Street**: +An acceptable alias for **Overworld** when describing the same default +portfolio-skimmer layer in design conversation. +_Avoid_: treating it as a separate place from Overworld, Ridge topology + +**Flat Portfolio Street**: +The accepted Overworld topology: one horizontal legible street of portfolio +stops, without its own geographic journey arc. Ridge owns meaningful place +progression; the street may later gain light inhabited zones or post-ship flavor +without becoming a second Exploration Map. +_Avoid_: overworld mountain-to-city arc, Ridge geography mirrored on the street, +sprawling neighborhood map during pre-production + +**Desk Versus Opened Page**: +The accepted Overworld-versus-Ridge visual split: the **Overworld** reads as a +slightly cleaner notebook-cover or portfolio-street layer, while **Ridge Pass** +through the basement computer reads as a rougher opened-sketchbook interior +using the same ink-and-paper family but looser Hand-Drawable production. +_Avoid_: totally separate art products, hyper-polished Ridge, overworld doodle +chaos that breaks recruiter legibility + +**Portfolio Pass**: +The default first-visit contract for the site: skim the Overworld, open word-forward +portfolio sections, and leave without needing Ridge progress or game literacy. +_Avoid_: requiring Ridge entry, quest completion, full route play + +**Ridge Pass**: +The optional discovered play path through Bridge Area, Concert Area, Dance +Festival Area, and Relay Spire for visitors who want the full sketchbook journey. +_Avoid_: default recruiter path, mandatory first visit, overworld replacement + +**Ridge Airlock**: +The basement computer entry that is the only player-facing way into Ridge Pass +until the First Playable Route is finished. Additional Overworld entry points may +be added only after Ridge ships. +_Avoid_: required overworld signage before Ridge ships, Ridge as default spawn, +parallel Ridge entrances during pre-production + +**Phased Ridge Discovery**: +The accepted rollout where Ridge stays basement-computer-only during Ridge +pre-production, then may gain extra discoverable Overworld paths once the route +is complete enough to stand as a finished secret game. +_Avoid_: multi-entry Ridge before route completion, hiding Ridge forever after ship + **Exploration Map**: -The shared side-view route outside opt-in mini-games where the player explores, -encounters Cicka, helps residents, finds artifacts, and reads the world through -shortcuts and changed landmarks. -_Avoid_: mini-game arena, old modal overworld +The Ridge-only shared side-view route outside opt-in mini-games where the player +explores, encounters Cicka, helps residents, finds artifacts, and reads the world +through shortcuts and changed landmarks. It lives inside the Ridge Pass, not on +the default Overworld street. +_Avoid_: mini-game arena, Overworld, Portfolio Street, portfolio building row **Sketchbook Neighborhood**: -The intended first-read fantasy of the Exploration Map: an inhabited paper place -where the player moves toward the Relay Spire by helping tiny residents and -seeing routes change. -_Avoid_: metroidvania platforming map, portfolio theme park, static building row +The intended first-read fantasy of the Ridge **Exploration Map**: an inhabited +paper place where the player moves toward the Relay Spire by helping tiny +residents and seeing routes change. +_Avoid_: metroidvania platforming map, portfolio theme park, Overworld street **Walkable Sketchbook Stage**: The accepted Ridge presentation direction: a Harold Halibut-like or theater-set @@ -512,10 +669,11 @@ should be an optional emotional reward, never a progression requirement. _Avoid_: Cicka Home hub, quest board, required backtracking checkpoint, inventory stash, required affection system, v0 interaction node, checklist gate **Bridge Resting Spot**: -The Cicka Resting Spot in the Bridge Area, staged near the unsafe crossing, -blank plan, or newly completed bridge so Cicka can quietly point attention to -the first resident-help route change. -_Avoid_: tutorial UI marker, required backtracking perch +The Cicka Resting Spot in the Bridge Area, staged inside the **Cornfield +Section** before resolution and near the **Simple Bridge Crossing** after +resolution so Cicka can quietly point attention to the first resident-help route +change. +_Avoid_: tutorial UI marker, required backtracking perch, draftsperson-visible perch **Concert Resting Spot**: The Cicka Resting Spot in the Concert Area: first a hidden musician-side place @@ -762,6 +920,11 @@ _Avoid_: boss gate, precision climb gate, arbitrary content checklist ## Flagged Ambiguities +- "overworld" and "portfolio street" both mean the default **Overworld** + portfolio-skimmer layer; **Exploration Map** means the Ridge route only. +- "enter Ridge" during pre-production means the **Ridge Airlock** basement + computer path; extra Overworld discovery belongs to **Phased Ridge Discovery** + after the route ships. - "map" can mean **Topology Map**, **Ridge Blockout**, or final art; resolved by naming the layer explicitly. - "scene" can mean a design beat or a Phaser runtime scene; use **Ridge Area** for the logical place, **Resident Beat** for the authored problem sequence, @@ -807,8 +970,11 @@ _Avoid_: boss gate, precision climb gate, arbitrary content checklist - "Cicka Home" is a current proof-of-concept/runtime blockout term; the newer linear story route should say **Cicka Resting Spot** unless a central home space is deliberately restored. -- "lighthouse" is current story shorthand for the **Relay Spire** as a beacon or - overlook; keep docs on **Relay Spire** unless the final landmark is renamed. +- "lighthouse" is retired from active Ridge visuals unless deliberately revived; + **Relay Summit Overlook** is bench + sea + sunset + Cicka. Keep **Relay Spire** + as the area name unless a rename pass lands. +- "high quality shading" is not the Ridge art goal; prefer **Hand-Drawable + Sketchbook** roughness that stays fast to draw, revise, and animate by hand. - "another cat" during **Opening Dance Shuttle Beat** means the **Unnamed Counterpart Cat**, not **Micka**, whose first appearance remains after the first-ending return unless that timing is deliberately reopened. diff --git a/README.md b/README.md index c6de797f..e6b494bf 100644 --- a/README.md +++ b/README.md @@ -30,11 +30,21 @@ pnpm fallow:baseline # regenerate committed CI baselines after cleanup Fallow CI policy and agent workflow: [`docs/agents/fallow.md`](docs/agents/fallow.md). -Current Bridge staging lives in -`src/game/scenes/ridge/bridge/stageComposition.ts`. In development, open the -Ridge Stage Debugger with `?mode=ridge-stage-debugger` after starting -`pnpm dev`; it hosts the live Bridge preview, route-beat controls, Walk Rail -readouts, Stage Spot movement, and Bridge Stage debug overlays. +Headless / AI playtest (full game graph): + +```bash +pnpm game:console +pnpm game:console --script "look; go right 4; interact" --json +``` + +Ridge Bridge is also a console-driven stick prototype: + +- Visual play: `pnpm dev` then `?mode=interactive&startScene=ridge` +- Ridge-only headless: `pnpm ridge:console` (alias of `game:console --scene ridge`) + +Full-game console: `src/game/core/console/` (ADR-0006). Ridge gameplay: +`src/game/core/ridge/`; stick art is a replaceable VisualProvider under +`src/game/scenes/ridge/art/`. Stampede Sketch remains a standalone mini-game scene (`stampedeSketch`) for optional future content; reach it via the dev scene switcher or basement console diff --git a/docs/adr/0004-ridge-stage-composition-source.md b/docs/adr/0004-ridge-stage-composition-source.md index 8c7bd249..79dc0b19 100644 --- a/docs/adr/0004-ridge-stage-composition-source.md +++ b/docs/adr/0004-ridge-stage-composition-source.md @@ -1,6 +1,6 @@ # Adopt Ridge Stage Composition Source For Experimental Ridge Exploration -Status: accepted for experimental Ridge exploration and the First Playable Route. +Status: superseded by ADR-0005 for First Playable Route runtime. Kept as historical staging-source reference. Ridge exploration will use a typed Ridge Stage Composition Source as the primary authoring surface for Walkable Sketchbook Stages. The source owns Walk Rails, Stage Spots, Stage Plates, Stage Objects, Stage Occluders, camera framing, and beat-state-driven presentation, while route/story logic remains in the route state and interaction systems. diff --git a/docs/adr/0005-ridge-console-core-and-stick-visuals.md b/docs/adr/0005-ridge-console-core-and-stick-visuals.md new file mode 100644 index 00000000..9d37d982 --- /dev/null +++ b/docs/adr/0005-ridge-console-core-and-stick-visuals.md @@ -0,0 +1,43 @@ +# Ridge Console Core And Stick Visual Provider + +Status: accepted for the Ridge runtime rebuild. + +Ridge gameplay authority lives in a pure console-capable core under +`src/game/core/ridge/`. Phaser and React are presentation adapters. Stick-figure +math art is the default Visual Provider and can be replaced later without +rewriting route logic. + +## Decision + +1. **Console core is gameplay truth.** Progress, beats, nearby interactables, + conversation halt, and route outcomes are decided in pure TypeScript with no + Phaser, React, or DOM imports. +2. **AI and humans share one command surface.** Commands such as `look`, + `go left|right`, `interact`, `advance`, and `choose` drive both + `pnpm ridge:console` and the Phaser scene. +3. **Stage spots are progress-based.** A Compact Ridge Stage is a left-to-right + progress line (`0..1`) with labeled spots and interact radii. This replaces + the heavy Walk-Rail authoring / Stage Debugger workflow for First Playable + Route work. +4. **VisualProvider hides art volatility.** Runtime renders a view model through + a stick provider today; future iPad/Procreate art plugs in behind the same + interface. +5. **Conversation uses Scene UI.** Talking halts explore mode in core; React + Scene UI owns the Persona-style panel (speaker, portrait slot, text, + advance/choices). + +## Relationship To Prior ADRs + +- Supersedes ADR-0004 as the active First Playable Route runtime authority. +- ADR-0004 Stage Composition Source ideas remain historical reference for + detailed plate/rail authoring if a later area needs that complexity again. +- ADR-0001 / ADR-0003 remain historical folded-desk blockout records. + +## Consequences + +- Ridge internals may break while Overworld and other shipped surfaces stay + protected. +- Large PNG stage plates and the Ridge Stage Debugger are disposable for the + Bridge tracer rebuild. +- Future areas should start from console content + stick visuals, then swap art + through VisualProvider when ready. diff --git a/docs/adr/0006-game-console-core.md b/docs/adr/0006-game-console-core.md new file mode 100644 index 00000000..9afb4c09 --- /dev/null +++ b/docs/adr/0006-game-console-core.md @@ -0,0 +1,44 @@ +# Game Console Core (Headless Full-Game Play) + +Status: accepted. + +The whole interactive game — Overworld, Basement, Potassium Slip (discrete), and +Ridge — is playable through one pure TypeScript console session. Phaser and +React remain presentation adapters. AI agents and humans share the same command +surface via `pnpm game:console`. + +## Decision + +1. **`GameConsoleSession` is the cross-scene gameplay authority for headless + play.** It owns inventory, equipped items, discovered secrets, active scene, + and overlay focus. Sub-areas (overworld walk, basement room, discrete + Potassium campaign, Ridge bridge) are delegated modules, not parallel stores. +2. **No bridge/store imports in `src/game/core/**`.** Durable browser bridge + state stays in `src/game/bridge/store.ts` for the live app. The console + session mirrors the same inventory/progress vocabulary as plain data so Node + scripts and tests never need Phaser or React. +3. **One command surface.** Shared verbs (`look`, `go`, `interact`, `inventory`, + `equip`, `close`, plus scene-local `advance`/`choose`/`fight`/`draft`) drive + `pnpm game:console` and can later drive thin Phaser adapters the same way + Ridge already does. +4. **Potassium headless mode is discrete, not Arcade Physics.** Wave clear, + draft pick, win → Circuit are turn commands. The live Phaser Potassium scene + keeps real-time physics; the console proves route reachability and reward + wiring for AI playtests. +5. **`pnpm ridge:console` remains a Ridge-only alias.** Prefer + `pnpm game:console` for full-route smoke paths (glasses → peel → Circuit → + CRT → Ridge). + +## Relationship To Prior ADRs + +- Extends ADR-0005's console-core pattern from Ridge to the full game graph. +- Does not replace ADR-0005 for Ridge-internal stage/conversation truth. + +## Consequences + +- Full AI smoke paths no longer require a browser. +- Overworld and Basement Phaser scenes are thin adapters over + `GameConsoleSession` (physics/art stay in Phaser; interact/gating decisions + and shared street/room spots live in `src/game/core/console/`). +- Real-time Potassium physics stays Phaser-only; do not pretend the discrete + console is a frame-accurate physics sim. diff --git a/docs/adr/README.md b/docs/adr/README.md index f3aeed4f..2fa9f3ef 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -16,4 +16,6 @@ override the Ridge pre-production route. | [`0001-ridge-blockout-as-exploration-map-source.md`](./0001-ridge-blockout-as-exploration-map-source.md) | Historical folded-desk blockout runtime (removed from repo); superseded as future route canon. | | [`0002-audio-adapter-boundary.md`](./0002-audio-adapter-boundary.md) | Current audio ownership boundary. | | [`0003-ridge-blockout-source-contract.md`](./0003-ridge-blockout-source-contract.md) | Historical blockout tooling contract (removed from repo); superseded as future route canon. | -| [`0004-ridge-stage-composition-source.md`](./0004-ridge-stage-composition-source.md) | Experimental Ridge exploration and First Playable Route staging source. | +| [`0004-ridge-stage-composition-source.md`](./0004-ridge-stage-composition-source.md) | Historical Stage Composition / Walk Rail staging source; superseded by ADR-0005 for First Playable Route runtime. | +| [`0005-ridge-console-core-and-stick-visuals.md`](./0005-ridge-console-core-and-stick-visuals.md) | Active Ridge runtime: console core, stick VisualProvider, Scene UI conversation. | +| [`0006-game-console-core.md`](./0006-game-console-core.md) | Full-game headless console: Overworld / Basement / discrete Potassium / Ridge. | diff --git a/docs/architecture-direction.md b/docs/architecture-direction.md index e6740d31..853a81aa 100644 --- a/docs/architecture-direction.md +++ b/docs/architecture-direction.md @@ -5,9 +5,10 @@ why behind the runtime shape, and the direction future refactors should preserve For what exists in code today, treat [`runtime-architecture.md`](runtime-architecture.md), `AGENTS.md`, and the scoped `.agents/rules/` files as the operational guidance. -Ridge caveat: the folded-desk blockout stack was removed from the repo. Active -Ridge runtime spatial truth lives in Bridge stage composition -(`src/game/scenes/ridge/bridge/`). Historical blockout notes in ADRs and +Ridge caveat: the folded-desk blockout stack and Stage Debugger were removed. +Active Ridge gameplay truth lives in the Ridge Console Core +(`src/game/core/ridge/`) with stick VisualProvider presentation under +`src/game/scenes/ridge/art/`. Historical blockout notes in ADRs and [`game-design/ridge/map-language.md`](./game-design/ridge/map-language.md) do not override the pre-production route in [`game-design/ridge/README.md`](./game-design/ridge/README.md). @@ -54,9 +55,12 @@ paths, prefer [`runtime-architecture.md`](runtime-architecture.md). When proposing future refactors: - Prefer extending existing lifecycle, bridge, overlay, scene UI, and shared runtime seams instead of re-introducing callback-only scene orchestration, ad-hoc overlay maps, or ad-hoc global state. -- For current Ridge runtime work, keep spatial truth in Bridge stage - composition (`stageComposition.ts`) instead of rebuilding parallel catalogs or - resurrecting the removed blockout pipeline. +- For current Ridge runtime work, keep gameplay truth in the Ridge Console Core + (`src/game/core/ridge/`) and art behind VisualProvider, instead of rebuilding + parallel catalogs or resurrecting removed debugger/blockout pipelines. +- For full-game AI/headless playtests, prefer `GameConsoleSession` + (`src/game/core/console/`, ADR-0006) and `pnpm game:console` over browser-only + smoke paths. - Introduce shared render helpers only when repeated render policy code appears. --- diff --git a/docs/game-design/README.md b/docs/game-design/README.md index b38700d8..7b7d045b 100644 --- a/docs/game-design/README.md +++ b/docs/game-design/README.md @@ -11,8 +11,9 @@ Do not treat every file here as equally active. - **Active Ridge pre-production story/route canon:** [`ridge/story-level-bible.md`](./ridge/story-level-bible.md). - **Active Ridge pre-production area design:** [`ridge/areas/`](./ridge/areas/README.md). - **Current Ridge runtime/prototype snapshot:** [`ridge/ridge-snapshot.md`](./ridge/ridge-snapshot.md). -- **Current Bridge runtime spatial source:** [`stageComposition.ts`](../../src/game/scenes/ridge/bridge/stageComposition.ts), described by - [`ridge/ridge-snapshot.md`](./ridge/ridge-snapshot.md). +- **Current Bridge runtime:** console core + stick VisualProvider, described by + [`ridge/ridge-snapshot.md`](./ridge/ridge-snapshot.md) and + [`../adr/0005-ridge-console-core-and-stick-visuals.md`](../adr/0005-ridge-console-core-and-stick-visuals.md). - **Legacy blockout contract (historical):** [`ridge/map-language.md`](./ridge/map-language.md) — source removed from repo. - **Pre-production product vision:** [`ridge/summit.md`](./ridge/summit.md). - **Milestone map:** [`ridge/milestone-plan.md`](./ridge/milestone-plan.md) @@ -101,7 +102,8 @@ prefer GitHub Issues. `mini-games/stampede-sketch.md`. These are local mini-game contracts, not required first-ending Living Proof. - **Current Ridge runtime/prototype reality:** `ridge/ridge-snapshot.md`. -- **Current runtime source:** `../../src/game/scenes/ridge/bridge/stageComposition.ts`. +- **Current runtime source:** `../../src/game/core/ridge/` plus + `../../src/game/scenes/ridge/`. - **Historical blockout documentation:** `ridge/map-language.md`. - **Legacy/prototype topology and planning history:** `ridge/legacy/`. - **Reference/provenance:** `ridge/reference/` plus `docs/research/`. These can diff --git a/docs/game-design/mini-games/potassium-slip.md b/docs/game-design/mini-games/potassium-slip.md index 7fbdc10c..949bd244 100644 --- a/docs/game-design/mini-games/potassium-slip.md +++ b/docs/game-design/mini-games/potassium-slip.md @@ -42,10 +42,11 @@ ricochet lane and should remain the benchmark for a deep opt-in mini-game, not the template every future mini-game must match. The Circuit is Potassium's durable world reward in the current/prototype -runtime. For the active Ridge pre-production plan, Potassium is optional side -fun and should not be required Living Proof for the first ending unless the -active route canon deliberately reopens that decision. Current runtime code -that reads the Circuit should use existing inventory ownership with +runtime. Inserting it into the overworld street CRT boots Ridge as an optional +secret side story (game-inside-the-game). Potassium remains optional side fun +and should not be required Living Proof for the first ending unless the active +route canon deliberately reopens that decision. Current runtime code that reads +the Circuit should use existing inventory ownership with `isItemOwned('circuit')`, or the equivalent bridge inventory read, instead of adding a separate Ridge progress flag such as `circuitOwned`. diff --git a/docs/game-design/player-manual.md b/docs/game-design/player-manual.md index 2c599ab5..6224ffb4 100644 --- a/docs/game-design/player-manual.md +++ b/docs/game-design/player-manual.md @@ -32,6 +32,7 @@ Current shipped behavior for interactive mode. Concept and future-design notes l - Closing an overlay returns to the street, unless the overlay was opened from an interior parent scene. - The basement entrance is the hole under the `TODO?` sign. - A banana peel secret can be discovered in the street. After discovery, interacting with it enters Potassium Slip. +- A blank street CRT sits between the Projects and Abilities buildings. Without a Circuit it only shows a locked prompt. After winning Potassium Slip and owning the Circuit, interact with the CRT to insert it and enter Ridge (secret side story). ## Hobbies Room @@ -57,11 +58,23 @@ Current shipped behavior for interactive mode. Concept and future-design notes l - Launch the banana by dragging toward a target and releasing. - Hold while the banana is moving to yo-yo it back to the launch pad, then relaunch. - Clear falling 5-column enemy waves, draft stackable upgrades, and defeat the boss for the Circuit. +- The Circuit can be inserted into the overworld street CRT to enter Ridge. - You start with 5 lives; losing all lives ends the run. - After game over, choose `Retry` or `Return to City`. After winning, choose `Endless Mode` or `Return to City`. - Potassium keeps a local top-5 records list for finished runs. - Press `R` to retry after a terminal screen, `E` to return, `Space` to continue into endless after winning, or `Esc` to return to the city. +## Headless / AI Console + +- Full interactive route is playable without a browser via `pnpm game:console`. +- Shared commands: `look`, `go left|right [n]`, `interact [name]`, `inventory`, + `equip` / `unequip`, `close`. +- Potassium in console mode is discrete (`start`, `fight`, `draft`) — not the + live drag-banana physics. Winning still grants the Circuit. +- Ridge conversation uses `advance` / `choose`. `pnpm ridge:console` is an alias + that starts already inside Ridge. +- One-shot scripts: `pnpm game:console --script "look; go right 3; interact" --json`. + ## Runtime Notes For Playtesting - React overlays should always pause Phaser input. diff --git a/docs/game-design/ridge/README.md b/docs/game-design/ridge/README.md index 07163755..bfa2f11f 100644 --- a/docs/game-design/ridge/README.md +++ b/docs/game-design/ridge/README.md @@ -28,7 +28,8 @@ Use each file for exactly one concern: | Current Ridge route canon | [`story-level-bible.md`](./story-level-bible.md) | Route spine, area barricade chain, cross-area Cicka/guitar logic, ending order. | | Area-specific design canon | [`areas/`](./areas/README.md) | Local geography, blockers, residents, prompts, staging, Cicka Resting Spots, visual/audio notes. | | Dialogue conventions | [`dialogue-conventions.md`](./dialogue-conventions.md) | Pre-production dialogue file structure, line IDs, placeholder policy, and migration rules. | -| Current runtime/prototype truth | [`ridge-snapshot.md`](./ridge-snapshot.md) | What exists now in the Phaser prototype, what can be reused, and what is disposable. | +| Current runtime/prototype truth | [`ridge-snapshot.md`](./ridge-snapshot.md) | What exists now: console core, stick visuals, conversation panel. | +| Runtime architecture ADR | [`../../adr/0005-ridge-console-core-and-stick-visuals.md`](../../adr/0005-ridge-console-core-and-stick-visuals.md) | Console-first gameplay authority and replaceable VisualProvider. | | Active open questions | [`open-questions.md`](./open-questions.md) | True design unknowns and blockout-detail TBD. Area premises may already be accepted even when prompt/topology details remain open. | | Product vision | [`summit.md`](./summit.md) | Durable fantasy and pillars, not detailed route implementation. | | Implementation sequencing | [`milestone-plan.md`](./milestone-plan.md) | Current route-reset milestones, Bridge Tracer Slice, source stack, Agent-Ready Slice Contract, prototype reuse rules, and agent checklist. Not live backlog. | @@ -71,7 +72,8 @@ Treat these as prototype/reference unless a task explicitly says to adapt them: The Ridge Blockout Source implementation was removed from the repo; keep [`map-language.md`](./map-language.md) and git history as contract reference. -The Ridge Stage Debugger (`?mode=ridge-stage-debugger`) remains Bridge-first. +The Ridge Stage Debugger was removed; use `pnpm ridge:console` and the stick +Bridge scene (`?mode=interactive&startScene=ridge`) for playtest. See ADR-0005. Superseded planning docs live under [`legacy/`](./legacy/README.md). Do not search that folder first when updating active route design. diff --git a/docs/game-design/ridge/areas/01-bridge/README.md b/docs/game-design/ridge/areas/01-bridge/README.md index 1ef54d3a..e93bdba8 100644 --- a/docs/game-design/ridge/areas/01-bridge/README.md +++ b/docs/game-design/ridge/areas/01-bridge/README.md @@ -57,10 +57,10 @@ art.** Use **Bridge Resting Spot** as the practical label. -- Before resolution: Cicka peacefully plays with or carries the toy car / - weight-test prop, then the player recontextualizes the toy as the missing - bridge test car. -- After resolution: Cicka settles near the completed bridge sketch or leaves a +- Before resolution: Cicka peacefully plays with the toy car inside the + **Cornfield Section**, hidden from the Bridge Draftsperson by corn and the + **Cornfield Exit** screen. +- After resolution: Cicka settles near the **Simple Bridge Crossing** or leaves a tiny mark by the crossing. The resting spot is readable flavor, not a gate. @@ -70,20 +70,22 @@ The resting spot is readable flavor, not a gate. Stage-order sketch: ```text -Nature / hill entry - -> Cicka + toy car play spot - -> blocked bridge + unfinished blueprint +Cornfield Section (simple bg) + -> Cicka + toy car play spot inside corn + -> Cornfield Exit (foreground corn screen) + -> blocked Simple Bridge Crossing + blueprint -> Bridge Draftsperson / prop zone -> Cicka Parallel Play return - -> toy car bridge test + completed crossing + -> toy car bridge test + completed crossing (Route Landmark Backdrop may open here) -> Concert transition exit ``` -Framing intent: start with a gentle walk-right introduction, then compose the -blocked crossing and blueprint as the readable center of the stage. Foreground -paper edges and failed sketch props can frame the playable lane; background -layers should make the bridge feel like a small handmade crossing rather than a -platforming gauntlet. +Framing intent: start inside a readable **Cornfield Section** with a simple +background and no full-route landmark backdrop. Use foreground corn at the +**Cornfield Exit** so leaving the field feels clear and explains why the Bridge +Draftsperson could not see Cicka with the toy car. The crossing itself stays a +**Simple Bridge Crossing** before and after completion, with the optional +promise-shot backdrop only once the bridge vista opens. ## Prototype Floor @@ -94,10 +96,11 @@ Blockout conventions before the other required areas fan out. The first blockout only needs: -- walk-right nature/hill intro -- peaceful Cicka + toy car first encounter +- walk-right **Cornfield Section** intro with simple background +- peaceful Cicka + toy car first encounter inside the corn - basic sit/play interaction -- blocked bridge +- **Cornfield Exit** foreground corn screen before bridge site +- blocked **Simple Bridge Crossing** - Bridge Draftsperson + missing middle span blueprint - return to Cicka for Cicka Parallel Play - toy car bridge test @@ -111,8 +114,8 @@ layers. Definition of done for the Bridge Tracer Slice: -1. player can start at Bridge and move through the nature/hill entry -2. player can encounter Cicka with the toy car +1. player can start at Bridge and move through the **Cornfield Section** +2. player can encounter Cicka with the toy car inside the corn 3. player can talk to the Bridge Draftsperson at the unfinished blueprint 4. player can use Cicka Parallel Play to receive or unlock the toy car test prop 5. player can run the toy-car bridge test @@ -158,32 +161,33 @@ Minimum outcomes: - Cicka is grounded at the Bridge Resting Spot and visibly plays with the toy car / weight-test prop instead of floating near a generic marker. - Keep the current left-to-right route geometry for this pass. Solve the - Cicka / Draftsperson sightline problem through visual staging: foreground - paper folds, trees, terrain, or set dressing should screen Cicka's toy-car - play spot from the Draftsperson's work zone without adding a new pocket, - traversal branch, or route mechanic. -- Stage the Bridge Area as a wooded paper-set scene with multi-parallax forest - layers: pale distant trees, midground pines/brush, and a few foreground - paper-fold or tree screens. The environment should read as "wood like" before - individual props are noticed. + Cicka / Draftsperson sightline problem through the **Cornfield Section** and + **Cornfield Exit**: Cicka's toy-car play spot stays inside the corn where the + Bridge Draftsperson cannot see her, without adding a new pocket, traversal + branch, or route mechanic. +- Stage the left Bridge approach as a sunny **Cornfield Section** with simple + background layers only. Do not show the full **Route Landmark Backdrop** here. +- Stage the bridge work site with a **Simple Bridge Crossing**: one very simple + missing gap before resolution and one equally simple completed crossing after + the toy-car test. The promise-shot backdrop may open only at this vista. - The Bridge Draftsperson has a distinct blueprint work zone, such as a tiny taped paper construction nook with a small desk/blueprint table. Add a nearby worker rest cue, such as a rough tent, tiny cabin facade, or sleep shelter, so the Draftsperson feels like someone stationed at the bridge rather than a generic marker. - Keep the Bridge Draftsperson's construction zone visually simple: one clear - bridge, one clear blueprint/plan surface, one clear worker silhouette, and one - clear rest shelter cue. Avoid random paper scraps, oversized overlapping + simple bridge, one clear blueprint/plan surface, one clear worker silhouette, + and one clear rest shelter cue. Avoid random paper scraps, oversized overlapping shapes, or doodles that compete with the bridge read. - The worker rest cue is visual-only in this pass: no enterable pocket, food pickup, treat shortcut, new prompt, or route mechanic. -- Cicka's space and the Draftsperson's work zone are separated by local terrain, - trees, paper folds, or foreground/background staging so the Draftsperson does +- Cicka's space and the Draftsperson's work zone are separated by the cornfield, + **Cornfield Exit** foreground corn, and local staging so the Draftsperson does not appear able to see Cicka holding the toy car. - The bridge, missing span, completed span, floor, background, and prop placeholders read as one handmade paper set rather than unrelated debug shapes. -- For the first playable forest read, prefer **monochrome-first staging**: +- For the first playable farm read, prefer **monochrome-first staging**: black ink on off-white paper, with value/line weight/hatching doing the work. Yellowed paper fills should be subdued by runtime presentation rather than becoming a new Bridge palette. Later color can be applied deliberately as a @@ -206,37 +210,30 @@ Minimum outcomes: unrelated environment cutouts. The Bridge Area should use a small number of coherent horizontal sketch plates for scenery, then keep interactive objects modular: - 1. deep sky / mountain plate - 2. far forest plate with varied pines and rounded bushy trees - 3. mid forest / work-camp staging plate - 4. playable terrain / gorge plate with visible top surface, cliff faces, - hatching, and bridge supports - 5. modular before/after bridge state + 1. simple cornfield sky / field plate for the left approach + 2. foreground corn exit / screen plate + 3. simple riverbank / bridge work-site plate + 4. optional promise-shot backdrop plate at the bridge vista only + 5. modular before/after **Simple Bridge Crossing** state 6. modular Cicka, toy car, Bridge Draftsperson, prompts, and route-state objects - Do not return to a single baked whole-map image. The layered plates should keep parallax, camera framing, and runtime route-state flexibility while preventing the scene from reading as a collage of isolated generated props. -- Individual tree, bush, rock, and terrain cutouts are allowed only when they - support a coherent layer plate or a clear gameplay staging need. They should - not be the main way the forest read is created. -- When the player reaches the bridge vista, the background can become the first +- Individual corn stalks, fence posts, or riverbank props are allowed only when + they support a coherent layer plate or a clear gameplay staging need. +- When the player reaches the bridge vista, the background may become the first route-wide promise shot. The deep layers may eventually hint at later Ridge - areas beyond the gorge: a distant city / Concert Area silhouette, the Ridge - Threshold, and the area near the threshold that leads toward the Dance - Festival. Keep these as tiny environmental reads in the far distance, not - literal UI labels or busy landmarks. If the downstream areas are not visually - settled yet, leave the current forest/mountain vista intact and add these - silhouettes after their art direction exists. -- The Bridge plate must include both staging moods, not only the bridge vista: - the left approach should stay on the same continuous sidescroller path while - close trees and brush hide most distant layers; the bridge approach should - then open into the ridge vista so the player feels the space clear as they - reach the crossing. Do not turn the Cicka approach into a separate lower - forest floor, blocked foreground-room composition, new traversal pocket, or - angled/isometric top-plane view. The scene may imply cliff height at the gorge - and ridge edges, but the playable lane should read primarily as a side-on - horizontal path. + areas beyond the river: a distant city / Concert Area silhouette, the Dance + Festival foothill read, and the Relay coastal hill. Keep these as tiny + environmental reads in the far distance, not literal UI labels or busy + landmarks. The **Cornfield Section** itself should stay on a simple background. +- The Bridge plate must include both staging moods: the left **Cornfield + Section** stays on the same continuous sidescroller path with simple field + layers and a readable **Cornfield Exit**; the bridge approach then opens into + the crossing vista and optional promise-shot backdrop. Do not turn the Cicka + approach into a separate lower floor, blocked foreground-room composition, + new traversal pocket, or angled/isometric top-plane view. - Keep Bridge assets layered and individually placeable rather than compositing the full stage into one image. A generated full-scene image may be used as target/reference, but Phaser should still own placement, before/after bridge diff --git a/docs/game-design/ridge/map-language.md b/docs/game-design/ridge/map-language.md index d5988aac..53383504 100644 --- a/docs/game-design/ridge/map-language.md +++ b/docs/game-design/ridge/map-language.md @@ -96,9 +96,9 @@ encounter logic stay outside v0. ## Tooling Status The blockout generator scripts and the former Ridge blockout source tree were -removed. Current Bridge rail/stage work uses the Ridge Stage Debugger at -`?mode=ridge-stage-debugger` and the Bridge Stage Composition Source under -`src/game/scenes/ridge/bridge/stageComposition.ts`. +removed. Current Bridge runtime uses the Ridge Console Core +(`src/game/core/ridge/`) and stick VisualProvider; see ADR-0005 and +`ridge-snapshot.md`. ## Core Shape diff --git a/docs/game-design/ridge/ridge-snapshot.md b/docs/game-design/ridge/ridge-snapshot.md index ed3eb64e..c83d3d2a 100644 --- a/docs/game-design/ridge/ridge-snapshot.md +++ b/docs/game-design/ridge/ridge-snapshot.md @@ -12,8 +12,7 @@ - **Ridge source router:** [`README.md`](./README.md). - **Active story/route canon:** [`story-level-bible.md`](./story-level-bible.md). - **Active area design:** [`areas/`](./areas/README.md). -- **Legacy blockout language (historical):** [`map-language.md`](./map-language.md) — superseded; folded-desk source removed from the repo. -- **Legacy folded topology reference:** [`legacy/`](./legacy/README.md). +- **Runtime architecture ADR:** [`../../adr/0005-ridge-console-core-and-stick-visuals.md`](../../adr/0005-ridge-console-core-and-stick-visuals.md). - **Product vision:** [`summit.md`](./summit.md). - **Live implementation work:** GitHub Issues. @@ -25,395 +24,91 @@ doc about future route intent, the active design docs win. ## Current Runtime Shape -Ridge is a separate Phaser scene that can boot directly in development with: +Player-facing Ridge entry (secret side game): ```text -?mode=interactive&startScene=ridge +Overworld banana peel + -> win Potassium Slip (earn Circuit) + -> insert Circuit into the street CRT (between Projects and Abilities) + -> Ridge ``` -Current runtime characteristics: - -- Ridge Bridge exploration uses a Bridge-owned Stage Composition Source and a - Primary Walk Rail as movement authority. Left/right input advances rail - progress, then the player foot position, scale, depth, and interaction - checks resolve from the rail and Stage Spots. -- The active `ridge` scene is now the **Bridge Tracer Slice**: a flat - Bridge-only route from nature/hill entry to Cicka's toy-car play spot, - blocked blueprint bridge, Bridge Draftsperson, auto-success toy-car test, - completed crossing, and Bridge-to-Concert handoff. -- Bridge stage plates, modular objects, Stage Spots, Stage Occluders, and - route-beat presentation state are authored in the colocated Bridge source - under `src/game/scenes/ridge/bridge/`. -- `?mode=ridge-stage-debugger` opens the current Ridge Stage Debugger: a - Bridge-first live preview with route-beat controls, Walk Rail readouts, Stage - Spot movement, copyable rail snippets, and Bridge Stage debug overlays. -- First Playable Route progress lives in the bridge store as - `firstPlayableRoute`, a typed route state that only permits active Bridge - beats while `activeAreaId` is `bridge` and records the Concert handoff as the - post-Bridge area state. -- Bridge prompts and dialogue are mirrored into a small typed runtime data layer - from the accepted Bridge dialogue IDs. -- The legacy folded-desk blockout stack (source compiler, generated map, - traversal helpers, Cicka Home mutations, trail-card anchors) was removed from - the repo; see git history and [`map-language.md`](./map-language.md) for the - old contract description. -- **Stampede Sketch** remains as a standalone scene and development entry point - (dev switcher, basement `stampede` command). It is not on the active Bridge - route; keep it for optional mini-game content in a future rework. - -## Active Runtime Route Read - -The current playable Ridge route is the Bridge tracer: +Dev direct boot: ```text -Nature / hill entry - -> Cicka + toy car play spot - -> blocked bridge + unfinished blueprint - -> Bridge Draftsperson - -> Cicka Parallel Play - -> toy-car bridge test - -> completed crossing - -> Bridge-to-Concert handoff +?mode=interactive&startScene=ridge ``` -The Bridge-to-Concert handoff is only a transition proof. Concert, Dance -Festival, Relay, and the ending sequence remain future implementation slices. - -## Legacy Folded Blockout Read - -The current blockout route is the folded desk Ridge: +Headless / AI playtest: ```text -Outskirts - -> Cicka Home - -> Work Artifact Ledge - -> Stampede Blanket - -> Switchback Shelf - -> Telegraph Terrace - -> Guide Overlook - -> Relay Gate - -> Domino Desk +pnpm ridge:console +pnpm ridge:console --script "look; go right 3; interact; advance" +pnpm ridge:console --script "look" --json ``` -Future or optional promises in the blockout include: - -- Paw Underpath from Outskirts/Cicka Home. -- Lucky Luna Drop Pocket from Guide Overlook toward Stampede Blanket. -- High Ledge above Domino Desk. -- Telegraph cord drop back to Cicka Home. -- Domino lift back to Cicka Home. - -The level-design goal is that the player walks a compact lived-in sketchbook -neighborhood, sees Relay early, follows subtle Cicka field-presence hints at -route blockers, helps tiny residents change the route, and learns Danilo -through artifacts rather than static portfolio buildings. - -## Current Landmarks - -- **Outskirts:** old city edge / future Overworld absorption point, Basement - hatch promise, Potassium hint space, early Relay sightline. -- **Cicka Home:** progress memory space, Cicka home base, memento/home mutation - space, future underpath clue. -- **Work Artifact Ledge:** first obvious work/project artifact area with a - side-shelf skill scrap promise. -- **Stampede Blanket:** first new opt-in mini-game anchor and first earned - Ridge memory source. -- **Switchback Shelf:** first spatial fold above Cicka Home and Stampede - shortcut promise. -- **Telegraph Terrace:** future one-button parry/timing lane landmark. -- **Guide Overlook:** reorientation point and Relay sightline. -- **Relay Gate:** first-ending promise and proof-slot destination. -- **Domino Desk:** future deterministic puzzle/lift promise. +Full-game headless (street → Circuit → Ridge) uses `pnpm game:console`; see +ADR-0006 and the player manual. `ridge:console` is `game:console --scene ridge`. -## Current Rewards And Memory - -Durable Ridge progress is intentionally small: - -- stamps -- manual pages -- glide pips -- shortcuts -- first playable route state - -Current accepted behavior: - -- Bridge route state tracks the Bridge beat from `intro` through - `concert_handoff` without introducing inventory, quest logs, or objective - trackers. -- First Stampede clear awards the `stampede-sketch` Ridge stamp. -- First Stampede clear awards one glide pip. -- Repeat Stampede clears do not duplicate those rewards. -- Ridge derives visible Stampede blanket memory from the stamp instead of - storing sticker ids. -- Cicka Home mutation facts are compiled from the blockout and become active - only when a real durable progress source exists. -- Cicka can provide early pre-translator presence and Stampede-gated memory - flavor without becoming a quest board, vendor, or inventory checklist. +Current runtime characteristics: -## Future Design Target +- Gameplay authority lives in pure `src/game/core/ridge/` (`RidgeConsoleSession`). +- Phaser `RidgeScene` is a thin adapter: left/right (+ touch) walks progress, + interact starts conversation, Scene UI owns the Persona-style talk panel. +- Default art is mathematical stick figures via `StickVisualProvider` + (`src/game/scenes/ridge/art/`). Replace that provider later for iPad art + without rewriting route logic. +- Bridge route beats still use bridge-store `firstPlayableRoute` + (`intro` -> `needs_toy_car` -> `toy_car_shared` -> `bridge_complete` -> + concert handoff). +- The Ridge Stage Debugger, Walk-Rail authoring commit plugin, and large PNG + stage-plate pipeline were removed as disposable prototype tooling. +- **Stampede Sketch** remains a standalone optional scene; it is not on the + active Bridge route. -The active future design target is the linear Ridge route from -[`story-level-bible.md`](./story-level-bible.md): +## Active Runtime Route Read ```text -Bridge Area / Blueprint Bridge - -> Concert Area / Concert Crossing Beat - -> Dance Festival Area / Opening Dance Shuttle Beat - -> Relay Spire / Guitar Farewell / Cicka Threshold Farewell +Nature / hill entry + -> Cicka + toy car play spot (optional Persona-style choice) + -> blocked bridge + unfinished blueprint + -> Bridge Draftsperson + -> Cicka Parallel Play (receive toy car) + -> toy-car bridge test + -> completed crossing + -> Bridge-to-Concert handoff ``` -The folded/Cicka Home route in [`legacy/`](./legacy/README.md) remains legacy -prototype and topology reference until it is rewritten around this route. - -Before implementing the next Ridge runtime or blockout rewrite, write and accept -a prose story/level/character plan for the core Exploration Map scenes and -their route blockers. Start from the middle/end emotional and progression beats, -then work backward to the opening so the first scene teaches the right promise. -Mini-games, shortcuts, and optional platforming pockets may stay rougher in -this prose pass, but the core overworld/Ridge route blockers should be legible -in words first. - -The accepted ending anchor is the **Cicka Threshold Farewell**: Cicka recurs -through the Ridge as field presence, then leaves the seated player through a -warm sketchbook threshold somewhere the player cannot follow. The ending should -stay tender and non-literal rather than becoming a death scene or grief speech. -Design the canonical first ending before designing optional endings. Multiple -endings are not required for the Ridge; add alternate endings only if a later -story/level pass proves they create meaningful replay value without weakening -the Cicka farewell. -The first prose artifact should be a short ending sequence outline before the -required Ridge Areas and their Resident Beats are designed: arrival state, Relay -readiness, Cicka's final field presence, farewell action, threshold departure, -and return-to-Ridge state. - -Accepted first ending outline: - -1. The fixed main route carries the player through Bridge Area, Concert Area, - and Dance Festival Area before Relay. -2. Clearing the Dance Festival barricade grants the last daylight ride to Relay. -3. Cicka appears in her final field-presence spot, calm and familiar. -4. The player uses **Sit and Play** to share a quiet Guitar Farewell with Cicka, - ideally under a warm sunset or other cozy threshold light. This is the v0 - final trigger, not setup for a separate post-song send prompt. -5. Cicka moves toward the threshold, pauses, turns back toward the seated - player, gives one small raw meow, then slips into a warm - sketchbook-threshold artifact beyond the player's path. It can read as warm - light, a scratch-like seam, a paper hole, or a gentle glitch-portal-like - artifact rather than a literal page-edge fold. The player cannot follow into - that threshold. The initial version uses no translated farewell line. -6. After Cicka disappears, hold on the empty sunset spot for a short silent beat, - then fade to a restrained non-diegetic dedication card: - **For Cicka.** / **Thank you for playing.** Do not present this card as Cicka - writing, speaking, or explaining the farewell. The card should hold briefly, - then auto-fade into the clean Bridge Area reset without a button prompt. Do - not add a separate credits card or "The End" card in v0. - Exact hold, fade, montage, prompt-delay, and silence timings should be tuned - in prototype/playtest with tasteful recommended defaults rather than fixed in - pre-production. -7. A final Relay trace or Concert Resting Spot echo can exist only as visual - staging if earlier route art has taught that language. Do not present - paw/page marks as a mechanic or required symbol in v0. -8. For the first playable version, use **First Playable Reset Return** after the - **Dedication Card**: cleanly reset route progress and return the player - directly to the beginning of Bridge Area rather than opening a post-game - free-travel Ridge or title/menu return. Long-term, this can become an **Open - Ridge Return State** where completed areas remain freely revisitable after - the farewell, once optional mini-games, return content, and completed-area - revisits exist. - Do not surface any visible ending-seen memory in v0: no Micka, no changed - post-ending world, no special marker, and no new objective after reset. - -The guitar should be established earlier as a meaningful comfort item: -something the player can pick up and carry before the Relay **Guitar Farewell**. -Initial local Cicka Resting Spots can stay mostly visual instead of becoming -repeatable guitar prompts or dedicated interaction nodes. -Petting, lap resting, and similar affection interactions can wait for a later -polish pass. A hug is optional and should wait until character art can support -it without making the farewell feel awkward or over-staged. -The guitar should enter mid-route through a Resident Beat as an entrusted -reward or responsibility, not as a random pickup. A resident can give or lend it -after the player helps with a local music/concert problem; the route can then -stage Cicka around local Resting Spots so the final Relay comfort moment feels -prepared without requiring resting-spot interactions in v0. -Middle route area: **Concert Area**, containing **Concert Crossing Beat**. A -blocked concert/traffic crossing halts the route because the local guitarist -cannot play, the paper-stage setup is tangled, or the guitar needs a small -repair. Cicka subtly points attention to the guitar case, loose string, or -blocked crossing. The player learns a small -Guitar-Hero-like performance mini-game, helps the concert continue, the crossing -opens, and the guitar is entrusted to the player afterward. Keep the comedy -gentle; avoid making the guitarist a joke-only drunk if that undercuts the later -tribute. -Because dialogue UI, character assets, route blockers, trees/props, and audio -are already enough production load, the Concert Crossing Beat needs a non-arcade -fallback: conversation, collection, practice together, or a forgiving auto-play -path can resolve the main route while the Guitar-Hero-like mini-game remains the -ideal or optional version. - -The final route structure should be an **Area Barricade Chain**. Bridge, -Concert, and Dance Festival each contain one concrete local barricade; clearing -the Dance Festival barricade brings the player to Relay. Earlier "Living Proof" -language should now be treated as poetic shorthand for completed area changes, -not a separate proof resource, checklist, or optional readiness system. -Planning assumption: start with 2-3 required main-path Resident Help Beats -before the first ending, plus optional extra residents for return play. Treat the -exact count as a Level Designer and Story/Tone tuning variable, not a fixed -rule. +Concert, Dance Festival, Relay, and the ending sequence remain future +implementation slices. Copy the Bridge console-content + stick-visual pattern. -The target asks for: +## Console Contract (for AI agents) -- a readable mostly forward neighborhood route toward Relay Spire -- fixed Bridge -> Concert -> Dance Festival -> Relay Area Barricade Chain -- changed Cicka Resting Spots as emotional echoes, not separate proof sources -- first ending path completable through conversations, collection, authored - traversal, Ridge Areas, Resident Beats, and world changes without requiring - full arcade mini-games -- Mini-Game Entrances as optional fun, rewards, shortcuts, or future alternate - ways to finish a level, not required first-ending proof -- non-arcade fallback for any required beat that contains arcade-like interaction -- tiny resident problems that create visible route changes -- first route area as **Bridge Area**, containing **Blueprint Bridge**: a - required soft gate where helping a resident finish a bridge drawing/blueprint - makes the crossing real -- 2-3 required main-path Ridge Areas before the first ending, each centered on a - Resident Beat / Resident Help Beat, with optional extra residents only if they - add texture instead of errand fatigue -- final required route area as **Dance Festival Area**, containing **Opening - Dance Shuttle Beat**: - a nervous dance/romance setup at the foot of the Relay hill. The night dance - festival is real, but the required route beat happens during afternoon setup. - The route blocker should be practical: festival barriers, lantern lines, - chair stacks, stage/speaker cables, and a locked service-road gate block the - hill route until setup passes inspection. The locked route-agent is the - nervous hill-shuttle driver, whose daily job and emotional problem both - connect to the final approach. His romantic partner is the shy **Last-Stop - Operations Helper**, whose visible plaza-table work connects shuttle - questions, setup checks, volunteer handoffs, service-road clearance, and the - night dance. Keep these as role-first character labels for now; proper names - can wait until a later character pass. Accepted arrival premise: **Opening - Dance Setup + Last Daylight Shuttle**. Introduce the beat through a practical - wayfinding loop: - the player asks how to reach Relay, notices the blocked service road and - shuttle sign, then discovers the emotional layer through delayed setup and the - last daylight shuttle window before the road closes for the night festival. - Reveal the relationship through a triangulated discovery flow: the driver and - Last-Stop Operations Helper give truthful but incomplete practical answers, - then nearby locals help the player understand that they are avoiding each - other. Do not let the player solve the beat by directly confronting them with - their feelings. The Last-Stop Operations Helper's readiness favor is - **Operations Handoff Check**: the player helps prove the plaza setup is safe - enough for the **Dance Teacher** to keep watch once the night dance starts. - The point is not replacing her planning; it is showing that she did enough - and can enjoy the event. The driver's readiness favor is **One-Step - Practice**: the player helps - him privately learn one tiny dance step before the setup handoff, last - daylight ride, and later night-dance promise can happen. After both favors, - the connector is **Folded Song Request**: a tiny paper invitation where the - driver requests a simple, cute, guitar-friendly song and asks her for one - dance later without a public confession. Do not lock the beat to bachata or - any other specific dance genre; the exact requested song can remain unshown - and unheard by the player. The physical - route solve is **Setup Clearance Walkthrough**: final visible setup details - clear the service lane enough for the steward to open the gate and the driver - to offer the last daylight ride. Present it as a **Prompt-Driven Playable - Montage**: three default symbolic one-action snaps, plus a visible sky/plaza - time shift, not full manual chores or a pure cutscene. The default snaps are: - secure the lantern line, clear or tape the service-lane obstruction, and flip - the shuttle sign to "Last daylight ride." These can collapse further if the - beat needs to move faster. Conversation choices can - create different tones, orderings, and recoverable awkward paths, but should - not permanently block the first ending. Cicka's role is quiet threshold - observer near the operations table, shuttle step, or service gate; she can - loaf with the **Unnamed Counterpart Cat** as implied continuity foreshadowing, - using only silhouette/color specificity such as a pale or light-ink contrast - to Cicka. The scene should not name him, give him dialogue, confirm parentage, - or spend Micka before the post-ending return. -- required route spine: Bridge Area / Blueprint Bridge means changing the world - through art, Concert Area / Concert Crossing Beat means turning memory into - comfort, Dance Festival Area / Opening Dance Shuttle Beat means life can keep - moving with someone new, then Relay Spire / Guitar Farewell resolves Cicka's - threshold farewell -- Guitar Farewell can include a brief **Route Memory Montage**: Blueprint Bridge - being used, Concert Crossing continuing, Opening Dance beginning at night as - an emotional echo under the player's guitar, and earlier Cicka Resting Spots - carrying changed visual states. Keep it wordless or nearly wordless so the - focus stays on Cicka. -- Relay should use sunset for **Sit and Play Prompt** and the Cicka Threshold - Farewell. The montage can show the night festival beginning elsewhere, but - Relay should not become full night before the farewell. -- The shuttle ride from Last-Stop Plaza to Relay should be a **Short Threshold - Transition**, not a controllable driving segment: the driver can say "All - aboard the last ride," the vehicle starts over a brief blackout, the player - respawns at Relay Spire under sunset, and control resumes at Cicka's overlook - spot. -- The final Relay Spire interaction should be a **Sit and Play Prompt** near - Cicka. It starts the Guitar Farewell and commits to the ending without rhythm - UI, fail state, final skill check, or separate post-song send prompt. -- Cicka's departure should be physical and mostly silent: Cicka moves toward - the threshold alone, turns back toward the seated player, gives one small raw - meow, then slips into a warm sketchbook-threshold artifact beyond the player's - path. It can read as warm light, a scratch-like seam, a paper hole, or a - gentle glitch-portal-like artifact rather than a literal page-edge fold. The - player cannot follow into that threshold. The initial version should use no - translated farewell line. -- Immediate v0 post-ending behavior should use **First Playable Reset Return**: - after the **Dedication Card**, cleanly reset route progress and return the - player directly to the beginning of Bridge Area instead of opening a - post-game free-travel Ridge. Long-term, an **Open Ridge Return State** can - make completed areas freely revisitable, quiet, and minimally absence-marked - once optional mini-games, return content, or completed-area revisits make that - return valuable. Do not surface visible ending-seen memory in v0; Micka - remains delayed until a later post-ending trigger rather than appearing - immediately. -- Cicka field presence in every required Resident Help Beat, with one local - **Cicka Resting Spot** per required Ridge Area: Bridge Resting Spot, Concert - Resting Spot, and Dance Festival Resting Spot. Her role can vary from subtle - obstacle hint to changed-object observer to quiet trust marker. -- optional residents, NPCs, interactive props, and chill spaces that can offer - mini-games, atmosphere, jokes, or company without solving barricades -- Bridge Resting Spot, Concert Resting Spot, and Dance Festival Resting Spot as - local progress-memory spaces, not hubs or v0 prompt nodes -- two tiny visual states for each Cicka Resting Spot: before resolution, Cicka - draws attention to the local problem; immediately after resolution, the spot - becomes calmer through a settled pose, tiny mark, changed prop, or later - absence echo; the Guitar Farewell montage can revisit these already-changed - states rather than introducing them -- changed resting-spot states as optional emotional rewards, not required - noticing, revisiting, checklist, or route-gate steps -- no main-path slope reliance in v0; use chunky stairs, cords, shelves, bridges, - lifts, and soft drops -- no required jump button on the v0 main route; use authored climb, descend, - drop, lift, bridge, enter, and inspect interactions instead -- main route traversal that stays mobile-safe and object-driven -- first-walk route mastery before dexterity -- folded shortcuts after mini-game clears or resident help beats -- artifacts as physical learning objects, not resume modal replacements +Useful commands: -## Not Current Scope +| Command | Meaning | +| --- | --- | +| `look` / `status` | Surroundings, nearby distances, beat, progress | +| `go left\|right [n]` | Walk the progress line | +| `interact [name]` | Start nearest/named conversation | +| `advance` | Continue halted conversation | +| `choose ` | Persona-style reply | +| `leave` | Exit conversation early | +| `help` | Command list | -- Replacing the old Overworld as the default scene. -- Adding a minimap. -- Adding a generic mini-game framework. -- Adding stored sticker state. -- Making Cicka Resting Spots into checklist hubs, shops, or quest boards. -- Making Cicka a continuous follower, puzzle solver, or explicit objective giver. -- Using slopes/ramps as required main-path traversal in the v0 Ridge blockout. -- Requiring jump as a core Exploration Map button in the v0 main route. -- Requiring wall jump, double jump, or precision platforming for the first - farewell route. -- Moving the runtime blockout source again without an explicit migration issue - that updates imports and documentation together. +Observation JSON (`--json`) includes `nearby[].distance`, actors, inventory, +conversation state, and hints. -## Update Rule +## What Remains Disposable -Update this file when: +- Stick art itself (replace via VisualProvider) +- Exact progress numbers / interact radii +- Temporary conversation choice copy on first Cicka meet -- the current blockout route changes meaningfully -- a room or landmark becomes active/inactive in the runtime -- a reward changes durable state shape -- the active future route target changes in a way that affects runtime/prototype - interpretation -- a prior topology/design spike is superseded +## What Should Survive Area Blueprints -Do not update this file just to record every implementation issue. Use GitHub -Issues for live planning and agent-ready briefs. +1. Pure console session + scripted playthrough tests +2. Progress-based Compact Ridge Stage spots +3. Conversation halt + React Scene UI panel +4. VisualProvider seam between gameplay and art +5. Bridge-store `firstPlayableRoute` for durable route beats diff --git a/docs/runtime-architecture.md b/docs/runtime-architecture.md index 605a914e..48e155b3 100644 --- a/docs/runtime-architecture.md +++ b/docs/runtime-architecture.md @@ -43,13 +43,17 @@ game vocabulary is explicit: - **Shared scene runtime** - reusable Phaser-facing machinery lives in `src/game/sharedSceneRuntime`: side-view player lifecycle, camera policy, scene presentation, resume policy, keyboard pause, interior interactions, text, textures, and vision helpers. - **Pure gameplay decisions** - `src/game/core` contains deterministic ECS, input, and player logic that can be tested without Phaser, React, browser globals, or bridge state. - **Scene-owned modules** - scene folders own local layout, triggers, Phaser objects, scene contexts, scene-local overlays, and heavy scene runtime modules. -- **Ridge exploration runtime** - Bridge exploration treats - `src/game/scenes/ridge/bridge/stageComposition.ts` as the Bridge Stage - Composition Source: it owns the Primary Walk Rail, Stage Spots, Stage Plates, - Stage Objects, Stage Occluders, camera bounds, and route-beat presentation - predicates consumed by the Ridge scene. The legacy folded-desk blockout stack - was removed from the repo; see [`game-design/ridge/map-language.md`](game-design/ridge/map-language.md) - for the historical contract. +- **Ridge exploration runtime** - Bridge exploration is console-first + (`src/game/core/ridge/`): progress-based spots, conversation halt, and + command/observation APIs shared by `pnpm ridge:console` and Phaser + `RidgeScene`. Stick art renders through `RidgeVisualProvider`. See + [`adr/0005-ridge-console-core-and-stick-visuals.md`](adr/0005-ridge-console-core-and-stick-visuals.md). +- **Full-game console** - `GameConsoleSession` (`src/game/core/console/`) + orchestrates Overworld, Basement, discrete Potassium Slip, overlays, and Ridge + for headless AI/human play via `pnpm game:console`. See + [`adr/0006-game-console-core.md`](adr/0006-game-console-core.md). Overworld + street spot coordinates are shared with Phaser through + `core/console/content/overworldSpots.ts`. ## Scene presentation and camera @@ -107,10 +111,10 @@ re-apply camera bounds/profile math. - Use `sceneResumePolicy` for resume persistence and reset rules. The low-level resume store should not be imported directly by scenes or adapters. - Side-view player scenes should compose `SideViewPlayerRuntime` before creating colliders against `runtime.player`; pass its camera config when the scene should follow and clamp the player. - Interior rooms should describe prop targets and effect commands, then let `InteriorInteractionRuntime` choose the active target and prompt/effect result. Phaser text mutation, bridge writes, and scene-local helpers stay in the scene. -- Ridge spatial changes for the active route should start in - `src/game/scenes/ridge/bridge/stageComposition.ts` (Bridge Stage Composition - Source) and the colocated Bridge tracer modules, not the removed blockout - pipeline. +- Ridge route/gameplay changes should start in `src/game/core/ridge/` (console + session + Bridge stage content). Art swaps go through + `src/game/scenes/ridge/art/` VisualProvider. Conversation UI stays in Ridge + Scene UI. ## Folder ownership @@ -158,12 +162,10 @@ Potassium uses focused runtime modules to keep the large arcade scene navigable: Ridge runtime modules for the active Bridge tracer: -- `bridge/stageComposition.ts` — Bridge Stage Composition Source (rails, spots, - plates, objects, route-beat presentation). -- `bridge/bridgeTracerSlice.ts` — interaction targets and world constants for - the flat Bridge route. -- `bridge/BridgeTracerStageRuntime.ts` — stage visuals, prompts, dialogue, toy-car test. -- `bridge/BridgeWalkRailPlayerRuntime.ts` — rail-based player movement. +- `src/game/core/ridge/` — pure console session, commands, conversation, Bridge stage. +- `scenes/ridge/runtime/RidgeScene.ts` — thin Phaser input/presentation adapter. +- `scenes/ridge/art/stick/` — default stick-figure VisualProvider. +- `scenes/ridge/sceneUi/` — Persona-style Character Conversation Panel. - Opt-in mini-games such as Stampede Sketch keep their own scenes and movement systems under `src/game/scenes/stampedeSketch/`. diff --git a/package.json b/package.json index 77947e59..a170a14b 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,9 @@ "fallow:baseline": "fallow dead-code --save-baseline fallow-baselines/dead-code.json --quiet; fallow health --save-baseline fallow-baselines/health.json --quiet; fallow dupes --save-baseline fallow-baselines/dupes.json --quiet", "test": "vitest run", "test:changed": "vitest run --changed", - "test:watch": "vitest" + "test:watch": "vitest", + "game:console": "vite-node scripts/game-console.ts", + "ridge:console": "vite-node scripts/game-console.ts --scene ridge" }, "dependencies": { "clsx": "^2.1.1", diff --git a/public/assets/ridge/bridge/README.md b/public/assets/ridge/bridge/README.md index e36fc280..5bcd3a0b 100644 --- a/public/assets/ridge/bridge/README.md +++ b/public/assets/ridge/bridge/README.md @@ -1,53 +1,17 @@ -# Bridge Ridge Runtime Assets +# Bridge Ridge Assets Owner: `src/game/scenes/ridge/**` -Runtime loader: `src/game/scenes/ridge/bridge/assets.ts` +Current Bridge runtime art is **procedural stick-figure drawing** in +`src/game/scenes/ridge/art/stick/`. It does not load PNGs from this folder. -Generated by: built-in `image_gen`, then locally sliced from chroma-key sheets -or saved as coherent wide source plates for layered stage composition. +Legacy PNG folders under `modular/` and `layered-reset/` are kept only as +historical reference. Do not wire them back without a VisualProvider that +intentionally replaces the stick provider. -## Current Use +## Replace With Hand-Drawn Art Later -Ridge now uses generated proof plates under `layered-reset/plates/` for the -Bridge playable proof: - -- `far-mountains-clouds.png`: slow far parallax paper/mountain/cloud layer -- `close-stage-no-stumps.png`: transparent close playable terrain, camp, and - gorge layer - -The Bridge runtime consumes the typed Bridge Stage Composition Source in -`src/game/scenes/ridge/bridge/stageComposition.ts` for stage plates, Walk Rail -placement, Stage Spots, modular object placement, and before/after presentation. -Route state still lives outside the asset folder. The `modular/` folder is kept -only for currently drawn modular runtime props: Bridge Draftsperson and the toy -car. - -## Style Contract - -- off-white paper and black ink only -- runtime presentation should stay monochrome-first; temporary yellow paper - fills should be subdued programmatically rather than treated as the Bridge - palette -- silhouette-first reads at gameplay scale -- hatching and line weight for depth instead of color -- use a hybrid layered stage: coherent wide scenery plates for sky/mountains, - far forest, mid forest/work-camp staging, and playable terrain/gorge -- keep bridge state, Cicka, toy car, Bridge Draftsperson, prompts, and other - interactive or stateful pieces modular -- avoid building the forest read from many unrelated tree/bush cutouts; use - individual cutouts only to reinforce a coherent layer or clear gameplay beat -- distant layers should use simpler, lower-detail full-opacity drawings rather - than translucent full-detail assets; depth comes from scale, line weight, - value, and detail density -- Bridge terrain should read primarily as a side-scroller lane. Gorge/ridge - edges can show vertical cliff depth, but the playable path should not become - an angled/isometric top-plane view. -- Bridge-owned until repeated Ridge asset reuse proves a broader framework - -## Runtime Contract - -`layered-reset/plates/` contains the playable proof split generated from the -accepted layered direction. These are proof assets for feel and feedback, not -final art. Removed exploratory source concepts and unused cutouts should be -regenerated from design notes rather than kept in `public/` as runtime cargo. +1. Implement a new `RidgeVisualProvider` (for example sprite-backed). +2. Keep `src/game/core/ridge/` unchanged. +3. Point `RidgeScene` at the new provider. +4. Normalize frame sizes/pivots through the sketchbook sprite pipeline skill. diff --git a/public/assets/ridge/bridge/layered-reset/plates/close-stage-no-stumps.png b/public/assets/ridge/bridge/layered-reset/plates/close-stage-no-stumps.png deleted file mode 100644 index 6c26d39c..00000000 Binary files a/public/assets/ridge/bridge/layered-reset/plates/close-stage-no-stumps.png and /dev/null differ diff --git a/public/assets/ridge/bridge/layered-reset/plates/cornfield-bridge-stage.png b/public/assets/ridge/bridge/layered-reset/plates/cornfield-bridge-stage.png new file mode 100644 index 00000000..3cdab274 Binary files /dev/null and b/public/assets/ridge/bridge/layered-reset/plates/cornfield-bridge-stage.png differ diff --git a/public/assets/ridge/bridge/layered-reset/plates/cornfield-far-hill.png b/public/assets/ridge/bridge/layered-reset/plates/cornfield-far-hill.png new file mode 100644 index 00000000..081e28a6 Binary files /dev/null and b/public/assets/ridge/bridge/layered-reset/plates/cornfield-far-hill.png differ diff --git a/public/assets/ridge/bridge/layered-reset/plates/cornfield-ground.png b/public/assets/ridge/bridge/layered-reset/plates/cornfield-ground.png new file mode 100644 index 00000000..50de038a Binary files /dev/null and b/public/assets/ridge/bridge/layered-reset/plates/cornfield-ground.png differ diff --git a/public/assets/ridge/bridge/layered-reset/plates/cornfield-sky.png b/public/assets/ridge/bridge/layered-reset/plates/cornfield-sky.png new file mode 100644 index 00000000..f25c3468 Binary files /dev/null and b/public/assets/ridge/bridge/layered-reset/plates/cornfield-sky.png differ diff --git a/public/assets/ridge/bridge/layered-reset/plates/far-mountains-clouds.png b/public/assets/ridge/bridge/layered-reset/plates/far-mountains-clouds.png deleted file mode 100644 index 1c68ae72..00000000 Binary files a/public/assets/ridge/bridge/layered-reset/plates/far-mountains-clouds.png and /dev/null differ diff --git a/public/assets/ridge/bridge/modular/bridge-builder.png b/public/assets/ridge/bridge/modular/bridge-builder.png index 237cd33b..8b56c887 100644 Binary files a/public/assets/ridge/bridge/modular/bridge-builder.png and b/public/assets/ridge/bridge/modular/bridge-builder.png differ diff --git a/public/assets/ridge/bridge/modular/manifest.json b/public/assets/ridge/bridge/modular/manifest.json index a80a1bf9..0f52f47e 100644 --- a/public/assets/ridge/bridge/modular/manifest.json +++ b/public/assets/ridge/bridge/modular/manifest.json @@ -1,17 +1,20 @@ { - "source": "built-in image_gen + local chroma-key slicing", + "status": "historical-reference", + "note": "Bridge runtime uses stick VisualProvider under src/game/scenes/ridge/art/stick/. PNG modular assets here are not loaded.", "assets": [ { "file": "bridge-builder.png", "kind": "character", - "width": 105, - "height": 220 + "width": 140, + "height": 210, + "note": "Legacy reference only" }, { "file": "toy-car.png", "kind": "prop", - "width": 132, - "height": 72 + "width": 80, + "height": 49, + "note": "Legacy reference only" } ] } diff --git a/public/assets/ridge/bridge/modular/toy-car.png b/public/assets/ridge/bridge/modular/toy-car.png index 1f3c35d5..4b3c4f95 100644 Binary files a/public/assets/ridge/bridge/modular/toy-car.png and b/public/assets/ridge/bridge/modular/toy-car.png differ diff --git a/public/assets/ridge/cicka/README.md b/public/assets/ridge/cicka/README.md index 441cd4f9..539b574c 100644 --- a/public/assets/ridge/cicka/README.md +++ b/public/assets/ridge/cicka/README.md @@ -1,30 +1,17 @@ -# Cicka Ridge Runtime Assets +# Cicka Ridge Assets Owner: `src/game/scenes/ridge/**` -Runtime loader: `src/game/scenes/ridge/cicka/assets.ts` +Current Bridge runtime draws Cicka as a stick figure via +`src/game/scenes/ridge/art/stick/`. PNG sheets in this folder are historical +reference only and are not loaded by the stick provider. -Promoted from: `asset-sources/prepared/characters/cicka/` +When hand-drawn Cicka art returns, implement a sprite-backed +`RidgeVisualProvider` and keep `src/game/core/ridge/` unchanged. -External source archive: `prepared-assets` -> -`characters/cicka/` in `asset-sources/settings.local.json` - -## Current Use - -Ridge loads `cicka-spritesheet.png` as a display-only NPC/perch sprite. Cicka -does not use a Phaser physics body in this slice; the existing Ridge -interaction target remains the gameplay affordance. - -## Frame Contract +## Legacy Frame Contract (reference) - frame size: `128x96` - frame count: `12` - origin: `{ "x": 0.5, "y": 1 }` -- runtime scale: `0.58` -- initial animations: perch idle and notice - -`manifest.json` is the runtime-facing contract and points back to the retained -local source sheet plus the external prepared archive key. The local -`asset-sources/prepared/**` copy is ignored and should be treated as a Drive -mirror, not repo-owned source. `cicka-debug-contact.png` is retained here as the -promoted runtime QA sheet because this is the first AI sprite adoption slice. +- former runtime scale: `0.58` diff --git a/scripts/game-console.ts b/scripts/game-console.ts new file mode 100644 index 00000000..02dc52bd --- /dev/null +++ b/scripts/game-console.ts @@ -0,0 +1,162 @@ +#!/usr/bin/env node +// fallow-ignore-file complexity +/** + * Headless full-game console for humans and AI agents. + * + * Interactive: + * pnpm game:console + * + * One-shot script (semicolon-separated): + * pnpm game:console --script "look; go right 3; interact" + * + * JSON observation stream: + * pnpm game:console --script "look" --json + * + * Start inside Ridge (skip street): + * pnpm game:console --scene ridge + */ +import * as readline from 'node:readline/promises'; +import { stdin as input, stdout as output } from 'node:process'; +import { + GameConsoleSession, + type GameCommandResult, + type GameSceneId, + formatGameObservation +} from '../src/game/core/console/index'; +import type { BridgeDialogueCatalog } from '../src/game/core/ridge/index'; +import { enMessages } from '../src/shared/i18n/messages/en'; + +function loadCatalog(): BridgeDialogueCatalog { + const bridge = enMessages.scenes.ridge.bridge; + return { + speakers: { + prompt: bridge.speakers.prompt, + cicka: bridge.speakers.cicka, + bridgeDraftsperson: bridge.speakers.bridgeDraftsperson + }, + lines: { ...bridge.dialogue } + }; +} + +function parseScene(args: string[]): GameSceneId { + const index = args.indexOf('--scene'); + const raw = index >= 0 ? args[index + 1] : 'overworld'; + const allowed: GameSceneId[] = [ + 'overworld', + 'basement', + 'hobbies', + 'potassium', + 'ridge', + 'stampedeSketch' + ]; + if (raw && (allowed as string[]).includes(raw)) return raw as GameSceneId; + return 'overworld'; +} + +function createSession(sceneId: GameSceneId): GameConsoleSession { + return new GameConsoleSession({ + dialogue: loadCatalog(), + sceneId + }); +} + +function printResult(result: GameCommandResult, asJson: boolean): void { + if (asJson) { + output.write( + `${JSON.stringify( + { + ok: result.ok, + message: result.message, + events: result.events, + observation: result.observation + }, + null, + 2 + )}\n` + ); + return; + } + + output.write(`\n${result.message}\n\n`); + output.write(`${formatGameObservation(result.observation)}\n`); +} + +async function runScript( + script: string, + asJson: boolean, + sceneId: GameSceneId +): Promise { + const session = createSession(sceneId); + const results = session.execScript(script); + for (const result of results) { + printResult(result, asJson); + } + if (results.length === 0) { + printResult(session.exec('look'), asJson); + } + return 0; +} + +async function runRepl(asJson: boolean, sceneId: GameSceneId): Promise { + const session = createSession(sceneId); + const rl = readline.createInterface({ input, output }); + + output.write('Game Console — Overworld / Basement / Potassium / Ridge\n'); + output.write('Type help. Ctrl+C or quit to exit.\n\n'); + output.write(`${session.format()}\n`); + + while (true) { + const answer = await rl.question('\ngame> '); + const trimmed = answer.trim(); + if (!trimmed) continue; + if (trimmed === 'quit' || trimmed === 'exit') break; + + if (trimmed.includes(';')) { + for (const result of session.execScript(trimmed)) { + printResult(result, asJson); + } + continue; + } + + printResult(session.exec(trimmed), asJson); + } + + rl.close(); + return 0; +} + +async function main(): Promise { + const args = process.argv.slice(2); + const asJson = args.includes('--json'); + const sceneId = parseScene(args); + const scriptIndex = args.indexOf('--script'); + const script = scriptIndex >= 0 ? args[scriptIndex + 1] : undefined; + + if (script) { + return runScript(script, asJson, sceneId); + } + + if (!input.isTTY) { + const chunks: Buffer[] = []; + for await (const chunk of input) { + chunks.push(Buffer.from(chunk)); + } + const scriptFromStdin = Buffer.concat(chunks).toString('utf8').trim(); + if (!scriptFromStdin) { + output.write('No commands on stdin. Example: echo "look; help" | pnpm game:console\n'); + return 1; + } + return runScript(scriptFromStdin, asJson, sceneId); + } + + return runRepl(asJson, sceneId); +} + +main() + .then((code) => { + process.exitCode = code; + }) + .catch((error: unknown) => { + console.error(error); + process.exitCode = 1; + }); diff --git a/src/App.tsx b/src/App.tsx index 207bf7b5..e87ce9e1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,7 +5,6 @@ import { LoadingFallback } from '@/shared/ui'; import { StaticPortfolio } from '@/static'; const InteractiveApp = lazy(() => import('@/game/shell')); -const RidgeStageDebugger = lazy(() => import('@/dev/ridgeStageDebugger')); function App() { const { route, setMode } = useReadMode(); @@ -18,14 +17,6 @@ function App() { return setMode('interactive')} />; } - if (import.meta.env.DEV && route === 'ridge-stage-debugger') { - return ( - }> - - - ); - } - return ( }> setMode('static')} /> diff --git a/src/README.md b/src/README.md index 6b7f5a81..43f16e79 100644 --- a/src/README.md +++ b/src/README.md @@ -36,7 +36,9 @@ This folder is organized by surface ownership. - `game/shell/use*.ts` - Focused React hooks for Phaser boot, bridge callbacks, touch controls, and scale refresh. - `game/sceneLifecycle` - Scene lifecycle orchestration, scene manager, lifecycle events, and context assembly. - `game/sharedSceneRuntime` - Reusable Phaser-facing machinery shared by multiple scenes. -- `game/core` - Pure ECS, input, and player decision logic with no Phaser/React/browser imports. +- `game/core` - Pure ECS, input, player, and Ridge console decision logic with no Phaser/React/browser imports. +- `game/scenes/ridge/art` - Replaceable Ridge VisualProvider (stick figures by default). +- `game/scenes/ridge/sceneUi` - Persona-style Character Conversation Panel. - `game/adapters` - Concrete adapters to external engines/APIs, such as the Phaser scene adapter. - `shared/i18n/messages/en/` - English user-facing copy for React and Phaser. `index.ts` composes the locale from domain files. Add new display strings here first; do not add scene `text.ts` compatibility shims. diff --git a/src/dev/ridgeStageDebugger/RidgeRuntimePreview.tsx b/src/dev/ridgeStageDebugger/RidgeRuntimePreview.tsx deleted file mode 100644 index e83b39f1..00000000 --- a/src/dev/ridgeStageDebugger/RidgeRuntimePreview.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import { useEffect, useMemo, useRef } from 'react'; -import { resolveRidgePreviewStatus } from './resolveRidgePreviewStatus'; -import Game from '@/game/shell/Game'; -import { bridgeActions, useBridgeState } from '@/game/bridge/store'; -import { OverlayHost } from '@/game/overlays/OverlayHost'; -import { SceneUiHost } from '@/game/sceneUi/SceneUiHost'; -import { getPhaserScenePresentationMode } from '@/game/sharedSceneRuntime/phaserScenePresentation'; -import { RIDGE_SCENE_ID, type SceneId } from '@/game/scenes/sceneIds'; -import type { RidgeDevControls } from '@/game/scenes/ridge/runtime/ridgeDevControls'; - -function ignorePreviewSceneClose(): void {} - -export type RidgePreviewInputOwner = 'game' | 'panel'; - -export function RidgeRuntimePreview({ - authoringActive = false, - inputOwner, - isActive, - onClaimGameInput, - previewZoom, - ridgeDevControls -}: { - authoringActive?: boolean; - inputOwner: RidgePreviewInputOwner; - isActive: boolean; - onClaimGameInput: () => void; - previewZoom: number; - ridgeDevControls: RidgeDevControls; -}) { - const bridge = useBridgeState(); - const previewRef = useRef(null); - const previewStatus = useMemo( - () => resolveRidgePreviewStatus({ - activeOverlayId: bridge.activeOverlayId, - authoringActive, - inputOwner, - isActive, - isPaused: bridge.isPaused, - loadingSceneId: bridge.loadingSceneId - }), - [ - authoringActive, - bridge.activeOverlayId, - bridge.isPaused, - bridge.loadingSceneId, - inputOwner, - isActive - ] - ); - const { inputStatusLabel, isInputPaused, pauseReason, showPausedOverlay } = previewStatus; - const presentationMode = getPhaserScenePresentationMode(bridge.activeSceneId); - const isRidgeSceneActive = bridge.activeSceneId === RIDGE_SCENE_ID; - - useEffect(function bootRidgePreviewScene() { - bridgeActions.closeOverlay(); - bridgeActions.clearSceneUi(); - bridgeActions.setSceneLoading(null); - bridgeActions.resetTouch(); - bridgeActions.enterScene(RIDGE_SCENE_ID); - - return () => { - bridgeActions.resetTouch(); - bridgeActions.closeOverlay(); - bridgeActions.clearSceneUi(); - bridgeActions.returnToOverworld(); - }; - }, []); - - function claimGameInput(): void { - if (!isActive) return; - onClaimGameInput(); - previewRef.current?.focus({ preventScroll: true }); - } - - function enterPreviewScene(sceneId: SceneId): void { - bridgeActions.enterScene(sceneId); - onClaimGameInput(); - requestAnimationFrame(() => { - previewRef.current?.focus({ preventScroll: true }); - }); - } - - function returnToRidgePreview(): void { - enterPreviewScene(RIDGE_SCENE_ID); - } - - return ( -
{ - if (isActive && event.target === event.currentTarget) { - onClaimGameInput(); - } - }} - onPointerDown={(event) => { - if (!isActive) return; - if ((event.target as HTMLElement).closest('[data-ridge-preview-focus-button]')) return; - claimGameInput(); - }} - ref={previewRef} - tabIndex={isActive ? 0 : -1} - > -
-

- Live Bridge -

-

- Camera zoom {Math.round(previewZoom * 100)}% -

-
- - {inputStatusLabel} - - {!isRidgeSceneActive ? ( - - ) : null} -
-
- -
-
- -
-
- {bridge.sceneUi.status ? ( -
-
- -
-
- ) : null} - - {showPausedOverlay ? ( -
-
-

- Paused -

-

- {pauseReason} -

- -
-
- ) : null} - {bridge.loadingSceneId ? ( -
-

- Loading Ridge -

-
- ) : null} -
- ); -} diff --git a/src/dev/ridgeStageDebugger/RidgeStageDebugger.test.tsx b/src/dev/ridgeStageDebugger/RidgeStageDebugger.test.tsx deleted file mode 100644 index 877d84bc..00000000 --- a/src/dev/ridgeStageDebugger/RidgeStageDebugger.test.tsx +++ /dev/null @@ -1,281 +0,0 @@ -// @vitest-environment jsdom - -import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { bridgeActions, bridgeStore } from '@/game/bridge/store'; -import { RIDGE_SCENE_ID, STAMPEDE_SKETCH_SCENE_ID } from '@/game/scenes/sceneIds'; -import type { RidgeDevControls } from '@/game/scenes/ridge/runtime/ridgeDevControls'; -import { - BRIDGE_STAGE_SOURCE, - resolveBridgeStageSpot -} from '@/game/scenes/ridge/bridge/stageComposition'; -import RidgeStageDebugger from './RidgeStageDebugger'; - -let lastRidgeDevControls: RidgeDevControls | undefined; -let lastReturnToOverworld: (() => void) | undefined; -let lastGameIsPaused: boolean | undefined; -let lastGamePresentationMode: string | undefined; - -vi.mock('@/game/shell/Game', () => ({ - default: ({ - activeSceneId, - isPaused, - presentationMode, - ridgeDevControls, - onReturnToOverworld - }: { - activeSceneId: string; - isPaused: boolean; - presentationMode: string; - ridgeDevControls?: RidgeDevControls; - onReturnToOverworld: () => void; - }) => { - lastRidgeDevControls = ridgeDevControls; - lastReturnToOverworld = onReturnToOverworld; - lastGameIsPaused = isPaused; - lastGamePresentationMode = presentationMode; - return
Runtime scene: {activeSceneId}
; - } -})); - -describe('RidgeStageDebugger', () => { - beforeEach(() => { - window.history.replaceState(window.history.state, '', '/?mode=ridge-stage-debugger'); - resetBridgeForDebuggerTest(); - }); - - afterEach(() => { - cleanup(); - resetBridgeForDebuggerTest(); - }); - - it('boots a Bridge-first runtime debugger without the legacy blockout model UI', async () => { - render(); - - expect(screen.getByRole('heading', { name: 'Ridge Stage Debugger' })).not.toBeNull(); - expect(screen.getByTestId('ridge-runtime-preview')).not.toBeNull(); - expect(await screen.findByText('Runtime scene: ridge')).not.toBeNull(); - expect(lastGameIsPaused).toBe(false); - expect(screen.getByText('Live Bridge')).not.toBeNull(); - expect(screen.getByText('Stage Source')).not.toBeNull(); - expect(screen.queryByRole('button', { name: 'Model' })).toBeNull(); - expect(screen.queryByText(/Folded Desk Ridge/)).toBeNull(); - }); - - it('pauses game input while debugger controls own focus', async () => { - render(); - - await userEvent.click(screen.getByRole('button', { name: 'Reset Player' })); - - expect(document.activeElement).toBe(screen.getByRole('button', { name: 'Reset Player' })); - expect(lastGameIsPaused).toBe(true); - expect(screen.getByTestId('ridge-preview-input-status').textContent).toContain('Panel focus'); - expect(screen.getByTestId('ridge-preview-paused-overlay').textContent).toContain('Paused'); - - await userEvent.click(screen.getByRole('button', { name: 'Focus game' })); - - expect(lastGameIsPaused).toBe(false); - expect(document.activeElement).toBe(screen.getByTestId('ridge-runtime-preview')); - }); - - it('updates preview zoom and Bridge Stage overlay settings', async () => { - render(); - - expect(lastRidgeDevControls?.resolveCameraZoom?.()).toBe(1); - expect(lastRidgeDevControls?.resolveDebugSettings?.()).toMatchObject({ - graybox: false, - showPlayerBody: false, - showInteractZones: false, - showTraversalAssists: true - }); - - await userEvent.click(screen.getByRole('button', { name: 'Zoom preview in' })); - await userEvent.click(screen.getByRole('checkbox', { name: 'Walk Rail + Spots' })); - await userEvent.click(screen.getByRole('checkbox', { name: 'Interaction Zones' })); - - expect(lastRidgeDevControls?.resolveCameraZoom?.()).toBe(1.25); - expect(screen.getByLabelText('Preview zoom')).toHaveProperty('value', '1.25'); - expect(lastRidgeDevControls?.resolveDebugSettings?.()).toMatchObject({ - graybox: false, - showPlayerBody: false, - showInteractZones: true, - showTraversalAssists: false - }); - }); - - it('dispatches stage spot teleport requests from the Bridge Stage Source', async () => { - render(); - - await userEvent.click(screen.getByRole('button', { - name: 'Move player to Stage Spot draftsperson' - })); - - const draftsperson = resolveBridgeStageSpot(BRIDGE_STAGE_SOURCE, 'draftsperson'); - const request = lastRidgeDevControls?.consumeTeleportRequest?.(); - expect(request).toMatchObject({ - label: 'draftsperson', - applySpawnOffset: false, - x: draftsperson.x, - y: draftsperson.y - }); - expect(screen.getByText('Sent: spot draftsperson')).not.toBeNull(); - }); - - it('dispatches route beat requests without mutating Bridge route state from React', async () => { - render(); - - await userEvent.click(screen.getByRole('button', { name: /Bridge Complete/ })); - - expect(bridgeStore.getState().progress.ridge.firstPlayableRoute).toEqual({ - activeAreaId: 'bridge', - bridgeBeat: 'intro' - }); - expect(lastRidgeDevControls?.consumeRouteBeatRequest?.()).toMatchObject({ - bridgeBeat: 'bridge_complete', - label: 'bridge_complete' - }); - - await userEvent.click(screen.getByRole('button', { name: /Concert Handoff/ })); - - expect(bridgeStore.getState().progress.ridge.firstPlayableRoute).toEqual({ - activeAreaId: 'bridge', - bridgeBeat: 'intro' - }); - expect(lastRidgeDevControls?.consumeRouteBeatRequest?.()).toMatchObject({ - bridgeBeat: 'concert_handoff', - label: 'concert_handoff' - }); - }); - - it('exposes Stage Authoring controls and draft source wiring', async () => { - render(); - - expect(screen.getByLabelText('Stage Authoring Mode')).toHaveProperty('checked', false); - expect(lastRidgeDevControls?.resolveAuthoringState?.()).toEqual({ - active: false, - selection: null - }); - expect(lastRidgeDevControls?.resolveCompositionSource?.()).toBeUndefined(); - - await userEvent.click(screen.getByLabelText('Stage Authoring Mode')); - - expect(screen.getByLabelText('Stage Authoring Mode')).toHaveProperty('checked', true); - expect(lastRidgeDevControls?.resolveAuthoringState?.()).toEqual({ - active: true, - selection: null - }); - expect(lastRidgeDevControls?.resolveCompositionSource?.()?.id).toBe('bridge'); - expect(lastRidgeDevControls?.resolveDebugSettings?.()).toMatchObject({ - showTraversalAssists: true - }); - }); - - it('updates the authoring draft and snippet from sidebar field edits', async () => { - render(); - - await userEvent.click(screen.getByLabelText('Stage Authoring Mode')); - await userEvent.selectOptions( - screen.getByLabelText('Authoring target'), - 'Spot draftsperson' - ); - - expect(screen.getByText('Stage Spot draftsperson')).not.toBeNull(); - - const offsetYInput = screen.getByLabelText('offset.y'); - fireEvent.change(offsetYInput, { target: { value: '-6' } }); - - expect(lastRidgeDevControls?.resolveCompositionSource?.()?.spots.find((spot) => spot.id === 'draftsperson')?.offset) - .toEqual({ x: 0, y: -6 }); - expect(screen.getByLabelText('Copyable Ridge Stage Source replacement snippet').textContent) - .toContain("id: 'draftsperson'"); - expect(screen.getByLabelText('Copyable Ridge Stage Source replacement snippet').textContent) - .toContain('offset: { x: 0, y: -6 }'); - }); - - it('renders the latest rail snapshot and copyable source snippet', () => { - render(); - - act(() => { - lastRidgeDevControls?.publishPlayerSnapshot?.({ - x: 1275, - y: 500, - railProgress: 0.48, - railScale: 1, - railDepth: 31, - bridgeBeat: 'needs_toy_car', - crossingOpen: false, - playerProgressMax: 0.56, - nearestStageSpotId: 'draftsperson', - sourceSnippet: 'railProgress: 0.480' - }); - }); - - expect(screen.getByText('1275, 500')).not.toBeNull(); - expect(screen.getByText('0.480 @ draftsperson')).not.toBeNull(); - expect(screen.getByText('scale 1.00 / depth 31')).not.toBeNull(); - expect(screen.getByText('0.000-0.560')).not.toBeNull(); - expect(screen.getByLabelText('Copyable Ridge Stage Source snippet')).toHaveProperty( - 'value', - 'railProgress: 0.480' - ); - }); - - it('renders Trail Card overlays inside the runtime preview', async () => { - render(); - - act(() => { - bridgeActions.openOverlay('trailCard', { - params: { - title: 'Stampede Sketch', - mood: 'Kite overexcited ink ideas', - timeEstimate: '60-90 seconds', - rewardPreview: 'Stamp + glide pip', - enterSceneId: STAMPEDE_SKETCH_SCENE_ID - }, - returnToSceneId: RIDGE_SCENE_ID - }); - }); - - expect(lastGameIsPaused).toBe(true); - expect(screen.getByTestId('ridge-preview-input-status').textContent).toContain('Runtime paused'); - expect(await screen.findByRole('dialog', { name: 'Stampede Sketch' })).toBeDefined(); - - await userEvent.click(screen.getByRole('button', { name: 'Enter' })); - - await waitFor(() => { - expect(screen.getByText('Runtime scene: stampedeSketch')).toBeDefined(); - }); - expect(lastGamePresentationMode).toBe('vertical-board'); - - await userEvent.click(screen.getByRole('button', { name: 'Back to Ridge' })); - - await waitFor(() => { - expect(screen.getByText('Runtime scene: ridge')).toBeDefined(); - }); - }); - - it('keeps the runtime preview in Ridge when the scene close callback fires', async () => { - render(); - expect(await screen.findByText('Runtime scene: ridge')).not.toBeNull(); - - act(() => { - lastReturnToOverworld?.(); - }); - - expect(screen.getByText('Runtime scene: ridge')).not.toBeNull(); - }); -}); - -function resetBridgeForDebuggerTest(): void { - bridgeActions.returnToOverworld(); - bridgeActions.closeOverlay(); - bridgeActions.clearSceneUi(); - bridgeActions.setSceneLoading(null); - bridgeActions.resetTouch(); - bridgeActions.resetProgress(); - lastRidgeDevControls = undefined; - lastReturnToOverworld = undefined; - lastGameIsPaused = undefined; - lastGamePresentationMode = undefined; -} diff --git a/src/dev/ridgeStageDebugger/RidgeStageDebugger.tsx b/src/dev/ridgeStageDebugger/RidgeStageDebugger.tsx deleted file mode 100644 index 76ac5591..00000000 --- a/src/dev/ridgeStageDebugger/RidgeStageDebugger.tsx +++ /dev/null @@ -1,484 +0,0 @@ -import { useCallback, useMemo, useState, type ReactNode } from 'react'; -import { - Bug, - Crosshair, - Gauge, - LocateFixed, - Minus, - Plus, - RotateCcw, - Route, - Waypoints -} from 'lucide-react'; -import { - useBridgeState, - type RidgeBridgeBeatState -} from '@/game/bridge/store'; -import type { - RidgeDevDebugSettings, - RidgeDevPlayerSnapshot -} from '@/game/scenes/ridge/runtime/ridgeDevControls'; -import { - BRIDGE_STAGE_SOURCE, - resolveBridgeStageSpot, - type BridgeStageSpotId, - type ResolvedBridgeStageSpot -} from '@/game/scenes/ridge/bridge/stageComposition'; -import { - RidgeRuntimePreview, - type RidgePreviewInputOwner -} from './RidgeRuntimePreview'; -import { IconButton } from './components/IconButton'; -import { Detail } from './components/Detail'; -import { PREVIEW_ZOOM_OPTIONS } from './constants'; -import { AuthoringPanel } from './components/AuthoringPanel'; -import { useRidgePreviewControls } from './hooks/useRidgePreviewControls'; -import { useStageAuthoring } from './hooks/useStageAuthoring'; - -const BRIDGE_BEAT_OPTIONS = [ - { id: 'intro', label: 'Intro' }, - { id: 'needs_toy_car', label: 'Needs Car' }, - { id: 'toy_car_shared', label: 'Shared Car' }, - { id: 'bridge_complete', label: 'Bridge Complete' }, - { id: 'concert_handoff', label: 'Concert Handoff' } -] as const satisfies readonly { id: RidgeBridgeBeatState; label: string }[]; - -const STAGE_SPOT_GROUPS = [ - { - title: 'Player Rail', - spotIds: ['spawn', 'bridge-left-bank', 'bridge-center', 'bridge-right-bank', 'concert-exit'] - }, - { - title: 'Cast + Props', - spotIds: ['cicka-play', 'cicka-settled', 'draftsperson', 'cicka-toy-car', 'blueprint'] - }, - { - title: 'Bridge Test', - spotIds: ['toy-car-test-start', 'toy-car-test-end', 'handoff-note'] - } -] as const satisfies readonly { title: string; spotIds: readonly BridgeStageSpotId[] }[]; - -export default function RidgeStageDebugger() { - const bridge = useBridgeState(); - const [previewInputOwner, setPreviewInputOwner] = useState('game'); - const previewControls = useRidgePreviewControls(); - const authoring = useStageAuthoring(); - const claimPanelInput = useCallback(() => setPreviewInputOwner('panel'), []); - const claimGameInput = useCallback(() => setPreviewInputOwner('game'), []); - const routeState = bridge.progress.ridge.firstPlayableRoute; - const resolvedSpots = useMemo( - () => BRIDGE_STAGE_SOURCE.spots.map((spot) => resolveBridgeStageSpot(BRIDGE_STAGE_SOURCE, spot.id)), - [] - ); - const spotsById = useMemo(() => { - const next = new Map(); - resolvedSpots.forEach((spot) => next.set(spot.id, spot)); - return next; - }, [resolvedSpots]); - - const ridgeDevControls = useMemo(() => ({ - ...previewControls.ridgeDevControls, - resolveCompositionSource: authoring.resolveCompositionSource, - resolveAuthoringState: () => ({ - active: authoring.active, - selection: authoring.selection - }), - publishAuthoringPick: authoring.handlePick, - publishAuthoringDrag: authoring.handleDrag, - resolveDebugSettings: () => ({ - ...previewControls.debugSettings, - showTraversalAssists: previewControls.debugSettings.showTraversalAssists || authoring.active - }) - }), [ - authoring.active, - authoring.handleDrag, - authoring.handlePick, - authoring.resolveCompositionSource, - authoring.selection, - previewControls.debugSettings, - previewControls.ridgeDevControls - ]); - - return ( -
-
-
-
-

- Dev QA -

-

- Ridge Stage Debugger -

- - -
-
- - {BRIDGE_STAGE_SOURCE.primaryWalkRail.points.length} rail pts -
-
- -
-
- -
- - -
-
-
- ); -} - -function StageSourcePanel({ spotCount }: { spotCount: number }) { - return ( -
- } title="Stage Source" /> -
- - - - - - - -
-
- ); -} - -function PreviewPanel({ - lastCommandLabel, - onPreviewZoomChange, - onPreviewZoomIn, - onPreviewZoomOut, - onResetPlayer, - playerSnapshot, - previewZoom -}: { - lastCommandLabel: string | null; - onPreviewZoomChange: (zoom: number) => void; - onPreviewZoomIn: () => void; - onPreviewZoomOut: () => void; - onResetPlayer: () => void; - playerSnapshot: RidgeDevPlayerSnapshot | null; - previewZoom: number; -}) { - return ( -
- } title="Live Player" /> -
-
- - - - - - - -
-
- - - - - - -
- {playerSnapshot?.sourceSnippet ? ( - event.currentTarget.select()} - readOnly - value={playerSnapshot.sourceSnippet} - /> - ) : null} - {lastCommandLabel ? ( -

- Sent: {lastCommandLabel} -

- ) : null} - -
-
- ); -} - -function RouteBeatPanel({ - activeBeat, - onBridgeBeatChange -}: { - activeBeat: RidgeBridgeBeatState; - onBridgeBeatChange: (bridgeBeat: RidgeBridgeBeatState) => void; -}) { - return ( -
- } title="Route Beat" /> -
- {BRIDGE_BEAT_OPTIONS.map((beat) => { - const active = beat.id === activeBeat; - return ( - - ); - })} -
-
- ); -} - -function DebugPanel({ - debugSettings, - onToggleDebugSetting -}: { - debugSettings: RidgeDevDebugSettings; - onToggleDebugSetting: (setting: keyof RidgeDevDebugSettings) => void; -}) { - return ( -
- } title="Overlays" /> -
- onToggleDebugSetting('graybox')} - /> - onToggleDebugSetting('showTraversalAssists')} - /> - onToggleDebugSetting('showInteractZones')} - /> - onToggleDebugSetting('showPlayerBody')} - /> -
-
- ); -} - -function StageSpotPanel({ - onTeleport, - spotsById -}: { - onTeleport: (spotId: BridgeStageSpotId) => void; - spotsById: ReadonlyMap; -}) { - return ( -
- } title="Stage Spots" /> -
- {STAGE_SPOT_GROUPS.map((group) => ( -
-

- {group.title} -

-
- {group.spotIds.map((spotId) => { - const spot = spotsById.get(spotId); - if (!spot) return null; - return ( - - ); - })} -
-
- ))} -
-
- ); -} - -function DebugToggle({ - checked, - label, - onChange -}: { - checked: boolean; - label: string; - onChange: () => void; -}) { - return ( - - ); -} - -function SectionTitle({ icon, title }: { icon: ReactNode; title: string }) { - return ( -

- {icon} - {title} -

- ); -} - -function StatusChip({ - label, - tone = 'paper' -}: { - label: string; - tone?: 'green' | 'paper' | 'yellow'; -}) { - const toneClass = { - green: 'bg-[#d7f2d1]', - paper: 'bg-[#fbfbf9]', - yellow: 'bg-[#f3df8b]' - }[tone]; - return ( - - {label} - - ); -} diff --git a/src/dev/ridgeStageDebugger/components/AuthoringPanel.tsx b/src/dev/ridgeStageDebugger/components/AuthoringPanel.tsx deleted file mode 100644 index 7ac1dd31..00000000 --- a/src/dev/ridgeStageDebugger/components/AuthoringPanel.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import { PencilLine, RotateCcw, Trash2 } from 'lucide-react'; -import type { - StageAuthoringField, - StageAuthoringTargetOption -} from '../hooks/useStageAuthoring'; -import { - areStageAuthoringSelectionsEqual, - type StageAuthoringSelection -} from '@/game/scenes/ridge/bridge/stageAuthoring'; -import { ScalarFieldControl } from './ScalarFieldControl'; - -export function AuthoringPanel({ - active, - fields, - isDirty, - onDiscardDraft, - onResetSelection, - onSelectTarget, - onToggleActive, - onUpdateField, - selection, - selectionLabel, - snippet, - targetOptions -}: { - active: boolean; - fields: readonly StageAuthoringField[]; - isDirty: boolean; - onDiscardDraft: () => void; - onResetSelection: () => void; - onSelectTarget: (selection: StageAuthoringSelection) => void; - onToggleActive: (active: boolean) => void; - onUpdateField: (field: string, value: number) => void; - selection: StageAuthoringSelection | null; - selectionLabel: string; - snippet: string; - targetOptions: readonly StageAuthoringTargetOption[]; -}) { - return ( -
-
-

- - Authoring -

- -
- -

- {active - ? 'Drag empty space to pan. Drag markers to move. Shift+drag a spot for offset-only nudges.' - : 'Turn on Authoring Mode to select and nudge source-backed markers.'} -

- - {active ? ( -
- - -

- {selectionLabel} -

- - {fields.length > 0 ? ( -
- {fields.map((field) => ( - onUpdateField(field.field, value)} - step={field.step} - value={field.value} - /> - ))} -
- ) : null} - - {snippet ? ( -