An options map for the resolvers, and sid beside the clip it is in

Finishing the last commit, which traded a simpler definition for noisier call
sites: deleting the arity ladders left `(symbol/resolver sym st pal/index-of
nil nil)` at twenty-odd places, and two trailing nils tell a reader nothing
except to go and count positions.

The ladder was a symptom. The disease is five positional parameters, and the
split that matters is which of them are OPTIONAL:

  store, palette    positional, because neither is optional. A dense channel
                    cannot be read without the store it names — that is the
                    crash two commits ago — and every op carries a colour.
  pose-tracks       one call site, in `clip/resolver`'s own recursion
  source/picture-fps  two call sites

So the last three become one `opts` map, and the common call loses a nil. The
point is not the nil: it is that the sixth option, whenever it arrives, is a
key at one call site rather than a nil at fifty.

`clip/resolver` also had `sid` FOURTH, behind two arguments that say nothing
about which symbol is being resolved. It is second now, beside the clip it is
in: `(clip/resolver c :main store pal/index-of nil)`.

62 call sites rewritten by parsing the forms rather than by regex, because
`clip/resolver`'s arguments move past each other and a regex cannot see that.
An earlier attempt at this dropped `palette` on the floor and still compiled
at 62 sites — it only surfaced as an arity error, so if that had been a
same-arity mistake the tests would have been the last line of defence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-09-30 12:21:12 -04:00
parent ee66680a0c
commit 624242b407
20 changed files with 84 additions and 81 deletions

View file

@ -26,4 +26,4 @@
"Draw ops for one frame, via the specification path. The page uses "Draw ops for one frame, via the specification path. The page uses
`symbol/resolver` instead; this is here for the REPL." `symbol/resolver` instead; this is here for the REPL."
[f] [f]
(symbol/eval-frame main f nil pal/index-of nil nil)) (symbol/eval-frame main f nil pal/index-of nil))

View file

@ -174,7 +174,7 @@
own frame. Nil for a node that was not on that frame. It is how something own frame. Nil for a node that was not on that frame. It is how something
drawn beside the picture, like a tracing photo, rides a node inside it without drawn beside the picture, like a tracing photo, rides a node inside it without
resolving anything a second time." resolving anything a second time."
[clip store palette sid {:keys [picture-fps] :as opts}] [clip sid store palette {:keys [picture-fps] :as opts}]
(letfn [(build [sid chain pose-tracks] (letfn [(build [sid chain pose-tracks]
(when (some #{sid} chain) (when (some #{sid} chain)
(throw (ex-info "symbol cycle" {:chain (conj chain sid)}))) (throw (ex-info "symbol cycle" {:chain (conj chain sid)})))
@ -183,8 +183,9 @@
nodes (:nodes sym) nodes (:nodes sym)
rank (symbol/draw-rank nodes (symbol/order nodes)) rank (symbol/draw-rank nodes (symbol/order nodes))
ids (sort-by rank (keys nodes)) ids (sort-by rank (keys nodes))
own (symbol/resolver sym store palette pose-tracks own (symbol/resolver sym store palette
(assoc opts :source-fps (:fps clip))) (assoc opts :pose-tracks pose-tracks
:source-fps (:fps clip)))
children (into {} children (into {}
(for [[id n] nodes :when (= :instance (:kind n))] (for [[id n] nodes :when (= :instance (:kind n))]
[id (build (:of n) (conj chain sid) [id (build (:of n) (conj chain sid)
@ -243,7 +244,7 @@
Effects' anchor point are set once and left. A symbol that grows later keeps Effects' anchor point are set once and left. A symbol that grows later keeps
its instances' pivots where they were, so nothing on screen moves." its instances' pivots where they were, so nothing on screen moves."
[clip store sid] [clip store sid]
(let [resolve (resolver clip store pal/index-of sid nil) (let [resolve (resolver clip sid store pal/index-of nil)
bounds (fn [[x0 y0 x1 y1 :as b] x y] bounds (fn [[x0 y0 x1 y1 :as b] x y]
(if b [(min x0 x) (min y0 y) (max x1 x) (max y1 y)] [x y x y])) (if b [(min x0 x) (min y0 y) (max x1 x) (max y1 y)] [x y x y]))
[x0 y0 x1 y1] [x0 y0 x1 y1]

View file

@ -33,7 +33,7 @@
[clip store sid frame id] [clip store sid frame id]
(let [sym (clip/symbol clip sid) (let [sym (clip/symbol clip sid)
sym (update sym :nodes select-keys (symbol/lineage (:nodes sym) id)) sym (update sym :nodes select-keys (symbol/lineage (:nodes sym) id))
r (symbol/resolver sym store pal/index-of nil {:source-fps (:fps clip)})] r (symbol/resolver sym store pal/index-of {:source-fps (:fps clip)})]
(r frame) (r frame)
r)) r))

View file

@ -101,7 +101,7 @@
(let [sid (:of n) (let [sid (:of n)
frames (clip/frames document sid) frames (clip/frames document sid)
loop? (get-in n [:time :loop?]) loop? (get-in n [:time :loop?])
resolve (clip/resolver document store pal/index-of sid nil)] resolve (clip/resolver document sid store pal/index-of nil)]
(fn [f] (fn [f]
(let [f (if loop? (mod f frames) f)] (let [f (if loop? (mod f frames) f)]
(when (< -1 f frames) (when (< -1 f frames)

View file

@ -26,11 +26,13 @@
Two ways to evaluate one at a frame: Two ways to evaluate one at a frame:
(eval-frame sym f store) THE SPECIFICATION. Allocating, order-free, (eval-frame sym f store palette opts)
THE SPECIFICATION. Allocating, order-free,
obviously correct. Use it in tests and for a obviously correct. Use it in tests and for a
one-off render. one-off render.
(resolver sym store) -> (fn [f] ops). What playback uses. Caches the (resolver sym store palette opts)
-> (fn [f] ops). What playback uses. Caches the
topological order and the z paths, holds one topological order and the z paths, holds one
CURSOR per channel and one PREALLOCATED point CURSOR per channel and one PREALLOCATED point
buffer per node, so a frame allocates the op buffer per node, so a frame allocates the op
@ -422,11 +424,10 @@
the only place the space changes. the only place the space changes.
This is the definition of what a frame means. `resolver` is what plays it." This is the definition of what a frame means. `resolver` is what plays it."
[sym f store palette pose-tracks opts] [sym f store palette {:keys [pose-tracks source-fps picture-fps]}]
(let [nodes (nodes-of sym) (let [nodes (nodes-of sym)
choices (pose/prepare pose-tracks) choices (pose/prepare pose-tracks)
traces (prepared-traces nodes) traces (prepared-traces nodes)
{:keys [source-fps picture-fps]} opts
ord (order nodes)] ord (order nodes)]
(eval-into {:read (fn [id path c lf] (eval-into {:read (fn [id path c lf]
(ch/value-at c (channel-frame choices traces nodes (ch/value-at c (channel-frame choices traces nodes
@ -481,8 +482,15 @@
The op maps themselves are allocated fresh, and deliberately: there are a dozen The op maps themselves are allocated fresh, and deliberately: there are a dozen
of them per frame against hundreds of points, so pooling them would buy of them per frame against hundreds of points, so pooling them would buy
nothing and cost the ability to hand an op list around as plain data." nothing and cost the ability to hand an op list around as plain data.
[sym store palette pose-tracks {:keys [source-fps picture-fps]}]
`store` and `palette` are POSITIONAL because neither is optional: a dense
channel cannot be read without the store it names, and every op carries a
colour index. `opts` is a map because the rest genuinely are optional, and
because a fifth of them later is then a key rather than a nil at every one of
these call sites — which is what the arity ladder that used to be here was
standing in for."
[sym store palette {:keys [pose-tracks source-fps picture-fps]}]
(let [nodes (nodes-of sym) (let [nodes (nodes-of sym)
choices (pose/prepare pose-tracks) choices (pose/prepare pose-tracks)
traces (prepared-traces nodes) traces (prepared-traces nodes)

View file

@ -195,8 +195,7 @@
;; inside it still resolve — clip/resolver is the function that knows ;; inside it still resolve — clip/resolver is the function that knows
;; how. ;; how.
doc (assoc-in clip [:symbols sid] sym) doc (assoc-in clip [:symbols sid] sym)
resolve-frame (clip/resolver doc store palette sid resolve-frame (clip/resolver doc sid store palette {:picture-fps picture-fps})
{:picture-fps picture-fps})
ras (raster/make width height) ras (raster/make width height)
bg (get palette :bg 0)] bg (get palette :bg 0)]
(-> (audio! doc sid store audio-url) (-> (audio! doc sid store audio-url)

View file

@ -126,7 +126,7 @@
:<- [::playback/display-fps] :<- [::playback/display-fps]
(fn [[document sid store palette picture-fps] _] (fn [[document sid store palette picture-fps] _]
(when (and document (clip/symbol document sid)) (when (and document (clip/symbol document sid))
(clip/resolver document store palette sid {:picture-fps picture-fps})))) (clip/resolver document sid store palette {:picture-fps picture-fps}))))
(rf/reg-sub (rf/reg-sub
::shown ::shown

View file

@ -29,7 +29,7 @@
:disc (select-keys op [:kind :cx :cy :r]) :disc (select-keys op [:kind :cx :cy :r])
:rect (select-keys op [:kind :cx :cy :size]) :rect (select-keys op [:kind :cx :cy :size])
nil)) nil))
((clip/resolver document st pal/index-of sid nil) 0)))) ((clip/resolver document sid st pal/index-of nil) 0))))
(defn symbol! (defn symbol!
"Start carrying symbol `sid` of the loaded document into the open symbol." "Start carrying symbol `sid` of the loaded document into the open symbol."

View file

@ -24,7 +24,7 @@
(/ dt n)))) (/ dt n))))
(deftest bench (deftest bench
(let [res (symbol/resolver (clip/symbol @swarm/clip :main) @swarm/store pal/index-of nil nil) (let [res (symbol/resolver (clip/symbol @swarm/clip :main) @swarm/store pal/index-of nil)
ras (raster/make 320 200) ras (raster/make 320 200)
dest (js/Uint8ClampedArray. (* 320 200 4)) dest (js/Uint8ClampedArray. (* 320 200 4))
n 120] n 120]

View file

@ -34,7 +34,7 @@
(defn- drawn [c path] (defn- drawn [c path]
(partition 2 (take 6 (array-seq (:pts (first (filter #(= path (:node %)) (partition 2 (take 6 (array-seq (:pts (first (filter #(= path (:node %))
((clip/resolver c nil pal/index-of :main nil) 16)))))))) ((clip/resolver c :main nil pal/index-of nil) 16))))))))
(defn- near? [a b] (every? #(< (js/Math.abs %) 1e-9) (map - (flatten a) (flatten b)))) (defn- near? [a b] (every? #(< (js/Math.abs %) 1e-9) (map - (flatten a) (flatten b))))

View file

@ -36,7 +36,7 @@
:channels {[:xform :pos] (ch/framed [120 50])}}}}) :channels {[:xform :pos] (ch/framed [120 50])}}}})
(assoc-in [:symbols :sym/test] (assoc-in [:symbols :sym/test]
(assoc (get-in source [:symbols :main]) :id :sym/test))) (assoc (get-in source [:symbols :main]) :id :sym/test)))
resolve (clip/resolver document nil pal/index-of :main nil) resolve (clip/resolver document :main nil pal/index-of nil)
at (fn [f] (mapv (juxt :node :cx) (resolve f)))] at (fn [f] (mapv (juxt :node :cx) (resolve f)))]
(is (empty? (clip/problems document))) (is (empty? (clip/problems document)))
(is (= [[[:left :mark] 110]] (at 1))) (is (= [[[:left :mark] 110]] (at 1)))
@ -75,9 +75,9 @@
:parent :root :z "a2" :parent :root :z "a2"
:playback {:tracks {:mouth {0 0, 8 8}}}}}} :playback {:tracks {:mouth {0 0, 8 8}}}}}}
:sym/poses symbol}} :sym/poses symbol}}
resolve (clip/resolver document {"sizes" {:data values}} pal/index-of :main nil) store {"sizes" {:data values}}
low-resolve (clip/resolver document {"sizes" {:data values}} resolve (clip/resolver document :main store pal/index-of nil)
pal/index-of :main {:picture-fps 8}) low-resolve (clip/resolver document :main store pal/index-of {:picture-fps 8})
at (fn [f] (into {} (map (fn [op] [(:node op) op])) (resolve f))) at (fn [f] (into {} (map (fn [op] [(:node op) op])) (resolve f)))
low-at (fn [f] (into {} (map (fn [op] [(:node op) op])) (low-resolve f)))] low-at (fn [f] (into {} (map (fn [op] [(:node op) op])) (low-resolve f)))]
(is (empty? (clip/problems document))) (is (empty? (clip/problems document)))
@ -99,13 +99,11 @@
(is (= 8 (:cx (get (low-at 8) [:first :eye]))) (is (= 8 (:cx (get (low-at 8) [:first :eye])))
"authored position ignores the picture grid") "authored position ignores the picture grid")
(let [sym (get-in document [:symbols :sym/poses]) (let [sym (get-in document [:symbols :sym/poses])
opts {:source-fps 30 :picture-fps 8}] opts {:source-fps 30 :picture-fps 8 :pose-tracks {:mouth {0 0, 8 20}}}]
(is (= (mapv #(select-keys % [:node :cx :size]) (is (= (mapv #(select-keys % [:node :cx :size])
(symbol/eval-frame sym 8 {"sizes" {:data values}} (symbol/eval-frame sym 8 store pal/index-of opts))
pal/index-of {:mouth {0 0, 8 20}} opts))
(mapv #(select-keys % [:node :cx :size]) (mapv #(select-keys % [:node :cx :size])
((symbol/resolver sym {"sizes" {:data values}} ((symbol/resolver sym store pal/index-of opts) 8)))
pal/index-of {:mouth {0 0, 8 20}} opts) 8)))
"pure evaluation and playback apply the same pose choice")))) "pure evaluation and playback apply the same pose choice"))))
(deftest stage-pose-edits-preserve-earlier-motion-and-survive-save (deftest stage-pose-edits-preserve-earlier-motion-and-survive-save

View file

@ -40,7 +40,7 @@
{:keys [sid frame pts]} (nest/drawn-inside c nil :main [u] 16 drawn) {:keys [sid frame pts]} (nest/drawn-inside c nil :main [u] 16 drawn)
c (paint/new-shape c sid :shape frame pts :brow) c (paint/new-shape c sid :shape frame pts :brow)
[op] (filter #(= [u :shape] (:node %)) [op] (filter #(= [u :shape] (:node %))
((clip/resolver c nil pal/index-of :main nil) 16))] ((clip/resolver c :main nil pal/index-of nil) 16))]
(is (= :box sid)) (is (= :box sid))
(is (= 6 frame) "frame 16 of main is frame 6 of an instance placed at 10") (is (= 6 frame) "frame 16 of main is frame 6 of an instance placed at 10")
(is (every? #(< (js/Math.abs %) 1e-9) (is (every? #(< (js/Math.abs %) 1e-9)
@ -64,7 +64,7 @@
(turn :mid v [5 -3] 0.3 1.5) (turn :mid v [5 -3] 0.3 1.5)
(paint/new-shape :box :shape 4 [0 0 10 0 5 10] :brow)) (paint/new-shape :box :shape 4 [0 0 10 0 5 10] :brow))
draw #(take 6 (array-seq (:pts (first (filter (fn [op] (= [u v :shape] (:node op))) draw #(take 6 (array-seq (:pts (first (filter (fn [op] (= [u v :shape] (:node op)))
((clip/resolver % nil pal/index-of :main nil) 16)))))) ((clip/resolver % :main nil pal/index-of nil) 16))))))
{:keys [frame matrix time]} (nest/inside c nil :main [u v :shape] 16) {:keys [frame matrix time]} (nest/inside c nil :main [u v :shape] 16)
out (js/Float64Array. 2) out (js/Float64Array. 2)
seen (mapcat (fn [[x y]] (vec (array-seq (node/apply-pt! out 0 matrix x y)))) seen (mapcat (fn [[x y]] (vec (array-seq (node/apply-pt! out 0 matrix x y))))
@ -105,7 +105,7 @@
"What `sid` draws at each of `fs`, without the node paths a move changes: "What `sid` draws at each of `fs`, without the node paths a move changes:
per frame, the sorted marks with their points rounded to a thousandth." per frame, the sorted marks with their points rounded to a thousandth."
[c sid fs] [c sid fs]
(let [resolve (clip/resolver c nil pal/index-of sid nil) (let [resolve (clip/resolver c sid nil pal/index-of nil)
round #(/ (js/Math.round (* 1000 %)) 1000)] round #(/ (js/Math.round (* 1000 %)) 1000)]
(mapv (fn [f] (mapv (fn [f]
(sort-by str (map (fn [op] (sort-by str (map (fn [op]
@ -251,7 +251,7 @@
(is (= [0 8] (:span heard)) (is (= [0 8] (:span heard))
"own frames 0-8: it starts on inner's 2 and inner ends on 10") "own frames 0-8: it starts on inner's 2 and inner ends on 10")
(is (= [7 15] (node/placed-span heard)) "inner starts on 5 of outer") (is (= [7 15] (node/placed-span heard)) "inner starts on 5 of outer")
(is (empty? ((clip/resolver c nil pal/index-of :inner nil) 3)) (is (empty? ((clip/resolver c :inner nil pal/index-of nil) 3))
"and it draws nothing") "and it draws nothing")
(is (= c (clip/place-sound c :inner {:sound "tone"} "tone.mp3" 40 1 10 (random-uuid))) (is (= c (clip/place-sound c :inner {:sound "tone"} "tone.mp3" 40 1 10 (random-uuid)))
"nor lands past the end of its symbol") "nor lands past the end of its symbol")

View file

@ -31,5 +31,5 @@
(is (empty? (channel/problems mixed))) (is (empty? (channel/problems mixed)))
;; The demo's root is exposed on 2s. Paint at frame 3 must still appear at 3. ;; The demo's root is exposed on 2s. Paint at frame 3 must still appear at 3.
(is (some #(= :paint-test (:node %)) (is (some #(= :paint-test (:node %))
(symbol/eval-frame (get-in c2 [:symbols :main]) 3 nil pal/index-of nil nil))) (symbol/eval-frame (get-in c2 [:symbols :main]) 3 nil pal/index-of nil)))
(is (= mixed-clip (leaf/clip :c1 (leaf/leaves :c1 mixed-clip)))))) (is (= mixed-clip (leaf/clip :c1 (leaf/leaves :c1 mixed-clip))))))

View file

@ -148,7 +148,7 @@
;; frame rather than hidden, and its partner is not. ;; frame rather than hidden, and its partner is not.
(let [back (wired :c1 @gappy) (let [back (wired :c1 @gappy)
drawn (into #{} (map :node) drawn (into #{} (map :node)
((symbol/resolver (face-symbol (:clip back)) (:store back) pal/index-of nil nil) 12))] ((symbol/resolver (face-symbol (:clip back)) (:store back) pal/index-of nil) 12))]
(is (not (contains? drawn :eye-r))) (is (not (contains? drawn :eye-r)))
(is (contains? drawn :eye-l)) (is (contains? drawn :eye-l))
(is (contains? drawn :mouth)))) (is (contains? drawn :mouth))))

View file

@ -27,7 +27,7 @@
{:nodes (into {} (map (juxt :id identity)) nodes)}) {:nodes (into {} (map (juxt :id identity)) nodes)})
(defn- ids-at [scene f] (defn- ids-at [scene f]
(mapv :node (symbol/eval-frame scene f nil pal/index-of nil nil))) (mapv :node (symbol/eval-frame scene f nil pal/index-of nil)))
(def ^:private pts-of ops/points) (def ^:private pts-of ops/points)
@ -70,9 +70,9 @@
(is (identical? (get-in s [:nodes :b]) (get-in s' [:nodes :b])) (is (identical? (get-in s [:nodes :b]) (get-in s' [:nodes :b]))
"and so is the new one") "and so is the new one")
(is (= [[0 0] [10 0] [10 10]] (is (= [[0 0] [10 0] [10 10]]
(pts-of (first (filter #(= :c (:node %)) (symbol/eval-frame s 0 nil pal/index-of nil nil)))))) (pts-of (first (filter #(= :c (:node %)) (symbol/eval-frame s 0 nil pal/index-of nil))))))
(is (= [[100 0] [110 0] [110 10]] (is (= [[100 0] [110 0] [110 10]]
(pts-of (first (filter #(= :c (:node %)) (symbol/eval-frame s' 0 nil pal/index-of nil nil)))))))) (pts-of (first (filter #(= :c (:node %)) (symbol/eval-frame s' 0 nil pal/index-of nil))))))))
;; ---- draw order ---- ;; ---- draw order ----
@ -129,7 +129,7 @@
:channels {[:xform :pos] (ch/framed [100 50]) :channels {[:xform :pos] (ch/framed [100 50])
[:xform :scale] (ch/framed [2 2])}} [:xform :scale] (ch/framed [2 2])}}
(poly :p :g "a1" [0 0 10 0 10 10 0 10] :skin-base)) (poly :p :g "a1" [0 0 10 0 10 10 0 10] :skin-base))
op (first (symbol/eval-frame s 0 nil pal/index-of nil nil))] op (first (symbol/eval-frame s 0 nil pal/index-of nil))]
(is (= [[100 50] [120 50] [120 70] [100 70]] (pts-of op))))) (is (= [[100 50] [120 50] [120 70] [100 70]] (pts-of op)))))
(deftest a-keyed-group-position-moves-its-children-and-holds-between-keys (deftest a-keyed-group-position-moves-its-children-and-holds-between-keys
@ -139,7 +139,7 @@
:channels {[:xform :pos] :channels {[:xform :pos]
(ch/keyed {0 [0 0], 4 [10 0], 8 [10 10], 12 [0 10]} :hold)}} (ch/keyed {0 [0 0], 4 [10 0], 8 [10 10], 12 [0 10]} :hold)}}
(poly :p :g "a1" [0 0 2 0 2 2] :skin-base)) (poly :p :g "a1" [0 0 2 0 2 2] :skin-base))
at #(first (pts-of (first (symbol/eval-frame s % nil pal/index-of nil nil))))] at #(first (pts-of (first (symbol/eval-frame s % nil pal/index-of nil))))]
(is (= [0 0] (at 0))) (is (= [0 0] (at 0)))
(is (= [0 0] (at 3)) "held") (is (= [0 0] (at 3)) "held")
(is (= [10 0] (at 4))) (is (= [10 0] (at 4)))
@ -156,7 +156,7 @@
{:id :g :kind :group :parent :root :z "a1" {:id :g :kind :group :parent :root :z "a1"
:channels {[:xform :pos] (ch/keyed (into {} (map (juxt identity #(vector % 0))) (range 12)) :hold)}} :channels {[:xform :pos] (ch/keyed (into {} (map (juxt identity #(vector % 0))) (range 12)) :hold)}}
(poly :p :g "a1" [0 0 1 0 1 1] :skin-base)) (poly :p :g "a1" [0 0 1 0 1 1] :skin-base))
x-at #(first (first (pts-of (first (symbol/eval-frame s % nil pal/index-of nil nil)))))] x-at #(first (first (pts-of (first (symbol/eval-frame s % nil pal/index-of nil)))))]
(is (= [0 0 0 3 3 3 6 6 6 9 9 9] (mapv x-at (range 12))))) (is (= [0 0 0 3 3 3 6 6 6 9 9 9] (mapv x-at (range 12)))))
(testing "and a node may set its own grid, which the model permits deliberately" (testing "and a node may set its own grid, which the model permits deliberately"
@ -164,7 +164,7 @@
{:id :g :kind :group :parent :root :z "a1" :time {:mode :map :expose 4} {:id :g :kind :group :parent :root :z "a1" :time {:mode :map :expose 4}
:channels {[:xform :pos] (ch/keyed (into {} (map (juxt identity #(vector % 0))) (range 12)) :hold)}} :channels {[:xform :pos] (ch/keyed (into {} (map (juxt identity #(vector % 0))) (range 12)) :hold)}}
(poly :p :g "a1" [0 0 1 0 1 1] :skin-base)) (poly :p :g "a1" [0 0 1 0 1 1] :skin-base))
x-at #(first (first (pts-of (first (symbol/eval-frame s % nil pal/index-of nil nil)))))] x-at #(first (first (pts-of (first (symbol/eval-frame s % nil pal/index-of nil)))))]
(is (= [0 0 0 0 4 4 4 4 8 8 8 8] (mapv x-at (range 12))))))) (is (= [0 0 0 0 4 4 4 4 8 8 8 8] (mapv x-at (range 12)))))))
(deftest offset-is-per-node-which-is-the-entire-point-of-mouth-lead (deftest offset-is-per-node-which-is-the-entire-point-of-mouth-lead
@ -178,7 +178,7 @@
{:id :mouth :kind :group :parent :root :z "a2" :time {:mode :map :offset 2} {:id :mouth :kind :group :parent :root :z "a2" :time {:mode :map :offset 2}
:channels {[:xform :pos] (ch/keyed keys :hold)}} :channels {[:xform :pos] (ch/keyed keys :hold)}}
(poly :mouth-p :mouth "a1" [0 0 1 0 1 1] :mouth-dark)) (poly :mouth-p :mouth "a1" [0 0 1 0 1 1] :mouth-dark))
x-of (fn [f id] (->> (symbol/eval-frame s f nil pal/index-of nil nil) x-of (fn [f id] (->> (symbol/eval-frame s f nil pal/index-of nil)
(filter #(= id (:node %))) first pts-of first first))] (filter #(= id (:node %))) first pts-of first first))]
(is (= [0 1 2 3] (mapv #(x-of % :plate-p) (range 4)))) (is (= [0 1 2 3] (mapv #(x-of % :plate-p) (range 4))))
(is (= [2 3 4 5] (mapv #(x-of % :mouth-p) (range 4))) "the mouth reads ahead"))) (is (= [2 3 4 5] (mapv #(x-of % :mouth-p) (range 4))) "the mouth reads ahead")))
@ -221,11 +221,11 @@
:dense {:store "pts" :offset 0 :stride 6 :frames 2}} :dense {:store "pts" :offset 0 :stride 6 :frames 2}}
[:style :color] (ch/framed :mouth-dark)}} [:style :color] (ch/framed :mouth-dark)}}
(poly :teeth :m "a2" [0 0 1 0 1 1] :teeth))] (poly :teeth :m "a2" [0 0 1 0 1 1] :teeth))]
(is (= [:child] (mapv :node (symbol/eval-frame absent-pos 0 store pal/index-of nil nil)))) (is (= [:child] (mapv :node (symbol/eval-frame absent-pos 0 store pal/index-of nil))))
(is (= [] (mapv :node (symbol/eval-frame absent-pos 1 store pal/index-of nil nil))) (is (= [] (mapv :node (symbol/eval-frame absent-pos 1 store pal/index-of nil)))
"an absent transform gives the children nowhere to be") "an absent transform gives the children nowhere to be")
(is (= [:m :teeth] (mapv :node (symbol/eval-frame absent-pts 0 store pal/index-of nil nil)))) (is (= [:m :teeth] (mapv :node (symbol/eval-frame absent-pts 0 store pal/index-of nil))))
(is (= [:teeth] (mapv :node (symbol/eval-frame absent-pts 1 store pal/index-of nil nil))) (is (= [:teeth] (mapv :node (symbol/eval-frame absent-pts 1 store pal/index-of nil)))
"an absent outline removes only itself"))) "an absent outline removes only itself")))
;; ---- stencils ---- ;; ---- stencils ----
@ -239,7 +239,7 @@
{:id :iris :kind :disc :parent :root :stencil :sclera :z "a2" {:id :iris :kind :disc :parent :root :stencil :sclera :z "a2"
:channels {[:geom :radius] (ch/framed 4) :channels {[:geom :radius] (ch/framed 4)
[:style :color] (ch/framed :iris)}}) [:style :color] (ch/framed :iris)}})
ops (symbol/eval-frame s 0 nil pal/index-of nil nil)] ops (symbol/eval-frame s 0 nil pal/index-of nil)]
(is (= [:sclera :iris] (mapv :node ops))) (is (= [:sclera :iris] (mapv :node ops)))
(is (= (:eye-white pal/index-of) (:stencil (second ops)))))) (is (= (:eye-white pal/index-of) (:stencil (second ops))))))
@ -266,7 +266,7 @@
:channels {[:geom :radius] (ch/framed 3) [:style :color] (ch/framed :iris)}} :channels {[:geom :radius] (ch/framed 3) [:style :color] (ch/framed :iris)}}
{:id :r :kind :rect :parent :g :z "a2" {:id :r :kind :rect :parent :g :z "a2"
:channels {[:geom :size] (ch/framed 1.7) [:style :color] (ch/framed :pupil)}}) :channels {[:geom :size] (ch/framed 1.7) [:style :color] (ch/framed :pupil)}})
[d r] (symbol/eval-frame s 0 nil pal/index-of nil nil)] [d r] (symbol/eval-frame s 0 nil pal/index-of nil)]
(is (= [50 60 6] [(:cx d) (:cy d) (:r d)])) (is (= [50 60 6] [(:cx d) (:cy d) (:r d)]))
(is (= 3.4 (:size r))))) (is (= 3.4 (:size r)))))
@ -291,7 +291,7 @@
(deftest the-resolver-reuses-one-buffer-per-node (deftest the-resolver-reuses-one-buffer-per-node
;; At 30fps per-frame allocation is the only thing that will make this stutter, ;; At 30fps per-frame allocation is the only thing that will make this stutter,
;; and fixed topology is what makes the buffer size knowable at all. ;; and fixed topology is what makes the buffer size knowable at all.
(let [res (symbol/resolver demo/main nil pal/index-of nil nil) (let [res (symbol/resolver demo/main nil pal/index-of nil)
buf-of (fn [f id] (->> (res f) (filter #(= id (:node %))) first :pts))] buf-of (fn [f id] (->> (res f) (filter #(= id (:node %))) first :pts))]
(is (identical? (buf-of 0 :card) (buf-of 30 :card))))) (is (identical? (buf-of 0 :card) (buf-of 30 :card)))))
@ -308,15 +308,15 @@
;; The mistake this split makes easy: both are maps with an :id, and the wrong ;; The mistake this split makes easy: both are maps with an :id, and the wrong
;; one resolves to no ops rather than to an error. ;; one resolves to no ops rather than to an error.
(is (thrown-with-msg? ExceptionInfo #"not a symbol" (is (thrown-with-msg? ExceptionInfo #"not a symbol"
(symbol/resolver demo/clip nil pal/index-of nil nil))) (symbol/resolver demo/clip nil pal/index-of nil)))
(is (thrown-with-msg? ExceptionInfo #"not a symbol" (is (thrown-with-msg? ExceptionInfo #"not a symbol"
(symbol/eval-frame demo/clip 0 nil pal/index-of nil nil))))) (symbol/eval-frame demo/clip 0 nil pal/index-of nil)))))
(deftest the-hand-written-clip-renders-and-moves (deftest the-hand-written-clip-renders-and-moves
;; port-plan step 2's done condition, as an assertion rather than a look: the ;; port-plan step 2's done condition, as an assertion rather than a look: the
;; scene rasterises, it writes only palette indices, and the pixels are not the ;; scene rasterises, it writes only palette indices, and the pixels are not the
;; same on every frame. ;; same on every frame.
(let [res (symbol/resolver demo/main nil pal/index-of nil nil) (let [res (symbol/resolver demo/main nil pal/index-of nil)
render (fn [f] render (fn [f]
(let [r (raster/make (:width demo/clip) (:height demo/clip))] (let [r (raster/make (:width demo/clip) (:height demo/clip))]
(raster/clear! r (:bg pal/index-of)) (raster/clear! r (:bg pal/index-of))
@ -334,7 +334,7 @@
;; Exposure 2 on the clip root, inherited, so odd frames are identical to the ;; Exposure 2 on the clip root, inherited, so odd frames are identical to the
;; even frame before them. If this fails, exposure is being applied somewhere ;; even frame before them. If this fails, exposure is being applied somewhere
;; other than the frame the channels are sampled at. ;; other than the frame the channels are sampled at.
(let [res (symbol/resolver demo/main nil pal/index-of nil nil) (let [res (symbol/resolver demo/main nil pal/index-of nil)
render (fn [f] render (fn [f]
(let [r (raster/make (:width demo/clip) (:height demo/clip))] (let [r (raster/make (:width demo/clip) (:height demo/clip))]
(raster/clear! r (:bg pal/index-of)) (raster/clear! r (:bg pal/index-of))
@ -351,7 +351,7 @@
(deftest the-hand-written-clip-keeps-the-iris-and-pupil-inside-the-card (deftest the-hand-written-clip-keeps-the-iris-and-pupil-inside-the-card
;; The stencil chain, on real pixels: the iris is clipped by the card and the ;; The stencil chain, on real pixels: the iris is clipped by the card and the
;; pupil by the iris, and neither is expressed anywhere as a chain. ;; pupil by the iris, and neither is expressed anywhere as a chain.
(let [res (symbol/resolver demo/main nil pal/index-of nil nil)] (let [res (symbol/resolver demo/main nil pal/index-of nil)]
(doseq [f (range 0 demo/frames 4)] (doseq [f (range 0 demo/frames 4)]
(let [before (raster/make (:width demo/clip) (:height demo/clip)) (let [before (raster/make (:width demo/clip) (:height demo/clip))
after (raster/make (:width demo/clip) (:height demo/clip)) after (raster/make (:width demo/clip) (:height demo/clip))
@ -384,9 +384,9 @@
(poly :p :root "a1" [0 0 10 0 10 10] :skin-base)) (poly :p :root "a1" [0 0 10 0 10 10] :skin-base))
day {:skin-base 1} day {:skin-base 1}
night {:skin-base 17}] night {:skin-base 17}]
(is (= 1 (:color (first (symbol/eval-frame s 0 nil day nil nil))))) (is (= 1 (:color (first (symbol/eval-frame s 0 nil day nil)))))
(is (= 17 (:color (first (symbol/eval-frame s 0 nil night nil nil))))) (is (= 17 (:color (first (symbol/eval-frame s 0 nil night nil)))))
(is (= 17 (:color (first ((symbol/resolver s nil night nil nil) 0)))) (is (= 17 (:color (first ((symbol/resolver s nil night nil) 0))))
"and the playback path agrees"))) "and the playback path agrees")))
(deftest a-tone-the-ramp-does-not-define-is-loudly-wrong (deftest a-tone-the-ramp-does-not-define-is-loudly-wrong
@ -394,7 +394,7 @@
;; authored data and should be impossible to miss. ;; authored data and should be impossible to miss.
(let [s (sc {:id :root :kind :group :z "a1"} (let [s (sc {:id :root :kind :group :z "a1"}
(poly :p :root "a1" [0 0 10 0 10 10] :skin-base))] (poly :p :root "a1" [0 0 10 0 10 10] :skin-base))]
(is (= 255 (:color (first (symbol/eval-frame s 0 nil {} nil nil))))))) (is (= 255 (:color (first (symbol/eval-frame s 0 nil {} nil)))))))
(deftest partitioning-the-index-space-stops-two-palettes-colliding-on-a-stencil (deftest partitioning-the-index-space-stops-two-palettes-colliding-on-a-stencil
;; A stencil is a colour key, so two nodes sharing a tone share a stencil — ;; A stencil is a colour key, so two nodes sharing a tone share a stencil —
@ -407,6 +407,6 @@
[:style :color] (ch/framed :iris)}}) [:style :color] (ch/framed :iris)}})
;; :night's tones sit above :day's in one concatenated space ;; :night's tones sit above :day's in one concatenated space
night {:eye-white 14 :iris 15} night {:eye-white 14 :iris 15}
ops (symbol/eval-frame s 0 nil night nil nil)] ops (symbol/eval-frame s 0 nil night nil)]
(is (= 14 (:stencil (second ops))) (is (= 14 (:stencil (second ops)))
"the stencil resolves to the index the stencil node actually drew in"))) "the stencil resolves to the index the stencil node actually drew in")))

View file

@ -52,7 +52,7 @@
(defn- photo-at (defn- photo-at
"The photo matrix of face-1 alone at frame `f`, the still being 1000px tall." "The photo matrix of face-1 alone at frame `f`, the still being 1000px tall."
[c f] [c f]
(let [r (symbol/resolver (clip/symbol c :face-1) @store pal/index-of nil nil) (let [r (symbol/resolver (clip/symbol c :face-1) @store pal/index-of nil)
h (head c)] h (head c)]
(r f) (r f)
(vec (array-seq (trace/photo-matrix (symbol/world-of r :head) h @store (vec (array-seq (trace/photo-matrix (symbol/world-of r :head) h @store
@ -111,7 +111,7 @@
;; The same answer as `nest/placement`, which walks and resolves the path all ;; The same answer as `nest/placement`, which walks and resolves the path all
;; over again — the resolver has it already, from drawing the frame. ;; over again — the resolver has it already, from drawing the frame.
(let [c (wrapped) (let [c (wrapped)
r (clip/resolver c @store pal/index-of :wrap nil) r (clip/resolver c :wrap @store pal/index-of nil)
path [:m :face-1 :head]] path [:m :face-1 :head]]
(doseq [f [0 17 60]] (doseq [f [0 17 60]]
(r f) (r f)

View file

@ -59,7 +59,7 @@
(is (not (ch/nothing? (sample :eye-l f)))) (is (not (ch/nothing? (sample :eye-l f))))
(is (not (ch/nothing? (sample :mouth f))))) (is (not (ch/nothing? (sample :mouth f)))))
(let [drawn (into #{} (map :node) (let [drawn (into #{} (map :node)
((symbol/resolver (clip/symbol clip :face-1) store pal/index-of nil nil) 11))] ((symbol/resolver (clip/symbol clip :face-1) store pal/index-of nil) 11))]
(is (not (contains? drawn :eye-r))) (is (not (contains? drawn :eye-r)))
(is (not (contains? drawn :iris-r))) (is (not (contains? drawn :iris-r)))
(is (contains? drawn :eye-l)) (is (contains? drawn :eye-l))

View file

@ -56,7 +56,7 @@
(defn- ops-at (defn- ops-at
"Ops for one frame of a TIMELINE." "Ops for one frame of a TIMELINE."
[sym f] [sym f]
((symbol/resolver sym @store pal/index-of nil nil) f)) ((symbol/resolver sym @store pal/index-of nil) f))
(defn- render (defn- render
"One frame of a CLIP into a byte buffer. The stage's size comes off the clip and "One frame of a CLIP into a byte buffer. The stage's size comes off the clip and
@ -64,7 +64,7 @@
[c f] [c f]
(let [r (raster/make (:width c) (:height c))] (let [r (raster/make (:width c) (:height c))]
(raster/clear! r (get pal/index-of :bg)) (raster/clear! r (get pal/index-of :bg))
(raster/draw-ops! r ((clip/resolver c @store pal/index-of :main nil) f)) (raster/draw-ops! r ((clip/resolver c :main @store pal/index-of nil) f))
(vec (array-seq (:buf r))))) (vec (array-seq (:buf r)))))
(defn- drawn (defn- drawn
@ -195,7 +195,7 @@
;; checking arithmetic against itself; this checks `node/local!`, `node/world!` ;; checking arithmetic against itself; this checks `node/local!`, `node/world!`
;; and `emit` as well. ;; and `emit` as well.
(let [c (freeze/head-mode {} @frozen) (let [c (freeze/head-mode {} @frozen)
res (clip/resolver c @store pal/index-of :main nil) res (clip/resolver c :main @store pal/index-of nil)
k (first (:value (chan :face [:xform :scale]))) k (first (:value (chan :face [:xform :scale])))
anc (:value (chan :face [:xform :anchor])) anc (:value (chan :face [:xform :anchor]))
pos (:value (chan :face [:xform :pos])) pos (:value (chan :face [:xform :pos]))
@ -249,14 +249,11 @@
"the trace survives the document round trip"))) "the trace survives the document round trip")))
(deftest trace-keys-hold-the-whole-measured-transform (deftest trace-keys-hold-the-whole-measured-transform
(let [free (symbol/resolver (face-symbol (freeze/head-mode {} @frozen)) (let [free (symbol/resolver (face-symbol (freeze/head-mode {} @frozen)) @store pal/index-of nil)
@store pal/index-of nil nil)
held (symbol/resolver (face-symbol held (symbol/resolver (face-symbol
(freeze/head-mode {:trace {:origin :keys :frames [12 88]}} @frozen)) (freeze/head-mode {:trace {:origin :keys :frames [12 88]}} @frozen)) @store pal/index-of nil)
@store pal/index-of nil nil)
start (symbol/resolver (face-symbol start (symbol/resolver (face-symbol
(freeze/head-mode {:trace {:origin :start :frames [12 88]}} @frozen)) (freeze/head-mode {:trace {:origin :start :frames [12 88]}} @frozen)) @store pal/index-of nil)
@store pal/index-of nil nil)
world (fn [resolver frame] world (fn [resolver frame]
(resolver frame) (resolver frame)
(vec (array-seq (symbol/world-of resolver :head))))] (vec (array-seq (symbol/world-of resolver :head))))]
@ -511,7 +508,7 @@
(is (not (ch/nothing? (at :eye-r 60)))) (is (not (ch/nothing? (at :eye-r 60))))
(is (not (ch/nothing? (at :eye-l 50)))) (is (not (ch/nothing? (at :eye-l 50))))
(is (not (ch/nothing? (at :mouth 50)))) (is (not (ch/nothing? (at :mouth 50))))
(let [drawn-nodes (into #{} (map :node) ((symbol/resolver sym (:store c) pal/index-of nil nil) 50))] (let [drawn-nodes (into #{} (map :node) ((symbol/resolver sym (:store c) pal/index-of nil) 50))]
(is (not (contains? drawn-nodes :eye-r))) (is (not (contains? drawn-nodes :eye-r)))
(is (contains? drawn-nodes :eye-l)) (is (contains? drawn-nodes :eye-l))
(is (contains? drawn-nodes :mouth))) (is (contains? drawn-nodes :mouth)))
@ -586,7 +583,7 @@
;; Hoisted: the resolver caches its order and reuses its buffers, so the ;; Hoisted: the resolver caches its order and reuses its buffers, so the
;; node ids come out before the next frame is asked for. ;; node ids come out before the next frame is asked for.
nodes-at (fn [c] nodes-at (fn [c]
(let [r (symbol/resolver (face-symbol (:clip c)) (:store c) pal/index-of nil nil)] (let [r (symbol/resolver (face-symbol (:clip c)) (:store c) pal/index-of nil)]
(fn [f] (into #{} (map :node) (r f))))) (fn [f] (into #{} (map :node) (r f)))))
ref-at (nodes-at ref) ref-at (nodes-at ref)
occ-at (nodes-at occ) occ-at (nodes-at occ)
@ -623,7 +620,7 @@
c (freeze/clip (assoc take/params :name "gappy") c (freeze/clip (assoc take/params :name "gappy")
{:face-1 (assoc @take/measured :detected det)}) {:face-1 (assoc @take/measured :detected det)})
sym (face-symbol (:clip c)) sym (face-symbol (:clip c))
res (symbol/resolver sym (:store c) pal/index-of nil nil)] res (symbol/resolver sym (:store c) pal/index-of nil)]
(doseq [f [39 40 50 59 60]] (doseq [f [39 40 50 59 60]]
(let [ops (res f)] (let [ops (res f)]
(if (contains? gap f) (if (contains? gap f)
@ -682,7 +679,7 @@
shot (fn [f] shot (fn [f]
(let [r (raster/make W H) (let [r (raster/make W H)
mouth (filter #(= [:face-1 :mouth] (:node %)) mouth (filter #(= [:face-1 :mouth] (:node %))
((clip/resolver locked @store pal/index-of :main nil) f))] ((clip/resolver locked :main @store pal/index-of nil) f))]
(raster/clear! r (get pal/index-of :bg)) (raster/clear! r (get pal/index-of :bg))
(raster/draw-ops! r mouth) (raster/draw-ops! r mouth)
(vec (array-seq (:buf r))))) (vec (array-seq (:buf r)))))

View file

@ -35,7 +35,7 @@
(defn channel [entry subject node path] (defn channel [entry subject node path]
(get-in entry [:clip :symbols subject :nodes node :channels path])) (get-in entry [:clip :symbols subject :nodes node :channels path]))
(defn snapshot [c store f] (ops/snapshot ((clip/resolver c store pal/index-of :main nil) f))) (defn snapshot [c store f] (ops/snapshot ((clip/resolver c :main store pal/index-of nil) f)))
(defn by-node [c store f] (into {} (map (juxt :node identity)) (snapshot c store f))) (defn by-node [c store f] (into {} (map (juxt :node identity)) (snapshot c store f)))
(deftest subjects-share-local-names-without-sharing-blocks (deftest subjects-share-local-names-without-sharing-blocks
@ -178,8 +178,8 @@
(merge (dissoc (:nodes (clip/symbol clip :main)) :face-1) (merge (dissoc (:nodes (clip/symbol clip :main)) :face-1)
(assoc-in (get-in clip [:symbols :face-1 :nodes]) (assoc-in (get-in clip [:symbols :face-1 :nodes])
[:head :parent] :face))) [:head :parent] :face)))
nested (clip/resolver clip store pal/index-of :main nil) nested (clip/resolver clip :main store pal/index-of nil)
reference (clip/resolver flat store pal/index-of :main nil)] reference (clip/resolver flat :main store pal/index-of nil)]
(doseq [f [0 1 7 20 39]] (doseq [f [0 1 7 20 39]]
(let [a (ops/snapshot (nested f)) b (ops/snapshot (reference f))] (let [a (ops/snapshot (nested f)) b (ops/snapshot (reference f))]
(is (= (mapv (comp second :node) a) (mapv :node b))) (is (= (mapv (comp second :node) a) (mapv :node b)))

View file

@ -52,11 +52,11 @@
"(fn [f] -> snapshot) through `eval-frame`, the specification." "(fn [f] -> snapshot) through `eval-frame`, the specification."
([sym store] (specified sym store pal/index-of)) ([sym store] (specified sym store pal/index-of))
([sym store palette] ([sym store palette]
(fn [f] (snapshot (symbol/eval-frame sym f store palette nil nil))))) (fn [f] (snapshot (symbol/eval-frame sym f store palette nil)))))
(defn resolved (defn resolved
"(fn [f] -> snapshot) through `resolver`, the playback path." "(fn [f] -> snapshot) through `resolver`, the playback path."
([sym store] (resolved sym store pal/index-of)) ([sym store] (resolved sym store pal/index-of))
([sym store palette] ([sym store palette]
(let [res (symbol/resolver sym store palette nil nil)] (let [res (symbol/resolver sym store palette nil)]
(fn [f] (snapshot (res f)))))) (fn [f] (snapshot (res f))))))