diff --git a/README.md b/README.md index f2fee0f..c9af120 100644 --- a/README.md +++ b/README.md @@ -131,23 +131,30 @@ need to find it. ## Current Phase -Stage 9 — Better Numerics -- not yet started: Stage 8 (Recording & -Playback, inserted ahead of this stage on 2026-09-07 -- `docs/planning/ -roadmap.md`'s own "Fourth divergence" entry) closed the same day it -opened, all three of its own tasks (TASK-045/046/047 -- headless -recording, deterministic windowed replay, interactive playback) landing -together. Its live status, generated from the roadmap rather than -restated here: -[Stage 9 in the status report](docs/planning/status.md#stage-9----better-numerics). +Stage 8 — Recording & Playback -- **reopened** 2026-09-09: an audit, +prompted by the maintainer's own suspicion that this stage "never +actually went through a design/planning session," found the suspicion +correct. Its original five completion criteria (TASK-045/046/047, +2026-09-07) are still met; four more were added the same day they were +found missing -- the Goal's own "scrubbed to any point" had shipped +with no seek mechanism at all, and two of the stage's own stated +deferrals (declared-field playback, partial-overlap cache reuse) plus +one gap nobody had named (checkpoint retention) were pulled forward +rather than left indefinitely deferred. TASK-048/049/050/051 will close +the other four, one branch each. Its live status, generated from the +roadmap rather than restated here: +[Stage 8 in the status report](docs/planning/status.md#stage-8----recording--playback). +Stage 9 (Better Numerics) still follows, once Stage 8 closes again. **Stage 8's own record, for anyone tracking how reliably this section -stays current**: opened and closed in one day, so the multi-day -staleness windows the two paragraphs below describe for Stages 7 and 8's -own *earlier* drafts of this section never had a chance to recur here -- -not because the failure mode was fixed, but because there was no gap -of real time for it to go unnoticed in. Don't read this as the pattern -solved; read Stage 9's own eventual entry here as the next real test of -it. +stays current**: opened and closed in one day (2026-09-07), then +reopened two days later by an audit this same session's own change is +keeping in sync -- the multi-day staleness windows the two paragraphs +below describe for Stages 7 and 8's own *earlier* drafts of this +section didn't recur here, because this edit landed in the same change +that reopened the stage rather than after. Don't read this as the +pattern solved; read whichever of Stage 8's closure or Stage 9's own +eventual entry comes next as the next real test of it. **This sentence said "not yet started" for Stage 8 itself, twice, while that stage was still open** -- once for the same reason a fourth time as @@ -179,8 +186,10 @@ golden demo renders a *solved* velocity field live. **Stage 6 is the proof that the engine underneath it is field-centric**: four named physical fields, added by configuration. -Stages 0 through 8 are complete, each closed against its own written -completion criteria (`docs/planning/roadmap.md`): +Stages 0 through 7 are complete, and Stage 8 is reopened (five of nine +criteria met, see below) -- each closed, or in Stage 8's case pending +re-closure, against its own written completion criteria +(`docs/planning/roadmap.md`): - Stage 0 — planning system, capability map, repository structure, development tooling, CI. Deliberately no CFD functionality. @@ -243,16 +252,21 @@ completion criteria (`docs/planning/roadmap.md`): added 93 step definitions, 28% of the repository's whole step vocabulary, which is evidence against its own claim rather than for it. -**Stage 8 (Recording & Playback) is complete.** `pyflow record`/ -`pyflow resume`/`pyflow play` (TASK-045/046/047, all 2026-09-07): record -a run headlessly, resume it from any checkpoint, or watch it back in a -real window with live pause and speed control -- no rendering window -ever needed for the first two, and no simulation code re-run for the -third. Its own Golden Demo is Lid-Driven Cavity (moved there from an -earlier Heat Diffusion choice once playback -- which renders a solved -velocity field -- turned out incompatible with a demo that has none; see -`docs/planning/roadmap.md`'s own Stage 8 Status section for the full -account). Try the whole pipeline: +**Stage 8 (Recording & Playback) is reopened -- five of nine criteria +met.** `pyflow record`/`pyflow resume`/`pyflow play` (TASK-045/046/047, +all 2026-09-07): record a run headlessly, resume it from any +checkpoint, or watch it back in a real window with live pause and speed +control -- no rendering window ever needed for the first two, and no +simulation code re-run for the third. Its own Golden Demo is Lid-Driven +Cavity (moved there from an earlier Heat Diffusion choice once playback +-- which renders a solved velocity field -- turned out incompatible +with a demo that has none; see `docs/planning/roadmap.md`'s own Stage 8 +Status section for the full account). **Reopened 2026-09-09** for four +more criteria an audit found the Goal itself already promised: live +scrub (keyboard and a mouse-draggable bar), combined solved-velocity + +declared-field playback (grounded in Smoke Transport), opt-in checkpoint +retention, and partial-overlap cache reuse -- TASK-048/049/050/051, +not yet built. Try the whole pipeline as it stands today: ```bash uv run python -m pyflow record --config examples/golden-demos/lid_driven_cavity.yaml --max-frames 500 --checkpoint-interval 100 diff --git a/docs/planning/backlog.md b/docs/planning/backlog.md index bdd5eeb..29d4505 100644 --- a/docs/planning/backlog.md +++ b/docs/planning/backlog.md @@ -2411,6 +2411,22 @@ here.): here; open a new backlog item if a demo needs it before Stage 9 does anything else that would motivate it. + **Reopened 2026-09-09, not via a new backlog item.** An audit of + this stage, prompted by the maintainer's own suspicion that it + "never actually went through a design/planning session," found + the suspicion correct and one further gap beyond the two named + above: the Goal's own "scrubbed to any point" shipped with no + operationalised criterion and no seek mechanism at all, alongside + checkpoint retention (no gap anyone had named as a gap before this + audit). Rather than a fifth backlog item duplicating what + `docs/planning/roadmap.md`'s own Stage 8 now tracks directly (four + new Completion Criteria 6-9, TASK-048/049/050/051), this item's own + two named deferrals above are folded into that reopening instead. + Read Stage 8's own **Status as of 2026-09-09** section for the + current state; this item stays marked done for the halves it + actually built (recording, replay, playback's own first cut) and + does not track the reopening's own progress. + --- # Part III — Audit history diff --git a/docs/planning/roadmap.md b/docs/planning/roadmap.md index af582ff..3f0314c 100644 --- a/docs/planning/roadmap.md +++ b/docs/planning/roadmap.md @@ -306,7 +306,13 @@ This paragraph previously said `make install` and `make test` were still expected to fail, pending `uv.lock` and a test suite (B2/C1) -- stale since 2026-08-16 and corrected 2026-08-19. Both now succeed: `uv.lock` is committed (B2) and `make test` runs the suite with coverage -(C1a/C1b): **1171 tests as of 2026-09-08**, up from 1163 the same day (8 +(C1a/C1b): **1172 tests as of 2026-09-09**, up from 1171 the day before +(the Stage 8 reopening audit's own regression test, +`test_the_real_roadmap_reports_stage_0_as_complete` in +`tests/unit/test_check_stages.py` -- proving Stage 0's eleven tasks, +newly given the per-task `**Status: Done**` marker every later stage's +tasks already carry, are now read as `complete` rather than `opened`), +1171 itself up from 1163 the same day (8 new tests from the failure-mode audit: `test_hook_does_not_strip_an_ import_with_no_usage_yet`, two `check_manifest.py` tests for the new `claude-md-count-matches-live` rule, and five for the new @@ -1056,6 +1062,11 @@ isn't. ## TASK-000 — Create Engine Skeleton +**Status: Done, 2026-08-15** (date from this stage's own summary table +above; the inline marker itself added 2026-09-09, when a `check_stages.py` +audit found Stage 0's eleven tasks had never carried one -- see that +table's own row for what was actually verified). + ### Purpose Create the initial package structure and architectural skeleton for the PyFlow engine. @@ -1104,6 +1115,9 @@ No implementation beyond package initialisation is required. ## TASK-001 — Development Environment +**Status: Done, 2026-08-15** (see TASK-000's own note, above, for why +this marker was only added 2026-09-09). + ### Purpose Create a fully reproducible development environment. @@ -1158,6 +1172,8 @@ without manual configuration. ## TASK-002 — Build System +**Status: Done, 2026-08-15** (see TASK-000's own note, above). + ### Purpose Provide a consistent interface for common engineering tasks. @@ -1191,6 +1207,8 @@ Every documented command executes successfully. ## TASK-003 — Automated Testing +**Status: Done, 2026-08-16** (see TASK-000's own note, above). + ### Purpose Establish regression testing from the beginning of the project. @@ -1221,6 +1239,8 @@ Tests execute locally and produce coverage reports. ## TASK-004 — Continuous Integration +**Status: Done, 2026-08-19** (see TASK-000's own note, above). + ### Purpose Automatically validate every commit. @@ -1251,6 +1271,8 @@ Every pull request executes the validation pipeline automatically. ## TASK-005 — Configuration Framework +**Status: Done, 2026-08-16** (see TASK-000's own note, above). + ### Purpose Separate engine construction from engine execution. @@ -1283,6 +1305,8 @@ The application can be started entirely from configuration. ## TASK-006 — Logging Framework +**Status: Done, 2026-08-16** (see TASK-000's own note, above). + ### Purpose Provide consistent diagnostic output throughout the engine. @@ -1312,6 +1336,8 @@ Every subsystem logs through the common logging framework. ## TASK-007 — Rendering Framework +**Status: Done, 2026-08-16** (see TASK-000's own note, above). + ### Purpose Establish the rendering subsystem that will support all future visualisation. @@ -1348,6 +1374,12 @@ A rendering window opens, updates and closes cleanly. ## TASK-008 — Repository Documentation +**Status: Done** (see TASK-000's own note, above, for why this marker +was only added 2026-09-09; no completion date is recorded for this task +specifically -- its own summary-table row above states only that it was +*corrected* to say Done on 2026-08-19, not when the underlying work +actually finished, and this entry does not guess one). + ### Purpose Establish the repository as the authoritative source of project knowledge. @@ -1384,6 +1416,8 @@ Every core document exists and provides sufficient information for future develo ## TASK-009 — CLAUDE.md Hierarchy +**Status: Done, 2026-08-19** (see TASK-000's own note, above). + ### Purpose Provide concise contextual guidance to coding agents throughout the repository. @@ -1419,6 +1453,8 @@ Each file provides sufficient local context while remaining compact enough to mi ## TASK-010 — Engine Bootstraps +**Status: Done, 2026-08-16** (see TASK-000's own note, above). + ### Purpose Validate that the engineering infrastructure functions as a coherent system. @@ -11241,6 +11277,84 @@ established this project follows. and a check that the rendered pixels actually stop changing once paused, not only that `PlaybackState.paused` flips in isolation. +**Criteria 6-9 added 2026-09-09, when this stage was reopened -- see the +Status section below for why.** Drafted the same way 1-5 were meant to +be and, per that section's own honest accounting, mostly weren't: from +this stage's own Goal, independent of the four tasks that will discharge +them, which had not been drafted yet when these were written. + +6. **Playback can seek to any frame inside the materialized window, + live, not only play forward at a fixed speed from where it was + launched.** The Goal's own "scrubbed to any point" -- the one clause + of this stage's Goal that TASK-047 shipped without, and the clause no + criterion above ever named, which is exactly why it went unnoticed + while this stage was marked complete. + - **Both a keyboard and a mouse reach every frame in `[from_frame, + to_frame]`.** Left/Right step one frame; Home/End jump to the + window's own start/end; a draggable scrub bar reaches any frame in + between directly, not only by repeated stepping. + - **Checked against real rendered pixels, the same way Space's own + pause already is** -- not only against `PlaybackState` in + isolation. A keyboard seek and a mouse drag each change + `window.renderer.snapshot()`'s content, confirmed with a real + injected event against a genuinely running window. + - **Scoped to the window already requested at launch, not the whole + recording** -- a real, stated exclusion decided before code, not + discovered after: seeking past `from_frame`/`to_frame` still needs + a different `pyflow play` invocation, exactly as today. Extending + scrub to seek beyond the loaded window is real, deferred future + work, not built here. +7. **A config combining a solved velocity field with one or more + declared fields plays back correctly, not only a solved-velocity-only + config.** TASK-047's own stated scope boundary + (`UnsupportedPlaybackConfigError` for any config with declared + `fields`) closes here, against a real demo rather than a synthetic + fixture. + - **Grounded in Smoke Transport** + (`examples/golden-demos/smoke_transport.yaml`) -- solved velocity + plus a declared `smoke` field with two configured display panels, + run record-then-play through the real CLI end to end. + - **Both the arrows and every configured panel render from the same + materialized frame** -- checked by rebuilding a panel from a + `MaterializedWindow` frame and confirming it matches what a live + run's own `_add_declared_field_transport` would have coloured at + the same simulation state, not only that something is drawn. + - **No duplicate panel-rendering implementation** -- the + colour-mapping/legend logic this reuses is the same one + `bootstrap.py`'s live path calls, extracted into + `rendering/field_visualization.py` rather than copied, per this + project's own P-011 (single authoritative source). +8. **A recording's own checkpoint count can be bounded, opt-in, without + changing any existing config's behaviour.** Criterion 2's own "never + one file per frame" bounds the interval between checkpoints, not the + total count over a long run -- a real gap in what "bounded footprint" + means that this closes. + - **`RecordingConfig.max_checkpoints_retained`, unset by default** -- + every existing config and golden demo writes exactly the + checkpoints it always did; the cap only changes behaviour for a + config that sets it. + - **Frame 0 is never pruned**, whatever the cap -- a capped recording + still has a starting point to restart from. + - **Checked directly against a real multi-hundred-frame `pyflow + record` run**, not only against the pruning function in isolation: + the files actually on disk after the run match what the policy + predicts, not merely what a unit test of the deletion logic + asserts. +9. **Watching a sub-range of an already-cached wider window costs no + re-simulation.** TASK-046's own stated scope boundary + (`materialize_or_load_window`'s cache serving exact-range matches + only) narrows here for the one case with a concrete, avoidable cost: a + narrower request the cache could already answer. + - **Scoped to a requested range that is a full subset of an existing + cached range** -- checked by deleting every checkpoint before the + second call and confirming the narrower request still succeeds, the + same technique TASK-046's own exact-match test already established. + - **A request that only partially overlaps a cached range, or + extends past its edge, still falls back to full materialization** -- + a real, stated exclusion, not silently handled either way; full + stitching across a cached window's own edge is deferred future + work. + ### Discharge map | Criterion | Discharged by | @@ -11251,18 +11365,41 @@ established this project follows. | 4. A checkpoint file is self-contained | TASK-045 | | 5. Golden Demo runs end to end (record half) | TASK-045 | | 5. Golden Demo runs end to end (playback half) | TASK-046/047 | - -### Status as of 2026-09-07: Stage 8 complete, five of five criteria met - -**"Complete" here means both things at once, for the first time in this -stage's own history**: every `## TASK-NNN` entry under this heading is -Done (`stage-shape.yaml`'s own mechanical lifecycle meaning), *and* this -stage's own Goal ("recorded... and played back afterward") is actually -built, not only partially. The status line below was deliberately "in -progress" while only TASK-045 existed, precisely so `README.md`'s own -"Current Phase" cross-check would not advance past real, undrafted work --- see that entry's own note for the mechanism and the template this -line has to match exactly (`generate_status_report.py`'s `STATUS_LINE`). +| 6. Live scrub, keyboard and mouse, checked against rendered pixels | TASK-048 | +| 7. Combined solved-velocity + declared-field playback | TASK-051 | +| 8. Checkpoint retention, opt-in, frame 0 never pruned | TASK-049 | +| 9. Partial-overlap (subset) cache reuse | TASK-050 | + +### Status as of 2026-09-09: Stage 8 reopened, five of nine criteria met + +**This stage was audited 2026-09-09, at the maintainer's own request, +against the suspicion that it "never actually went through a +design/planning session" -- and the audit confirmed it.** Raised +2026-09-04 as a side comment while scoping unrelated work, opened and +fully built in a single day (2026-09-07), with Completion Criteria 1-5 +written the same day as TASK-045, its own first task, rather than +independently derived from the Goal beforehand the way this section's +own 2026-09-07 text originally claimed. That produced a real, +previously unrecorded gap: the Goal's own "paused, **scrubbed to any +point**, and watched at a different speed" was never operationalised by +any of the five criteria that shipped, so nothing caught `PlaybackState` +landing with pause and speed but no seek. Three further gaps -- named by +TASK-046/047 themselves as deliberate, stated deferrals, not +oversights -- were pulled forward into this stage rather than left +recorded-but-deferred indefinitely: declared-field/scalar-colormap +playback, partial-overlap cache reuse, and checkpoint retention (a gap +in what "bounded footprint" means that nobody had named as a gap at +all, until this audit). Criteria 6-9, above, are the four; TASK-048-051 +will discharge them, one branch each, in that numeric order except +049/050 (the two library-only changes) landing before 048/051 (the two +that touch rendering). + +**"Complete" reopened, not "complete" corrected** -- 2026-09-07's own +five criteria were genuinely met by what TASK-045/046/047 built; nothing +about the record above this line is being retracted. What changed is +that this stage's own Goal turned out to need four more checkable claims +than its first pass wrote down, found by re-reading the Goal against +what shipped rather than against the criteria that were meant to operationalise it. | Criterion | Verdict | |-----------|---------| @@ -11271,15 +11408,21 @@ line has to match exactly (`generate_status_report.py`'s `STATUS_LINE`). | 3. Resuming reproduces the same trajectory, bit-identically | **Met** -- TASK-045, mutation-tested | | 4. A checkpoint file is self-contained | **Met** -- TASK-045 | | 5. Golden Demo runs end to end, both halves | **Met** -- TASK-045 (record), TASK-046/047 (playback), against Lid-Driven Cavity | - -All five criteria are met. **One real course-correction happened along -the way, recorded rather than smoothed over**: TASK-045's own original -Golden Demo choice (Heat Diffusion) turned out incompatible with -TASK-047's own scope decision (playback renders a solved velocity field; -Heat Diffusion has none) -- found when TASK-047 was actually scoped, not -anticipated in advance, and resolved by reconciling the whole stage onto -one demo (Lid-Driven Cavity) rather than carrying two. See the stage's -own **Golden Demo** entry above for the full account. +| 6. Live scrub, keyboard and mouse | **Open** -- TASK-048, drafted, not started | +| 7. Combined solved-velocity + declared-field playback | **Open** -- TASK-051, drafted, not started | +| 8. Checkpoint retention, opt-in | **Open** -- TASK-049, drafted, not started | +| 9. Partial-overlap (subset) cache reuse | **Open** -- TASK-050, drafted, not started | + +Five of nine criteria are met; the stage is **in progress**, not +complete, until TASK-048/049/050/051 close the other four. **One real +course-correction happened during the original build, recorded rather +than smoothed over**: TASK-045's own original Golden Demo choice (Heat +Diffusion) turned out incompatible with TASK-047's own scope decision +(playback renders a solved velocity field; Heat Diffusion has none) -- +found when TASK-047 was actually scoped, not anticipated in advance, and +resolved by reconciling the whole stage onto one demo (Lid-Driven +Cavity) rather than carrying two. See the stage's own **Golden Demo** +entry above for the full account. --- @@ -11877,6 +12020,174 @@ updated in this same change. --- +## TASK-049 — Checkpoint Retention Policy + +**Status: Not started, drafted 2026-09-09**, the day this stage was +reopened -- see the Status section above for why, and +`docs/planning/stage-specification.md`'s "What a task entry is called" +section for what a `Not started` entry means and why `make check-stages` +already handles it correctly. Discharges Completion Criterion 8. + +### Purpose + +Bound a recording's total on-disk checkpoint count, opt-in -- the gap +between Criterion 2's own "never one file per frame" (bounds the +*interval* between checkpoints) and what "bounded footprint" actually +needs on a very long run (bounds the *total*), found by the audit that +reopened this stage. + +### Dependencies + +`checkpoint.py`/`recording.py` (TASK-045) only. + +### Design decisions, recorded here + +Settled directly with the maintainer when this stage was reopened, +before implementation, the same "ask directly, with a recommendation +and the trade-off named" discipline TASK-046/047 already used: + +1. **Opt-in, unbounded by default.** `RecordingConfig. + max_checkpoints_retained: int | None = None` -- every existing config + and golden demo keeps writing exactly the checkpoints it always did; + the cap only changes behaviour for a config that sets it. +2. **Frame 0 is never pruned**, whatever the cap -- a capped recording + still has a starting point to resume from. +3. **The frame-number-from-filename parsing `replay.py`'s own private + `_CHECKPOINT_FILENAME` regex already does is factored into a shared + `checkpoint.py` helper**, used by both the new pruning logic and + `replay.find_checkpoint_at_or_before`, rather than duplicated a + second time -- this project's own P-011 (single authoritative + source), the same reasoning that produced `checkpoint.field_tensors`. + +Artifacts, Acceptance Criteria and Discharges are written when this task +is actually built, the same as every other entry in this file. + +--- + +## TASK-050 — Partial-Overlap Cache Reuse, Subset Only + +**Status: Not started, drafted 2026-09-09.** Discharges Completion +Criterion 9. + +### Purpose + +Let a `pyflow play --cache DIR` request that falls fully inside an +already-cached wider window reuse it directly, at zero re-simulation +cost -- narrowing TASK-046's own stated scope boundary (exact-range +cache matches only) for the one case with a concrete, avoidable cost: a +narrower request the cache could already answer. + +### Dependencies + +`replay.py` (TASK-046) only. + +### Design decisions, recorded here + +1. **Scoped to a requested range that is a full subset of an existing + cached range.** `materialize_or_load_window` also globs `cache_dir` + for any `window_{from:08d}_{to:08d}.pt` whose own range is a superset + of the request, and slices `frames[requested_from - cached_from : + requested_to - cached_from + 1]` -- no re-simulation, no new file + written for the sliced sub-range. +2. **A request that only partially overlaps a cached range, or extends + past its edge, still falls back to full `materialize_window`** -- a + real, stated exclusion, not silently handled either way, the + maintainer's own bounding of this task when the stage reopened. Full + stitching across a cached window's own edge is real, deferred future + work. + +Artifacts, Acceptance Criteria and Discharges are written when this task +is actually built, the same as every other entry in this file. + +--- + +## TASK-048 — Live Scrub + +**Status: Not started, drafted 2026-09-09.** Discharges Completion +Criterion 6. + +### Purpose + +Close the one clause of this stage's own Goal that TASK-047 shipped +without: seeking to any frame in the materialized window, live, by +keyboard and by mouse -- not only playing forward at a fixed speed from +wherever `pyflow play` was launched. + +### Dependencies + +`playback.py` (TASK-047), for `PlaybackState`/`play()`. `window.py`'s +existing pointer-drag pan machinery (`_begin_pan`/`_update_pan`/ +`_end_pan`) is the one real open risk this task starts from -- see +Design decisions below. + +### Design decisions, recorded here + +Settled directly with the maintainer when this stage was reopened: + +1. **Scoped to the window already requested at launch** + (`[from_frame, to_frame]`) -- seeking past either edge still needs a + different `pyflow play` invocation, exactly as today. Seeking beyond + the loaded window is real, deferred future work, not built here. +2. **Keyboard: Left/Right step one frame; Home/End jump to the window's + own start/end.** +3. **Mouse: a draggable scrub bar**, reaching any frame in the window + directly rather than only by repeated stepping. +4. **Open technical risk, to resolve empirically before building the + widget, not by assumption:** `RenderWindow.run()` already wires + generic camera-pan pointer handlers unconditionally on every + interactive run (`window.py`). A scrub-bar drag must not also pan the + camera underneath it. First step of this task: verify whether pygfx/ + rendercanvas's event dict supports stopping propagation to a + same-canvas handler registered afterward -- the same "verify sign + conventions and event behaviour before relying on them" discipline + `rendering/CLAUDE.md`'s own pan/zoom entries already establish. If it + does not, the fallback is disabling `RenderWindow`'s built-in pan for + playback windows specifically, the same shape `close_keys=None` + already gives a caller for the close-key default. + +Artifacts, Acceptance Criteria and Discharges are written when this task +is actually built, the same as every other entry in this file. + +--- + +## TASK-051 — Combined Solved-Velocity + Declared-Field Playback + +**Status: Not started, drafted 2026-09-09.** Discharges Completion +Criterion 7. + +### Purpose + +Let `pyflow play` render a config that combines a solved velocity field +with one or more declared fields -- Smoke Transport's own shape -- +rather than rejecting it outright with `UnsupportedPlaybackConfigError`. + +### Dependencies + +`playback.py` (TASK-047). `bootstrap.py`'s `_add_declared_field_transport` +(TASK-030/042), whose panel-rendering half (`_PanelRenderState`/ +`_panel_colors`/`_add_panel_legend`/`_panel_caption`) needs extracting +into `rendering/field_visualization.py` as public functions before +`playback.py` can reuse it, rather than reaching into another module's +private helpers -- the same "extract before reusing" precedent TASK-045 +already set for `simulation_run.py`. + +### Design decisions, recorded here + +1. **Grounded in Smoke Transport** + (`examples/golden-demos/smoke_transport.yaml`) -- solved velocity + plus a declared `smoke` field with two configured display panels -- + rather than a synthetic fixture, per the maintainer's own choice when + this stage reopened. +2. **The extraction is verified behaviour-preserving by the full + existing test suite passing unmodified**, before anything new is + added, the same way TASK-045's `simulation_run.py` extraction was + verified. + +Artifacts, Acceptance Criteria and Discharges are written when this task +is actually built, the same as every other entry in this file. + +--- + # Stage 9 — Better Numerics Goal diff --git a/docs/planning/stage-specification.md b/docs/planning/stage-specification.md index de4e16a..9e1dc0f 100644 --- a/docs/planning/stage-specification.md +++ b/docs/planning/stage-specification.md @@ -254,6 +254,23 @@ rendering stage done" was written about pixels; the same sentence holds for a document, and this is what it turned up the first time it was applied to one. +**A task entry's own `**Status:**` line has, until now, always read +`Done, ` -- every entry in this file's history was written the +moment it was built. `Not started, drafted ` is an equally valid +value, added 2026-09-09 when Stage 8's reopening needed to record four +tasks' Purpose, Dependencies and settled Design decisions ahead of +building any of them**, the same reason `docs/planning/stage-shape.yaml` +lets a stage stay `sketched` before its first task exists, applied one +level down to a single task inside a stage that is otherwise built. +`check_stages.py`'s own `TASK_DONE` pattern already matches only the +literal string `Done` -- it was never a closed enum, so a `Not started` +entry needs no change to the checker: the stage's lifecycle correctly +stays `opened` rather than advancing to `complete` while any task reads +anything else. Draft an entry this way when its design decisions are +genuinely settled and worth recording durably before implementation +starts, not as a place to park an idea that is still being shaped -- +that is what `intended-work`'s own bullet list is for, one section up. + --- ## What is deliberately not required diff --git a/docs/planning/status.md b/docs/planning/status.md index fc068b1..54017c3 100644 --- a/docs/planning/status.md +++ b/docs/planning/status.md @@ -17,14 +17,14 @@ demand, not part of this file. ## Progress -**48/48 tasks complete (100%)** across 16 planned stages. For the full plan, including +**48/52 tasks complete (92%)** across 16 planned stages. For the full plan, including stages below not yet broken into tasks: [roadmap.md](roadmap.md). ```mermaid pie showData title "Tasks across the roadmap" "Done" : 48 - "Not started" : 0 + "Not started" : 4 ``` ### Milestones @@ -37,16 +37,15 @@ pie showData - **Stage 5 -- First Fluid Solver** complete (2026-08-29) - **Stage 6 -- Additional Physical Fields** complete (2026-08-31) - **Stage 7 -- Rendering Annotations** complete (2026-09-03) -- **Stage 8 -- Recording & Playback** complete (2026-09-07) ### Up next -**Stage 9 -- Better Numerics** is next, and has not been broken into tasks yet. +**Stage 8 -- Recording & Playback** is next, starting with TASK-049 (Checkpoint Retention Policy), 3 more not yet started in this stage. ## Live repository facts - **49** `CLAUDE.md` files -- **1171** tests collected +- **1172** tests collected - **144** Gherkin scenarios (`tests/features/*.feature`) ## Stages @@ -57,17 +56,17 @@ pie showData | Task | Status | Date | Artifact | |------|--------|------|----------| -| TASK-000 -- Create Engine Skeleton | Done | 2026-08-15 | | -| TASK-001 -- Development Environment | Done | 2026-08-15 | | +| TASK-000 -- Create Engine Skeleton | Done | 2026-08-15 | `check_stages.py` | +| TASK-001 -- Development Environment | Done | 2026-08-15 | `docs/practices.md` | | TASK-002 -- Build System | Done | 2026-08-15 | | | TASK-003 -- Automated Testing | Done | 2026-08-16 | | | TASK-004 -- Continuous Integration | Done | 2026-08-19 | | | TASK-005 -- Configuration Framework | Done | 2026-08-16 | | | TASK-006 -- Logging Framework | Done | 2026-08-16 | | -| TASK-007 -- Rendering Framework | Done | 2026-08-16 | | +| TASK-007 -- Rendering Framework | Done | 2026-08-16 | `adr/ADR-005-compute-rendering-instances.md` | | TASK-008 -- Repository Documentation | Done | | | | TASK-009 -- CLAUDE.md Hierarchy | Done | 2026-08-19 | | -| TASK-010 -- Engine Bootstraps | Done | 2026-08-16 | | +| TASK-010 -- Engine Bootstraps | Done | 2026-08-16 | `knowledge-architecture.md` | ### Stage 1 -- Representing Space @@ -154,13 +153,17 @@ pie showData ### Stage 8 -- Recording & Playback -**complete, as of 2026-09-07** -- `██████████` 3/3 tasks; 5/5 criteria met +**no status recorded** -- `████░░░░░░` 3/7 tasks; 9 criteria defined, no status line yet | Task | Status | Date | Artifact | |------|--------|------|----------| | TASK-045 -- Periodic Checkpointing (Headless Recording) | Done | 2026-09-07 | `docs/planning/backlog.md` | | TASK-046 -- Deterministic Windowed Replay | Done | 2026-09-07 | `docs/planning/roadmap.md` | | TASK-047 -- Interactive Playback (`pyflow play`) | Done | 2026-09-07 | `replay.py` | +| TASK-049 -- Checkpoint Retention Policy | Not started | | | +| TASK-050 -- Partial-Overlap Cache Reuse, Subset Only | Not started | | | +| TASK-048 -- Live Scrub | Not started | | | +| TASK-051 -- Combined Solved-Velocity + Declared-Field Playback | Not started | | | ### Stage 9 -- Better Numerics diff --git a/planning/data/features.yaml b/planning/data/features.yaml index 2730757..f87e333 100644 --- a/planning/data/features.yaml +++ b/planning/data/features.yaml @@ -643,3 +643,43 @@ entities: to: stage-8 - type: depends_on to: task-046 + + - id: task-049 + name: "TASK-049 — Checkpoint Retention Policy" + documented_in: docs/planning/roadmap.md + must_appear_in: docs/planning/roadmap.md + edges: + - type: belongs_to + to: stage-8 + - type: depends_on + to: task-045 + + - id: task-050 + name: "TASK-050 — Partial-Overlap Cache Reuse, Subset Only" + documented_in: docs/planning/roadmap.md + must_appear_in: docs/planning/roadmap.md + edges: + - type: belongs_to + to: stage-8 + - type: depends_on + to: task-046 + + - id: task-048 + name: "TASK-048 — Live Scrub" + documented_in: docs/planning/roadmap.md + must_appear_in: docs/planning/roadmap.md + edges: + - type: belongs_to + to: stage-8 + - type: depends_on + to: task-047 + + - id: task-051 + name: "TASK-051 — Combined Solved-Velocity + Declared-Field Playback" + documented_in: docs/planning/roadmap.md + must_appear_in: docs/planning/roadmap.md + edges: + - type: belongs_to + to: stage-8 + - type: depends_on + to: task-047 diff --git a/tests/unit/test_check_stages.py b/tests/unit/test_check_stages.py index 9c940e6..6a75ed7 100644 --- a/tests/unit/test_check_stages.py +++ b/tests/unit/test_check_stages.py @@ -337,6 +337,27 @@ def test_the_real_roadmap_has_the_shape_it_declares() -> None: assert problems == [] +def test_the_real_roadmap_reports_stage_0_as_complete() -> None: + """Stage 0's own eleven tasks predate the per-task inline + `**Status: Done**` marker every later stage's task entries carry -- + they used to record completion only in the stage's own summary + table, never in a form `TASK_DONE` matches, so `Stage.lifecycle` + read `opened` with `done_tasks=0` despite every task having long + been finished. Fixed by adding the marker to each of the eleven + task entries (2026-09-09), using the date already recorded in that + same summary table -- not by a stage-specific exemption in this + checker, so the general mechanism stays the only mechanism. Reads + the committed roadmap, so a failure here means the roadmap + regressed, not that this rule is broken. + """ + stages = parse_stages(ROADMAP_PATH.read_text(encoding="utf-8")) + stage_0 = next(s for s in stages if s.number == 0) + assert stage_0.lifecycle == "complete", ( + f"Stage 0: lifecycle is {stage_0.lifecycle!r} " + f"({stage_0.done_tasks}/{len(stage_0.task_lines)} tasks read as Done)" + ) + + def test_every_declared_rule_id_is_covered_by_a_test_in_this_module() -> None: """The guard against this module and the shape file drifting apart.