Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
8b0bf93
feat(check): bench a folder's load, first frame, allocations and frames
dXnSix Oct 27, 2026
050fa8f
perf(nsh): find where outlines switch commands without making the paths
dXnSix Oct 27, 2026
7744e03
fix(check): bench counts reallocations once
dXnSix Oct 27, 2026
d0d3f07
perf(nsh): encode the plan's images as fast as the moving rasters
dXnSix Oct 27, 2026
32fe684
perf(nsh): place a group's lengths from its chain's transforms, witho…
dXnSix Oct 27, 2026
cc74057
perf(nsh): bisect outline switches on the easing alone
dXnSix Oct 27, 2026
bfa8a50
perf(nsh): demultiply a PNG's pixels into a buffer of its size
dXnSix Oct 27, 2026
50f02d7
perf(render): box blur over pixel chunks
dXnSix Oct 27, 2026
e874f1e
perf(render): blur columns a whole row at a time
dXnSix Oct 27, 2026
e5a3555
perf(nsh): compile the plan once on load, for the view, and draw with it
dXnSix Oct 27, 2026
846c2ee
perf(nsh): share the loaded scene with what plays it
dXnSix Oct 27, 2026
5e739f5
perf(core): a frame's fill and stroke take the shape's path rather th…
dXnSix Oct 27, 2026
8b01342
perf(core): sample a shape of one untrimmed outline without merging i…
dXnSix Oct 27, 2026
2eed04c
perf(nsh): read a repeating track as one repetition modulo its period
dXnSix Oct 27, 2026
9eb5859
perf(render): the horizontal blur copies a row whole
dXnSix Oct 27, 2026
1821a74
perf(svg): read each keyframe value once
dXnSix Oct 27, 2026
d4d0389
perf(svg): unroll each CSS iteration's values with one copy
dXnSix Oct 27, 2026
9e333c7
perf(svg): match a selector's classes without collecting the element's
dXnSix Oct 27, 2026
e254a83
perf(svg): index a sheet's selectors by the id, class or tag they need
dXnSix Oct 27, 2026
066cc56
perf(lottie): baked outlines move into their keyframes
dXnSix Oct 27, 2026
66c6207
perf(nsh): keep only the samples a sampled value needs
dXnSix Oct 27, 2026
aa1593c
perf(nsh): a gradient's reach is sampled within a thousandth
dXnSix Oct 27, 2026
97511e7
perf(lottie): escape a JSON pointer's key only where it holds ~ or /
dXnSix Oct 27, 2026
d3355eb
perf(nsh): a track or lookup of one value is a constant
dXnSix Oct 27, 2026
1fe9327
perf(nsh): canvases drop the saves that keep nothing
dXnSix Oct 27, 2026
270d269
perf(nsh): still translations in a row are one
dXnSix Oct 27, 2026
05baeed
perf(core): build rectangles', ellipses' and stars' outlines at their…
dXnSix Oct 27, 2026
2b26404
perf(render): composite a layer only where it draws
dXnSix Oct 27, 2026
50e8c01
perf(render): composite normal layers directly, as the pipeline would
dXnSix Oct 27, 2026
d585323
perf(core): read a compiled scene in one pass
dXnSix Oct 27, 2026
5ffa2cb
feat(check): the bench reads JSON files as imported values too
dXnSix Oct 27, 2026
f0f22b4
perf: read an imported JSON value as it is, not as text again
dXnSix Oct 27, 2026
164b20d
perf(lottie): read a value without copying it unless expressions are …
dXnSix Oct 27, 2026
4e03983
perf(svg): share an element's cascaded declarations rather than copy …
dXnSix Oct 27, 2026
8b6f7a2
perf(svg): read CSS declarations without copies of their text
dXnSix Oct 27, 2026
100fa16
perf(nsh): tell graph nodes apart by a fast hash
dXnSix Oct 27, 2026
005e0f9
perf(nsh): know a still scene without sampling its frames
dXnSix Oct 27, 2026
5cf36e9
perf(render): write a blur back in a flat pass
dXnSix Oct 27, 2026
9e9e7ef
perf(lottie): size merge paths' polygon buffers ahead
dXnSix Oct 27, 2026
e08e517
perf(nsh): add to a sampled part without copying it
dXnSix Oct 27, 2026
b2faedf
perf(render): composite directly byte by byte, without branches
dXnSix Oct 27, 2026
c88941a
docs: what playing costs, after the performance improvement loop
dXnSix Oct 27, 2026
ed1c28b
build: take the NSH fork's animation graph fixes
dXnSix Oct 28, 2026
f4dbe17
perf(check): count what canvases redraw each frame
dXnSix Oct 28, 2026
a05e7e6
perf(nsh): move still groups carried by a moving one as layers
dXnSix Oct 28, 2026
dd2debe
perf(nsh): move groups inside still rectangle clips as clipped layers
dXnSix Oct 28, 2026
651935a
perf(nsh): size canvases that draw again each frame to where they draw
dXnSix Oct 28, 2026
1a6c19c
perf(nsh): size moving canvases within their clips, past what only ta…
dXnSix Oct 28, 2026
a752cae
perf(nsh): sample moving canvases' bounds and still transforms with l…
dXnSix Oct 28, 2026
ef26ef2
docs: what playing costs, with what canvases draw again each frame
dXnSix Oct 28, 2026
6f6e92e
fix(nsh): draw a rastered part's last frame when the playback ends
dXnSix Oct 28, 2026
9177698
fix(nsh): reach past the view as far as a cast shadow's blur
dXnSix Oct 28, 2026
ddb86f1
perf(nsh): sample moving layers and canvases over playbacks up to 20 s
dXnSix Oct 28, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
{
"schemaVersion": 1,
"id": "f0287285-7a36-4cb5-8eef-94cdd163e5a7",
"loopId": "9e7a8b6d-0104-4e72-a31e-3b168c0b3881",
"index": 1,
"type": "baseline",
"name": "Baseline discovery",
"slug": "baseline-discovery",
"path": "2026-10-27-01-baseline-discovery.json",
"createdAt": "2026-10-27T20:14:29Z",
"startedAt": "2026-10-27T20:14:29Z",
"finishedAt": "2026-10-27T20:16:00Z",
"state": "done",
"status": "success",
"checkpointCommit": "a9eba8e8fb24625468974d341186c970ecfdad3c",
"resultCommit": "a9eba8e8fb24625468974d341186c970ecfdad3c",
"rollbackCommand": "git -C \"/Users/dinko/work-private/projects/dXn/dXn.VectorAnimation\" reset --hard a9eba8e8fb24625468974d341186c970ecfdad3c",
"repositories": [
{
"name": "main",
"path": ".",
"role": "main",
"checkpointCommit": "a9eba8e8fb24625468974d341186c970ecfdad3c",
"resultCommit": "a9eba8e8fb24625468974d341186c970ecfdad3c",
"rollbackCommand": "git -C \"/Users/dinko/work-private/projects/dXn/dXn.VectorAnimation\" reset --hard a9eba8e8fb24625468974d341186c970ecfdad3c",
"status": "success"
}
],
"repositoryCommits": [
{
"name": "main",
"path": ".",
"role": "main",
"commit": "a9eba8e8fb24625468974d341186c970ecfdad3c"
}
],
"atomicDiscipline": {
"oneHypothesisOnly": true,
"singleImplementationAttempt": true,
"noUnplannedFixesInsideExperiment": true,
"failedAttemptRequiresImmediateRevert": true,
"successfulAttemptRequiresCommitBeforeNextExperiment": true,
"rule": "Run exactly the printed hypothesis. If the attempted change fails, degrades, or suggests a different fix, revert to the checkpoint and finish this experiment as failure. Start a new experiment for the next idea."
},
"primaryTarget": "native release build on macOS over the 53-file corpus (artifacts-temp/corpus)",
"hypothesisMarkdown": "## Baseline Discovery\n\nRead-only discovery of where playback time, allocations, memory and time to first frame go in the scene model (`Scene::frame_at`), the importers, the reference renderer and the NSH player (`source::read`, `plan::compile`, rasters, the component's load path). No source edits.",
"measurementPlanMarkdown": "### Measurement Plan\n\n- `cargo build --release -p dxn_vector_animation_check` then `vector-animation-check profile artifacts-temp/corpus` (53 corpus files, 200 points, 3 pixels a point): plan compile time, graph nodes and breakpoints, canvases, layers, ops a frame, engine-thread ms a frame.\n- Source reading of the hot paths.\n- Gap: no allocation counter, no import or time-to-first-frame timing, no `frame_at` timing exists in the repository: the next experiment adds a corpus bench (measurement enablement only).",
"expectedOutcomeMarkdown": "### Expected Outcome\n\nStarting numbers and a ranked backlog of single-mechanism candidates.",
"diagnostics": {
"playwright": [],
"rayx": [],
"counters": [],
"artifacts": []
},
"measurements": [
{
"metric": "plan compile, sum over 53 files",
"before": "~70 ms (max 21.7 ms repeater, 18.2 ms merge paths)",
"after": "",
"delta": "",
"source": "vector-animation-check profile artifacts-temp/corpus"
},
{
"metric": "graph nodes, sum",
"before": "~6,100",
"after": "",
"delta": "",
"source": "profile"
},
{
"metric": "graph breakpoints, sum",
"before": "~124,000 (thunder.svg 29,142; blend-28 22,483; repeater 17,107; merge paths 12,634)",
"after": "",
"delta": "",
"source": "profile"
},
{
"metric": "engine-thread work a frame",
"before": "0 except tristate 0.03 ms, onboarding 0.22 ms (moving blur rasters), merge paths 0.019 ms (sampled)",
"after": "",
"delta": "",
"source": "profile"
}
],
"reportMarkdown": "## Baseline Discovery Finished\n\n### Where the work is\n\n- **Time to first frame (player)**: `component.rs` reads the source off thread, then compiles the plan **twice** (`plan::compile` at identity only to learn native/mixed, then `plan::compile_for` at the fitted base), clones the whole scene for `played` (`Rc::new(scene.clone())`), and per raster unit runs `plan::in_view` (deep clone of the scene root), `plan::still` (17 `frame_at` calls compared) and the first `plan::raster` (render + PNG encode + base64 data URI).\n- **Playback (UI thread)**: NSH evaluates the graph (nodes, interpolation breakpoints) and replays canvases whose values move; fewer nodes, breakpoints and ops are less work each frame and less memory. Breakpoints dominate the graph's size (124k over the corpus).\n- **Playback (engine thread / reference)**: `Scene::frame_at` allocates per shape: a `Vec<Path>` of its paths, a merged `Path`, then clones that path for the fill and again for the stroke; `Animated::Static` paths are cloned on every sample; gradient stops are collected per sample. `from_frame` + painter run each frame for sampled parts. Moving rasters render, convert premultiplied pixels into a new Vec and PNG-encode each frame.\n- **Import**: SVG (roxmltree + CSS + SMIL) and Lottie (serde_json into a Value tree, then conversion) are not timed or allocation-counted anywhere.\n\n### Ranked candidate backlog\n\n1. Measurement: a corpus bench with an allocation counter (read, compile, time to first frame, retained bytes, frame_at and engine work a frame, graph size). Mechanism: measurement only.\n2. Player: compile the plan once on load instead of twice (TTFF, allocations).\n3. Core `frame_at`: move the shape's path into the last item that takes it instead of cloning for each (allocations a frame).\n4. Core `frame_at`: a shape of one path without trim or path transform takes it without the intermediate `Vec<Path>` and merged copy.\n5. Plan: `still()` short-circuits when nothing in the scene is animated (TTFF for still rasters).\n6. Plan/graph: interpolation breakpoints thinned where a straight line stands for the samples (memory, UI-thread work) — needs pixel-equivalence via `tests/plans.rs`.\n7. Graph: dedupe identical interpolation nodes / constant folding (nodes).\n8. Rasters: PNG encode without the intermediate RGBA Vec; cheaper filter (engine work a frame).\n9. Importers: avoid intermediate `serde_json::Value` copies / string allocations (TTFF).\n10. Track sampling: `Animated::Static` sample by reference where callers only read.\n\nUncertain: tiny-skia render cost (external), NSH graph evaluation (external, not measurable here).\n\nAlready simple, leave: `Track::sample` binary search, `fit`, `Playback`.",
"decisionMarkdown": "### Decision\n\nBaseline recorded; no source changed. Next: the measurement harness experiment.",
"notes": "Raw profile output: artifacts-temp/agentx-experiments/performance-improvements/01-baseline/profile.txt (transient)."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
{
"schemaVersion": 1,
"id": "08798e18-46c1-499e-bcbb-9ac3dd3b826f",
"loopId": "9e7a8b6d-0104-4e72-a31e-3b168c0b3881",
"index": 2,
"type": "hypothesis",
"name": "Corpus bench harness",
"slug": "corpus-bench-harness",
"path": "2026-10-27-02-corpus-bench-harness.json",
"createdAt": "2026-10-27T20:16:05Z",
"startedAt": "2026-10-27T20:16:05Z",
"finishedAt": "2026-10-27T20:18:20Z",
"state": "done",
"status": "success",
"checkpointCommit": "a9eba8e8fb24625468974d341186c970ecfdad3c",
"resultCommit": "8b0bf9378dbd560fbdfaef63e91cd49811441164",
"rollbackCommand": "git -C \"/Users/dinko/work-private/projects/dXn/dXn.VectorAnimation\" reset --hard a9eba8e8fb24625468974d341186c970ecfdad3c",
"repositories": [
{
"name": "main",
"path": ".",
"role": "main",
"checkpointCommit": "a9eba8e8fb24625468974d341186c970ecfdad3c",
"resultCommit": "8b0bf9378dbd560fbdfaef63e91cd49811441164",
"rollbackCommand": "git -C \"/Users/dinko/work-private/projects/dXn/dXn.VectorAnimation\" reset --hard a9eba8e8fb24625468974d341186c970ecfdad3c",
"status": "success"
}
],
"repositoryCommits": [
{
"name": "main",
"path": ".",
"role": "main",
"commit": "8b0bf9378dbd560fbdfaef63e91cd49811441164"
}
],
"atomicDiscipline": {
"oneHypothesisOnly": true,
"singleImplementationAttempt": true,
"noUnplannedFixesInsideExperiment": true,
"failedAttemptRequiresImmediateRevert": true,
"successfulAttemptRequiresCommitBeforeNextExperiment": true,
"rule": "Run exactly the printed hypothesis. If the attempted change fails, degrades, or suggests a different fix, revert to the checkpoint and finish this experiment as failure. Start a new experiment for the next idea."
},
"primaryTarget": "wasm/web with headed Playwright and WebGPU enabled",
"hypothesisMarkdown": "## Corpus Bench Harness Hypothesis\n\nA `bench` subcommand of `vector-animation-check`, with a counting global allocator (`stats_alloc`), measures reproducibly over the corpus: time to first frame (read, compile as the component does it twice, scene kept, first rasters and sampled frames), its allocations and bytes, bytes held after, the engine thread's work a frame, the reference `frame_at` a frame, and the graph's size.\n\n**Tested mechanism:** measurement enablement only.\n**Allowed changes:** `crates/dxn_vector_animation_check` (new `bench.rs`, `stats_alloc` dependency, dispatch), README line.\n**Forbidden:** any optimization.",
"measurementPlanMarkdown": "### Measurement Plan\n\n`vector-animation-check bench artifacts-temp/corpus` three times (release); totals agree within 10%.",
"expectedOutcomeMarkdown": "### Expected Outcome\n\nRepeatable totals; allocation counts identical run to run.",
"diagnostics": {
"playwright": [],
"rayx": [],
"counters": [],
"artifacts": []
},
"measurements": [
{
"metric": "read ms (sum)",
"before": "n/a",
"after": "73.9",
"delta": "",
"source": "bench median of 3"
},
{
"metric": "compile ms (sum)",
"before": "n/a",
"after": "179.6",
"delta": "",
"source": "bench"
},
{
"metric": "time to first frame ms (sum)",
"before": "n/a",
"after": "253.5",
"delta": "",
"source": "bench"
},
{
"metric": "ttff allocations / MB allocated",
"before": "n/a",
"after": "2,867,296 / 3061 MB",
"delta": "",
"source": "bench"
},
{
"metric": "held MB",
"before": "n/a",
"after": "979.7",
"delta": "",
"source": "bench"
},
{
"metric": "frame_at us/frame (sum) / allocs",
"before": "n/a",
"after": "354 / 7789",
"delta": "",
"source": "bench"
},
{
"metric": "engine us/frame (sum) / allocs",
"before": "n/a",
"after": "833 / 370",
"delta": "",
"source": "bench"
},
{
"metric": "graph nodes / breakpoints / ops",
"before": "n/a",
"after": "5026 / 115,936 / 45,331",
"delta": "",
"source": "bench"
}
],
"reportMarkdown": "## Corpus Bench Harness Finished\n\n**Passed Status**: true: three runs within 2% on time, allocations identical.\n\n**Attempt Count:** 1. **Out-of-scope changes:** none.\n\nHot spots it shows: merge paths (read 40 ms, 2 GB allocated, 331 MB held), the repeater (compile 43 ms, 372 MB allocated, 222 MB held), onboarding (compile 60 ms; 0.78 ms a frame of moving blur rasters), thunder.svg (29k breakpoints), timeline.svg (frame_at 57 us, 1804 allocs a frame).\n\n| Repo | Path | Checkpoint | Result Commit | Status |\n| --- | --- | --- | --- | --- |\n| main | . | a9eba8e | 8b0bf93 | committed |",
"decisionMarkdown": "### Decision\n\nCommitted: 8b0bf93 feat(check): bench a folder's load, first frame, allocations and frames.",
"notes": ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
{
"schemaVersion": 1,
"id": "11eedca8-3f9f-46b5-a2dc-83044d989326",
"loopId": "9e7a8b6d-0104-4e72-a31e-3b168c0b3881",
"index": 3,
"type": "hypothesis",
"name": "Switch detection without sampled paths",
"slug": "switch-detection-without-sampled-paths",
"path": "2026-10-27-03-switch-detection-without-sampled-paths.json",
"createdAt": "2026-10-27T20:19:04Z",
"startedAt": "2026-10-27T20:19:04Z",
"finishedAt": "2026-10-27T20:20:24Z",
"state": "done",
"status": "success",
"checkpointCommit": "8b0bf9378dbd560fbdfaef63e91cd49811441164",
"resultCommit": "050fa8f45d1d2f58fd95b45beb3001a88cc9a5b6",
"rollbackCommand": "git -C \"/Users/dinko/work-private/projects/dXn/dXn.VectorAnimation\" reset --hard 8b0bf9378dbd560fbdfaef63e91cd49811441164",
"repositories": [
{
"name": "main",
"path": ".",
"role": "main",
"checkpointCommit": "8b0bf9378dbd560fbdfaef63e91cd49811441164",
"resultCommit": "050fa8f45d1d2f58fd95b45beb3001a88cc9a5b6",
"rollbackCommand": "git -C \"/Users/dinko/work-private/projects/dXn/dXn.VectorAnimation\" reset --hard 8b0bf9378dbd560fbdfaef63e91cd49811441164",
"status": "success"
}
],
"repositoryCommits": [
{
"name": "main",
"path": ".",
"role": "main",
"commit": "050fa8f45d1d2f58fd95b45beb3001a88cc9a5b6"
}
],
"atomicDiscipline": {
"oneHypothesisOnly": true,
"singleImplementationAttempt": true,
"noUnplannedFixesInsideExperiment": true,
"failedAttemptRequiresImmediateRevert": true,
"successfulAttemptRequiresCommitBeforeNextExperiment": true,
"rule": "Run exactly the printed hypothesis. If the attempted change fails, degrades, or suggests a different fix, revert to the checkpoint and finish this experiment as failure. Start a new experiment for the next idea."
},
"primaryTarget": "wasm/web with headed Playwright and WebGPU enabled",
"hypothesisMarkdown": "## Switch detection without sampled paths Hypothesis\n\nBisecting where a morphing outline switches commands sampled the whole path each step (60 steps a switch) only to compare its commands. `commands_at` picks the keyframe whose commands `Track::sample` would give, without making the path: the same switch times, far less work.\\n\\n**Tested mechanism:** switch detection by keyframe choice. **Allowed:** `plan.rs` switches. **Forbidden:** other compile changes.",
"measurementPlanMarkdown": "### Measurement Plan\n\nRelease bench over the 53 corpus files (median of 3 runs, best of 5 each), plus the focused tests of the touched area.",
"expectedOutcomeMarkdown": "### Expected Outcome\n\nThe targeted metric improves with identical graph output and passing focused tests.",
"diagnostics": {
"playwright": [],
"rayx": [],
"counters": [],
"artifacts": []
},
"measurements": [
{
"metric": "compile ms",
"before": "179.6",
"after": "158.0",
"delta": "",
"source": "scratchpad bench.sh: median of 3 `vector-animation-check bench artifacts-temp/corpus`"
},
{
"metric": "ttff MB allocated",
"before": "3061",
"after": "1344",
"delta": "",
"source": "scratchpad bench.sh: median of 3 `vector-animation-check bench artifacts-temp/corpus`"
},
{
"metric": "ttff allocations",
"before": "2,867,296",
"after": "2,617,700",
"delta": "",
"source": "scratchpad bench.sh: median of 3 `vector-animation-check bench artifacts-temp/corpus`"
},
{
"metric": "graph nodes/breaks/ops",
"before": "5026/115936/45331",
"after": "5026/115936/45331 (identical)",
"delta": "",
"source": "scratchpad bench.sh: median of 3 `vector-animation-check bench artifacts-temp/corpus`"
}
],
"reportMarkdown": "## Switch detection without sampled paths Finished\n\n**Passed Status**: True\n\n**Attempt Count:** 1. **Out-of-scope changes:** none.\n\nCompile -12%, bytes allocated at load -56% (merge paths). Tests: plans, moving_outlines, partial_sampling pass; clippy clean.\n\n| Repo | Path | Checkpoint | Result Commit | Status |\n| --- | --- | --- | --- | --- |\n| main | . | 8b0bf93 | 050fa8f | committed |",
"decisionMarkdown": "### Decision\n\nCommitted 050fa8f: perf(nsh): find where outlines switch commands without making the paths",
"notes": ""
}
Loading
Loading