Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .opencode/plans/2026-10-05-r04-player-clock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# R04 player/world animation clock split

## Goal
Keep Aren's finite actions and native looping animation on simulation time while hostile/world animation remains on the scaled world clock, without breaking serialized checkpoint state.

## Context
- Branch: `task/R04-responsive-player-clock-20261005-a` from current main `0ef65af`.
- Parent issue: #25 remains open until genuine native rig/root-motion/contact and real-application acceptance pass.
- Contract: `docs/features/combat.md`; implementation: `src/game/combat/CombatEngine.ts`, `src/rendering/AnimationController.ts`, `src/rendering/GameRenderer.ts`.
- Existing implementation edits are in the assigned source/test files and need independent completion/review. Never convert fixture evidence into production acceptance.
- Workers must skip build/tests/formatters mid-flight and must not use a browser. The coordinator runs integrated verification once after both implementation slices and reviews.

## Tasks

### T1 — Simulation-clock combat actions and schema migration `[x]`
Files: `src/game/combat/CombatEngine.ts`, `tests/combat/time-dilation.test.ts`.
Acceptance:
- Aren finite action start/end/sample progression is based on `state.tick` and advances once per fixed simulation step through Perfect Dodge slow motion; non-player actions continue to use `worldTick` and `worldScale`.
- Checkpoint reset does not rebase Aren's simulation-clock action timestamps; it still rebases non-player/world-clock timestamps.
- Engine schema v3 migrates v2 player live/checkpoint action progress while preserving `normalizedTime`; v1 simulation-clock saves remain compatible.
- Deterministic tests cover player/enemy rates, a player action started during slow motion, action completion, v1 compatibility, and v2 live/checkpoint migration.
- Keep traversal's authored world-clock behavior unchanged. Do not claim native asset or game acceptance.

### T2 — Renderer loop sampling clock routing `[x]`
Files: `src/rendering/AnimationController.ts`, `src/rendering/GameRenderer.ts`, `tests/rendering/browser-smoke.ts`.
Acceptance:
- One-shot clips still use authoritative `normalizedTime` snapshots.
- Loop clips sample actual native clip duration from an authoritative clock: Aren from `simulationTick / 60`, all other actors from `worldTimeSeconds`.
- Existing callers/previews remain compatible.
- Browser renderer smoke covers a player and hostile loop under 0.25 world-time scale and detects the two distinct samples.
- Fix any existing renderer smoke failure caused by this slice; do not suppress unrelated errors or loosen assertions.

### T3 — Independent spec-compliance review `[x]`
T1 and T2 independently received Astra `SPEC_OK` and then fresh `QUALITY_OK` reviews. T1 caveats were traced through update/recovery/renderer consumers; no patch-introduced defect found. Static review only; runtime evidence is recorded under T5. Spec jobs: `R04CombatSpec` and `R04RendererSpec`; quality jobs: `R04CombatQuality` and `R04RendererQuality`; all review jobs used `openai-codex/gpt-6-astra`.

### T4 — Independent code-quality review `[x]`
Fresh Astra reviewers returned `QUALITY_OK` for both slices after spec review. Combat state lifetime, JSON persistence/checkpoints, migration caveats, renderer clock ownership, preview/runtime transitions, and production-path smoke coverage were checked; no actionable introduced defects. Implementer jobs were `R04CombatClock` and `R04RendererClock` (`openai-codex/gpt-6.1-sol`). Quality-review durations were 95s and 55.8s; absolute agent start/end times and usage were not exposed.

### T5 — Integrated verification `[x]`
Coordinator observed `./node_modules/.bin/tsc --noEmit`; `/tmp/wild-crown-bun/node_modules/.bin/bun test tests/combat/time-dilation.test.ts` (21 pass / 225 assertions); `/tmp/wild-crown-bun/node_modules/.bin/bun test` (565 pass / 1,507 assertions); and `PATH=/Users/hoainho/.nvm/versions/node/v20.18.1/bin:$PATH npm run build` (pass; Vite warned that 504.94 kB and 568.75 kB chunks exceed 500 kB). Chromium `/tests/rendering/browser-smoke.html` passed with zero page errors; two Three.js `skinning` warnings remain. `/tests/app/browser-smoke.html` passed all seven lifecycle checks with `failure: null` and cleanup true. The real development route and built preview both reject New Game with `greenwake-environment` blocked; this is the genuine fail-closed gate, not playable acceptance. Bun runs on ARM64 required installing the lockfile-matched optional Rollup/esbuild ARM64 binaries locally with `--no-save --package-lock=false`; initial full-suite failures were only missing platform binaries, and the complete suite passed after repair. Absolute verification timestamps and usage were not exposed.

### T6 — User-test readiness decision `[x]`
`NOT READY FOR USER TEST`. Production preview confirms New Game is blocked because `greenwake-environment` is `evidenceStatus: blocked`, `runtimeUrl: null`, `nativeGlb: null`. Required P6 actors also lack native GLBs/clip mappings; only approved Aren/Asha concept crops exist and both are `providerDispatch: false`, `modelReady: false`. Do not bypass: exact per-asset rights/Tripo grants or supplied native GLBs with provenance, workspace host-image consent, an official Jev harness `>=0.2.5` (upstream official `0.2.3` is incompatible), plus native rig/root-motion/contact evidence and full P6 application acceptance remain prerequisites. Keep #25 open.

## Boundaries
- No new Tripo jobs, reference uploads, consent changes, fake/greybox product assets, or weakened readiness gates.
- No changes outside each task's owned files except this harness; workers may commit only their owned source/test files when explicitly authorized by the coordinator. No push, merge, issue close, or browser use by workers.
- One reviewed increment is not full-game or playtest readiness.
4 changes: 2 additions & 2 deletions docs/features/combat.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ The engine maps all 17 approved Wild Flow actions to semantic action IDs. Dodge
`CombatSnapshot.worldClock` distinguishes integer `simulationTick` from fractional 60-Hz `worldTick` (`worldTimeSeconds = worldTick / 60`). Perfect Dodge applies a data-driven 0.25 world-time scale for 12 simulation ticks; all unique-hit extensions in one uninterrupted slow-motion episode share an 18-simulation-tick (0.30-second) total elapsed-plus-remaining budget. Pause, reset, region load, and expiry end the episode. It grants 20 Instinct once per attack-instance hit group, opens a 36-simulation-tick counter window, reduces Aren's actual recovery by 12 simulation ticks and the assigned companion's ability cooldown by 18 simulation ticks, and exposes the attacker for 36 world ticks with 1.5× damage.
The actual hit-window/i-frame intersection is evaluated in worldTick; input, counter, recovery, and reward windows remain simulation-tick based.

Enemy hazard/telegraph/AI recovery, stagger and possession, actor animation progress, companion navigation, movable-object animation, and StoryCue lifetimes advance on the scaled world clock. Input edges, dodge charges/iframes, player hit/counter windows, costs, rewards, and idempotency identities remain simulation-tick based. RAF/camera time is not scaled. CombatEngine.setTimePolicy('paused') freezes world time; resume and encounter/region load restore normal policy without rewinding the region clock. Checkpoint reset keeps worldTick monotonic and rebases actor world-clock deadlines (including non-player invulnerability) and lastAttackTick, while updating attackSelectionWorldOffset to preserve the virtual attack-choice clock across retries; it restores the simulation-clock counter window and player i-frames. Cues from the discarded attempt are cleared. Presentation cues keep their simulation tick/instance identity and receive engine-owned worldTimeSeconds at creation.
Runtime EngineState serialization uses schema version 2. Restoring schema-v1 data without a fractional world clock aligns worldTick to the legacy simulation tick so absolute hazard and actor deadlines remain valid. Legacy queued StoryCue timestamps derive from cue.tick / 60, and in-flight movableObjects.startedAtTick migrates to startedAtWorldTick. Version-2 fractional clock values, attackSelectionWorldOffset, and slow-motion episode elapsed ticks round-trip exactly; older v2 data defaults the offset to zero. This runtime migration does not change campaign IndexedDB saves.
Enemy hazard/telegraph/AI recovery, stagger and possession, non-player actor animation progress, companion navigation, movable-object animation, and StoryCue lifetimes advance on the scaled world clock. Aren's movement and finite action sampling advance on simulation ticks; GameRenderer samples looping Aren clips from simulationTick and other actor loops from worldTimeSeconds. Input edges, dodge charges/iframes, player hit/counter windows, costs, rewards, and idempotency identities remain simulation-tick based. RAF/camera time is not scaled. CombatEngine.setTimePolicy('paused') freezes both clocks; resume and encounter/region load restore normal policy without rewinding the region clock. Checkpoint reset keeps worldTick monotonic and rebases world-clock deadlines, non-player action timestamps (including non-player invulnerability), and lastAttackTick, while updating attackSelectionWorldOffset to preserve the virtual attack-choice clock across retries; it restores the simulation-clock counter window and player i-frames. Cues from the discarded attempt are cleared. Presentation cues keep their simulation tick/instance identity and receive engine-owned worldTimeSeconds at creation.
Runtime EngineState serialization uses schema version 3. Restoring schema-v1 data without a fractional world clock aligns worldTick to the legacy simulation tick so absolute hazard and actor deadlines remain valid. Legacy queued StoryCue timestamps derive from cue.tick / 60, and in-flight movableObjects.startedAtTick migrates to startedAtWorldTick. Version-2 fractional clock values, attackSelectionWorldOffset, and slow-motion episode elapsed ticks round-trip exactly; older v2 data defaults the offset to zero. Version-2 player action progress migrates to simulation-clock deadlines for both the live state and checkpoint state without changing its sampled normalized progress. This runtime migration does not change campaign IndexedDB saves.

Light 1–3, Heavy, Charged Heavy, Root Tether, Ferocity, Corruption, and Cleanse use the same target, reach, facing, and simulation-time rules. Cleanse is interruptible and resolves only when Aren stays within range through the active window. Encounter resolution emits stable, idempotency-keyed `WorldEvent`s; restoration is a separate semantic event, not a rendering side effect. Safe checkpoint restoration restores pre-reward state and uses the same deterministic event keys, so campaign processing remains idempotent.

Expand Down
Loading
Loading