Make every caller say what it means: no defaulted arities

Pre-alpha. Nothing here is owed a call shape it used to have.

Twelve convenience arities deleted, and the only reason to single any of
them out is that one of them was a live bug: `channel/value-at`'s `([ch f])`
filled in a nil tier-2 store, so a caller could omit it, read correctly for
every channel that happened not to be dense, and throw the first time one
was. That is the iris crash, and threading the store through `gesture/values`
last commit fixed the symptom while leaving the trapdoor open. Deleting the
arity found `node/toggle-key` standing on it too — the inspector's stopwatch
on a measured channel, the same throw, never reported.

Gone, and what the compiler then made explicit at each site:

  channel/value-at, cursor, dense-at   the store, and `nil` where a caller
                                       genuinely has none and means it
  channel/keyed                        `:hold`, which is a cut rather than a
                                       tween and not a thing to leave implied
  symbol/resolver (4), eval-frame (3)  store, palette, pose-tracks, opts
  clip/resolver                        opts
  mix/buffer!, store/install!          dead: no caller used the short form

`pick/local-bounds` goes the same way — it was `bounds-of` with the closure
thrown away, so callers build the closure and call it.

Every site was found by shadow-cljs `:fn-arity` rather than by grep, which is
the argument for the change: 90-odd call sites, and the compiler listed all of
them. BUILD BOTH TARGETS — the last three only appear in `:app`, since `:test`
compiles what the tests reach and the inspector, the pool drag and the vertex
overlay are not that.

Left alone, because an argument with a default is not the same thing as a
shim: genuine optionality like `fx/http`'s body, `geom`'s iteration count,
`zip`'s injected clock.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-09-30 12:10:22 -04:00
parent 11093079de
commit ee66680a0c
30 changed files with 224 additions and 217 deletions

View file

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

View file

@ -11,55 +11,55 @@
(deftest framed-is-the-same-value-at-every-frame
(let [c (ch/framed :skin-dark)]
(is (= :framed (ch/describe c)))
(is (every? #(= :skin-dark (ch/value-at c %)) (range -5 20)))))
(is (every? #(= :skin-dark (ch/value-at c % nil)) (range -5 20)))))
(deftest keyed-holds-until-the-next-key
;; Hold is the DEFAULT, not a special case: docs/design.md requires it of every
;; cut part, and a tweened mouth reads as puppet software.
(let [c (ch/keyed {0 :a, 4 :b, 12 :c})]
(let [c (ch/keyed {0 :a, 4 :b, 12 :c} :hold)]
(is (= :keyed (ch/describe c)))
(is (= [:a :a :a :a :b :b :b :b :b :b :b :b :c :c]
(mapv #(ch/value-at c %) (range 0 14))))))
(mapv #(ch/value-at c % nil) (range 0 14))))))
(deftest linear-vector-keys-interpolate-each-component
(let [c (ch/keyed {0 [0.4 0.6], 10 [0.6 0.4]} :linear)
frames [0 5 10 5 2]
cursor (ch/cursor c)]
cursor (ch/cursor c nil)]
(is (empty? (ch/problems c)))
(is (= [0.5 0.5] (ch/value-at c 5)))
(is (= (mapv #(ch/value-at c %) frames)
(is (= [0.5 0.5] (ch/value-at c 5 nil)))
(is (= (mapv #(ch/value-at c % nil) frames)
(mapv #(ch/sample! cursor %) frames)))))
(deftest one-channel-can-cut-then-tween
(let [c (assoc (ch/keyed {0 [0 0], 4 [4 0], 8 [8 0]})
(let [c (assoc (ch/keyed {0 [0 0], 4 [4 0], 8 [8 0]} :hold)
:segments {4 :linear})
cursor (ch/cursor c)]
(is (= [0 0] (ch/value-at c 2)))
(is (= [4 0] (ch/value-at c 4)))
(is (= [6 0] (ch/value-at c 6)))
(is (= (mapv #(ch/value-at c %) [0 2 4 6 8 3 7])
cursor (ch/cursor c nil)]
(is (= [0 0] (ch/value-at c 2 nil)))
(is (= [4 0] (ch/value-at c 4 nil)))
(is (= [6 0] (ch/value-at c 6 nil)))
(is (= (mapv #(ch/value-at c % nil) [0 2 4 6 8 3 7])
(mapv #(ch/sample! cursor %) [0 2 4 6 8 3 7])))))
(deftest a-frame-before-the-first-key-reads-the-first-key
;; The JS activeKey clamps low, and that is kept: a channel's first key is the
;; pose the part starts in. Having NO value is a different question — it is a
;; state bit, not an empty region of the key map.
(let [c (ch/keyed {10 :a, 20 :b})]
(is (= :a (ch/value-at c 0)))
(is (= :a (ch/value-at c 9)))
(is (= :b (ch/value-at c 999)) "and clamps high by holding the last key")))
(let [c (ch/keyed {10 :a, 20 :b} :hold)]
(is (= :a (ch/value-at c 0 nil)))
(is (= :a (ch/value-at c 9 nil)))
(is (= :b (ch/value-at c 999 nil)) "and clamps high by holding the last key")))
(deftest keys-are-a-map-so-frame-order-in-the-literal-cannot-matter
;; Transit and JSON both lose sortedness, so the sorted index is built at read
;; time. A resolver that trusted insertion order would work in the REPL and
;; fail after a round trip through the server, which is the worst possible way
;; to find out.
(let [forward (ch/keyed (array-map 0 :a, 4 :b, 12 :c))
backward (ch/keyed (array-map 12 :c, 4 :b, 0 :a))
shuffled (ch/keyed (array-map 4 :b, 12 :c, 0 :a))]
(let [forward (ch/keyed (array-map 0 :a, 4 :b, 12 :c) :hold)
backward (ch/keyed (array-map 12 :c, 4 :b, 0 :a) :hold)
shuffled (ch/keyed (array-map 4 :b, 12 :c, 0 :a) :hold)]
(doseq [c [backward shuffled]]
(is (= (mapv #(ch/value-at forward %) (range 0 16))
(mapv #(ch/value-at c %) (range 0 16)))))))
(is (= (mapv #(ch/value-at forward % nil) (range 0 16))
(mapv #(ch/value-at c % nil) (range 0 16)))))))
(deftest dense-reads-one-value-per-frame-out-of-a-typed-array
(let [store {"blk" {:data (js/Int16Array. #js [0 0, 10 20, 30 40, 50 60]) :state nil}}
@ -148,7 +148,7 @@
"Sample one cursor at each of `fs` in the order given, which is the point: a
cursor carries state between calls."
[c fs]
(let [cur (ch/cursor c)]
(let [cur (ch/cursor c nil)]
(mapv #(ch/sample! cur %) fs)))
(deftest the-cursor-agrees-with-the-specification-in-any-frame-order
@ -156,11 +156,11 @@
;; the WRONG POSE rather than an error, so nothing would report it: the mouth
;; would simply be a beat behind on some frames and not others, which reads as
;; a bad take.
(doseq [[label c] [["sparse" (ch/keyed {0 :a, 4 :b, 12 :c, 13 :d, 40 :e})]
["one key" (ch/keyed {7 :only})]
["dense-ish" (ch/keyed (into {} (map (juxt identity #(* 10 %))) (range 40)))]
(doseq [[label c] [["sparse" (ch/keyed {0 :a, 4 :b, 12 :c, 13 :d, 40 :e} :hold)]
["one key" (ch/keyed {7 :only} :hold)]
["dense-ish" (ch/keyed (into {} (map (juxt identity #(* 10 %))) (range 40)) :hold)]
["framed" (ch/framed :static)]]]
(let [spec #(ch/value-at c %)
(let [spec #(ch/value-at c % nil)
forward (range 0 45)
back (reverse forward)
jumpy [0 44 1 43 12 12 13 3 40 7 0 22 22 21 44]]
@ -220,20 +220,20 @@
;; step. Dropping one silently would present as a hand correction that did not
;; take — a correction the user made once, watched fail, and has no reason to
;; trust again.
(let [c (assoc (ch/keyed {0 [0 0]}) :over [{:blend :offset :keys {0 [2 0]}}])]
(is (thrown-with-msg? ExceptionInfo #":over" (ch/value-at c 0)))
(is (thrown-with-msg? ExceptionInfo #":over" (ch/cursor c)))
(let [c (assoc (ch/keyed {0 [0 0]} :hold) :over [{:blend :offset :keys {0 [2 0]}}])]
(is (thrown-with-msg? ExceptionInfo #":over" (ch/value-at c 0 nil)))
(is (thrown-with-msg? ExceptionInfo #":over" (ch/cursor c nil)))
(is (seq (ch/problems c)))))
(deftest an-empty-over-is-fine-and-is-what-scenes-carry
(is (empty? (ch/problems (ch/keyed {0 1}))))
(is (= 1 (ch/value-at (ch/keyed {0 1}) 0))))
(is (empty? (ch/problems (ch/keyed {0 1} :hold))))
(is (= 1 (ch/value-at (ch/keyed {0 1} :hold) 0 nil))))
;; ---- shape validation ----
(deftest problems-names-the-ways-a-channel-is-malformed
(is (empty? (ch/problems (ch/framed 1))))
(is (empty? (ch/problems (ch/keyed {0 1}))))
(is (empty? (ch/problems (ch/keyed {0 1} :hold))))
(testing "keys as a vector is the mistake most worth catching"
(is (seq (ch/problems {:animated? true :keys [[0 1]]}))))
(is (seq (ch/problems {:value 1})) "no :animated?")
@ -248,9 +248,9 @@
(deftest numeric-channels-can-ramp-between-keys
(let [c (ch/keyed {0 0.0, 10 1.0} :linear)
cursor (ch/cursor c)]
cursor (ch/cursor c nil)]
(is (= [0.0 0.5 1.0 1.0]
(mapv #(ch/value-at c %) [0 5 10 15])))
(mapv #(ch/value-at c % nil) [0 5 10 15])))
(is (= [0.0 0.5 1.0 0.2]
(mapv #(ch/sample! cursor %) [0 5 10 2])))))

View file

@ -34,7 +34,7 @@
(defn- drawn [c path]
(partition 2 (take 6 (array-seq (:pts (first (filter #(= path (:node %))
((clip/resolver c nil pal/index-of :main) 16))))))))
((clip/resolver c nil pal/index-of :main nil) 16))))))))
(defn- near? [a b] (every? #(< (js/Math.abs %) 1e-9) (map - (flatten a) (flatten b))))
@ -98,7 +98,7 @@
[c st open path f]
(let [{:keys [sid id world frame]} (nest/placement c st open path f)
n (get-in c [:symbols sid :nodes id])
[x0 y0 x1 y1] (pick/local-bounds c st n frame)]
[x0 y0 x1 y1] ((pick/bounds-of c st n) frame)]
{:corners (mapv #(at world %) [[x0 y0] [x1 y0] [x1 y1] [x0 y1]])
:pivot (at world (:anchor (gesture/values n frame st)))}))
@ -259,7 +259,7 @@
(deftest a-measured-transform-is-not-set-by-hand
(is (string? (gesture/refusal {:channels {[:xform :pos] {:animated? true :dense {:stride 2}}}})))
(is (nil? (gesture/refusal {:channels {[:xform :pos] (ch/keyed {0 [1 1]})}}))))
(is (nil? (gesture/refusal {:channels {[:xform :pos] (ch/keyed {0 [1 1]} :hold)}}))))
(deftest a-click-selects-the-level-figma-would
(let [hit [:a :b :c :shape]]
@ -287,5 +287,5 @@
(deftest an-instances-box-is-what-its-symbol-draws
(let [c (two-down)
{:keys [frame]} (nest/placement c nil :main [u v] 16)]
(is (= [0 0 10 10] (pick/local-bounds c nil (get-in c [:symbols :mid :nodes v]) frame)))
(is (= [0 0 10 10] (pick/local-bounds c nil (get-in c [:symbols :box :nodes :shape]) 4)))))
(is (= [0 0 10 10] ((pick/bounds-of c nil (get-in c [:symbols :mid :nodes v])) frame)))
(is (= [0 0 10 10] ((pick/bounds-of c nil (get-in c [:symbols :box :nodes :shape])) 4)))))

View file

@ -17,7 +17,7 @@
:nodes {:root {:id :root :kind :group :z "a1"}
:mark {:id :mark :kind :rect :parent :root :z "a1"
:channels {[:xform :pos] (ch/keyed {0 [0 0] 1 [10 0]
2 [20 0] 3 [30 0]})
2 [20 0] 3 [30 0]} :hold)
[:geom :size] (ch/framed 4)
[:style :color] (ch/framed :brow)}}}}}})
@ -36,7 +36,7 @@
:channels {[:xform :pos] (ch/framed [120 50])}}}})
(assoc-in [:symbols :sym/test]
(assoc (get-in source [:symbols :main]) :id :sym/test)))
resolve (clip/resolver document nil pal/index-of :main)
resolve (clip/resolver document nil pal/index-of :main nil)
at (fn [f] (mapv (juxt :node :cx) (resolve f)))]
(is (empty? (clip/problems document)))
(is (= [[[:left :mark] 110]] (at 1)))
@ -46,13 +46,13 @@
(deftest a-placement-holds-and-cuts-each-generated-shape-independently
(let [values (js/Int16Array. (clj->js (range 2 32)))
visible (ch/keyed {0 true 20 true 21 false})
visible (ch/keyed {0 true 20 true 21 false} :hold)
dense {:animated? true :interp :hold
:dense {:store "sizes" :offset 0 :stride 1 :frames 30}
:pose-sampled? true}
shape (fn [id z group]
{:id id :kind :rect :parent :root :z z :pose-group group
:channels {[:xform :pos] (ch/keyed {0 [0 0] 8 [8 0]})
:channels {[:xform :pos] (ch/keyed {0 [0 0] 8 [8 0]} :hold)
[:geom :size] dense
[:vis] (assoc visible :pose-sampled? true)
[:style :color] (ch/framed :brow)}})
@ -75,7 +75,7 @@
:parent :root :z "a2"
:playback {:tracks {:mouth {0 0, 8 8}}}}}}
:sym/poses symbol}}
resolve (clip/resolver document {"sizes" {:data values}} pal/index-of :main)
resolve (clip/resolver document {"sizes" {:data values}} pal/index-of :main nil)
low-resolve (clip/resolver document {"sizes" {:data values}}
pal/index-of :main {:picture-fps 8})
at (fn [f] (into {} (map (fn [op] [(:node op) op])) (resolve f)))
@ -178,15 +178,15 @@
scale (get-in left [:channels [:xform :scale]])
anchor (get-in left [:channels [:xform :anchor] :value])
pos (get-in left [:channels [:xform :pos]])
start-pos (ch/value-at pos 0)]
start-pos (ch/value-at pos 0 nil)]
(is (= [160 100] anchor) "the source center becomes a stored pivot")
(is (= [-120 -60] start-pos))
(is (not= start-pos (ch/value-at pos 40)) "the face drifts during playback")
(is (= [0.4 0.4] (ch/value-at scale 0)))
(is (= [0.56 0.56] (ch/value-at scale 12)))
(is (= [0.52 0.52] (ch/value-at scale 48)))
(is (not= start-pos (ch/value-at pos 40 nil)) "the face drifts during playback")
(is (= [0.4 0.4] (ch/value-at scale 0 nil)))
(is (= [0.56 0.56] (ch/value-at scale 12 nil)))
(is (= [0.52 0.52] (ch/value-at scale 48 nil)))
(doseq [f [0 12 48]]
(let [m (node/local! (node/mat) start-pos 0 (ch/value-at scale f) [0 0] anchor)
(let [m (node/local! (node/mat) start-pos 0 (ch/value-at scale f nil) [0 0] anchor)
out (js/Float64Array. 2)]
(node/apply-pt! out 0 m 160 100)
(is (= [40 40] [(aget out 0) (aget out 1)])
@ -200,10 +200,10 @@
(is (= [48 260] (node/placed-span (placement document :voice-right))))
(is (= 0.5 (ch/value-at
(get-in (placement document :voice-right)
[:channels [:audio :gain]]) 54)))
[:channels [:audio :gain]]) 54 nil)))
(is (< -0.8 (ch/value-at
(get-in (placement document :voice-right)
[:channels [:audio :pan]]) 110) 0.7))
[:channels [:audio :pan]]) 110 nil) 0.7))
(is (= document (leaf/clip "stage" (leaf/leaves "stage" document))))))
(defn- nested

View file

@ -40,7 +40,7 @@
{:keys [sid frame pts]} (nest/drawn-inside c nil :main [u] 16 drawn)
c (paint/new-shape c sid :shape frame pts :brow)
[op] (filter #(= [u :shape] (:node %))
((clip/resolver c nil pal/index-of :main) 16))]
((clip/resolver c nil pal/index-of :main nil) 16))]
(is (= :box sid))
(is (= 6 frame) "frame 16 of main is frame 6 of an instance placed at 10")
(is (every? #(< (js/Math.abs %) 1e-9)
@ -64,7 +64,7 @@
(turn :mid v [5 -3] 0.3 1.5)
(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)))
((clip/resolver % nil pal/index-of :main) 16))))))
((clip/resolver % nil pal/index-of :main nil) 16))))))
{:keys [frame matrix time]} (nest/inside c nil :main [u v :shape] 16)
out (js/Float64Array. 2)
seen (mapcat (fn [[x y]] (vec (array-seq (node/apply-pt! out 0 matrix x y))))
@ -86,7 +86,7 @@
(deftest a-placed-symbols-sound-is-heard-where-it-is-placed
(let [voice {:id :v :kind :audio :source {:footage "f"} :z "a1"
:span [10 40] :time {:mode :map :at -10 :rate 1}
:channels {[:audio :gain] (ch/keyed {0 0.0 5 1.0})}}
:channels {[:audio :gain] (ch/keyed {0 0.0 5 1.0} :hold)}}
c (-> (clip/blank)
(assoc-in [:symbols :talk] {:id :talk :frames 30 :nodes {:v voice}})
(clip/place-symbol nil :main :talk 50 #uuid "00000000-0000-4000-8000-0000000000bb" nil))
@ -105,7 +105,7 @@
"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."
[c sid fs]
(let [resolve (clip/resolver c nil pal/index-of sid)
(let [resolve (clip/resolver c nil pal/index-of sid nil)
round #(/ (js/Math.round (* 1000 %)) 1000)]
(mapv (fn [f]
(sort-by str (map (fn [op]
@ -122,7 +122,7 @@
[]
(let [tri (fn [id x keyed]
{:id id :kind :poly :z "a1" :paint? true :span [4 60]
:channels {[:geom :pts] (ch/keyed (into {} (map (fn [[f dx]] [f [x 10 (+ x dx) 10 x 40]])) keyed))
:channels {[:geom :pts] (ch/keyed (into {} (map (fn [[f dx]] [f [x 10 (+ x dx) 10 x 40]])) keyed) :hold)
[:style :color] (ch/framed :brow)}})]
(-> (clip/blank)
(assoc-in [:symbols :main :nodes :tri] (tri :tri 100 {4 20 30 40}))
@ -199,7 +199,7 @@
(update-in [:symbols :main :nodes] dissoc :tri)
(assoc-in [:symbols :main :nodes a-uuid :time :rate] 2)
(assoc-in [:symbols :box :nodes :inner :channels [:geom :pts]]
(ch/keyed {4 [5 10 15 10 5 40] 20 [5 10 45 10 5 40]})))
(ch/keyed {4 [5 10 15 10 5 40] 20 [5 10 45 10 5 40]} :hold)))
{slid :clip :as r} (nest/slide c :main [a-uuid :inner] 6)
fs [13 15 18 20]]
(is (nil? (:refused r)) (:refused r))
@ -251,7 +251,7 @@
(is (= [0 8] (:span heard))
"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 (empty? ((clip/resolver c nil pal/index-of :inner) 3))
(is (empty? ((clip/resolver c nil pal/index-of :inner nil) 3))
"and it draws nothing")
(is (= c (clip/place-sound c :inner {:sound "tone"} "tone.mp3" 40 1 10 (random-uuid)))
"nor lands past the end of its symbol")

View file

@ -145,14 +145,14 @@
(deftest transform-channels-default-to-the-identity
(let [chs (node/channels {:id :x :kind :group})]
(is (= [0.0 0.0] (ch/value-at (get chs [:xform :pos]) 0)))
(is (= [1.0 1.0] (ch/value-at (get chs [:xform :scale]) 0)))
(is (= true (ch/value-at (get chs [:vis]) 0))))
(is (= [0.0 0.0] (ch/value-at (get chs [:xform :pos]) 0 nil)))
(is (= [1.0 1.0] (ch/value-at (get chs [:xform :scale]) 0 nil)))
(is (= true (ch/value-at (get chs [:vis]) 0 nil))))
(testing "and a node's own channels win"
(let [chs (node/channels {:id :x :kind :group
:channels {[:xform :pos] (ch/framed [5 5])}})]
(is (= [5 5] (ch/value-at (get chs [:xform :pos]) 0)))
(is (= [1.0 1.0] (ch/value-at (get chs [:xform :scale]) 0))))))
(is (= [5 5] (ch/value-at (get chs [:xform :pos]) 0 nil)))
(is (= [1.0 1.0] (ch/value-at (get chs [:xform :scale]) 0 nil))))))
(deftest skew-and-anchor-are-in-the-shape-although-nothing-drives-them
;; A decomposition is not extensible after the fact: adding a component later
@ -193,26 +193,26 @@
(deftest keying-a-channel-from-the-inspector
(let [n {:id :x :kind :group}
a (node/set-channel n [:xform :rot] 3 1.0)
b (node/toggle-key a [:xform :rot] 3)
b (node/toggle-key a [:xform :rot] 3 nil)
c (-> b (node/set-channel [:xform :pos] 9 [5 5])
(node/toggle-key [:xform :pos] 0)
(node/toggle-key [:xform :pos] 0 nil)
(node/set-channel [:xform :pos] 10 [10 0]))
rot #(ch/value-at (get (node/channels %1) [:xform :rot]) %2)
pos #(ch/value-at (get (node/channels %1) [:xform :pos]) %2)]
rot #(ch/value-at (get (node/channels %1) [:xform :rot]) %2 nil)
pos #(ch/value-at (get (node/channels %1) [:xform :pos]) %2 nil)]
(is (= 1.0 (rot a 50)) "an unkeyed channel is its one value")
(is (= {3 1.0} (get-in b [:channels [:xform :rot] :keys])) "the first key is its value here")
(is (= [7.5 2.5] (pos c 5)) "an edit on a keyed channel keys it, and keys tween")
(is (= 1.0 (rot (node/set-channel b [:xform :rot] 8 2.0) 3)) "without moving the key before it")
(let [d (node/toggle-key b [:xform :rot] 3)]
(let [d (node/toggle-key b [:xform :rot] 3 nil)]
(is (not (:animated? (get-in d [:channels [:xform :rot]]))) "the last key off is one value again")
(is (= 1.0 (rot d 0))))
(is (= :hold (get-in (node/toggle-key n [:vis] 0) [:channels [:vis] :interp])) "a boolean holds")
(is (= :hold (get-in (node/toggle-key n [:vis] 0 nil) [:channels [:vis] :interp])) "a boolean holds")
(let [h (node/set-segment-interp c [:xform :pos] 0 :hold)]
(is (= [5 5] (pos h 5)) "a gap set to hold cuts at the next key")
(is (= [10 0] (pos h 10)))
(is (= [7.5 2.5] (pos (node/set-segment-interp h [:xform :pos] 0 :linear) 5)) "and back to a tween")
(is (= h (node/set-segment-interp h [:xform :pos] 10 :hold)) "the last key has no gap after it")
(is (empty? (ch/problems (get-in h [:channels [:xform :pos]])))))
(let [d (node/toggle-key (node/set-segment-interp c [:xform :pos] 0 :hold) [:xform :pos] 0)]
(let [d (node/toggle-key (node/set-segment-interp c [:xform :pos] 0 :hold) [:xform :pos] 0 nil)]
(is (not (contains? (get-in d [:channels [:xform :pos] :segments]) 0))
"taking a key off takes its gap's choice with it"))))

View file

@ -5,6 +5,7 @@
[arthur.domain.leaf :as leaf]
[arthur.domain.node :as node]
[arthur.domain.paint :as paint]
[arthur.domain.palette :as pal]
[arthur.domain.symbol :as symbol]))
(defn- geometry [clip]
@ -22,13 +23,13 @@
node/set-segment-interp paint/geometry 9 :linear)
mixed (geometry mixed-clip)]
(is (= [3 229] (get-in c2 [:symbols :main :nodes :paint-test :span])))
(is (= a (channel/value-at held 8)))
(is (= 10 (first (channel/value-at held 8))))
(is (= 22 (first (channel/value-at held 9))))
(is (= 10 (first (channel/value-at mixed 6))) "the first gap cuts")
(is (= 28 (first (channel/value-at mixed 12))) "the second gap tweens")
(is (= a (channel/value-at held 8 nil)))
(is (= 10 (first (channel/value-at held 8 nil))))
(is (= 22 (first (channel/value-at held 9 nil))))
(is (= 10 (first (channel/value-at mixed 6 nil))) "the first gap cuts")
(is (= 28 (first (channel/value-at mixed 12 nil))) "the second gap tweens")
(is (empty? (channel/problems mixed)))
;; The demo's root is exposed on 2s. Paint at frame 3 must still appear at 3.
(is (some #(= :paint-test (:node %))
(symbol/eval-frame (get-in c2 [:symbols :main]) 3)))
(symbol/eval-frame (get-in c2 [:symbols :main]) 3 nil pal/index-of nil nil)))
(is (= mixed-clip (leaf/clip :c1 (leaf/leaves :c1 mixed-clip))))))

View file

@ -21,6 +21,7 @@
[arthur.demo.take :as take]
[arthur.domain.channel :as ch]
[arthur.domain.clip :as clip]
[arthur.domain.palette :as pal]
[arthur.domain.project :as project]
[arthur.domain.symbol :as symbol]
[arthur.flow.freeze :as freeze]
@ -147,7 +148,7 @@
;; frame rather than hidden, and its partner is not.
(let [back (wired :c1 @gappy)
drawn (into #{} (map :node)
((symbol/resolver (face-symbol (:clip back)) (:store back)) 12))]
((symbol/resolver (face-symbol (:clip back)) (:store back) pal/index-of nil nil) 12))]
(is (not (contains? drawn :eye-r)))
(is (contains? drawn :eye-l))
(is (contains? drawn :mouth))))

View file

@ -27,7 +27,7 @@
{:nodes (into {} (map (juxt :id identity)) nodes)})
(defn- ids-at [scene f]
(mapv :node (symbol/eval-frame scene f)))
(mapv :node (symbol/eval-frame scene f nil pal/index-of nil nil)))
(def ^:private pts-of ops/points)
@ -70,9 +70,9 @@
(is (identical? (get-in s [:nodes :b]) (get-in s' [:nodes :b]))
"and so is the new one")
(is (= [[0 0] [10 0] [10 10]]
(pts-of (first (filter #(= :c (:node %)) (symbol/eval-frame s 0))))))
(pts-of (first (filter #(= :c (:node %)) (symbol/eval-frame s 0 nil pal/index-of nil nil))))))
(is (= [[100 0] [110 0] [110 10]]
(pts-of (first (filter #(= :c (:node %)) (symbol/eval-frame s' 0))))))))
(pts-of (first (filter #(= :c (:node %)) (symbol/eval-frame s' 0 nil pal/index-of nil nil))))))))
;; ---- draw order ----
@ -129,7 +129,7 @@
:channels {[:xform :pos] (ch/framed [100 50])
[:xform :scale] (ch/framed [2 2])}}
(poly :p :g "a1" [0 0 10 0 10 10 0 10] :skin-base))
op (first (symbol/eval-frame s 0))]
op (first (symbol/eval-frame s 0 nil pal/index-of nil nil))]
(is (= [[100 50] [120 50] [120 70] [100 70]] (pts-of op)))))
(deftest a-keyed-group-position-moves-its-children-and-holds-between-keys
@ -137,9 +137,9 @@
;; group whose [:xform :pos] is keyed on four frames.
(let [s (sc {:id :g :kind :group :z "a1"
:channels {[:xform :pos]
(ch/keyed {0 [0 0], 4 [10 0], 8 [10 10], 12 [0 10]})}}
(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))
at #(first (pts-of (first (symbol/eval-frame s %))))]
at #(first (pts-of (first (symbol/eval-frame s % nil pal/index-of nil nil))))]
(is (= [0 0] (at 0)))
(is (= [0 0] (at 3)) "held")
(is (= [10 0] (at 4)))
@ -154,17 +154,17 @@
;; odd frames against a mouth cutting on even ones reads as two performances.
(let [s (sc {:id :root :kind :group :z "a1" :time {:mode :map :expose 3}}
{:id :g :kind :group :parent :root :z "a1"
:channels {[:xform :pos] (ch/keyed (into {} (map (juxt identity #(vector % 0))) (range 12)))}}
: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))
x-at #(first (first (pts-of (first (symbol/eval-frame s %)))))]
x-at #(first (first (pts-of (first (symbol/eval-frame s % nil pal/index-of nil nil)))))]
(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"
(let [s (sc {:id :root :kind :group :z "a1" :time {:mode :map :expose 2}}
{: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)))}}
: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))
x-at #(first (first (pts-of (first (symbol/eval-frame s %)))))]
x-at #(first (first (pts-of (first (symbol/eval-frame s % nil pal/index-of nil nil)))))]
(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
@ -173,12 +173,12 @@
(let [keys (into {} (map (juxt identity #(vector % 0))) (range 12))
s (sc {:id :root :kind :group :z "a1"}
{:id :plate :kind :group :parent :root :z "a1"
:channels {[:xform :pos] (ch/keyed keys)}}
:channels {[:xform :pos] (ch/keyed keys :hold)}}
(poly :plate-p :plate "a1" [0 0 1 0 1 1] :skin-base)
{:id :mouth :kind :group :parent :root :z "a2" :time {:mode :map :offset 2}
:channels {[:xform :pos] (ch/keyed keys)}}
:channels {[:xform :pos] (ch/keyed keys :hold)}}
(poly :mouth-p :mouth "a1" [0 0 1 0 1 1] :mouth-dark))
x-of (fn [f id] (->> (symbol/eval-frame s f)
x-of (fn [f id] (->> (symbol/eval-frame s f nil pal/index-of nil nil)
(filter #(= id (:node %))) first pts-of first first))]
(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")))
@ -194,12 +194,12 @@
{:span [2 5]
:channels {[:geom :pts] (ch/framed [0 0 1 0 1 1])
[:style :color] (ch/framed :brow)
[:vis] (ch/keyed {0 true, 3 false, 4 true})}}))]
[:vis] (ch/keyed {0 true, 3 false, 4 true} :hold)}}))]
(is (= [[] [] [:p] [] [:p] [] []] (mapv #(ids-at s %) (range 7))))))
(deftest a-hidden-group-takes-its-children-with-it
(let [s (sc {:id :g :kind :group :z "a1"
:channels {[:vis] (ch/keyed {0 true, 2 false})}}
:channels {[:vis] (ch/keyed {0 true, 2 false} :hold)}}
(poly :p :g "a1" [0 0 1 0 1 1] :brow))]
(is (= [:p] (ids-at s 0)))
(is (= [] (ids-at s 2)))))
@ -221,11 +221,11 @@
:dense {:store "pts" :offset 0 :stride 6 :frames 2}}
[:style :color] (ch/framed :mouth-dark)}}
(poly :teeth :m "a2" [0 0 1 0 1 1] :teeth))]
(is (= [:child] (mapv :node (symbol/eval-frame absent-pos 0 store))))
(is (= [] (mapv :node (symbol/eval-frame absent-pos 1 store)))
(is (= [:child] (mapv :node (symbol/eval-frame absent-pos 0 store pal/index-of nil nil))))
(is (= [] (mapv :node (symbol/eval-frame absent-pos 1 store pal/index-of nil nil)))
"an absent transform gives the children nowhere to be")
(is (= [:m :teeth] (mapv :node (symbol/eval-frame absent-pts 0 store))))
(is (= [:teeth] (mapv :node (symbol/eval-frame absent-pts 1 store)))
(is (= [:m :teeth] (mapv :node (symbol/eval-frame absent-pts 0 store pal/index-of nil nil))))
(is (= [:teeth] (mapv :node (symbol/eval-frame absent-pts 1 store pal/index-of nil nil)))
"an absent outline removes only itself")))
;; ---- stencils ----
@ -239,7 +239,7 @@
{:id :iris :kind :disc :parent :root :stencil :sclera :z "a2"
:channels {[:geom :radius] (ch/framed 4)
[:style :color] (ch/framed :iris)}})
ops (symbol/eval-frame s 0)]
ops (symbol/eval-frame s 0 nil pal/index-of nil nil)]
(is (= [:sclera :iris] (mapv :node ops)))
(is (= (:eye-white pal/index-of) (:stencil (second ops))))))
@ -250,7 +250,7 @@
(poly :sclera :root "a1" [0 0 10 0 10 10] :eye-white
{:channels {[:geom :pts] (ch/framed [0 0 10 0 10 10])
[:style :color] (ch/framed :eye-white)
[:vis] (ch/keyed {0 true, 1 false})}})
[:vis] (ch/keyed {0 true, 1 false} :hold)}})
{:id :iris :kind :disc :parent :root :stencil :sclera :z "a2"
:channels {[:geom :radius] (ch/framed 4)
[:style :color] (ch/framed :iris)}})]
@ -266,7 +266,7 @@
:channels {[:geom :radius] (ch/framed 3) [:style :color] (ch/framed :iris)}}
{:id :r :kind :rect :parent :g :z "a2"
:channels {[:geom :size] (ch/framed 1.7) [:style :color] (ch/framed :pupil)}})
[d r] (symbol/eval-frame s 0)]
[d r] (symbol/eval-frame s 0 nil pal/index-of nil nil)]
(is (= [50 60 6] [(:cx d) (:cy d) (:r d)]))
(is (= 3.4 (:size r)))))
@ -291,7 +291,7 @@
(deftest the-resolver-reuses-one-buffer-per-node
;; 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.
(let [res (symbol/resolver demo/main)
(let [res (symbol/resolver demo/main nil pal/index-of nil nil)
buf-of (fn [f id] (->> (res f) (filter #(= id (:node %))) first :pts))]
(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
;; one resolves to no ops rather than to an error.
(is (thrown-with-msg? ExceptionInfo #"not a symbol"
(symbol/resolver demo/clip)))
(symbol/resolver demo/clip nil pal/index-of nil nil)))
(is (thrown-with-msg? ExceptionInfo #"not a symbol"
(symbol/eval-frame demo/clip 0)))))
(symbol/eval-frame demo/clip 0 nil pal/index-of nil nil)))))
(deftest the-hand-written-clip-renders-and-moves
;; 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
;; same on every frame.
(let [res (symbol/resolver demo/main)
(let [res (symbol/resolver demo/main nil pal/index-of nil nil)
render (fn [f]
(let [r (raster/make (:width demo/clip) (:height demo/clip))]
(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
;; even frame before them. If this fails, exposure is being applied somewhere
;; other than the frame the channels are sampled at.
(let [res (symbol/resolver demo/main)
(let [res (symbol/resolver demo/main nil pal/index-of nil nil)
render (fn [f]
(let [r (raster/make (:width demo/clip) (:height demo/clip))]
(raster/clear! r (:bg pal/index-of))
@ -351,7 +351,7 @@
(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
;; pupil by the iris, and neither is expressed anywhere as a chain.
(let [res (symbol/resolver demo/main)]
(let [res (symbol/resolver demo/main nil pal/index-of nil nil)]
(doseq [f (range 0 demo/frames 4)]
(let [before (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))
day {:skin-base 1}
night {:skin-base 17}]
(is (= 1 (:color (first (symbol/eval-frame s 0 nil day)))))
(is (= 17 (:color (first (symbol/eval-frame s 0 nil night)))))
(is (= 17 (:color (first ((symbol/resolver s nil night) 0))))
(is (= 1 (:color (first (symbol/eval-frame s 0 nil day nil nil)))))
(is (= 17 (:color (first (symbol/eval-frame s 0 nil night nil nil)))))
(is (= 17 (:color (first ((symbol/resolver s nil night nil nil) 0))))
"and the playback path agrees")))
(deftest a-tone-the-ramp-does-not-define-is-loudly-wrong
@ -394,7 +394,7 @@
;; authored data and should be impossible to miss.
(let [s (sc {:id :root :kind :group :z "a1"}
(poly :p :root "a1" [0 0 10 0 10 10] :skin-base))]
(is (= 255 (:color (first (symbol/eval-frame s 0 nil {})))))))
(is (= 255 (:color (first (symbol/eval-frame s 0 nil {} nil nil)))))))
(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 —
@ -407,6 +407,6 @@
[:style :color] (ch/framed :iris)}})
;; :night's tones sit above :day's in one concatenated space
night {:eye-white 14 :iris 15}
ops (symbol/eval-frame s 0 nil night)]
ops (symbol/eval-frame s 0 nil night nil nil)]
(is (= 14 (:stencil (second ops)))
"the stencil resolves to the index the stencil node actually drew in")))

View file

@ -52,7 +52,7 @@
(defn- photo-at
"The photo matrix of face-1 alone at frame `f`, the still being 1000px tall."
[c f]
(let [r (symbol/resolver (clip/symbol c :face-1) @store pal/index-of)
(let [r (symbol/resolver (clip/symbol c :face-1) @store pal/index-of nil nil)
h (head c)]
(r f)
(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
;; over again — the resolver has it already, from drawing the frame.
(let [c (wrapped)
r (clip/resolver c @store pal/index-of :wrap)
r (clip/resolver c @store pal/index-of :wrap nil)
path [:m :face-1 :head]]
(doseq [f [0 17 60]]
(r f)

View file

@ -13,10 +13,10 @@
;; THE reason this is transit. Keys are a map by FRAME, and `{"0" v}` is not
;; `{0 v}`: `value-at` would find no key at frame 0 and the part would hold its
;; first pose forever, on a document that looked fine.
(let [c (ch/keyed {0 true 4 false 12 true})]
(let [c (ch/keyed {0 true 4 false 12 true} :hold)]
(is (= c (round c)))
(is (every? number? (keys (:keys (round c)))))
(is (= true (ch/value-at (round c) 13)))))
(is (= true (ch/value-at (round c) 13 nil)))))
(deftest an-id-comes-back-a-keyword
(is (= {:id :mouth-in :kind :poly :parent :mouth :z "a2"}
@ -35,7 +35,7 @@
;; Transit loses sortedness, which is why `domain/channel` says keys are a PLAIN
;; map and builds the sorted index at read time. Asserted so that nobody
;; "improves" the codec into a sorted map that works until the first round trip.
(let [c (round (ch/keyed (into {} (map (juxt identity str)) (range 20))))]
(let [c (round (ch/keyed (into {} (map (juxt identity str)) (range 20)) :hold))]
(is (map? (:keys c)))
(is (not (sorted? (:keys c))))
(is (= (vec (range 20)) (ch/frames c)))))
@ -50,7 +50,7 @@
;; turned `[]` into nil or into `[nil]` would either lose the field or refuse to
;; play the document back.
(is (= {:over []} (round {:over []})))
(is (= [] (:over (round (ch/keyed {0 1}))))))
(is (= [] (:over (round (ch/keyed {0 1} :hold))))))
(deftest a-whole-leaf-map-round-trips-through-parsed-json
;; What a save actually does: transit, then parsed so the column holds JSON.

View file

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

View file

@ -56,7 +56,7 @@
(defn- ops-at
"Ops for one frame of a TIMELINE."
[sym f]
((symbol/resolver sym @store pal/index-of) f))
((symbol/resolver sym @store pal/index-of nil nil) f))
(defn- render
"One frame of a CLIP into a byte buffer. The stage's size comes off the clip and
@ -64,7 +64,7 @@
[c f]
(let [r (raster/make (:width c) (:height c))]
(raster/clear! r (get pal/index-of :bg))
(raster/draw-ops! r ((clip/resolver c @store pal/index-of :main) f))
(raster/draw-ops! r ((clip/resolver c @store pal/index-of :main nil) f))
(vec (array-seq (:buf r)))))
(defn- drawn
@ -195,7 +195,7 @@
;; checking arithmetic against itself; this checks `node/local!`, `node/world!`
;; and `emit` as well.
(let [c (freeze/head-mode {} @frozen)
res (clip/resolver c @store pal/index-of :main)
res (clip/resolver c @store pal/index-of :main nil)
k (first (:value (chan :face [:xform :scale])))
anc (:value (chan :face [:xform :anchor]))
pos (:value (chan :face [:xform :pos]))
@ -250,13 +250,13 @@
(deftest trace-keys-hold-the-whole-measured-transform
(let [free (symbol/resolver (face-symbol (freeze/head-mode {} @frozen))
@store pal/index-of)
@store pal/index-of nil nil)
held (symbol/resolver (face-symbol
(freeze/head-mode {:trace {:origin :keys :frames [12 88]}} @frozen))
@store pal/index-of)
@store pal/index-of nil nil)
start (symbol/resolver (face-symbol
(freeze/head-mode {:trace {:origin :start :frames [12 88]}} @frozen))
@store pal/index-of)
@store pal/index-of nil nil)
world (fn [resolver frame]
(resolver frame)
(vec (array-seq (symbol/world-of resolver :head))))]
@ -340,7 +340,8 @@
(is (= (nil? (gesture/refusal n)) (not (node/measured? n)))
(str sid "/" id ": `refusal` and `measured?` disagree"))
(when anchor
(let [[x0 y0 x1 y1] (reduce #(let [k (pick/local-bounds c @store n %2)]
(let [bounds (pick/bounds-of c @store n)
[x0 y0 x1 y1] (reduce #(let [k (bounds %2)]
(cond (nil? %1) k (nil? k) %1
:else (mapv (fn [op i] (op (nth %1 i) (nth k i)))
[min min max max] (range 4))))
@ -432,7 +433,7 @@
peak (reduce max ap)
want (mapv #(>= (/ % peak) 0.12) ap)]
(is (= :keyed (ch/describe c)))
(is (= want (mapv #(ch/value-at c %) (range take/frames)))
(is (= want (mapv #(ch/value-at c % nil) (range take/frames)))
"the held keys do not reproduce the threshold")
;; The reason it is keyed: a threshold crossing is a handful of transitions,
;; hold is the default, and keys are the shape a human can correct. A dense
@ -510,7 +511,7 @@
(is (not (ch/nothing? (at :eye-r 60))))
(is (not (ch/nothing? (at :eye-l 50))))
(is (not (ch/nothing? (at :mouth 50))))
(let [drawn-nodes (into #{} (map :node) ((symbol/resolver sym (:store c) pal/index-of) 50))]
(let [drawn-nodes (into #{} (map :node) ((symbol/resolver sym (:store c) pal/index-of nil nil) 50))]
(is (not (contains? drawn-nodes :eye-r)))
(is (contains? drawn-nodes :eye-l))
(is (contains? drawn-nodes :mouth)))
@ -585,7 +586,7 @@
;; Hoisted: the resolver caches its order and reuses its buffers, so the
;; node ids come out before the next frame is asked for.
nodes-at (fn [c]
(let [r (symbol/resolver (face-symbol (:clip c)) (:store c) pal/index-of)]
(let [r (symbol/resolver (face-symbol (:clip c)) (:store c) pal/index-of nil nil)]
(fn [f] (into #{} (map :node) (r f)))))
ref-at (nodes-at ref)
occ-at (nodes-at occ)
@ -622,7 +623,7 @@
c (freeze/clip (assoc take/params :name "gappy")
{:face-1 (assoc @take/measured :detected det)})
sym (face-symbol (:clip c))
res (symbol/resolver sym (:store c) pal/index-of)]
res (symbol/resolver sym (:store c) pal/index-of nil nil)]
(doseq [f [39 40 50 59 60]]
(let [ops (res f)]
(if (contains? gap f)
@ -631,9 +632,9 @@
;; And it is the MASK doing it, not a hidden flag: `[:vis]` on :mouth-in is
;; unchanged across the gap, because hiding and absence are different
;; questions with different answers.
(is (= (mapv #(ch/value-at (get-in (:nodes sym) [:mouth-in :channels [:vis]]) %)
(is (= (mapv #(ch/value-at (get-in (:nodes sym) [:mouth-in :channels [:vis]]) % nil)
(range take/frames))
(mapv #(ch/value-at (chan :mouth-in [:vis]) %) (range take/frames))))))
(mapv #(ch/value-at (chan :mouth-in [:vis]) % nil) (range take/frames))))))
;; ---------------------------------------------------------------------------
;; the rings are still rings
@ -681,7 +682,7 @@
shot (fn [f]
(let [r (raster/make W H)
mouth (filter #(= [:face-1 :mouth] (:node %))
((clip/resolver locked @store pal/index-of :main) f))]
((clip/resolver locked @store pal/index-of :main nil) f))]
(raster/clear! r (get pal/index-of :bg))
(raster/draw-ops! r mouth)
(vec (array-seq (:buf r)))))

View file

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

View file

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