Scaffolds frontend/ (shadow-cljs, reagent 1.2.0, re-frame 1.4.3) and ports everything below the data model, with the JS kept as a numeric oracle. domain/landmarks index tables, verbatim domain/ring subsample, offset, simplicity domain/geom similarity fit, procrustes, moving average domain/raster indexed scanline fill, stencil, disc, rect domain/palette the ramp, and the no-sampled-RGB rule 58 tests, 166 assertions. Parity with js/ on the identical 72-frame synthetic track: fit-similarity, procrustes-mean, fit-residual, moving-average, smooth-transforms, offset-ring and subsample-slots to 1e-9; the raster pixel-for-pixel over the whole buffer. Three deviations from the JS, each for a reason: - synth.cljs jitters from a SEEDED generator, not Math.random. Parity is only checkable if both sides can be handed the same track, and a failing assertion has to be reproducible. `:rand-fn` takes the generator over, so oracle.mjs stubs js/Math.random and js/ itself stays untouched. - raster/->rgba replaces toImageData. ImageData is a DOM type and domain/ may not touch the DOM; returning plain bytes also lets the no-intermediate-colours assertion run in node. ui/canvas wraps it later. - offset-ring lives in domain/ring, not domain/geom, per architecture.md: it is an operation on an ordered traversal, not on a transform. Step 1's "done" also names the swapped-iris vote, but pairIrises is in pipeline.js and belongs to step 7. The precondition is asserted instead -- `:swap-iris` really does move both blocks -- so the vote will have a track that disagrees with it when it arrives. One finding, recorded in full in the test that measures it: smooth-transforms buys nothing on the synthetic track. Against jitter-free ground truth, radius 1 helps by 17% on one noise realisation and hurts by 0.5% on another, so its benefit is within noise; from radius 2 up the cost is unambiguous, and by radius 5 the filter is below the true motion's own high-frequency energy, i.e. smoothing away performance. The test pins the shape of the knob rather than a preferred value. This may say more about the synth's jitter being unrealistically small (+/-0.001 normalised) than about the knob; step 6 settles it on real footage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| public | ||
| src/arthur | ||
| test | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| shadow-cljs.edn | ||
frontend
The ClojureScript half. See docs/port-plan.md for what is being built and in
what order; this file is only how to run it.
Once
mise install # from the REPO ROOT: java 21+, node 20, clojure, python
cd frontend && npm install
java must be 21+. On an older JDK shadow-cljs fails with "CompilerOptions has
been compiled by a more recent version of the Java Runtime", which reads like a
shadow-cljs bug and is not one. mise install is what prevents it.
The tests
cd frontend && npm test
That is three things in order: regenerate the JS oracle's answers, compile the
:test build, run it under node.
node test/parity/oracle.mjs # drives js/ and writes test/parity/oracle.json
shadow-cljs compile test
node out/node-tests.js
Run them separately if a compile error is in the way. The oracle JSON is generated, not committed.
The app
cd frontend && npx shadow-cljs watch app
Then open http://localhost:8778/index.html — with the /index.html, not
bare /. This shadow-cljs does no directory-index resolution, so / is a 404
whatever the roots are.
Through step 1 the page is a placeholder on purpose: there is nothing to render until the data model exists, and a shell built before the model is a shell built around a guess. Something moves on screen at step 2.
Port 8778 is deliberately not 8777. python3 serve.py from the repo root still
runs the old JS tool on 8777, and the two are meant to run side by side — that is
the whole reason js/ is still in the tree.
From step 9 Django serves the page and :dev-http goes away.
The oracle
js/ is the numeric oracle, not dead weight. test/parity/ runs both
implementations on the same synthetic track and diffs them: fit-similarity and
procrustes-mean agree to 1e-9, the raster pixel-for-pixel.
Both sides get the identical track because js/synth.js reads Math.random at
call time, so oracle.mjs stubs it to a constant and the CLJS side passes
:rand-fn (constantly 0.5). js/ itself is never modified.
test/parity/ and arthur.parity-test get deleted in one commit at step 5.
A parity test pins behaviour while code moves; keeping it afterwards would bake
the prototype's mistakes into the rewrite and make them permanent.
Layout
src/arthur/domain/ pure. No re-frame, no DOM, no flow/.
test/arthur/synth.cljs the synthetic track — test infrastructure, not src
test/parity/ the JS oracle harness. Deletable at step 5.
public/index.html dev host page. Django replaces it at step 9.