Skip to content

T3 · TracePlayer + sealed LumosSource — replay traces behind the existing render path #73

@wow-miley

Description

@wow-miley

Suggested model tier: Sonnet 4.6 medium effort (small API surface, but the seam placement matters)

Context

The playback half. The architectural seam already exists: consumers render from a Flow<VoxelFrame> (or equivalent — confirm in recon) that feeds ComposeLattice → LumosCanvas. TracePlayer produces that same stream from a VoxelTrace instead of a live simulation. Everything downstream — projection, rotation, glyph overlay, theming — stays live and unchanged, which is exactly why Option A was chosen: event-driven glyphs still fire over baked traces, because glyph overlay happens after the recording point.

LumosSource is the consumer-facing seal: callers declare where frames come from and never touch player internals.

Objective

A consumer can swap a live simulation for a trace with a one-line source change and see pixel-identical output for the same seed.

Expected outcomes

  • TracePlayer(trace: VoxelTrace) in :phosphor-trace: frames(timeSource: TimeSource = Monotonic): Flow<VoxelFrame> — maps elapsed wall time → recorded frame index (hold-last-frame semantics between recorded frames; display refresh runs faster than the recorded fps and that's fine), honors loop on the active segment, supports play/pause/seek(frameIndex)/setSegment(name).
  • Sealed LumosSource { data class Live(...existing runtime inputs...); data class Trace(trace: VoxelTrace, initialSegment: String) } with a single factory producing the frame stream either way — recon determines whether this lives in :phosphor-trace or :phosphor-lumos to keep dependency direction clean.
  • Reconstruction: dynamic channels + static lattice → full VoxelFrame instances, allocation-conscious (reuse buffers; no 1,500-object churn per frame).

Technical constraints

commonMain; kotlinx-coroutines Flow allowed here; zero changes to ComposeLattice/LumosCanvas signatures; Result-typed trace-load errors.

Tasks

A — Recon (comment before code): Identify the exact live frame-stream type consumers collect today (#54 shows lumosFrames.collectAsState() — find its producer and type). Confirm where LumosSource should live. STOP for approval.

B — TracePlayer + LumosSource: Implement per expected outcomes.

C — Golden equivalence test: Capture a trace with seed S (T2), run the live sim with seed S at the same fixed dt, assert frame-by-frame VoxelFrame equality. This test is the epic's keystone — it proves replay ≡ live.

D — Playback semantics tests: loop wrap has no index gap; seek lands exactly; pause holds frame; time-mapping holds correct frame between recorded timestamps.

Validation / DoD

Golden equivalence green on JVM; playback semantics tests green on JVM + iOS sim; demo app runs a trace through the existing LumosCanvas with glyph overlay visibly functioning on top.

Out of scope

State-transition logic between segments (T5 — this ticket only plays one segment at a time); crossfading; Socket integration; any change to projection or canvas code.

Human review gate

Miley reviews the recon seam decision before B; visually confirms the demo (trace + live glyph) before close.

Agent handoff prompt

You are implementing PHO-37 in socket-link/phosphor. Depends on PHO-36 (merged).
1. RECON FIRST: find the live frame-stream producer consumers collect
   (the thing behind lumosFrames in PHO-27). Comment its type, location, and
   your proposed LumosSource placement. STOP for approval.
2. Implement TracePlayer (time→frame mapping, hold-last semantics, loop,
   play/pause/seek/setSegment) and sealed LumosSource(Live | Trace).
3. Zero signature changes to ComposeLattice / LumosCanvas. Reuse frame buffers —
   no per-frame 1,500-object allocation.
4. Tests: golden equivalence (trace seed S == live seed S, frame by frame);
   loop/seek/pause/time-mapping semantics.
5. Wire the demo to play a .vxt through LumosCanvas with a glyph firing on top.
6. DoD: all green JVM + iosSimulatorArm64; demo screenshot in PR.
If recon contradicts any assumption in this ticket, STOP and comment.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions