     (fn [terminal]
       (RainEffect. terminal layer-id rain-state))))
 
-(defn swap-rain-mask! [terminal f]
-  (zaat/swap-matching-effect-or-filter! terminal
-                              (fn [fx] (= (zaat/id fx) :rain))
-                              (fn [rain-fx] (zaat/swap-mask! rain-fx f))))
-
-(defn reset-rain-mask! [terminal v]
-  (zaat/swap-matching-effect-or-filter! terminal
-                              (fn [fx] (= (zaat/id fx) :rain))
-                              (fn [rain-fx]
-                                (let [[columns rows] (size terminal)]
-                                  (zaat/reset-mask! rain-fx (repeat rows (repeat columns v)))))))
-
-(defn transform [terminal mask state transforms]
-  #_(println (format "drawing %d transforms" (count transforms)))
-  (doseq [transform transforms]
-    (let [path (zutil/line-segment-fast-without-endpoints (get transform :from-xy) (get transform :to-xy))
-          n    (int (/ @frame-count 1))]
-      (when (< n (count path))
-        #_(log/info "n" n "count path" (count path))
-        (let [[vx vy] path #_(rv/world-xy->screen-xy state (nth path n))]
-          (when (or true (get-in mask [vy vx]))
-            #_(println "drawing * @ " vx vy)
-            ;(zat/set-char! terminal :fx vx vy \*)
-            (zat/set-fg! terminal :fx vx vy (zcolor/color->rgb :white))
-            (zat/set-bg! terminal :fx vx vy (zcolor/color->rgb :black))))))))
-  
 (defrecord TransformEffect
-  [terminal mask state]
-  AId
-  (id [this]
-    :transform)
-  AEffect
-  (apply-effect! [this terminal]
-    (dosync 
-      (when-let [state @state]
-        (transform terminal @mask state (get-in state [:fx :transform])))))
-  AMask
-  (swap-mask! [this f]
-    (swap! mask f)
-    ;(println "=====================")
-    ;(doseq [line @mask]
-    ;  (println (mapv (fn [v] (if v 1 0)) line)))
-    this)
-  (reset-mask! [this new-mask]
-    (reset! mask new-mask)
-    this)
-  ARequiresState
-  (reset-state! [this new-state]
-    (reset! state new-state)
-    this)
-  Object
-  (toString [this]
-    (format "TransformEffect terminal, mask, state")))
+  [terminal layer-id ch from to duration]
+  ACmdStream
+  (stream [this]
+    (let [path (zutil/line-segment-fast-without-endpoints from to)
+          dt   (* 1000 (/ duration (count path)))]
+      (go-loop [[x y] (first path) xys (next path)]
+        (zat/clear! terminal layer-id)
+        (zat/put-chars! terminal layer-id [(assoc ch :x x :y y)])
+        (zat/refresh! terminal)
+        (<! (timeout dt))
+        (if xys
+          (recur (first xys) (next xys)))))))
 
 (defn make-transform-effect
-  [terminal cell-opts]
-  (let [[vw vh]    (size terminal)
-        mask       (atom (repeat vh (repeat vw true)))]
-    (TransformEffect. terminal mask (atom nil))))
-
-(defn blink [terminal state blinks]
-  ;(prinln (format "drawing %d blinks" (count blinks)))
-  (doseq [blink blinks]
-    #_(println @frame-count (mod @frame-count 15))
-    (when (< (mod @frame-count 15) 7)
-      (let [[vx vy] (get blink :xy)]
-          #_(println "drawing * @ " vx vy)
-          (zat/put-chars! terminal :fx [{:x vx :y vy :c \space}])))))
+  [terminal layer-id ch from to duration]
+    (fn [terminal]
+      (TransformEffect. terminal layer-id ch from to duration)))
 
 (defrecord BlinkEffect
-  [terminal state]
-  AId
-  (id [this]
-    :blink)
-  AEffect
-  (apply-effect! [this terminal]
-    (dosync 
-      (when-let [state @state]
-        (blink terminal state (get-in state [:fx :blink])))))
-  ARequiresState
-  (reset-state! [this new-state]
-    (reset! state new-state)
-    this)
-  Object
-  (toString [this]
-    (format "BlinkEffect terminal, state")))
+  [terminal layer-id characters initial intervals]
+  ACmdStream
+  (stream [this]
+    (let [dt 1000]
+      (go-loop [state initial]
+        (zat/clear! terminal layer-id)
+        (when state
+          (zat/put-chars! terminal layer-id characters))
+        (zat/refresh! terminal)
+        (<! (timeout dt))
+        (recur (not state))))))
 
 (defn make-blink-effect
-  [terminal cell-opts]
-  (let [[vw vh]    (size terminal)]
-    (BlinkEffect. terminal (atom nil))))
-
-(defn blip [terminal state blips]
-  (doseq [blip blips]
-    (let [[vx vy] blip #_(rv/world-xy->screen-xy state (get blip :xy))]
-      ;(println "drawing blip" blip "@" vx vy "frame-count" @frame-count)
-      ;; get the last instant before @frame-count
-      (when-let [instant (->> (get blip :instants)
-                              (filter (fn [instant]
-                                        (< (get instant :time) @frame-count)))
-                              (sort-by (fn [instant]
-                                         (get instant :time)))
-                              last)]
-        ;(println "drawing instant" instant)
-        #_(when-let [ch (get instant :ch)]
-          (zat/set-char! terminal :fx vx vy ch))
-        (when-let [fg (get instant :fg)]
-          (zat/set-fg! terminal :fx vx vy fg))
-        (when-let [bg (get instant :bg)]
-          (zat/set-bg! terminal :fx vx vy bg))))))
-    
-(defrecord BlipEffect
-  [terminal state]
-  AId
-  (id [this]
-    :blip)
-  AEffect
-  (apply-effect! [this terminal]
-    (dosync 
-      (when-let [state @state]
-        (blip terminal state (get-in state [:fx :blip])))))
-  ARequiresState
-  (reset-state! [this new-state]
-    (reset! state new-state)
-    this)
-  Object
-  (toString [this]
-    (format "BlipEffect terminal, state")))
+  ([layer-id characters]
+    (make-blink-effect layer-id characters true))
+  ([layer-id characters initial]
+    (make-blink-effect layer-id characters initial (constantly 1000)))
+  ([layer-id characters initial intervals]
+    (fn [terminal]
+      (BlinkEffect. terminal layer-id characters initial intervals))))
 
 (defn make-blip-effect
-  [terminal cell-opts]
-  (let [[vw vh]    (size terminal)]
-    (BlipEffect. terminal (atom nil))))
+  [layer-id characters duration]
+  (make-blink-effect layer-id characters [duration]))
 
 ;;; Queue cmds in a chan and foreard them to dst-chan on `(refresh)`.
 (defrecord WrappedAnimatedTerminal [terminal cmds dst-chan]

  (let [[vw vh] (size terminal)]
    (repeat vh (vec (repeat vw nil)))))

(defn lantern-flicker [opts x y fg]
  {:post [(vector? %)]}
  (if (get opts :lantern-flicker)
    (let [v                   (int (* 50 (/ (Math/abs (hash @frame-count)) Integer/MAX_VALUE)))

  (:require 
            [clojure.core.async :refer :all :as async :exclude [map into]]
            [zaffre.color :as zcolor]
            [zaffre.terminal :as zat]
            [zaffre.util :as zutil]
            [taoensso.timbre :as log]
            [overtone.at-at :as atat]))


#?(:clj
(set! *warn-on-reflection* true))

(def ^:dynamic *rain-rate* 0.96)


