Serve the document from a Django backend, split into three tiers
Step 9. The tier split was the work; Django was the easy half.
Tier 1 — the authored scene — is the document, and it is addressed as
independently versioned leaves rather than saved whole, so one vertex drag
cannot clobber a collaborator's keying. `domain/leaf` is the document as
path -> value; `domain/wire` puts it on the wire as transit, because JSON
has neither integer map keys nor keywords and a save would quietly turn
`{0 v}` into `{"0" v}`.
Tier 2 — the dense channel blocks — is content-addressed by a hash over
every input, with the detector version inside every key through the
analysis the block descriptor names. `flow/address`'s `block-knobs` is the
invalidation table, and `address-test` does not trust it: it re-freezes the
take once per knob and asserts the biconditional, that a block's bytes
changed if and only if its key changed. That found `brow-pos` not depending
on `contour-avg` — the brow ring is smoothed, the raise is not.
Tier 3 — frames and audio — is served by the hash of its bytes out of the
same store. A manifest now names frames and carries a URL for each, so the
frame layout stopped being a shared secret between a shell script and a
ClojureScript namespace, and the `?v=` cache-buster went with it: a blob's
name is the hash of its contents, so a stale copy is not a thing that can
happen. The synthetic take's `audio.wav` moved to `static/arthur/` — an
asset the project owns, not an extraction that churns.
The server verifies rather than trusting a name it was handed: it
recomputes every key from the descriptor stored beside it, refuses an
analysis that declares no detector version, and refuses a document naming
blocks it does not hold. It hashes the descriptor TEXT, because JS prints
an integral double as `1` and Python as `1.0`, and a scheme where both ends
re-render the numbers disagrees on the first parameter that happens to be
whole.
Two loose ends from step 8 closed on the way. `pack` no longer takes a
`(track, frame)` predicate whose call sites each re-derived a feature from
an index — every track names the feature it follows, which deleted five
hand-maintained mappings. And `:dev-http` is gone: Django serves the page,
shadow-cljs only builds into the staticfiles tree.
227 CLJS tests, 31 Django tests, green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
b6517f837a
commit
9cd5243983
61 changed files with 4694 additions and 269 deletions
|
|
@ -33,6 +33,11 @@
|
|||
(defn- node [id] (get-in @scene* [:nodes id]))
|
||||
(defn- chan [id path] (get-in (node id) [:channels path]))
|
||||
|
||||
(defn- block-of
|
||||
"The typed array a dense channel reads, through its own handle."
|
||||
[ch]
|
||||
(:data (get @store (:store (:dense ch)))))
|
||||
|
||||
(defn- pts-at
|
||||
"The mouth's [:geom :pts] at frame f, as a flat CLJS vector."
|
||||
[id f]
|
||||
|
|
@ -127,8 +132,11 @@
|
|||
(doseq [path [[:xform :pos] [:xform :rot] [:xform :scale]]]
|
||||
(is (nil? (:scale (:dense (get-in (node :head) [:measured path]))))
|
||||
(str path " should be plain Float32")))
|
||||
(is (instance? js/Int16Array (:data (get @store "take/geom"))))
|
||||
(is (instance? js/Float32Array (:data (get @store "take/head-pos")))))
|
||||
;; Reached through the channel's own handle rather than by naming a key. A key
|
||||
;; is a hash now, so a test that wrote one out would be asserting a digest.
|
||||
(is (instance? js/Int16Array (block-of (chan :mouth [:geom :pts]))))
|
||||
(is (instance? js/Float32Array
|
||||
(block-of (get-in (node :head) [:measured [:xform :pos]])))))
|
||||
|
||||
(deftest a-value-past-the-block-s-range-is-refused-rather-than-saturated
|
||||
;; Saturating reads as articulation flattening off at the extremes — a bad
|
||||
|
|
@ -305,10 +313,17 @@
|
|||
;; asking for a different stage moves and rescales the same geometry rather than
|
||||
;; re-measuring anything.
|
||||
(let [big (freeze/clip (assoc take/params :stage [640 480] :name "big")
|
||||
@take/measured)]
|
||||
@take/measured)
|
||||
key-of (fn [sc] (:store (:dense (get-in sc [:nodes :mouth :channels [:geom :pts]]))))]
|
||||
(is (= [640 480] [(:width (:scene big)) (:height (:scene big))]))
|
||||
(is (= (vec (array-seq (:data (get @store "take/geom"))))
|
||||
(vec (array-seq (:data (get (:store big) "big/geom")))))
|
||||
;; Stronger than it was, and for free: the stage is not an input to tier 2, so
|
||||
;; the two clips do not merely hold equal bytes — they name the SAME BLOCK, and
|
||||
;; a stage change cannot invalidate a bake. The clip's name is not an input
|
||||
;; either, which is why "big" and "take" still agree.
|
||||
(is (= (key-of @scene*) (key-of (:scene big)))
|
||||
"a different stage is a different document over the same tier 2")
|
||||
(is (= (vec (array-seq (:data (get @store (key-of @scene*)))))
|
||||
(vec (array-seq (:data (get (:store big) (key-of (:scene big)))))))
|
||||
"the geometry is the same numbers at either stage size")
|
||||
(is (not= (:value (get-in (:scene big) [:nodes :face :channels [:xform :scale]]))
|
||||
(:value (chan :face [:xform :scale]))))))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue