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:
parent
11093079de
commit
ee66680a0c
30 changed files with 224 additions and 217 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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])))))
|
||||
|
||||
|
|
|
|||
|
|
@ -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)))))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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"))))
|
||||
|
|
|
|||
|
|
@ -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))))))
|
||||
|
|
|
|||
|
|
@ -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))))
|
||||
|
|
|
|||
|
|
@ -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")))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)))))
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
|
|
@ -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))))))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue