Block out the new approach, correct house framing and adapt current flowers. Native motion and restrained opaque VFX keep their quality gates.
The actual game
comes before the picture.
A source-backed production plan for the two of you: meet in the flowers, fight toward town, discover the verses and earn your powers.
Planning only · 02 November 2026 · No game changesNo red enemy target circle or ordinary overhead red enemy bar. Both E spells use an adapted directional arrow: ice reach is fixed, meteor distance follows its tip. Confirmed manual aim never snaps to a different enemy. Generated slabs, colours, proportions and ornament are not approved assets.
One arrow. Two distance rules.
Rule diagram only, not final arrow art. Numerical ranges are configurable starting values. Ice length stays unchanged while you move this slider.Complete swings, role-specific holds, one accepted cast location/time and one clear power state. No stacked contradictory old mechanics.
Two seats, ready/start, reconnect, sequential discoveries and private bounded prayers. Test awkward real-life cases, not only the happy path.
Design and game behaviorRequirements, proposed defaults, source findings and exact aiming rules.
Haru-machi: prototype production design
Date: 2026-11-02. Scope: planning only. This document records the current conversation's direction and proposes concrete defaults where Vikas has not fixed a rule. It does not authorize gameplay implementation. The latest user decisions supersede contradictory older project descriptions; the engine, asset, hardware and review constraints remain.
Read the implementation plan and acceptance scenarios alongside this design. Generated images are rough mood/flow references only, not geometry blueprints, UI specifications, animation evidence or quality approval. The existing illustrated storyboard is linked from the planning site.
1. The smallest complete game
Two people meet in a beautiful flower field, start together, fight along a readable route toward the town, discover verses, experiment with affectionate theatrical prayers, gradually earn stronger powers and face the shrine boss. The game creates excuses to say sincere, silly, cheesy things about each other. It must remain a small playable prototype.
Ship one valley, two roles, existing skeleton enemies, three sequential verse rewards, one personal Q per role, one reusable awakening ceremony and one coherent UI family. No new class roster, equipment economy, inventory, sprawling quest framework, generated abilities, relationship scoring system or map-generation project.
APK/TWA packaging and voice-based role selection are deferred. First make the browser game reliable on desktop and Pad 7. No account setup is required for this prototype.
2. Requirements versus proposed defaults
Requirements from this conversation
- Maximum two players. Random assignment produces one Knight and one Mage, with fixed base stats. Either person can receive either role.
- Title, loading, Create World / Join World, then an in-world flower waiting field. Guest readies; host starts. Characters use gentle native emotes while waiting. Start settles the camera behind each player's own character.
- A generated phrase identifies the room, with an invite link. No silent replacement of a disconnected partner by AI in the normal two-person flow.
- Expand the route to town deliberately. Discover verses in any order but release powers sequentially. Reading any first inscription reveals Commune.
- Mage tap attack is a normal combo; hold is slow, powerful, stationary Shatterlance. Full native animation and recovery; no stamina bar and no silent conversion of the fourth charged attack into a normal attack.
- Mage E selects between Frost Lance and Cinder Fall. Tab cycles the equipped elemental spell. Shatterlance is no longer an E spell; electricity is removed from the selectable kit. Q is a dangerous major storm.
- Hold E provides directional aiming for BOTH E spells. Frost Lance has fixed reach; meteor distance is set by the arrow tip. No red enemy target circles or persistent red overhead enemy marking as depicted in generated images.
- Knight tap attack is a complete sword combo; hold raises the shield, not a charged attack. Tab selects fire/ice, E activates the selected sword. Q is a brief fire/water transformation followed by empowered combat.
- Small jump button, low priority. Portrait/role emblem opens character information; Tab does not swap characters.
- Remove artificial partner-distance restrictions. Real attack range, terrain and shield coverage still apply.
- Retry is shown only after a valid recording shorter than eight seconds stops. Such a recording is not submitted. Stop at eight seconds or longer submits; recording automatically ends at fifteen seconds. Recording failure remains retryable.
- Prayer audio/transcript must not be relayed to the partner. One ceremony at a time. The god's actual name and solutions stay out of planning artifacts, logs, generated prompts and review reports.
- First spoken divine response requires correct first contact. Later speech is rare. Every completed prayer can receive a bounded gameplay reaction without requiring a speech.
- The god judges the actual situation and understands effort, attraction, imperfection and commitment. Archaic grammar is not a password. Rudeness can bring severe punishment; persistent deliberate abuse can eventually punish both players. Accidental duplicates and technical faults do not count as offenses.
- UI visual elements feel sculpted and integral to the world. No developer clutter. VFX remain physically opaque, weighty and cinematic. Routine screen shake and FOV pumping go away.
Concrete defaults proposed for review
These allow an executable plan without pretending that balance values or ambiguous interactions are final.
- Three shared verse stages: first meaningful correct-name prayer grants the first E modes; second stage grants fire modes; third stage grants personal Q abilities. Discovery is shared for this pair. Shatterlance starts as the Mage's base charged action. First E defaults to Knight ice and Mage Frost Lance. This order can be changed as data before content production.
- Input thresholds: 220 ms separates attack tap/hold; 250 ms separates E quick-cast/manual aim. These are ergonomics seeds, not hard user requirements. Normal attack resolves on a short release; holding never also emits a normal hit.
- Aiming: hold E to enter aim; releasing E keeps aiming armed. Click/tap the desired ground point to confirm. Escape, the visible Cancel affordance or dodge cancels. Camera-look and aim gestures have separate pointer ownership. Switching element cancels the existing aim. This preserves the requested tablet ground-tap interaction.
- Ranges: initially reuse Frost Lance 14.5 m fixed reach and meteor 16 m maximum from the current balance table. Meteor minimum is a tunable 3 m seed. Units are measured from the caster's feet; weapon/VFX emitter offsets must not shift the impact point.
- Sword timing: initial 13 s active / 18 s cooldown measured from activation, leaving 5 s downtime. Switching the selected element does not change an already-active blade until the next legal activation. Divine mixed mode takes precedence over ordinary E mode. No current Mage spell automatically replaces the Knight's selected blade.
- Blocking: frontal protection, reduced movement, no simultaneous attack. Heavy attacks can break guard after a readable wind-up. Do not introduce a permanent stamina UI. Damage reduction/coverage are tuning values; blocking is not omnidirectional invulnerability.
- Charge and Q: Shatterlance has a complete native clip, one release marker, a committed follow-through, one damage event and one fresh press per charge. A dodge can cancel before release. Mage Q starts with a 6 s stationary channel; an unprotected hit during it downs the Mage. Knight interception/absorbed hits count as protection. A pre-release cancel consumes half Q charge; a completed release consumes full charge. Validate fun and fairness before locking this policy.
- Progress: shared kill XP, five prototype levels and no skill tree. Level bonuses are data driven; the five-level cap is a scope proposal. Boss and encounter balance must be calibrated against the new roles rather than retaining obsolete numbers blindly.
- Reconnect: pause the encounter while either player reconnects, reserve their seat for 60 s, preserve the run and role, and show the connection state. If the timeout expires, offer return to the flower waiting state at the last encounter checkpoint. Do not reroll, simulate an unattended death or silently activate wingman AI.
- Both downed: respawn together at the last sanctuary/encounter checkpoint; retain discovered verses, earned unlocks and XP, reset the unfinished encounter and transient effects. Exact difficulty penalties are omitted from this prototype.
- Prayer locks: ordinary recording anchors the speaker; deliberate move/dodge cancels. The partner retains control. Emergency protection is bounded to one accepted invocation window and cannot be refreshed by repeated presses. A 15 s recording cap and a 12 s judging timeout are initial upper bounds. A service failure safely ends the ceremony with no punishment.
3. What is actually on disk
| Existing implementation | Reuse or correction |
|---|---|
world/index.html, world/src/main.js | Existing lobby and HUD are wired into the running world. Extract app-flow management instead of layering more booleans into main. |
world/src/player-controller.js | Inputs can forcibly restart actions; attack steps/hop and camera impulses run on button presses. Existing remote spell handling recalculates targeting. Replace those pathways with explicit action/aim contracts. |
world/src/combat/campaign-loop.js | Four spells, auto-targeting, immediate damage, old shared ultimate, buffs and distance checks. Preserve useful VFX/encounter infrastructure; change ownership, release timing and rewards deliberately. |
world/src/combat/combat-balance.js | Pure-JS balance rules and old headless campaign simulator. Share geometry-free rules with the server. Existing tests are not proof of the new design. |
world/assets/characters/index.js | Native clip mapping and play returning AnimationAction. Resolve clip duration and normalized release markers from actual clips. Existing wave is an alias with fallbacks, not proof of a real wave clip. |
world/assets/vfx/frost-lance.js | Three saved palettes; _spikeEmergence explicitly adds decaying sinusoidal overshoot after growth. Remove that spring for the new weight requirement. |
world/assets/vfx/cinder-fall.js | cast currently takes options.range as its length and ignores options.targetPos; impact is origin + direction × length. Passing a close target alongside maximum range is therefore insufficient. Feed canonical distance/impact and recompute flight timing from it. |
world/assets/vfx/artorious-vfx.js | Cupped-petal builder exists, but mesh creation still uses glacier geometry. Lotus integration remains incomplete. Ordinary heal is currently much larger than the new hierarchy proposes. |
worker/room-do.js, world/tools/serve.mjs | Current protocol mostly relays client events. The worker can replace an existing player when full; it has no ready/start/reconnect contract. Local altar/commune HTTP behavior is not implemented in the production Worker. |
world/src/net/coop-client.js | Current room normalization is short-code oriented; it cannot simply truncate human phrases. Solo fallback is unsuitable for the normal two-person flow. |
world/assets/houses/{index,parts}.js | Shared procedural house families. Reserve window openings when placing framing; do not hide intersections by changing the camera. |
| terrain/blockout geography | The current constant is PLAY_BOUNDARY_RADIUS = 125; older documents mention 140. Use source constants and a reviewed layout, not historical prose or generated scene distances. |
The current strongest configured enemy is the Hollow Sovereign: 1,400 HP, 34 base damage, two Bone Aegis layers, accompanied by two royal guards. Enemy spawning currently raises the boss shield at spawn despite an older half-health rule in the balance description. Resolve one intended shield policy during balancing. This is a larger existing skeleton variant, not a unique new monster asset.
4. Spellbook reference: what to adapt
Read-only local source inspected:
C:/Users/silen/Downloads/VFX-godot/spellbook/src/input/AimController.jsC:/Users/silen/Downloads/VFX-godot/spellbook/src/effects/AimIndicator.jsC:/Users/silen/Downloads/VFX-godot/spellbook/src/config/abilities/{ice,meteor}.js
The controller separates target resolution from visuals, reprojects a stationary pointer while the camera moves, validates range and emits origin/direction/distance. The arrow has a measured shaft/head, forward-moving chevrons and an endpoint glyph, keeping physical widths stable as length changes. Those ideas fit.
Do not copy its renderer configuration or shader. It uses additive transparency, a flat ground plane and fragment noise loops; these conflict with the game's opacity, terrain and performance rules. Its current controller also supports a separate zone/circle style. Vikas's newer request overrides that: BOTH Mage E spells use an adapted directional arrow, not the zone circle.
Exact aiming behavior
- Frost Lance: the arrow rotates about the caster. Its tip remains exactly the configured fixed distance away even when the pointer is near the caster. Shaft/head thickness does not grow when rotating. The arrow's corridor width represents the damaging footprint and has a readable far-end tip.
- Meteor: the arrow both rotates and changes length. Its tip is the impact centre, clamped to legal minimum/maximum distance. Use a small terminal glyph for the point, not a red disc under an enemy. Do not imply that the entire meteor radius is a pinpoint; show its footprint only if a restrained non-circular treatment is readable and separately reviewed.
- Quick-cast: normal attacks and tap E may choose the nearest legal, visible enemy within the forward 75-degree soft-lock cone, with a short target-stability interval. Frost still travels its full range along that heading. Meteor can use the selected enemy's ground position. No valid target means forward cast at a configured distance, with actual terrain validation.
- Manual-cast: pointer/touch position controls the aim. No soft-target acquisition, snapping, homing, endpoint replacement or automatic re-aiming after confirmation. Moving enemies are allowed to leave the chosen impact point.
- Ground: project against collision/terrain surfaces, not a y=0 plane, foliage or every scene mesh. Sample the terrain height along the arrow; avoid clipping, hovering, stair tunnelling and z-fighting. Invalid/no-ground input cannot cast and preserves the last valid heading without inventing a valid endpoint.
- Feedback: cold ivory/cerulean for ice, warm ivory/amber for meteor; consistent thin dark edge for bright-ground contrast. Invalid aim uses a broken/crossed endpoint shape and a muted state, not red enemy rings. No permanent arrow when not aiming.
- Cost: aim alone consumes no cooldown or power. Confirmation locks one immutable aim snapshot, then the native cast animation reaches its release point. All consumers use that snapshot.
5. One accepted cast, one place and time
Define one geometry-free AimSnapshot shared by client preview, server validation, VFX and damage:
const example = {
version: 2,
mode: 'manual', // or quick
abilityId: 'cinder_fall',
origin: { x: 0, y: 0, z: 0 }, // caster feet at confirmation
direction: { x: 0, z: 1 }, // unit XZ
distance: 9,
targetPos: { x: 0, y: 0, z: 9 },
valid: true,
};
Client sends action intent, not damage or arbitrary multipliers. Server checks role, unlock, life/action state, cooldown, origin tolerance, legal surface and distance, and returns accepted cast ID, simulation tick, canonical aim, seed and release/impact schedule. The owner's immediate preview can predict animation but reconciles to acceptance. Both clients render the same accepted cast; neither calls an auto-targeting cast function again on receipt.
Normal swing damage occurs once at its native contact marker. Charged damage occurs at spear release/impact as defined by that ability, not button-down. Meteor damage occurs on accepted meteor impact, not initial input. Use accepted IDs and per-target hit records to prevent duplicate damage. A late packet may seek an effect to its current age; it must not re-run gameplay damage locally.
6. App and room state
Client screen states: title → loading → room_entry → flower_lobby → starting → playing, with reconnecting, paused, victory overlays. Title credits are exactly Developed by Argon & Sol, Directed by Vikas, For Ishika. A failed load has a real retry path; do not fake completion percentages or force mic permission to enter.
The server generates an opaque room ID and a unique human phrase such as Love is curiosity. Normalization trims, folds case and collapses spaces; never apply the old eight-character truncation. The phrase is a friendly lookup key, not authentication. Create returns host seat and resume token; join claims the second seat. A third visitor is rejected, never allowed to evict a player. Resume tokens are unguessable runtime values scoped to a seat; do not log or show them.
The host's initial random role is authoritative; the guest gets its complement. Readiness requires assets loaded. Host Start is accepted only with exactly two connected, asset-ready players and a ready guest. One run_started ID/tick transitions both clients; repeated clicks are harmless. Assign roles once per run; no ordinary in-game role swap. Idle emotes are chosen from verified clips, with a shared seed if needed, while locomotion/attacks are gated.
Pause, reconnect and resume must not reinterpret stale held buttons as new actions. Resume is acknowledged by both clients. Microphone permission/audio-context unlocking is optional in the lobby and requested meaningfully on first use. Browser tab hiding releases recording and combat buttons; it does not silently submit an invalid short prayer.
7. World and encounters
Keep the town, pond, shrine and arena as spatial anchors. Add a safe flower waiting pocket and a purposeful approach route with bends, sightlines and two small encounter pockets. Review blockout coordinates before asset work; do not freeze generated-image geography. Preserve mountain framing and the boundary-wrap rule. Prevent enemies and their aggro/leashes from reaching the waiting field. Spawns are seeded per run in approved pockets, with occupancy checks and line-of-sight warning distance; no arbitrary spawning beside a poem reader.
The exploration loop is not forced to teleport players through the old act sequence. Retain useful encounters and the shrine boss as authored milestones. An early encounter must be completable with base attacks/shield/charge, before E unlock. Later discoveries enable stronger optional fights. The first prototype reuses existing landmarks for three verses; no new regions are required.
Shared kill XP awards once per defeated enemy, not per last hit, and never on replayed death packets. Support contributes by belonging to the active pair. Scale Knight damage/ice control and Mage damage modestly; keep the Mage fragile. Balance enemy compositions and readable attack schedules before inflating health. The current five-visible-enemy / three-attacker limits remain initial upper bounds.
Fix window/beam collisions in the house generator: window and door rectangles reserve clear space; beams terminate or reroute at openings with plausible framing. Test all current building variants, front/side/rear/dormer elevations. Generated images are not evidence that this is already corrected.
8. Powers and motion
- Knight: four complete native sword actions, one buffered follow-up, bounded smooth mash-speed increase, no restart per click. No charged sword. Frontal native shield block while held; remove the automatic fourth-hit hop and instant forward steps unless a reviewed native root-motion treatment justifies them.
- Fire E: additional direct damage and a capped, refreshable burn. Define burn owner, tick schedule and expiry; no unbounded per-hit stacking.
- Ice E: per-enemy movement/attack-tempo reduction and outgoing-damage reduction, scaled by Knight level with boss floors. Do not change global time, partner input speed, Mage channel duration or network clocks. Refresh duration rather than multiplying slow every hit.
- Knight Q: a short verified native pose/raise, a physical fire/water descending surge, then empowered attacks and protection. Starting duration 10 s and approximately double offensive power are tuning hypotheses. It overrides ordinary infusion visuals; restore the still-valid ordinary state or steel on expiry.
- Mage normals: three-step staff attack, stable nearest-valid soft target, no ordinary screen kick. Preserve full native motions; camera and model grounding are independent.
- Shatterlance charge: full wind-up/release/recovery, stationary, no stamina bar, no repeat from one continuous hold, no zero-time follow-up. Reuse its existing VFX with the new action schedule.
- Frost Lance: rapid dense eruption, no height overshoot, no bobbing/elastic settle. A darker V2-like starting palette with restrained highlights is proposed; inspect in daylight, dusk and motion before locking it. Preserve opacity, internal depth and grounded rime. Geometry reaches full size then remains stable until its authored physical breakup/dissolution.
- Cinder Fall: retain the substantial rock/fire treatment, with accepted arrow-tip impact and scheduled damage. No electricity E.
- Mage Q: new winter/water storm, stationary dangerous gathering and decisive release; no revival of the removed electricity spell as a normal ability. Stage 4 preview and cost gate before gameplay wiring. It must not cover incoming enemy warnings so completely that protecting the Mage becomes guesswork.
Own Q charge is earned by useful combat participation, including protection, with no duplicated awards. The old shared couples-ultimate path and automatic spell-to-Knight infusion must be removed from the ordinary player flow, not left as hidden conflicting systems. Existing elemental detonation rules are retained only if explicitly mapped into the new balance; they are not silently layered onto proposed damage multipliers.
9. Verses, prayer and the voice
Use a small private catalog with three stable verse IDs, stage prerequisites, existing landmark IDs and fixed reward IDs. Do not put the protected name or solution aliases in client bundles, public JSON, screenshots or these documents. Deliver each verse's public poem text only through the discovery/read flow. Authoring the real poems and private expected meanings is a separate protected content step inside the implementation plan.
Recordings use a monotonic clock. A stop at 7,999 ms yields Retry with no network request; 8,000 ms qualifies. A valid 15,000 ms limit submits once. Failure/denial/empty audio does not become a smite. Stop tracks immediately; finishing MediaRecorder data collection must not keep the hardware mic open. No partner audio replay, shared transcript, live caption or record/retry commentary by the god.
Only one room-owned invocation ID can record/judge/respond. Losing a race for ownership never opens a second mic. Current speaker/partner roles are frozen into the accepted request. On completion, the server takes the actual game state: health/max health, downed status, enemies nearby, recent damage, encounter state, known name, discovered verses, unlocked stage and recent valid attempts. It must not trust a client's claimed emergency or allow player text to override system rules.
One qualified recording makes one bounded audio-capable judge request. Supply current audio and at most three compact previous prayer/outcome summaries, never the full session or old audio. Keep history private and serialized per room. This is turn-based recorded-audio judging as requested here; do not silently add an always-on Live session from older documents. Verify the configured provider/model's audio/structured-output capability during implementation; no model migration or new paid service is prescribed by this plan.
Judge output is schema-validated: explicit verdict enum, sincerity/context interpretation, eligible-stage evidence, rude/spam signals, and an optional allowed voice-clip ID. Server chooses/clamps the actual fixed reward. No arbitrary function calls, new spells, health amounts or spoken monologues invented by the model. If a transcript is used for protected name validation, it stays server-side and never appears in peer packets. Audio/key/provider-error bodies are not logged.
Routine reactions are small physical/gameplay effects, including a small heal, rebuke or bounded damage. First true contact, unlock, exceptional rescue and clear severe warning are the scarce speaking occasions. Reuse a limited authored voice atlas. Use a central intelligible voice, quiet stereo-width layers and a wide tail; no out-of-phase-only dialogue that disappears on tablet speakers. New dramatic delivery supersedes the older uniformly deadpan direction. Swearing alone is not rudeness; context distinguishes playful affection from deliberate contempt.
Spam policy counts completed valid attempts, not short retries, duplicate IDs or mic failures. Use clear escalating gameplay warnings before a terminal joint punishment; harmless client cooldown rejection does not itself injure players. A rude-prayer damage value around 99 is a reviewable seed, not a universal subtraction irrespective of state. Severe punishments still resolve through ordinary downed/checkpoint rules.
Emergency invocation is a distinct eligible state. Bound speaker protection to the accepted ceremony; no global time manipulation, repeated refresh or distance-based denial. Visual protection is centred on the speaker; healing/rescue resolves at the recipient's actual location. No teleporting the pair or stealing the partner's camera. A single definitive result wins races between HTTP/WS delivery, cancellation, expiry and reconnect. A late cancelled result cannot resurrect an old ceremony.
10. UI, feedback and performance
The generated stone slabs, flowers, oversized portraits, inconsistent red health bars, huge ability announcements and red enemy rings are not approved assets. Adapt one restrained kit to actual art, layout, readability and performance.
Use the existing renderer and a screen-anchored orthographic HUD camera for shallow UI meshes. Batch shared token geometry/materials. Keep accessible transparent DOM hit targets aligned to these meshes; clear subtitles and small labels can remain flat text. Render on top without world-depth occlusion, keep a stable horizon, and do not inherit attack shake. Do not create a second full-time WebGL renderer because the temporary study used one.
Always-available information: two small health indicators, owned ability readiness and pause. Show shields, downed/connection warnings and current infusion only when relevant. Ordinary enemies do not get permanent overhead red health bars or ground target rings. A compact boss-health presentation may appear during the boss encounter; it is not a target marker. Telegraph enemy attacks with their native preparation, facing, weapon/readable movement and audio; additional ground danger art requires its own review.
Tablet: floating left joystick; reachable primary attack, evade and low-priority jump; E element selector, Q once unlocked and Commune after first inscription. Knight hold = block, Mage hold = charge. At least 48 CSS px secondary hit targets, 64–80 px primary hit target; visual token size can be smaller. No overlap with partner status or touch camera area. Desktop hides learned movement/attack buttons, retaining compact power keys. The character-info page contains role, level, actual stats, unlocked abilities and controls; no invented equipment slots.
No routine attack/empty swing/spell/ordinary damage shake or FOV pumping. Selected boss-shield breaks and major impacts may receive one brief capped directional impulse with no stacking. Camera comfort can disable it fully. Ordinary heal remains grounded/intimate; blessing and miracle escalate deliberately. A first unlock uses the reusable cinematic but is not a full emergency spectacle.
Complete-scene targets remain: Pad 7 DPR <=1.5, <=48 draws, <=380k triangles, 90–120 FPS target; RX 6700 DPR <=2, <=60 draws, <=620k triangles, 120–144 FPS target. Spell preview systems stay within their verified per-system draw/time caps, aim arrow targets <=2 draws and zero when inactive. Actual combined scenes decide pass/fail; standalone render timings, average FPS and saved historical reports cannot certify these budgets. Profile UI, simultaneous spells, enemies, post-processing and audio on actual devices.
11. Production gates and evidence
Follow the existing five-stage order for each affected layer: map blockout, procedural environment assets, native character motion, VFX, then gameplay/network/voice integration. Pure contract tests can be prepared earlier, but do not call integrated production complete before its art/motion gates.
New/changed asset families use the required dedicated builders and fresh independent Pass 2 critic-fixers, with strict directory ownership or isolated worktrees. Blind critics must read the judging skill and see raw rendered evidence before code, receive no previous report or known-flaw briefing, and never be reused across rounds. Baseline and updated scene evidence is captured separately. No critic is being commissioned by this planning task.
Every implemented gate produces current images plus videos for motion and a scoped test report. Use Desktop 1600×900 and Tablet 1440×960 captures; evaluate actual Pad 7 controls and sustained performance separately. If native video review tools are unavailable, record the missing gate as unverified rather than substituting stills and claiming success. Publish the real game only from an allowlisted build directory; .env/secret catalogs and the local repository-serving fallback are not a deployment artifact.
Twelve implementation tasksFiles, interfaces, regression fixtures, ordering, commits and review gates.
Haru-machi prototype revision implementation plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:executing-plans to execute this plan task-by-task after explicit implementation authorization. Use the project's required dedicated asset builders and fresh blind critic-fixers at asset gates. Steps use checkbox syntax for tracking.
Goal: Turn the agreed two-player opening, compact role combat, directional spell aiming, sequential verse discoveries and private contextual prayers into a small reliable playable prototype.
Architecture: Keep the current Three.js world and verified asset packages. Introduce small pure rule modules for room/action/aim/progression decisions, used by browser and server. Separate accepted game events from visual playback; adapt existing controllers and VFX at their actual integration points.
Tech stack: Three.js 0.170.0, native KayKit clips, existing Node development server/test tools, Cloudflare Worker + Durable Object, configured audio-capable judge and existing voice atlas. No new frontend framework or second game renderer.
Spec: DESIGN.md. Behavior checks: ACCEPTANCE.md.
Global constraints
- This is a future execution plan. This task creates only temporary documents and planning presentation. No gameplay code, map or secrets are changed by writing it.
- Before future execution, read the four mandatory project documents in order and this design. Carry the latest user decisions into the canonical vision/continuation documents so an old four-spell/shared-Q description does not silently win.
- Never read/import/copy the banned world code. Do not use forbidden environment prop packs. Preserve native skeletal animation and zero-glass VFX rules.
- Generated images are rough mood/flow references only. No red under-enemy targeting circles or persistent overhead red enemy marking. Spellbook behavior is adapted, not copied wholesale.
- Tablet 1440×960 test capture and desktop 1600×900 test capture are required; actual Pad 7 and RX 6700 checks remain separate from headless tests. Preserve complete-scene budgets from DESIGN.
- Use native workspace isolation or the approved
.worktrees/workflow when implementing. Keep asset-family changes isolated. Do not create implementation worktrees merely to write these temporary documents. - Respect layer gates: map blockout → environment assets → native character motion → VFX → mechanics/network/voice. Pure rule tests can precede integrations.
- Commit each passing implementation gate with the continuation plan updated. Never label a recovery checkpoint or saved screenshot report as a quality sign-off.
Delivery slices and dependencies
- Contract and baseline checkpoint.
- Reviewed field/route blockout.
- Environment corrections and reusable UI art study.
- Full native action timing and stable camera.
- Shared aiming math and both-device input.
- Arrow/ice/meteor/charge/Q visual gate.
- Two-seat room lifecycle and accepted-event authority.
- New role powers, encounters and shared progression.
- Private verse/unlock catalog.
- Private bounded recording/judgment and ceremonies.
- Integrated contextual 3D HUD and screens.
- End-to-end device review and deployment.
Tasks 2–6 refine existing layers before the new integrated game loop is signed off. A stage may be rejected without throwing away unrelated already-verified assets.
File ownership and proposed new modules
| New module | Single responsibility |
|---|---|
world/src/shared/room-rules.js | Pure two-seat room reducer, roles/readiness/run/reconnect transitions. |
world/src/shared/aim-rules.js | Fixed/variable-distance aim math and canonical immutable snapshots. |
world/src/shared/action-rules.js | Action phases, one buffered attack, release markers and control locks. |
world/src/shared/terrain-rules.js | Existing scalar terrain/walkable rules extracted without renderer imports, preserving output. |
world/src/shared/encounter-rules.js | Server-safe encounter state, hit scheduling, status expiry and XP. |
world/src/shared/progression-rules.js | Verse discovery order, eligible reward and idempotent unlock application. |
world/src/input/aim-controller.js | Desktop/touch pointer ownership, terrain projection and confirm/cancel. |
world/src/ui/app-flow.js | Title/loading/room/lobby/playing/pause/info state and view bindings. |
world/src/ui/hud3d.js | Shared-renderer HUD scene, batches and accessible hit-target alignment. |
world/src/net/commune-client.js | Mic lifecycle, 8/15-second rule, one invocation and no peer recording. |
world/assets/vfx/aim-arrow.js | Terrain-following physical arrow/glyph with zero idle visibility. |
world/assets/vfx/mage-storm.js | Bounded standalone winter/water Q visual sequence. |
worker/commune-judge.js | One bounded judge request, schema validation, safe reward policy and private history. |
worker/scripture-catalog.js | Catalog metadata and protected content lookup; no client solution export. |
Do not pre-scaffold every file. Create a module with its first test and actual consumer. Existing main.js, player-controller.js, campaign-loop.js, touch-hud.js, coop-client.js, combat-balance.js, worker/index.js, room-do.js and serve.mjs are explicit integration boundaries, not invitations to rewrite the entire game.
Task 1: Preserve the baseline and resolve old-contract conflicts
Files: canonical SOUL.md, continuation plan; existing combat/co-op test tools; proposed test folder world/tools/tests/.
- □ Record current HEAD, clean/dirty paths, active assets and original test outputs before implementation. Protect existing user files and today's temporary plans.
- □ Update canonical decisions: random roles/base stats, no speech selection, two E modes, charged Shatterlance, Knight hold-block, separate Q, tap-record Commune, no proximity gate and arrow aiming. Retain old sections as explicitly superseded history where useful.
- □ Run
node world/tools/test-combat-sim.mjs. Run the co-op suite with outgoing judge requests stubbed so server prewarm does not consume quota. Record that these tests still cover some old behavior and will be revised intentionally. - □ Add a test runner using Node's built-in
node:testunderworld/tools/tests/. Keep root archived npm test commands out of the workflow. - □ Record visual/motion gates as unverified until fresh evidence exists. Commit
docs(plan): record approved prototype revision and baseline.
Baseline command that stubs only the unrelated model prewarm:
// Run with node --input-type=module, preserving local HTTP/WebSocket calls.
const realFetch = globalThis.fetch;
globalThis.fetch = (input, options) =>
String(input).startsWith('https://generativelanguage.googleapis.com/')
? Promise.resolve(new Response('{}', { status: 200 }))
: realFetch(input, options);
await import('./world/tools/test-coop-room.mjs');
Exit: baseline is recorded; old assertions are identified, not silently deleted to make failures disappear. No feature claimed complete.
Task 2: Block out the safe flower pocket and useful journey
Files: world/src/blockout/{geography,scene-builder,main}.js, world/assets/terrain/geography.js, world/tools/shoot-blockout.mjs; proposed world/tools/tests/layout.test.mjs.
Interface: add exported pure OPENING_LAYOUT = { lobby: {center,radius}, approach: [{x,z}], encounterPockets: [{id,center,radius}], sanctuaryIds } in the existing geography data, shared between visual blockout and runtime placement. It is a candidate layout until player review.
- □ Candidate starting placement: safe south pocket near
(0,100), radius approximately 12 m, inside the current 125 m playable boundary; route bends toward the existing town. Place two small encounter pockets beyond the safe edge, not in the lobby. These are the first blockout measurements to inspect, not generated-image coordinates. - □ Write a layout test that no encounter circle intersects the safe circle, all sampled route points have legal walkable heights and field boundary has clearance from the wrap trigger. Run it red against missing data.
- □ Build cheap blockout masses and paths first. Keep existing town, pond, arena and shrine recognizable. Introduce distance through terrain, bends and sightlines; do not uniformly scale all coordinates.
- □ Walk camera-level routes in both directions, sample boundary wrap from approach positions and check house/path collisions. Adjust candidate coordinates from that evidence.
- □ Capture current multi-angle blockout views; commission a new truly blind critic under the project protocol, then obtain user blockout sign-off before environment asset work.
- □ Commit
feat(map): add reviewed flower lobby and approach routeonly after this gate.
Test geometry invariant to include:
import assert from 'node:assert/strict';
function assertSafePocket(lobby, pockets) {
for (const p of pockets) {
const d = Math.hypot(p.center.x-lobby.center.x, p.center.z-lobby.center.z);
assert.ok(d > p.radius + lobby.radius, `${p.id} overlaps safe field`);
}
}
Exit: actual reviewed blockout, legal route and defined spawn/aggro exclusions. A pretty concept does not satisfy this task.
Task 3: Correct houses and adapt the existing flower assets
Files: world/assets/houses/{parts,index}.js, world/assets/houses/{shoot,verify}.mjs, flower/terrain modules and their critique/evidence files. Proposed world/assets/houses/facade-layout.js, world/tools/tests/facades.test.mjs.
Interfaces: buildFacadeLayout(spec) -> { openings, structuralSegments }; each opening has local-plane bounds, each segment a swept beam polygon and a type. The renderer and validation consume the same layout. Deliberate window mullions are explicitly typed and excluded from the structural-beam check.
- □ Write tests using every
VILLAGE_BUILDING_SPECSentry that no structural beam polygon intersects the clear interior of a window/door. Include front, side, rear and dormer cases, not only the user's reported facade. - □ Make the smallest layout change that reserves openings before
buildTimberFrameplaces posts/diagonals. Preserve plausible headers, sill framing and actual wall thickness. Do not fix the issue by hiding the window, shrinking every beam or changing the camera. - □ Use a dedicated houses builder and independent Pass 2 critic-fixer. Keep work in that asset family; apply the same ownership rules to any meadow-density/layout change.
- □ Reuse current flower species and GPU batching in the new field. Do not reproduce the generator's larger asset count or new plants literally. Place emote feet on ground with enough visual clearance.
- □ Prototype one reusable shallow stone/brass UI token family as isolated art, not a second renderer architecture. Restrained frames and one coherent icon/health style supersede the varying generated slabs.
- □ Capture all affected variants and full-scene budgets, complete required blind review and commit per passing asset family.
Exit: no framing crossing real openings, plausible field, no forbidden props, verified family artifacts. No red target ring/health-bar art is added from the storyboard.
Task 4: Full native actions, role holds and a stable camera
Files: new world/src/shared/action-rules.js; existing character clip API, player controller, touch HUD and input section in main; new world/tools/tests/actions.test.mjs.
Interfaces: createActionState(role); stepAction(state, event, config) -> {state,effects}. Events: press, release, tick, dodge, hit, down, lock, unlock. Configuration supplies measured native duration/release marker and threshold. Effects: play_clip, strike, release_charge, block_on, block_off, cancel_action. State carries actionId, kind, phase, elapsed, releaseFired, bufferedAttack, requiresFreshPress, locked.
- □ Resolve actual native clips, durations and release poses from the current pack; save a small animation manifest. No guessed clip length, nonzero startTime slice or procedural bone movement. Emote aliases must be checked visually before lobby use.
- □ Write red tests for one buffered swing, no forced restart, no repeated release, locked actions rejecting input, Knight long hold blocking without a charged hit, Mage long hold releasing exactly one Shatterlance, and fresh-press requirement after recovery.
- □ Replace button-down damage/advance/camera side effects. Decide tap on release below the 220 ms seed; a long hold must never also fire a normal attack. Clear held pointers/keys on blur, downed, screen transition and ceremony exit.
- □ Normal attacks advance the full clip, with one marker-triggered hit; normal mashing can adjust tempo smoothly within a reviewed cap. Charged/Q clips do not accelerate with mashing. Dodge cancels only in explicitly allowed phases.
- □ Remove routine camera/FOV impulses in local and peer input paths. Remove/inspect automatic fourth-hit hop, instantaneous lunge and root correction separately from camera follow. Do not follow a weapon bone or incidental animation bob with the camera pivot.
- □ Record slow taps, rapid taps, hold/release, moving attacks, shielding, charging, dodge cancel and remote playback. Complete native-motion review before integrating new VFX. Commit
feat(motion): preserve native actions and role-specific holds.
Example release-once regression fixture:
import assert from 'node:assert/strict';
import {createActionState,stepAction} from '../../src/shared/action-rules.js';
const config={holdMs:220,clipMs:1200,releaseAt:.65};
let s=createActionState('Mage'), releases=0;
for(const event of [
{type:'press',button:'attack'},
{type:'tick',dtMs:300},
{type:'tick',dtMs:900},
{type:'tick',dtMs:900},
]) {
const out=stepAction(s,event,config);s=out.state;
releases+=out.effects.filter(e=>e.type==='release_charge').length;
}
assert.equal(releases,1);
assert.equal(s.requiresFreshPress,true);
Run: node --test world/tools/tests/actions.test.mjs. Add the Knight equivalent to prove there is no charged sword path.
Task 5: Aim math and input that mean the same thing
Files: new world/src/shared/aim-rules.js, world/src/input/aim-controller.js, tests aim.test.mjs; integrations main.js, player-controller.js, touch-hud.js, campaign-loop.js.
Interface: resolveAim(input, rules) -> AimSnapshot. Input: {abilityId,mode,origin,pointerGround,fallbackYaw}. Rules: {frostRange,meteorMin,meteorMax,heightAt,isLegal}. Invalid snapshots have valid:false and a bounded reason enum, not arbitrary text. Valid nested vectors/snapshot are frozen/copied on confirmation.
- □ Write and run the fixed-range, variable-range, no-target, invalid-ground, zero-vector, slope-height and immutable-confirm tests below before implementation.
- □ Implement planar direction math once. Frost ignores pointer radius; meteor clamps radius. Ground height is resolved at canonical tip. Keep source
originas feet; VFX hand offsets do not modify distance. - □ Project pointer onto actual terrain/collision surfaces, recomputing after camera changes. Keep the last valid heading for a degenerate point but do not mislabel a no-ground result as valid.
- □ Route only short E taps through nearest-valid soft-lock. Long E begins manual aim, release leaves it armed, one ground confirmation consumes it. Suppress normal attack/camera tap from the same pointer event. Expose visible Cancel; Escape/dodge/element switch/lock clears aim.
- □ Desktop pointer-lock needs an explicit transition: release it for aim, store camera yaw/pitch, use cursor projection, consume confirm/cancel and reacquire only on the next permitted player gesture. Do not steal camera control while waiting for permission.
- □ Tablet uses distinct pointer IDs for movement, camera, ability hold and confirmation. A finger on joystick is never interpreted as the meteor target. Validate landscape and common browser UI insets.
- □ Remove downstream soft-target recomputation from confirmed manual casts, including peer playback. Pass the full accepted snapshot. Commit
feat(aim): unify fixed ice and variable meteor targetingafter tests and input checks.
import assert from 'node:assert/strict';
import {resolveAim} from '../../src/shared/aim-rules.js';
const rules={frostRange:14.5,meteorMin:3,meteorMax:16,
heightAt:(x,z)=>z*.1,isLegal:()=>true};
const input={mode:'manual',origin:{x:0,y:0,z:0},fallbackYaw:0};
for(const z of [2,9,30]) {
const a=resolveAim({...input,abilityId:'frost_lance',pointerGround:{x:0,z}},rules);
assert.equal(a.distance,14.5);assert.equal(a.targetPos.z,14.5);
}
const near=resolveAim({...input,abilityId:'cinder_fall',pointerGround:{x:0,z:6}},rules);
const far=resolveAim({...input,abilityId:'cinder_fall',pointerGround:{x:0,z:99}},rules);
assert.equal(near.distance,6);assert.ok(Math.abs(near.targetPos.y-.6)<1e-6);
assert.equal(far.distance,16);
assert.equal(resolveAim({...input,abilityId:'cinder_fall',pointerGround:null},rules).valid,false);
Add an integration assertion that cinderFall.cast receives aim.distance and its computed ground impact equals aim.targetPos; a maximum-range argument alongside a different targetPos must fail the test. The same accepted ID rendered twice must not produce two hit events.
Task 6: Gate the arrow and revised spells as VFX
Files: new world/assets/vfx/aim-arrow.js, mage-storm.js; existing frost-lance.js, cinder-fall.js, shatterlance.js, sword-vfx.js, artorious-vfx.js, VFX preview/shoot/verify tools; tests vfx-contracts.test.mjs.
Interfaces: createAimArrow({getGroundHeight}) -> {group,setAim(snapshot),clear(),getStats(),dispose()}. Spell playback consumes AcceptedCast={id,abilityId,aim,startTick,releaseTick,impactTick,seed}; it does not acquire targets or award damage. frostGrowth(progress) is monotonic on emergence and exactly 1 after completion. Add a headless exported pure growth helper only if needed for this meaningful motion invariant.
- □ Render the arrow against sunlit sand, meadow, stairs and slopes. Use shallow geometry or opaque/hard-cutout strips and shared NoiseAtlas if required. Adapt source shaft/head/chevrons/tip measurements, not additive transparency or expensive reference noise loops. Width stays fixed while meteor arrow length changes. Zero draws when clear; target <=2 when active.
- □ Write the pure frost monotonic/no-overshoot test, then remove
_spikeEmergencesinusoidal settle. Retain quick travelling eruption and grounded mass; compare V1/V2/V3 in motion with the proposed darker starting treatment. No new bouncing at dissolve. - □ Correct meteor
cast: it currently ignorestargetPos. Useaim.distanceand exact canonical impact; recompute flight time from that distance and preserve one impact event. Validate near/mid/max targets with a moving enemy deliberately ignored after manual confirmation. - □ Reuse Shatterlance's actual spear with full native charged release. Build the two personal Q spectacles as separately bounded sequences, keeping intended hazard readability during the Mage's channel.
- □ Finish lotus geometry wiring without using the first unlock, simple heal and emergency miracle at identical scale. Ordinary heal is grounded; unlock levitation is limited to receiver; emergency protection is a distinct effect.
- □ Use the mandatory dedicated VFX family builder and independent Pass 2 workflow. Capture current PNGs and videos, per-system and combined cost. Commit only reviewed families; leave failed motion/quality gates unmarked.
Example growth invariant:
import assert from 'node:assert/strict';
function assertMonotonicGrowth(sample) {
let last=0;
for(let ms=0;ms<=1500;ms+=5){
const value=sample(ms/1000);
assert.ok(value>=last-1e-6 && value<=1+1e-6);
last=value;
}
assert.equal(sample(2),1);
}
Exit: no claim based only on geometry helper tests. The reviewed video must actually stop looking elastic.
Task 7: Two-seat rooms, ready/start and accepted game events
Files: world/src/shared/room-rules.js, worker/room-do.js, worker/index.js, world/tools/serve.mjs, world/src/net/coop-client.js; tests rooms-v2.test.mjs and revised test-coop-room.mjs.
Interfaces: createRoom({id,seed}); reduceRoom(room,event) -> {state,events,error}. Events: join, assets_ready, guest_ready, start, disconnect, resume, resume_ready, reconnect_timeout, leave, new_run. State includes version:2, phase, two seat records, runId, checkpoint, eventSeq. Seats contain assigned role, connected/assets/ready flags and token hash; public snapshots omit authentication material.
- □ Write red tests: complement roles for every seeded draw, third seat rejected without eviction, guest cannot start, early host Start rejected, repeated Start yields one run ID, stale socket cannot evict resumed seat, reconnect preserves role/progression, timeout cannot activate AI.
- □ Implement create/join endpoints with opaque IDs, collision-checked human phrases and seat resume tokens. Store a lookup for normalized phrases; old room-code truncation is not reused. Return bounded user-facing errors for missing/full/expired room.
- □ Implement room state transitions once and consume them in both server adapters. In normal play ignore client
swap_roles, foreign-role casts, arbitraryenemy_hitdamage and forgedworld_syncprogression. Increment protocol version and revise old relay tests explicitly. - □ Define a single authority for acceptance/damage/spawns: Durable Object in production, equivalent local room simulation in development. Do not keep host and guest both applying authoritative damage. Extract scalar terrain rules from
geography.js, leaving DataTexture generation client-side; prove sampled heights unchanged before using them on server. - □ Use active-room simulation ticks, accepted action IDs and ordered public event sequence. Start, cast, impact, downed, unlock and checkpoint are idempotent events. Resume sends a consistent snapshot plus sequence, and clients discard older duplicates.
- □ Route new screens from room state; avoid starting enemies behind a loading or lobby overlay. No new permanent account system. Commit
feat(coop): coordinate two-seat start and reconnect.
Minimum lifecycle test fixture:
import assert from 'node:assert/strict';
import {createRoom,reduceRoom} from '../../src/shared/room-rules.js';
let room=createRoom({id:'fixture',seed:123});
function send(event){const out=reduceRoom(room,event);room=out.state;return out;}
send({type:'join',seatId:'a'});send({type:'join',seatId:'b'});
assert.deepEqual(new Set(room.seats.map(s=>s.role)),new Set(['Knight','Mage']));
assert.equal(send({type:'join',seatId:'c'}).error,'ROOM_FULL');
assert.equal(room.seats.length,2);
assert.equal(send({type:'start',seatId:'a'}).error,'NOT_READY');
send({type:'assets_ready',seatId:'a'});send({type:'assets_ready',seatId:'b'});
send({type:'guest_ready',seatId:'b',ready:true});
const start=send({type:'start',seatId:'a'});
assert.equal(start.events.filter(e=>e.type==='run_started').length,1);
assert.equal(send({type:'start',seatId:'a'}).events.length,0);
Task 8: Role powers, roaming encounters and balance
Files: world/src/shared/encounter-rules.js, world/src/combat/{combat-balance,campaign-loop,enemy-manager}.js, room simulation consumers; tests combat-v2.test.mjs, status-expiry.test.mjs.
Interfaces: createEncounter(seed,definition); stepEncounter(state,dtMs); applyAcceptedAction(state,action); applyEnemyDefeat(state,event); results return public ordered events. activateBlade(hero,element,nowMs,config) rejects until next-ready time, stores active expiry separately from selected element, and never accepts a role-inappropriate action.
- □ Write tests for one swing/contact, one meteor impact, fire burn refresh/cap, ice effect refresh/floor, no global time change, Q single cost, duplicate kill giving no XP, and removal of all artificial partner-distance checks.
- □ Re-map the ability catalog: base Mage Shatterlance charge; E Frost/Cinder; Knight selected ice/fire E; separate personal Q; no ordinary Storm Lance E and no old four-slot/wingman-command shortcuts.
- □ Stop auto-infusing the Knight from every Mage cast. Define mixed Q/divine priority and restoration on expiry. Clear every damage/empowerment flag when the owning buff ends. A selected element change alone cannot restart duration/cooldown.
- □ Seed authored roaming pockets; respect safe-field/poem exclusions, max visible enemies and attack tokens. Enemy threats remain readable without red circles; do not restore target art to compensate for weak animation timing.
- □ Implement five-level shared XP seed and role-stat curves from the existing enemy table. Instrument ordinary-hit survival and timed encounter results, then tune against actual replayed input—not just the headless auto-win simulator.
- □ Define one boss shield schedule and adjust elite/boss health/telegraphs to the new Q mechanics. A full Mage Q should feel devastating without trivially ending the complete boss encounter. Protecting the cast must be feasible under the concurrent-attacker cap.
- □ Implement checkpoint recovery and consensual new-run reset. Commit
feat(combat): implement revised role power loopafter current tests and two-client checks.
Blade timing test (13/18 are explicitly provisional):
import assert from 'node:assert/strict';
import {activateBlade} from '../../src/shared/encounter-rules.js';
const config={activeMs:13000,cooldownMs:18000};
const hero={role:'Knight',blade:null,bladeReadyAt:0};
const first=activateBlade(hero,'fire',1000,config);
assert.equal(first.hero.blade.expiresAt,14000);
assert.equal(first.hero.bladeReadyAt,19000);
assert.equal(activateBlade(first.hero,'ice',18999,config).error,'COOLDOWN');
assert.equal(activateBlade(first.hero,'ice',19000,config).error,null);
Task 9: Three verses, private solutions and sequential rewards
Files: world/src/shared/progression-rules.js, worker/scripture-catalog.js, read endpoint in worker/index.js, local parity in serve.mjs; proposed tests progression.test.mjs, public-content.test.mjs.
Interfaces: createProgression() returns {discovered:[],stage:0,nameKnown:false,communeVisible:false,appliedUnlockIds:[]}; discoverVerse(state,id) adds discovery and reveals Commune; eligibleVerseId(state,catalog) returns only the next discovered unmet stage or null; applyUnlock(state,{id,verseId}) is idempotent. Public poem responses contain id/title/poem only, never expected meaning, aliases or solution flags.
- □ Write red tests: discovering verse 3 first reveals Commune but cannot skip stages; rediscovery/duplicate result has no effect; correct-name first contact and stage progress remain distinct validated facts.
- □ Author three short, forgiving poems tied to existing stele/waterside/shrine locations. Use the actual protected configured name only in private content tooling; never include it in plans, logs, public code or generated examples. Keep exact authoring and solution fixtures outside public artifacts. The first name clue must be solvable; later themes invite personal effort/devotion rather than exact recitation.
- □ Store reward IDs in a fixed private catalog with stage prerequisites. Proposed stages are first E, fire modes, personal Q; freeze the reward order with Vikas before recording final lines or building bespoke unlock art.
- □ Persist shared discovered/stage/name-known facts with the room/run checkpoint. Reload/reconnect cannot farm rewards. No cross-account system is required.
- □ Add world interaction prompts only near discoverable objects and show the role-info page's actual earned abilities. Existing gate requires neither both players near the tablet nor a partner tether.
- □ Commit
feat(progression): add bounded sequential valley versesafter endpoint leakage and order tests pass.
import assert from 'node:assert/strict';
import {createProgression,discoverVerse,eligibleVerseId,applyUnlock}
from '../../src/shared/progression-rules.js';
const catalog=[{id:'verse_1',stage:1},{id:'verse_2',stage:2},{id:'verse_3',stage:3}];
let p=discoverVerse(createProgression(),'verse_3');
assert.equal(p.communeVisible,true);assert.equal(eligibleVerseId(p,catalog),null);
p=discoverVerse(p,'verse_1');assert.equal(eligibleVerseId(p,catalog),'verse_1');
p=applyUnlock(p,{id:'unlock-a',verseId:'verse_1'});
assert.equal(p.stage,1);
assert.deepEqual(applyUnlock(p,{id:'unlock-a',verseId:'verse_1'}),p);
Task 10: Private recording, contextual judgment and rare voice
Files: new world/src/net/commune-client.js, worker/commune-judge.js; room ownership in room rules/DO; existing voice atlas, main mic handlers and Artorious effects; tests recording.test.mjs, prayer-races.test.mjs, judge-policy.test.mjs.
Interfaces: export pure recordingDecision({durationMs,hasAudio,failed,cancelled}) -> 'retry'|'submit'|'cancel'; beginInvocation(room,seatId) -> {id,owner,error}; settleInvocation(room,{id,result}) commits at most once. Judge API returns an allowlisted normalized reaction; raw transcript/audio/provider errors never enter public packets.
- □ Boundary-test 7,999 / 8,000 / 15,000 ms, empty capture, denial, track errors, short retry and duplicate stop. A monotonically measured elapsed time is used, not container metadata or wall-clock changes.
- □ Acquire room ownership before opening the mic. If a stop/cancel happens while permission is pending, immediately stop the eventual stream and do not create an abandoned recording. On valid stop call recorder stop and track stop immediately; collect the final chunk asynchronously without extending device capture. Clear tracks/timers on blur, downed, disconnect and teardown.
- □ Implement tap-record/tap-stop for keyboard and touch with no hold-to-pray remnants. Under-eight stop shows only Retry; no network request and no diegetic 'already heard' line. At fifteen seconds auto-stop submits once. Same-pointer bubbling must not also attack or reopen recording.
- □ Accept audio only for the room-owned ID and authorised seat. Build context from authoritative room state, including actual emergency and next eligible verse. The transport submits one bounded turn; do not add always-on streaming without a new decision.
- □ Validate schema/enums/types; keep at most three private compact history items. Server determines reward IDs, stage eligibility, health deltas, severity and speaking allowance. First-contact, major-unlock/rescue/warning gates select rare fixed voice clips. Provider timeout/malformed response releases ceremony without punishment; no automatic fallback smite.
- □ Test concurrent two-player begin, duplicate HTTP/WS delivery, cancellation while judging, late result after downed/reset, role change on stale socket, spoofed low health, and abuse-count exemption for retries/errors. Three timestamp checks are not a replacement for invocation IDs.
- □ Author limited grand voice lines and mix them with a clear central voice plus restrained stereo width/tail; check mono tablet speakers and headphone/Bluetooth release. Never send the speaker's recording to the partner as a voice line.
- □ Emergency effects lock only the speaker, with bounded protection and no repeated refresh. Awakening locks only the receiver; clear buffered inputs on landing and restore controls even when a cinematic fails. Commit
feat(commune): resolve private bounded invocationsafter race and device tests.
import assert from 'node:assert/strict';
import {recordingDecision} from '../../src/net/commune-client.js';
const valid={hasAudio:true,failed:false,cancelled:false};
assert.equal(recordingDecision({...valid,durationMs:7999}),'retry');
assert.equal(recordingDecision({...valid,durationMs:8000}),'submit');
assert.equal(recordingDecision({...valid,durationMs:15000}),'submit');
assert.equal(recordingDecision({...valid,durationMs:9000,failed:true}),'retry');
assert.equal(recordingDecision({...valid,durationMs:9000,cancelled:true}),'cancel');
The module must expose that pure helper without opening devices or requiring window at import time.
Task 11: Assemble final screens and truthful 3D HUD
Files: world/src/ui/{app-flow,hud3d}.js, world/index.html, main.js, touch-hud.js; tests ui-state.test.mjs plus current browser interaction/capture tools.
Interfaces: deriveHudState(room,hero,progression,action) -> {health,controls,selectedElement,prayerStatus,connectionStatus}; createHud3d({renderer,camera,root}) -> {update(snapshot,viewport),render(),dispose()}. Use the existing renderer; actual drawing order clears HUD depth without clearing world color. Transparent DOM buttons consume events and expose accessible names. App flow has one transition table, not independent modals that can overlap.
- □ Write tests that lobby has no attack/movement controls, receiver lock hides/disables combat, Commune stays hidden before discovery, E/Q visibility matches real unlocks, Knight/Mage hold semantics are reflected, and disconnected status cannot masquerade as linked play.
- □ Implement title credits, honest loading, Create/Join phrase flow, flower field emotes and per-seat Ready/Start. Both client assets must be ready before one accepted start tick moves cameras. Use useful recovery for failed loads/full rooms, without developer error dumps.
- □ Replace the generated slabs/frames with one reviewed kit. Batch health, icon and cooldown visuals, use opaque/readable materials, align DOM hit targets after resize/profile/fullscreen changes. No emoji, permanent minimap/clock/inventory/mana, red enemy targeting ring or ordinary overhead red enemy bar.
- □ Add character information via the role emblem: actual role/base+level stats, unlocked powers and only relevant controls. Tab is always element cycle in play; it must not swap the hero or unexpectedly open a page.
- □ Show only the newly unlocked ability's short hint and clear it after successful use. E aiming can display Cancel and selected spell; no tutorial for obvious joystick movement. Put audio, camera comfort and leave-room actions in pause.
- □ Capture desktop and tablet at actual CSS sizes; measure hit targets, thumb reach and central sightline. Profile the added HUD against the complete scene, not the temporary demo's separate canvas. Commit
feat(ui): integrate contextual sculpted game screens.
Exit: no UI state lies about readiness, power ownership or recording. At least 48 px secondary and 64–80 px primary touch targets remain distinct even when icons are visually small.
Task 12: Playthrough, blind gate and actual deployment
Files: current capture/verification tools, new scenarios under world/tools/tests/, allowlisted static build tooling and Worker config; continuation plan/evidence report.
- □ Run all revised Node contract tests and updated legacy suites. Revise old half-health/proximity-revive/four-spell assertions into explicit new assertions; do not simply remove coverage.
- □ Execute every scenario in ACCEPTANCE on two clients, including disconnect during charge, aiming, prayer and awakening. Record actual outcomes and IDs; never capture protected content into public test artifacts.
- □ Record title-to-boss playthrough and focused sword/charge/aim/ice/prayer clips. Capture 1600×900 desktop and 1440×960 tablet layouts. Inspect motion with the required native-video workflow. If unavailable, list that gate as incomplete.
- □ Run sustained actual hardware checks including aim arrow + both heroes + enemies + simultaneous VFX + HUD + post-processing. Record worst cases and frame-time distribution, not only a pleasant empty-field FPS.
- □ Commission a fresh blind full-game critic under the exact judging protocol and obtain Vikas's playable sign-off. Handle any implementation fixes within their scoped gate; do not pass historical self-reports forward as evidence.
- □ Build an allowlisted asset directory for the real game and verified character assets. Never deploy the repo root,
.env, private scripture solutions or the temporary planning directory as the real game. Fix the development server's arbitrary-file fallback before sharing it beyond localhost. - □ Deploy the Worker room/verse/judge endpoints with secret bindings and matching static assets. Verify local/production behavior parity, mic HTTPS context and a real Desktop + Pad 7 join/start/reconnect. Packaging as an APK/TWA remains deferred.
- □ Update canonical continuation checkboxes and atomic final commit only for completed gates. Provide current image/video evidence, remaining limitations and the actual deployed game URL. Do not reuse the temporary Pages planning URL as evidence of a functioning co-op deployment.
Review before execution
The source-backed aiming correction is a requirement. The proposed verse reward order, input thresholds, reconnect timeout, five-level cap, Q refund rule and final damage/HP values are review points, clearly listed in DESIGN. Keep them configurable and validate them in a short playtest before treating them as locked content. No additional game subsystem is needed to start Task 1 once implementation is authorised.
Acceptance scenariosWhat should happen when both of you actually play, including failure cases.
Haru-machi acceptance scenarios
These are future pass/fail checks, not results already achieved. A task is complete only when the relevant scenarios have current evidence. Automated checks prove state transitions and values; motion, aiming readability, atmosphere and controls need actual gameplay/video and the mandatory blind review gate.
A. Opening and two-person rooms
| ID | Situation | Required result |
|---|---|---|
| A01 | Open game on either device | Correct title/credits and one Enter; no combat beneath title. |
| A02 | Slow or failed asset load | Progress reflects real completed work; failure has usable retry; no fake 100% or invisible active enemies. |
| A03 | Create world | Unique phrase and invite link, host seat, one authoritative random role and fixed stats. |
| A04 | Join through phrase with case/spacing differences | Resolve the same room without eight-character truncation. |
| A05 | Join through invite link | No need to retype phrase; second seat receives complementary role. |
| A06 | Third person/tab attempts ordinary join | Reject capacity; do not evict either original player. |
| A07 | Host presses Start before guest Ready/assets loaded | No start; show only missing readiness reason. |
| A08 | Host double-clicks Start and packets retry | Exactly one run ID and shared transition. |
| A09 | Guest presses Start through forged event | Reject; no role/cooldown/state change. |
| A10 | Guest readies then disconnects | Ready clears; Start disables; seat is reserved within the reconnect policy. |
| A11 | Either reloads/resumes with valid seat token | Same role, progression, checkpoint and current room state; stale connection cannot overwrite it. |
| A12 | Mid-fight disconnect | Explicit reconnect state and proposed room pause; no silent AI or offscreen deaths. |
| A13 | Pending resume exceeds timeout | Offer the specified checkpoint/lobby recovery, not an unexplained fresh run. |
| A14 | Connected flower lobby | Gentle verified native clips; no locomotion/combat/aggro; both characters stay grounded. |
| A15 | Start | Each camera settles behind its own hero; input begins once; no menu click also becomes an attack. |
| A16 | Mic denied in lobby | Joining/playing still works; explain permission only when Commune is requested. |
B. World and travel
| ID | Situation | Required result |
|---|---|---|
| B01 | Walk the new approach in both directions | Clear route and meaningful sightlines, no generated-image geometry copied literally or long empty detour. |
| B02 | View town/field from player height and alternate angles | Terrain/cottages/flowers make spatial sense; no hidden placement fix that works only for a screenshot. |
| B03 | Inspect every actual house variant | Structural beams do not cross window/door clear interiors; deliberate mullions remain plausible. |
| B04 | Inspect steps, dormers, shutters and side elevations | No new intersection introduced by facade correction. |
| B05 | Seed roaming encounters across repeated runs | Enemies remain in authored pockets, outside lobby and verse-reader exclusion areas; no spawning directly behind a player without warning. |
| B06 | Kill the first encounter before earning E | Base Knight and Mage actions can win with teamwork; no circular unlock dependency. |
| B07 | Cross outer boundary while moving or aiming | Approved wrap works; active aim/cast either cancels coherently or uses its frozen valid origin, never stretches across the entire valley. |
| B08 | Lure an enemy toward safe lobby | Leash/encounter rules prevent combat invading the waiting field. |
C. Native attack and hold behavior
| ID | Situation | Required result |
|---|---|---|
| C01 | Knight slow tap combo | Full native wind-up/contact/follow-through; one contact per attack. |
| C02 | Rapid clicking before previous contact | No forced animation restart, duplicate hit or skipped combo motion. |
| C03 | Stop mashing | Complete current swing and at most one intentionally buffered follow-up, not an attack backlog. |
| C04 | Knight hold/release | Shield, no charged sword or extra normal attack; no attack while blocking. |
| C05 | Attack from side/back during block | Protection matches agreed facing coverage rather than omnidirectional immunity. |
| C06 | Heavy enemy attack against guard | Readable native wind-up and agreed guard-break effect. |
| C07 | Mage short tap versus hold threshold | Exactly one normal OR one charged intent, never both. |
| C08 | Hold Mage attack continuously past recovery | One Shatterlance only; another requires a fresh press. |
| C09 | Three or more fresh repeated charges | Same understandable charge/recovery rules; no silent fourth-charge normal attack. |
| C10 | Charged wind-up | Stationary/exposed, full animation; damage/VFX occurs at intended marker, not button-down. |
| C11 | Dodge before versus after charged release | Only the documented cancel window applies; no invisible projectile after a pre-release cancel. |
| C12 | Blur, pointercancel, downed or cinematic lock while holding | Release state clears; no stuck shield/mic, ghost cast or attack on return. |
| C13 | Normal attacks with no enemies | No camera shake/FOV pumping, forward teleport or unwanted hop. |
| C14 | Peer plays the action | Same accepted action/seed/schedule; remote code does not forcibly splice a shorter different animation. |
| C15 | Move over uneven ground while attacking | Character grounding and camera follow remain stable independently of weapon motion. |
D. Directional E targeting
| ID | Situation | Required result |
|---|---|---|
| D01 | Hold E with Frost Lance selected | Physical, terrain-following direction arrow appears; no circle under the enemy. |
| D02 | Move pointer from near to far on same heading | Ice arrow tip stays exactly at configured fixed reach; only heading can change. |
| D03 | Hold E with meteor selected | Same visual language; arrow length changes to measured legal distance. |
| D04 | Set meteor target near, middle and max range | Actual impact centre equals arrow tip in all three cases. Flight time follows actual distance. |
| D05 | Confirm manual aim with a nearer enemy off to the side | No snap; accepted direction/distance remain chosen by the player. |
| D06 | Enemy moves after confirmation | Cast keeps the ground point; it does not home or reacquire. |
| D07 | Tap E with valid enemy ahead | Quick-cast uses nearest eligible forward target while preserving the spell's fixed/variable reach rule. |
| D08 | Tap E with no valid target | Legal forward fallback; no arbitrary turn or attempt to cast through invalid terrain. |
| D09 | Point at sky, own feet, water or forbidden surface | No NaN/direction snap/false-valid cast; documented invalid feedback; no cost consumed. |
| D10 | Rotate camera with stationary cursor while aiming | Arrow recomputes against the correct world surface. |
| D11 | Aim over slope or stairs | No flat-plane hovering/clipping; tip height matches damage/VFX ground height. |
| D12 | Switch element, dodge, cancel or become downed | Arrow clears; no late confirm/cooldown. |
| D13 | Tablet joystick + camera + E hold + ground confirmation | Pointer ownership is independent; no movement finger selecting an impact or confirmation also firing a normal attack. |
| D14 | Desktop pointer-lock enter/exit | Correct release/restore policy; no giant camera jump or forbidden automatic recapture. |
| D15 | Peer receives accepted cast twice | One visual instance per ID; no local authoritative damage or re-targeting. |
| D16 | Damaging meteor before its visible arrival | Test must fail; damage is scheduled to accepted impact. |
| D17 | Arrow on sunlit sand and dense meadow | Contrast readable without red target discs, glassy wash or huge solid obstruction. |
| D18 | Aim inactive | No residual arrow/glyph and zero idle draws. |
E. Roles, powers, levels and difficulty
| ID | Situation | Required result |
|---|---|---|
| E01 | Knight selects fire/ice without activating | Selection changes; active effect/cooldown does not restart. |
| E02 | Activate E at t=0 with 13/18 seed | Effect expires at t=13; next activation allowed at t=18, not t=31. |
| E03 | Repeated fire hits | Capped refreshable burn; no unbounded stack growth or duplicate network ticks. |
| E04 | Repeated ice hits and level increases | Local enemy slow/damage reduction respects cap/boss floor; other player's controls and clocks do not slow. |
| E05 | Mage casts E during Knight mixed Q | No downgrade/overwrite of stronger blade state. |
| E06 | Q/divine buff expires | Restore only still-valid ordinary blade; clear every related damage multiplier and visual flag. |
| E07 | Attempt removed electricity/four-slot/old shared-Q command | Not reachable through UI or accepted through forged/stale input. |
| E08 | Mage Q channel versus unprotected/protected hit | Agreed lethal exposure applies only to an unprotected hit; shield/ward protection is honoured. |
| E09 | Mage Q cancelled before release | Agreed half-cost seed applies once; no Q effect or later stray release. |
| E10 | Both press their own Q simultaneously | Distinct role-owned accepted actions and costs; no duplicate old couples ultimate. |
| E11 | Knight protects while Mage damages | Shared XP and agreed charge gains recognise useful participation; last hit is not required. |
| E12 | Enemy death packet repeats | No second XP/reward/spawn progression. |
| E13 | Both players fall | Defined checkpoint reset; no double reset, loss of earned discoveries or revival into an active enemy hit. |
| E14 | Fight Sovereign under new roles | Shield phase consistent, telegraphs readable, Q spectacular but not an automatic entire-boss win. |
F. Verse and prayer rules
| ID | Situation | Required result |
|---|---|---|
| F01 | Read later poem first | Discover it and reveal Commune; do not skip stage order. |
| F02 | Read same poem on both clients/reconnect | Shared discovery once, no duplicated unlock or ceremony. |
| F03 | Valid recording stops at 7,999 ms | Retry only; zero judge request and no spam count. |
| F04 | Valid recording stops at 8,000 ms | Submit once, no Retry. |
| F05 | Recording reaches 15,000 ms | Auto-stop and one submission; another tap cannot submit twice. |
| F06 | Denied/failed/empty recording | Retry/recovery; no smite or offense count. |
| F07 | Permission resolves after cancel | Stop eventual stream immediately; no mic left open. |
| F08 | Recorder final chunk arrives after stop | Hardware tracks already stopped; collect final data without retaining capture. |
| F09 | Both start Commune together | Server chooses one owner; losing client does not record; no restarted/shared overwritten effect. |
| F10 | Partner inspects their network/events | No speaker audio, raw transcript, private summary, solution or provider-key leakage. |
| F11 | Speaker falsely reports low HP or dead partner | Judge context uses authoritative room truth, not caller claims. |
| F12 | Dramatic dying claim while safe | Context-sensitive bounded response; no automatic emergency protection reward. |
| F13 | Simple honest affection while safe | Still eligible for modest meaningful response/progression; emergency is not required for all help. |
| F14 | First incorrect versus correct true-name contact | No spoken reveal before valid contact; protected comparison happens server-side. Public artifacts do not expose the answer. |
| F15 | Ordinary versus exceptional completed prayers | Gameplay feedback can occur each time, speech follows rarity gates, no continuous generated monologue. |
| F16 | Joke/profanity versus deliberate contempt | Judge fixtures distinguish context; a swear word alone does not trigger rude punishment. |
| F17 | Repeated deliberate abuse after clear warnings | Bounded escalation can reach severe/joint punishment; normal downed/checkpoint rules still work. |
| F18 | Model times out or returns malformed/forbidden reward | Safe failure and ceremony release; no fallback punishment, arbitrary spell or forged health delta. |
| F19 | HTTP result arrives before/after WS result | Same invocation settles once with one voice/effect/reward. |
| F20 | Cancel/reset/disconnect while judging | Late result cannot mutate a new run or reopen a cancelled ceremony. |
| F21 | Emergency invocation repeatedly tapped/cancelled | No indefinite protection/refresh exploit; technical failures are distinguished from intentional repeated starts. |
| F22 | Partner far away | No artificial distance denial; protection at speaker, healing at recipient, no teleport or peer camera takeover. |
| F23 | Unlock while receiver holds attack/dodge | Inputs disabled through ceremony, cleared on exit; no stored burst of actions on landing. |
| F24 | Receiver animation/effect fails | Controls recover deterministically, partner remains playable and unlock applies at most once. |
G. Visual, audio and deployment gates
- □ Current player-height PNGs cover field, path, all houses, town, pond and shrine at both target layouts.
- □ Current video covers full sword cycles, rapid mashing, block, Shatterlance, both aim modes, near/max meteor impacts and non-bouncing ice growth.
- □ Current video distinguishes simple heal, first unlock and emergency miracle; no shared identical spectacle for all three.
- □ Screenshot arrows and timing overlays are QA-only. No red under-enemy circle, ordinary overhead red bar or debug controls leak into play.
- □ Blind critic receives raw current evidence and neutral task instructions, not this flaw list, previous reports or code. Required skill loaded before critique; PNGs before source; fresh critic each round.
- □ Tablet thumb reach, minimum target size, labels in daylight, portrait/browser resizes and actual audio/Bluetooth behavior are physically checked.
- □ Voice intelligible in mono tablet speakers and headphones; width/tail does not obscure dialogue or gameplay warnings.
- □ Complete scene meets the project's draw/triangle targets and recorded actual-device frame-time gates; saved historical reports and software-WebGL warnings are not performance proof.
- □ Static deployment allowlist excludes repository/private configuration and includes required character assets legitimately.
- □ Production Worker implements room, verse and commune APIs rather than merely relaying events or falling through to static HTML.
- □ Two real devices can join through HTTPS, start together, aim at the same accepted points, pray privately and reconnect without role/progression loss.
- □ Evidence links, source revision, device/profile and remaining limits are recorded before final sign-off.
Evidence record template
For each completed scenario record: scenario ID, source commit, local/production environment, device/profile, input sequence, expected outcome, observed outcome, relevant accepted event IDs, image/video path and pass/fail. Redact tokens, audio/transcripts, protected name/solutions and provider responses. Store public visual evidence separately from protected content fixtures.
The planner has inspected source and rough references; none of the future scenarios above is marked passed by the planning task itself.
Download: Design · Implementation plan · Acceptance checklist