From ee66680a0cb3167476182ea4de1150a896f7876c Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 30 Sep 2026 12:10:22 -0400 Subject: [PATCH] Make every caller say what it means: no defaulted arities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- frontend/src/arthur/audio/mix.cljs | 15 ++-- frontend/src/arthur/demo.cljs | 3 +- frontend/src/arthur/domain/channel.cljs | 66 ++++++++++-------- frontend/src/arthur/domain/clip.cljs | 7 +- frontend/src/arthur/domain/node.cljs | 9 ++- frontend/src/arthur/domain/paint.cljs | 6 +- frontend/src/arthur/domain/pick.cljs | 8 +-- frontend/src/arthur/domain/symbol.cljs | 15 ++-- frontend/src/arthur/events/project.cljs | 3 +- frontend/src/arthur/flow/freeze.cljs | 4 +- frontend/src/arthur/footage/store.cljs | 15 ++-- frontend/src/arthur/subs/ui.cljs | 2 +- frontend/src/arthur/ui/drag.cljs | 2 +- frontend/src/arthur/ui/params.cljs | 4 +- frontend/src/arthur/ui/stage.cljs | 3 +- frontend/test/arthur/bench_test.cljs | 2 +- frontend/test/arthur/domain/channel_test.cljs | 68 +++++++++---------- frontend/test/arthur/domain/gesture_test.cljs | 10 +-- .../test/arthur/domain/instance_test.cljs | 26 +++---- frontend/test/arthur/domain/nest_test.cljs | 14 ++-- frontend/test/arthur/domain/node_test.cljs | 24 +++---- frontend/test/arthur/domain/paint_test.cljs | 13 ++-- frontend/test/arthur/domain/project_test.cljs | 3 +- frontend/test/arthur/domain/symbol_test.cljs | 66 +++++++++--------- frontend/test/arthur/domain/trace_test.cljs | 4 +- frontend/test/arthur/domain/wire_test.cljs | 8 +-- .../test/arthur/flow/eye_occlusion_test.cljs | 2 +- frontend/test/arthur/flow/freeze_test.cljs | 29 ++++---- .../test/arthur/flow/multi_face_test.cljs | 6 +- frontend/test/arthur/support/ops.cljs | 4 +- 30 files changed, 224 insertions(+), 217 deletions(-) diff --git a/frontend/src/arthur/audio/mix.cljs b/frontend/src/arthur/audio/mix.cljs index adec0d7..13c52c5 100644 --- a/frontend/src/arthur/audio/mix.cljs +++ b/frontend/src/arthur/audio/mix.cljs @@ -147,14 +147,13 @@ The raw product. `mix!` packages it as a WAV URL for the transport and `export/frames` packages it as WAV bytes in an archive; a muxer would take it as it is, which is why this is the function the others are written in terms of." - ([document sid] (buffer! document sid nil)) - ([document sid store] - (let [tracks (tracks-of document sid)] - (if (empty? tracks) - (js/Promise.resolve nil) - (-> (js/Promise.all - (into-array (map source! (distinct (map :source tracks))))) - (.then (fn [pairs] (render! document sid (into {} (array-seq pairs)) store)))))))) + [document sid store] + (let [tracks (tracks-of document sid)] + (if (empty? tracks) + (js/Promise.resolve nil) + (-> (js/Promise.all + (into-array (map source! (distinct (map :source tracks))))) + (.then (fn [pairs] (render! document sid (into {} (array-seq pairs)) store))))))) (defn decode! "Promise of the `AudioBuffer` behind a URL. What a clip whose audio is a plain diff --git a/frontend/src/arthur/demo.cljs b/frontend/src/arthur/demo.cljs index 46b527f..8bc3146 100644 --- a/frontend/src/arthur/demo.cljs +++ b/frontend/src/arthur/demo.cljs @@ -6,6 +6,7 @@ validates would not be the one that renders, and the model would be validated against a scene nobody ever looked at." (:require [arthur.domain.clip :as domain-clip] + [arthur.domain.palette :as pal] [arthur.domain.symbol :as symbol] [cljs.reader :as reader] [shadow.resource :as rc])) @@ -25,4 +26,4 @@ "Draw ops for one frame, via the specification path. The page uses `symbol/resolver` instead; this is here for the REPL." [f] - (symbol/eval-frame main f)) + (symbol/eval-frame main f nil pal/index-of nil nil)) diff --git a/frontend/src/arthur/domain/channel.cljs b/frontend/src/arthur/domain/channel.cljs index 9e4ef4f..d61262a 100644 --- a/frontend/src/arthur/domain/channel.cljs +++ b/frontend/src/arthur/domain/channel.cljs @@ -68,8 +68,11 @@ (defn framed [v] {:animated? false :value v}) (defn keyed - ([ks] (keyed ks :hold)) - ([ks interp] {:animated? true :interp interp :keys ks :over []})) + "A channel of keys, and how each one leads to the next. `interp` is an + argument, never a default: `:hold` and `:linear` are the difference between a + cut and a tween, which is the whole content of the channel." + [ks interp] + {:animated? true :interp interp :keys ks :over []}) ;; --------------------------------------------------------------------------- @@ -147,9 +150,9 @@ Decoding costs the view. `out` is a stride-sized destination the caller owns — `cursor` allocates one per channel — because a copy per node per frame is the allocation this whole model is arranged to avoid; passing nil allocates, which - is what `value-at`, the specification, does." - ([blk f st] (dense-at blk f st nil)) - ([{:keys [store offset stride scale] nf :frames} f st out] + is what `value-at`, the specification, does — and it says so by passing nil, + because there is no arity here that decides it for a caller." + [{:keys [store offset stride scale] nf :frames} f st out] (let [{:keys [data state]} (get st store)] (when (nil? data) (throw (ex-info "dense channel's store key is not in the store" @@ -164,7 +167,7 @@ :else (let [dst (or out (js/Float64Array. stride))] (dotimes [k stride] (aset dst k (/ (aget data (+ o k)) scale))) - dst)))))))) + dst))))))) ;; --------------------------------------------------------------------------- ;; the specification @@ -201,17 +204,23 @@ (defn value-at "Sample a channel at frame f. THE SPECIFICATION — correct, allocating, and - O(n) in the keys. `cursor`/`sample!` is what playback uses." - ([ch f] (value-at ch f nil)) - ([ch f store] - (check-unimplemented! ch) - (cond - (not (:animated? ch)) (:value ch) - (:dense ch) (dense-at (:dense ch) f store) - (:keys ch) (let [ks (:keys ch)] - (if (empty? ks) absent (keyed-at ch f))) - :else - (throw (ex-info "animated channel has neither :keys nor :dense" {:channel ch}))))) + O(n) in the keys. `cursor`/`sample!` is what playback uses. + + `store` IS AN ARGUMENT, NEVER A DEFAULT. A dense channel cannot be read + without the tier-2 store it names, and an arity that filled in nil let a + caller omit it, read correctly for every channel that happened not to be + dense, and throw the first time a selection landed on one that was. That is + how `gesture/values` took the stage down on an iris. A caller with no store + says `nil` and means it." + [ch f store] + (check-unimplemented! ch) + (cond + (not (:animated? ch)) (:value ch) + (:dense ch) (dense-at (:dense ch) f store nil) + (:keys ch) (let [ks (:keys ch)] + (if (empty? ks) absent (keyed-at ch f))) + :else + (throw (ex-info "animated channel has neither :keys nor :dense" {:channel ch})))) ;; --------------------------------------------------------------------------- ;; the playback path @@ -244,17 +253,18 @@ needs, for the same reason the resolver owns one point buffer per node. Only a wide fixed-point block gets a buffer: a stride-1 block decodes to a - number and a block with no `:scale` is handed back as a view." - ([ch] (cursor ch nil)) - ([ch store] - (check-unimplemented! ch) - (let [d (:dense ch)] - (->Cursor ch - (when (and (:animated? ch) (not d) (seq (:keys ch))) (frames ch)) - store - (when (and d (:scale d) (> (:stride d) 1)) - (js/Float64Array. (:stride d))) - 0)))) + number and a block with no `:scale` is handed back as a view. + + `store` is an argument for the reason it is one on `value-at`." + [ch store] + (check-unimplemented! ch) + (let [d (:dense ch)] + (->Cursor ch + (when (and (:animated? ch) (not d) (seq (:keys ch))) (frames ch)) + store + (when (and d (:scale d) (> (:stride d) 1)) + (js/Float64Array. (:stride d))) + 0))) (defn sample! "Value of the cursor's channel at f. O(1) when f is at or one key past where diff --git a/frontend/src/arthur/domain/clip.cljs b/frontend/src/arthur/domain/clip.cljs index d4089c5..56a9b5b 100644 --- a/frontend/src/arthur/domain/clip.cljs +++ b/frontend/src/arthur/domain/clip.cljs @@ -174,8 +174,7 @@ 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 resolving anything a second time." - ([clip store palette sid] (resolver clip store palette sid nil)) - ([clip store palette sid {:keys [picture-fps] :as opts}] + [clip store palette sid {:keys [picture-fps] :as opts}] (letfn [(build [sid chain pose-tracks] (when (some #{sid} chain) (throw (ex-info "symbol cycle" {:chain (conj chain sid)}))) @@ -230,7 +229,7 @@ (when (contains? @entered id) (symbol/frame-of (get children id) (vec more))) (symbol/frame-of own id))))))] - (build sid [] nil)))) + (build sid [] nil))) (defn center "The middle of everything symbol `sid` draws, over all its frames, in its own @@ -244,7 +243,7 @@ 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." [clip store sid] - (let [resolve (resolver clip store pal/index-of sid) + (let [resolve (resolver clip store pal/index-of sid nil) 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])) [x0 y0 x1 y1] diff --git a/frontend/src/arthur/domain/node.cljs b/frontend/src/arthur/domain/node.cljs index b4e72a8..c337e82 100644 --- a/frontend/src/arthur/domain/node.cljs +++ b/frontend/src/arthur/domain/node.cljs @@ -107,10 +107,13 @@ (defn toggle-key "Key channel `path` on the node's own frame `f` with the value it has there, or take the key there off. The first key starts the channel animating and taking - the last one off leaves it that one value. A boolean holds; anything else tweens." - [n path f] + the last one off leaves it that one value. A boolean holds; anything else tweens. + + `store` because the value it keys is read out of the channel, and a measured + channel's values live in tier 2." + [n path f store] (let [c (get (channels n) path) - v (ch/value-at c f) + v (ch/value-at c f store) ks (dissoc (:keys c) f)] (assoc-in n [:channels path] (cond diff --git a/frontend/src/arthur/domain/paint.cljs b/frontend/src/arthur/domain/paint.cljs index d62b91e..e96174e 100644 --- a/frontend/src/arthur/domain/paint.cljs +++ b/frontend/src/arthur/domain/paint.cljs @@ -25,7 +25,7 @@ {:id id :name (str "shape " (inc (count (shapes clip sid)))) :kind :poly :paint? true :parent nil :z z :span [frame end] - :channels {geometry (channel/keyed {frame points}) + :channels {geometry (channel/keyed {frame points} :hold) [:style :color] (channel/framed color) ;; Turned and scaled about its middle, as a placed ;; symbol is: set once here and never followed. @@ -41,7 +41,9 @@ [start end] (:span node)] (if (and (:paint? node) (<= start frame) (< frame end) ch) (assoc-in clip (into path [:channels geometry :keys frame]) - (vec (channel/value-at ch frame))) + ;; A drawing is authored and keyed, never dense, so there is + ;; no tier-2 store to read it out of. + (vec (channel/value-at ch frame nil))) clip))) (defn set-vertex [clip sid id key-frame vertex [x y]] diff --git a/frontend/src/arthur/domain/pick.cljs b/frontend/src/arthur/domain/pick.cljs index d1826c9..7957544 100644 --- a/frontend/src/arthur/domain/pick.cljs +++ b/frontend/src/arthur/domain/pick.cljs @@ -101,7 +101,7 @@ (let [sid (:of n) frames (clip/frames document sid) loop? (get-in n [:time :loop?]) - resolve (clip/resolver document store pal/index-of sid)] + resolve (clip/resolver document store pal/index-of sid nil)] (fn [f] (let [f (if loop? (mod f frames) f)] (when (< -1 f frames) @@ -128,12 +128,6 @@ (when-not (ch/nothing? s) (let [h (/ s 2)] [(- h) (- h) h h])))) (constantly nil)))) -(defn local-bounds - "`[x0 y0 x1 y1]` around what node `n` draws on its own frame `f`, in its own - coordinates, or nil when it draws nothing there." - [document store n f] - ((bounds-of document store n) f)) - (defn pivot "The middle of everything node `n` draws over its own frames `fs`, in its own coordinates — where it should turn and scale about. Nil for a node that draws diff --git a/frontend/src/arthur/domain/symbol.cljs b/frontend/src/arthur/domain/symbol.cljs index 162b72a..cb65360 100644 --- a/frontend/src/arthur/domain/symbol.cljs +++ b/frontend/src/arthur/domain/symbol.cljs @@ -422,10 +422,7 @@ the only place the space changes. This is the definition of what a frame means. `resolver` is what plays it." - ([sym f] (eval-frame sym f nil pal/index-of)) - ([sym f store] (eval-frame sym f store pal/index-of)) - ([sym f store palette] (eval-frame sym f store palette nil nil)) - ([sym f store palette pose-tracks opts] + [sym f store palette pose-tracks opts] (let [nodes (nodes-of sym) choices (pose/prepare pose-tracks) traces (prepared-traces nodes) @@ -440,7 +437,7 @@ :pinv-for (fn [id] (node/pinv (get nodes id))) :buf-for (fn [_id n] (js/Float64Array. (* 2 n))) :scratch (node/mat)} - nodes ord (draw-rank nodes ord) f)))) + nodes ord (draw-rank nodes ord) f))) ;; --------------------------------------------------------------------------- ;; the playback path @@ -485,11 +482,7 @@ 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 nothing and cost the ability to hand an op list around as plain data." - ([sym] (resolver sym nil pal/index-of nil nil)) - ([sym store] (resolver sym store pal/index-of nil nil)) - ([sym store palette] (resolver sym store palette nil nil)) - ([sym store palette pose-tracks] (resolver sym store palette pose-tracks nil)) - ([sym store palette pose-tracks {:keys [source-fps picture-fps]}] + [sym store palette pose-tracks {:keys [source-fps picture-fps]}] (let [nodes (nodes-of sym) choices (pose/prepare pose-tracks) traces (prepared-traces nodes) @@ -532,7 +525,7 @@ (-invoke [_ f] (step f)) IResolver (world-of [_ id] (:m (get @placed id))) - (frame-of [_ id] (:f (get @placed id))))))) + (frame-of [_ id] (:f (get @placed id)))))) ;; --------------------------------------------------------------------------- diff --git a/frontend/src/arthur/events/project.cljs b/frontend/src/arthur/events/project.cljs index 9ba53fe..fe3f87b 100644 --- a/frontend/src/arthur/events/project.cljs +++ b/frontend/src/arthur/events/project.cljs @@ -622,7 +622,8 @@ (rf/reg-event-db ::toggle-key (fn [db [_ sid id path frame]] - (edit/edit db #(update-in % [:symbols sid :nodes id] node/toggle-key path frame)))) + (let [st (:store (store/entry (:clip/current db)))] + (edit/edit db #(update-in % [:symbols sid :nodes id] node/toggle-key path frame st))))) ;; A face's trace frames and origin, on its symbol — see `domain/trace`. (rf/reg-event-db diff --git a/frontend/src/arthur/flow/freeze.cljs b/frontend/src/arthur/flow/freeze.cljs index c409f5e..67c4690 100644 --- a/frontend/src/arthur/flow/freeze.cljs +++ b/frontend/src/arthur/flow/freeze.cljs @@ -312,7 +312,7 @@ (when (or (zero? f) (not= (nth shown f) (nth shown (dec f)))) [f (nth shown f)]))) - (range (count shown)))) + (range (count shown))) :hold) :generated generated))) (defn- keyed-visibility [values generated] @@ -320,7 +320,7 @@ (when (or (zero? f) (not= (nth values f) (nth values (dec f)))) [f (nth values f)]))) - (range (count values)))) + (range (count values))) :hold) :generated generated)) (def ^:private pose-groups diff --git a/frontend/src/arthur/footage/store.cljs b/frontend/src/arthur/footage/store.cljs index 3e620a4..c89ba53 100644 --- a/frontend/src/arthur/footage/store.cljs +++ b/frontend/src/arthur/footage/store.cljs @@ -18,14 +18,13 @@ with nothing to evict it, and the timeline that would want several is out of scope. `kind` only names the id — `:footage/3`, `:project/4` — so that a clip's origin is legible in the db without a lookup." - ([entry] (install! entry "footage")) - ([entry kind] - (let [id (keyword kind (str (swap! serial inc)))] - (when-let [old (:audio @loaded)] - (when (and (not= old (:audio entry)) (.startsWith old "blob:")) - (js/URL.revokeObjectURL old))) - (reset! loaded (assoc entry :id id)) - id))) + [entry kind] + (let [id (keyword kind (str (swap! serial inc)))] + (when-let [old (:audio @loaded)] + (when (and (not= old (:audio entry)) (.startsWith old "blob:")) + (js/URL.revokeObjectURL old))) + (reset! loaded (assoc entry :id id)) + id)) (defn entry [id] (if (= id (:id @loaded)) diff --git a/frontend/src/arthur/subs/ui.cljs b/frontend/src/arthur/subs/ui.cljs index a00825a..0f10356 100644 --- a/frontend/src/arthur/subs/ui.cljs +++ b/frontend/src/arthur/subs/ui.cljs @@ -66,7 +66,7 @@ (when n (let [st (:store (store/entry clip-id))] (when-let [pl (nest/placement clip st open (or path [id]) f)] - (assoc pl :node n :bounds (pick/local-bounds clip st n (:frame pl)))))))) + (assoc pl :node n :bounds ((pick/bounds-of clip st n) (:frame pl)))))))) (rf/reg-sub ::project-footage diff --git a/frontend/src/arthur/ui/drag.cljs b/frontend/src/arthur/ui/drag.cljs index 690dfe3..9bbfde0 100644 --- a/frontend/src/arthur/ui/drag.cljs +++ b/frontend/src/arthur/ui/drag.cljs @@ -29,7 +29,7 @@ :disc (select-keys op [:kind :cx :cy :r]) :rect (select-keys op [:kind :cx :cy :size]) nil)) - ((clip/resolver document st pal/index-of sid) 0)))) + ((clip/resolver document st pal/index-of sid nil) 0)))) (defn symbol! "Start carrying symbol `sid` of the loaded document into the open symbol." diff --git a/frontend/src/arthur/ui/params.cljs b/frontend/src/arthur/ui/params.cljs index 667f20c..f7a4ab0 100644 --- a/frontend/src/arthur/ui/params.cljs +++ b/frontend/src/arthur/ui/params.cljs @@ -180,7 +180,9 @@ (defn- channel-control [sid id path ch frame] (let [keyed? (some? (:keys ch)) - v (channel/value-at ch (or frame 0)) + ;; No store: the call site below hands this only channels that are not + ;; `:dense`, which are the only ones with anything in tier 2 to read. + v (channel/value-at ch (or frame 0) nil) off? (and keyed? (nil? frame)) deg? (= path [:xform :rot]) ;; A boolean has nothing between true and false to tween through. diff --git a/frontend/src/arthur/ui/stage.cljs b/frontend/src/arthur/ui/stage.cljs index 351a124..d82a249 100644 --- a/frontend/src/arthur/ui/stage.cljs +++ b/frontend/src/arthur/ui/stage.cljs @@ -231,7 +231,8 @@ :w w :h h} points? @(rf/subscribe [::sub/points]) [sid id geom active editable? frame matrix] (when points? (editing)) - pts (when geom (through matrix (channel/value-at geom frame)))] + pts (when geom (through matrix (channel/value-at geom frame + (:store (store/entry clip-id)))))] [:svg {:class (str "paint-overlay" (when drawing? " drawing")) :width (* zoom w) :height (* zoom h) :view-box (str "0 0 " w " " h) diff --git a/frontend/test/arthur/bench_test.cljs b/frontend/test/arthur/bench_test.cljs index 23ac02f..adc331d 100644 --- a/frontend/test/arthur/bench_test.cljs +++ b/frontend/test/arthur/bench_test.cljs @@ -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] diff --git a/frontend/test/arthur/domain/channel_test.cljs b/frontend/test/arthur/domain/channel_test.cljs index 6ac5565..7c85efe 100644 --- a/frontend/test/arthur/domain/channel_test.cljs +++ b/frontend/test/arthur/domain/channel_test.cljs @@ -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]))))) diff --git a/frontend/test/arthur/domain/gesture_test.cljs b/frontend/test/arthur/domain/gesture_test.cljs index 8bf7e01..064c888 100644 --- a/frontend/test/arthur/domain/gesture_test.cljs +++ b/frontend/test/arthur/domain/gesture_test.cljs @@ -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))))) diff --git a/frontend/test/arthur/domain/instance_test.cljs b/frontend/test/arthur/domain/instance_test.cljs index fd293c1..1080dca 100644 --- a/frontend/test/arthur/domain/instance_test.cljs +++ b/frontend/test/arthur/domain/instance_test.cljs @@ -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 diff --git a/frontend/test/arthur/domain/nest_test.cljs b/frontend/test/arthur/domain/nest_test.cljs index 48c7039..d59b97a 100644 --- a/frontend/test/arthur/domain/nest_test.cljs +++ b/frontend/test/arthur/domain/nest_test.cljs @@ -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") diff --git a/frontend/test/arthur/domain/node_test.cljs b/frontend/test/arthur/domain/node_test.cljs index 55dff81..c7cc882 100644 --- a/frontend/test/arthur/domain/node_test.cljs +++ b/frontend/test/arthur/domain/node_test.cljs @@ -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")))) diff --git a/frontend/test/arthur/domain/paint_test.cljs b/frontend/test/arthur/domain/paint_test.cljs index 1e019ea..a8c01e4 100644 --- a/frontend/test/arthur/domain/paint_test.cljs +++ b/frontend/test/arthur/domain/paint_test.cljs @@ -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)))))) diff --git a/frontend/test/arthur/domain/project_test.cljs b/frontend/test/arthur/domain/project_test.cljs index aa31428..3b16ef2 100644 --- a/frontend/test/arthur/domain/project_test.cljs +++ b/frontend/test/arthur/domain/project_test.cljs @@ -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)))) diff --git a/frontend/test/arthur/domain/symbol_test.cljs b/frontend/test/arthur/domain/symbol_test.cljs index 663ff09..126b5ec 100644 --- a/frontend/test/arthur/domain/symbol_test.cljs +++ b/frontend/test/arthur/domain/symbol_test.cljs @@ -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"))) diff --git a/frontend/test/arthur/domain/trace_test.cljs b/frontend/test/arthur/domain/trace_test.cljs index fade283..a5ab51f 100644 --- a/frontend/test/arthur/domain/trace_test.cljs +++ b/frontend/test/arthur/domain/trace_test.cljs @@ -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) diff --git a/frontend/test/arthur/domain/wire_test.cljs b/frontend/test/arthur/domain/wire_test.cljs index 3e3b40b..97d4ffc 100644 --- a/frontend/test/arthur/domain/wire_test.cljs +++ b/frontend/test/arthur/domain/wire_test.cljs @@ -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. diff --git a/frontend/test/arthur/flow/eye_occlusion_test.cljs b/frontend/test/arthur/flow/eye_occlusion_test.cljs index 56cc854..18e4376 100644 --- a/frontend/test/arthur/flow/eye_occlusion_test.cljs +++ b/frontend/test/arthur/flow/eye_occlusion_test.cljs @@ -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)) diff --git a/frontend/test/arthur/flow/freeze_test.cljs b/frontend/test/arthur/flow/freeze_test.cljs index 324ede5..2223581 100644 --- a/frontend/test/arthur/flow/freeze_test.cljs +++ b/frontend/test/arthur/flow/freeze_test.cljs @@ -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))))) diff --git a/frontend/test/arthur/flow/multi_face_test.cljs b/frontend/test/arthur/flow/multi_face_test.cljs index e545aeb..53f16ec 100644 --- a/frontend/test/arthur/flow/multi_face_test.cljs +++ b/frontend/test/arthur/flow/multi_face_test.cljs @@ -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))) diff --git a/frontend/test/arthur/support/ops.cljs b/frontend/test/arthur/support/ops.cljs index ea72a2e..054cfda 100644 --- a/frontend/test/arthur/support/ops.cljs +++ b/frontend/test/arthur/support/ops.cljs @@ -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))))))