@lovelace_lol/polymer 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +226 -0
  2. package/dist/index.d.ts +2603 -0
  3. package/dist/index.js +1031 -0
  4. package/dist/test.cjs +1519 -0
  5. package/dist/worker/agency-worker.js +972 -0
  6. package/dist/worker/manifest.edn +1 -0
  7. package/package.json +57 -0
  8. package/shadow-cljs.edn +173 -0
  9. package/src/polymer/animation/agency.cljs +84 -0
  10. package/src/polymer/animation/domain.cljs +110 -0
  11. package/src/polymer/animation/planner.cljs +88 -0
  12. package/src/polymer/animation/runtime.cljs +137 -0
  13. package/src/polymer/animation/scheduler.cljs +178 -0
  14. package/src/polymer/animation/service.cljs +981 -0
  15. package/src/polymer/animation/state.cljs +112 -0
  16. package/src/polymer/blink/agency.cljs +131 -0
  17. package/src/polymer/blink/planner.cljs +115 -0
  18. package/src/polymer/blink/scheduler.cljs +90 -0
  19. package/src/polymer/blink/snippet.cljs +50 -0
  20. package/src/polymer/blink/state.cljs +102 -0
  21. package/src/polymer/camera_context/agency.cljs +132 -0
  22. package/src/polymer/camera_context/domain.cljs +186 -0
  23. package/src/polymer/camera_context/planner.cljs +50 -0
  24. package/src/polymer/camera_context/scheduler.cljs +113 -0
  25. package/src/polymer/camera_context/state.cljs +121 -0
  26. package/src/polymer/character.cljs +471 -0
  27. package/src/polymer/conversation/agency.cljs +83 -0
  28. package/src/polymer/conversation/domain.cljs +35 -0
  29. package/src/polymer/conversation/effector.cljs +130 -0
  30. package/src/polymer/conversation/planner.cljs +178 -0
  31. package/src/polymer/conversation/scheduler.cljs +202 -0
  32. package/src/polymer/conversation/service.cljs +329 -0
  33. package/src/polymer/conversation/state.cljs +186 -0
  34. package/src/polymer/core.cljs +172 -0
  35. package/src/polymer/eye_head/agency.cljs +75 -0
  36. package/src/polymer/eye_head/domain.cljs +133 -0
  37. package/src/polymer/eye_head/goap.cljs +106 -0
  38. package/src/polymer/eye_head/scheduler.cljs +176 -0
  39. package/src/polymer/eye_head/service.cljs +259 -0
  40. package/src/polymer/eye_head/snippet.cljs +92 -0
  41. package/src/polymer/eye_head/state.cljs +167 -0
  42. package/src/polymer/gaze/agency.cljs +85 -0
  43. package/src/polymer/gaze/domain.cljs +211 -0
  44. package/src/polymer/gaze/goap.cljs +208 -0
  45. package/src/polymer/gaze/scheduler.cljs +184 -0
  46. package/src/polymer/gaze/state.cljs +202 -0
  47. package/src/polymer/gesture/agency.cljs +185 -0
  48. package/src/polymer/gesture/goap.cljs +461 -0
  49. package/src/polymer/gesture/scheduler.cljs +122 -0
  50. package/src/polymer/gesture/snippet.cljs +198 -0
  51. package/src/polymer/gesture/state.cljs +273 -0
  52. package/src/polymer/lipsync/agency.cljs +593 -0
  53. package/src/polymer/lipsync/articulation/jaw.cljs +258 -0
  54. package/src/polymer/lipsync/articulation/snippet.cljs +605 -0
  55. package/src/polymer/lipsync/articulation/tongue.cljs +418 -0
  56. package/src/polymer/lipsync/articulation/visemes.cljs +429 -0
  57. package/src/polymer/lipsync/goap.cljs +132 -0
  58. package/src/polymer/lipsync/pronunciation/effector.cljs +51 -0
  59. package/src/polymer/lipsync/scheduler.cljs +302 -0
  60. package/src/polymer/lipsync/state.cljs +202 -0
  61. package/src/polymer/nlp/phonemes.cljs +164 -0
  62. package/src/polymer/prosodic/agency.cljs +196 -0
  63. package/src/polymer/prosodic/goap.cljs +122 -0
  64. package/src/polymer/prosodic/scheduler.cljs +99 -0
  65. package/src/polymer/prosodic/snippet.cljs +106 -0
  66. package/src/polymer/prosodic/state.cljs +139 -0
  67. package/src/polymer/runtime/embody.cljs +117 -0
  68. package/src/polymer/runtime/embody_wasm.cljs +177 -0
  69. package/src/polymer/runtime/rust_embody_host.cljs +645 -0
  70. package/src/polymer/society.cljs +239 -0
  71. package/src/polymer/stream.cljs +130 -0
  72. package/src/polymer/transcription/agency.cljs +69 -0
  73. package/src/polymer/transcription/domain.cljs +68 -0
  74. package/src/polymer/transcription/effector.cljs +611 -0
  75. package/src/polymer/transcription/planner.cljs +203 -0
  76. package/src/polymer/transcription/scheduler.cljs +225 -0
  77. package/src/polymer/transcription/service.cljs +209 -0
  78. package/src/polymer/transcription/state.cljs +239 -0
  79. package/src/polymer/tts/agency.cljs +503 -0
  80. package/src/polymer/tts/azure.cljs +290 -0
  81. package/src/polymer/tts/planner.cljs +178 -0
  82. package/src/polymer/tts/runtime.cljs +624 -0
  83. package/src/polymer/tts/scheduler.cljs +162 -0
  84. package/src/polymer/tts/state.cljs +191 -0
  85. package/src/polymer/worker.cljs +70 -0
@@ -0,0 +1,132 @@
1
+ (ns polymer.camera-context.agency
2
+ (:require [polymer.camera-context.domain :as domain]
3
+ [polymer.camera-context.planner :as planner]
4
+ [polymer.camera-context.scheduler :as scheduler]
5
+ [polymer.camera-context.state :as state]
6
+ [polymer.stream :as stream]))
7
+
8
+ ;; Camera Context publishes stable camera-relative facts. It does not own gaze
9
+ ;; behavior and does not import a rendering engine. Runtime-specific code can
10
+ ;; observe the camera and send plain pose/target facts into this agency.
11
+
12
+ (defn now-ms
13
+ []
14
+ (.now js/Date))
15
+
16
+ (defn create-camera-context-agency
17
+ [config]
18
+ (let [input-stream (stream/create-stream)
19
+ event-stream (stream/create-stream)
20
+ state-atom (atom (state/config->state config))
21
+ sequence (atom 0)
22
+ disposed? (atom false)
23
+ emit-input (:emit input-stream)
24
+ emit-event (:emit event-stream)
25
+ scheduler-atom (atom nil)]
26
+ (letfn [(publish-fact! [fact]
27
+ (let [published-at (now-ms)
28
+ payload (assoc fact
29
+ :type "camera.fact"
30
+ :publishedAt published-at)]
31
+ (swap! state-atom state/record-published fact published-at)
32
+ (emit-event payload)))
33
+
34
+ (invalidate-stale! [reason]
35
+ (when (state/can-invalidate? @state-atom)
36
+ (let [invalidated-at (now-ms)]
37
+ (swap! state-atom state/record-stale invalidated-at reason)
38
+ (emit-event (assoc (:lastInvalidation @state-atom)
39
+ :type "camera.stale")))))
40
+
41
+ (emit-status! [status reason]
42
+ (emit-event (cond-> {:type "camera.status"
43
+ :agency "cameraContext"
44
+ :status status
45
+ :stale (:stale @state-atom)
46
+ :at (now-ms)}
47
+ reason (assoc :reason reason))))
48
+
49
+ (schedule-camera-update! [payload]
50
+ (let [id (swap! sequence inc)
51
+ observed-at (now-ms)
52
+ fact (domain/normalize-camera-fact payload
53
+ (:config @state-atom)
54
+ observed-at
55
+ id)]
56
+ (swap! state-atom state/record-camera-update fact)
57
+ (when-let [agency-scheduler @scheduler-atom]
58
+ ((:schedule-camera-update agency-scheduler) fact))))
59
+
60
+ (dispatch! [command]
61
+ (when-not @disposed?
62
+ (let [payload (domain/data-map command)
63
+ plan (planner/plan-command payload @state-atom (now-ms))
64
+ failure (first (filter #(= "fail" (:op %)) (:steps plan)))]
65
+ (emit-input {:type "command"
66
+ :agency "cameraContext"
67
+ :command payload})
68
+ (swap! state-atom state/record-plan plan)
69
+ (if failure
70
+ (emit-event {:type "error"
71
+ :agency "cameraContext"
72
+ :message (:reason failure)
73
+ :commandType (:commandType failure)})
74
+ (case (:type payload)
75
+ "configure"
76
+ (do
77
+ (swap! state-atom state/configure (:config payload))
78
+ (emit-status! (:status @state-atom) "configured"))
79
+
80
+ "updateCamera"
81
+ (schedule-camera-update! payload)
82
+
83
+ "publishCameraFacts"
84
+ (when-let [agency-scheduler @scheduler-atom]
85
+ ((:publish-now agency-scheduler)))
86
+
87
+ "invalidateStale"
88
+ (when-let [agency-scheduler @scheduler-atom]
89
+ ((:invalidate-stale agency-scheduler)
90
+ (or (:reason payload) "manual")))
91
+
92
+ "reset"
93
+ (do
94
+ (when-let [agency-scheduler @scheduler-atom]
95
+ ((:reset agency-scheduler)))
96
+ (swap! state-atom state/reset-state)
97
+ (emit-status! "idle" "reset"))
98
+
99
+ nil)))))
100
+
101
+ (dispatch-type! [type]
102
+ (dispatch! #js {:type type}))]
103
+ (let [agency-scheduler (scheduler/create-scheduler
104
+ {:state-atom state-atom
105
+ :publish-fact! publish-fact!
106
+ :invalidate-stale! invalidate-stale!})]
107
+ (reset! scheduler-atom agency-scheduler)
108
+ #js {:dispatch dispatch!
109
+ :snapshot (fn [] (state/visible-state @state-atom))
110
+ :input (stream/writable-port input-stream dispatch!)
111
+ :events (stream/readable-port event-stream)
112
+ :subscribeInput (fn [listener] ((:subscribe input-stream) listener))
113
+ :subscribeEvents (fn [listener] ((:subscribe event-stream) listener))
114
+ :subscribe (fn [listener] ((:subscribe event-stream) listener))
115
+ :subscribeStatus (fn [listener] ((:subscribe event-stream) listener))
116
+ :updateCamera (fn [facts]
117
+ (dispatch! #js {:type "updateCamera"
118
+ :facts facts}))
119
+ :invalidateStale (fn
120
+ ([] (dispatch-type! "invalidateStale"))
121
+ ([reason] (dispatch! #js {:type "invalidateStale"
122
+ :reason reason})))
123
+ :reset (fn [] (dispatch-type! "reset"))
124
+ :queue (fn []
125
+ (when-let [agency-scheduler @scheduler-atom]
126
+ (clj->js ((:queue agency-scheduler)))))
127
+ :dispose (fn []
128
+ (when-not @disposed?
129
+ (reset! disposed? true)
130
+ ((:dispose agency-scheduler))
131
+ ((:dispose input-stream))
132
+ ((:dispose event-stream))))}))))
@@ -0,0 +1,186 @@
1
+ (ns polymer.camera-context.domain)
2
+
3
+ ;; Camera Context intentionally works with plain data, not Three.js objects.
4
+ ;; The host/runtime boundary can read camera, controls, and model handles, then
5
+ ;; pass stable vector/quaternion facts into Polymer. Everything below is pure
6
+ ;; math over those facts so Gaze, Eye/Head, and future agencies can consume the
7
+ ;; same camera-relative context without inheriting a rendering-engine import.
8
+
9
+ (def default-epsilon 1.0e-4)
10
+ (def default-yaw-weight 0.35)
11
+ (def default-pitch-weight 0.2)
12
+
13
+ (def zero-vector3 {:x 0 :y 0 :z 0})
14
+ (def identity-quaternion {:x 0 :y 0 :z 0 :w 1})
15
+ (def zero-offset {:x 0 :y 0})
16
+
17
+ (defn finite-number?
18
+ [value]
19
+ (and (number? value) (js/isFinite value)))
20
+
21
+ (defn number-or
22
+ [value fallback]
23
+ (if (finite-number? value) value fallback))
24
+
25
+ (defn clamp
26
+ [lo hi value]
27
+ (min hi (max lo value)))
28
+
29
+ (defn data-map
30
+ [value]
31
+ (cond
32
+ (map? value) value
33
+ value (js->clj value :keywordize-keys true)
34
+ :else {}))
35
+
36
+ (defn normalize-vector3
37
+ ([value] (normalize-vector3 value zero-vector3))
38
+ ([value fallback]
39
+ (let [input (data-map value)
40
+ fallback (merge zero-vector3 fallback)]
41
+ {:x (number-or (:x input) (:x fallback))
42
+ :y (number-or (:y input) (:y fallback))
43
+ :z (number-or (:z input) (:z fallback))})))
44
+
45
+ (defn normalize-quaternion
46
+ [value]
47
+ (let [input (data-map value)
48
+ raw {:x (number-or (:x input) 0)
49
+ :y (number-or (:y input) 0)
50
+ :z (number-or (:z input) 0)
51
+ :w (number-or (:w input) 1)}
52
+ length (js/Math.hypot (:x raw) (:y raw) (:z raw) (:w raw))]
53
+ (if (< length default-epsilon)
54
+ identity-quaternion
55
+ {:x (/ (:x raw) length)
56
+ :y (/ (:y raw) length)
57
+ :z (/ (:z raw) length)
58
+ :w (/ (:w raw) length)})))
59
+
60
+ (defn subtract-vector3
61
+ [a b]
62
+ {:x (- (:x a) (:x b))
63
+ :y (- (:y a) (:y b))
64
+ :z (- (:z a) (:z b))})
65
+
66
+ (defn length-sq
67
+ [v]
68
+ (+ (* (:x v) (:x v))
69
+ (* (:y v) (:y v))
70
+ (* (:z v) (:z v))))
71
+
72
+ (defn normalize-direction
73
+ [v epsilon]
74
+ (let [length (js/Math.sqrt (length-sq v))]
75
+ (if (< length epsilon)
76
+ zero-vector3
77
+ {:x (/ (:x v) length)
78
+ :y (/ (:y v) length)
79
+ :z (/ (:z v) length)})))
80
+
81
+ (defn invert-unit-quaternion
82
+ [q]
83
+ {:x (- (:x q))
84
+ :y (- (:y q))
85
+ :z (- (:z q))
86
+ :w (:w q)})
87
+
88
+ (defn apply-quaternion
89
+ [v q]
90
+ ;; This mirrors Three.js Vector3.applyQuaternion, but stays independent from
91
+ ;; Three so the agency can run in workers and tests with only CLJS data.
92
+ (let [x (:x v)
93
+ y (:y v)
94
+ z (:z v)
95
+ qx (:x q)
96
+ qy (:y q)
97
+ qz (:z q)
98
+ qw (:w q)
99
+ ix (+ (* qw x) (* qy z) (- (* qz y)))
100
+ iy (+ (* qw y) (* qz x) (- (* qx z)))
101
+ iz (+ (* qw z) (* qx y) (- (* qy x)))
102
+ iw (- 0 (* qx x) (* qy y) (* qz z))]
103
+ {:x (+ (* ix qw) (* iw (- qx)) (* iy (- qz)) (- (* iz (- qy))))
104
+ :y (+ (* iy qw) (* iw (- qy)) (* iz (- qx)) (- (* ix (- qz))))
105
+ :z (+ (* iz qw) (* iw (- qz)) (* ix (- qy)) (- (* iy (- qx))))}))
106
+
107
+ (defn compute-camera-relative-offset
108
+ "Return a normalized gaze offset from camera position relative to a target.
109
+
110
+ Positive x means the camera is toward the model's local right side. Positive y
111
+ means the camera is above the target. The weights match the Latticework helper
112
+ defaults, while the inputs are portable data facts instead of Three objects."
113
+ ([camera-position target-position model-quaternion]
114
+ (compute-camera-relative-offset camera-position target-position model-quaternion {}))
115
+ ([camera-position target-position model-quaternion options]
116
+ (let [options (data-map options)
117
+ epsilon (number-or (:epsilon options) default-epsilon)
118
+ yaw-weight (number-or (:yawWeight options)
119
+ (number-or (:yaw-weight options) default-yaw-weight))
120
+ pitch-weight (number-or (:pitchWeight options)
121
+ (number-or (:pitch-weight options) default-pitch-weight))
122
+ camera-position (normalize-vector3 camera-position)
123
+ target-position (normalize-vector3 target-position)
124
+ world-offset (subtract-vector3 camera-position target-position)]
125
+ (if (< (length-sq world-offset) epsilon)
126
+ zero-offset
127
+ (let [world-direction (normalize-direction world-offset epsilon)
128
+ local-direction (normalize-direction
129
+ (apply-quaternion world-direction
130
+ (invert-unit-quaternion
131
+ (normalize-quaternion model-quaternion)))
132
+ epsilon)
133
+ yaw-angle (js/Math.atan2 (:x local-direction) (:z local-direction))
134
+ pitch-angle (js/Math.atan2
135
+ (:y local-direction)
136
+ (max (js/Math.hypot (:x local-direction) (:z local-direction))
137
+ epsilon))
138
+ half-pi (/ (.-PI js/Math) 2)
139
+ third-pi (/ (.-PI js/Math) 3)]
140
+ {:x (* (clamp -1 1 (/ yaw-angle half-pi)) yaw-weight)
141
+ :y (* (clamp -1 1 (/ pitch-angle third-pi)) pitch-weight)})))))
142
+
143
+ (defn camera-payload
144
+ [command]
145
+ (let [payload (data-map command)
146
+ facts (data-map (:facts payload))]
147
+ (merge facts payload)))
148
+
149
+ (defn normalize-camera-fact
150
+ "Create the stable fact payload published by the agency.
151
+
152
+ The command can either put facts at the top level or under :facts. Keeping the
153
+ shape forgiving lets tests, workers, and runtime adapters evolve without
154
+ changing the internal fact contract."
155
+ [command config observed-at sequence]
156
+ (let [payload (camera-payload command)
157
+ camera-position (normalize-vector3
158
+ (or (:cameraPosition payload)
159
+ (:camera-position payload)
160
+ (get-in payload [:camera :position])
161
+ (:position payload)))
162
+ target-position (normalize-vector3
163
+ (or (:targetPosition payload)
164
+ (:target-position payload)
165
+ (get-in payload [:controls :target])
166
+ (:target payload)))
167
+ model-quaternion (normalize-quaternion
168
+ (or (:modelQuaternion payload)
169
+ (:model-quaternion payload)
170
+ (get-in payload [:model :quaternion])
171
+ (get-in payload [:model :worldQuaternion])
172
+ (:worldQuaternion payload)))
173
+ offset (compute-camera-relative-offset camera-position
174
+ target-position
175
+ model-quaternion
176
+ config)]
177
+ {:kind "camera.relative"
178
+ :agency "cameraContext"
179
+ :sequence sequence
180
+ :source (or (:source payload) "runtime")
181
+ :observedAt observed-at
182
+ :cameraPosition camera-position
183
+ :targetPosition target-position
184
+ :modelQuaternion model-quaternion
185
+ :relativeOffset offset
186
+ :stale false}))
@@ -0,0 +1,50 @@
1
+ (ns polymer.camera-context.planner)
2
+
3
+ ;; The first Camera Context planner is deliberately small. It validates the
4
+ ;; command vocabulary and produces auditable action data; the agency and
5
+ ;; scheduler still execute those actions explicitly.
6
+
7
+ (def supported-command-types
8
+ #{"configure"
9
+ "updateCamera"
10
+ "publishCameraFacts"
11
+ "invalidateStale"
12
+ "reset"})
13
+
14
+ (defn command-goal
15
+ [command state now-ms]
16
+ {:type (:type command)
17
+ :hasPublishedFact (boolean (:lastPublishedAt state))
18
+ :stale (:stale state)
19
+ :nowMs now-ms})
20
+
21
+ (defn failure-step
22
+ [goal]
23
+ (when-not (contains? supported-command-types (:type goal))
24
+ {:op "fail"
25
+ :reason "unsupported-command"
26
+ :commandType (:type goal)}))
27
+
28
+ (defn command-steps
29
+ [goal]
30
+ (if-let [failure (failure-step goal)]
31
+ [failure]
32
+ (case (:type goal)
33
+ "configure" [{:op "apply-config"}]
34
+ "updateCamera" [{:op "stabilize/coalesce-update"}
35
+ {:op "publish-camera-facts"}]
36
+ "publishCameraFacts" [{:op "publish-camera-facts"}]
37
+ "invalidateStale" [{:op "invalidate-stale"}]
38
+ "reset" [{:op "reset-state"}])))
39
+
40
+ (defn plan-ok?
41
+ [steps]
42
+ (not= "fail" (:op (first steps))))
43
+
44
+ (defn plan-command
45
+ [command state now-ms]
46
+ (let [goal (command-goal command state now-ms)
47
+ steps (command-steps goal)]
48
+ {:goal goal
49
+ :steps steps
50
+ :ok (plan-ok? steps)}))
@@ -0,0 +1,113 @@
1
+ (ns polymer.camera-context.scheduler)
2
+
3
+ ;; The scheduler is the only Camera Context namespace that owns timers. Runtime
4
+ ;; camera notifications can arrive at frame cadence, but the agency should only
5
+ ;; publish stabilized facts after this coalescing boundary.
6
+
7
+ (defn now-ms
8
+ []
9
+ (.now js/Date))
10
+
11
+ (defn clear-timeout!
12
+ [timer-atom]
13
+ (when-let [timer @timer-atom]
14
+ (js/clearTimeout timer)
15
+ (reset! timer-atom nil)))
16
+
17
+ (defn queue-entry
18
+ [effect index]
19
+ (assoc effect
20
+ :queueIndex index
21
+ :queuedAt (now-ms)))
22
+
23
+ (defn create-scheduler
24
+ [{:keys [state-atom publish-fact! invalidate-stale!]}]
25
+ (let [publish-timer (atom nil)
26
+ stale-timer (atom nil)
27
+ pending-fact (atom nil)
28
+ queue (atom [])
29
+ disposed? (atom false)]
30
+ (letfn [(enqueue! [effect]
31
+ (let [entry (queue-entry effect (count @queue))]
32
+ (swap! queue conj entry)
33
+ entry))
34
+
35
+ (schedule-stale! []
36
+ (clear-timeout! stale-timer)
37
+ (when-not @disposed?
38
+ (let [delay-ms (get-in @state-atom [:config :staleAfterMs])
39
+ published-at (:lastPublishedAt @state-atom)]
40
+ (when (pos? delay-ms)
41
+ (reset! stale-timer
42
+ (js/setTimeout
43
+ (fn []
44
+ (reset! stale-timer nil)
45
+ (when (and (not @disposed?)
46
+ (nil? @pending-fact)
47
+ (= published-at (:lastPublishedAt @state-atom)))
48
+ (enqueue! {:type "invalidateStale"
49
+ :agency "cameraContext"
50
+ :reason "timeout"})
51
+ (invalidate-stale! "timeout")))
52
+ delay-ms))))))
53
+
54
+ (publish-now! []
55
+ (when-not @disposed?
56
+ (clear-timeout! publish-timer)
57
+ (when-let [fact @pending-fact]
58
+ (reset! pending-fact nil)
59
+ (enqueue! {:type "publishCameraFact"
60
+ :agency "cameraContext"
61
+ :sequence (:sequence fact)})
62
+ (publish-fact! fact)
63
+ (schedule-stale!))))
64
+
65
+ (schedule-publish! [fact]
66
+ (when-not @disposed?
67
+ (reset! pending-fact fact)
68
+ (clear-timeout! publish-timer)
69
+ (clear-timeout! stale-timer)
70
+ (let [delay-ms (get-in @state-atom [:config :coalesceMs])]
71
+ (enqueue! {:type "coalesceCameraFact"
72
+ :agency "cameraContext"
73
+ :sequence (:sequence fact)
74
+ :coalesceMs delay-ms})
75
+ (if (pos? delay-ms)
76
+ (reset! publish-timer
77
+ (js/setTimeout publish-now! delay-ms))
78
+ (publish-now!)))))]
79
+ {:schedule-camera-update
80
+ (fn [fact]
81
+ (schedule-publish! fact))
82
+
83
+ :publish-now
84
+ (fn []
85
+ (publish-now!))
86
+
87
+ :invalidate-stale
88
+ (fn [reason]
89
+ (when-not @disposed?
90
+ (clear-timeout! stale-timer)
91
+ (enqueue! {:type "invalidateStale"
92
+ :agency "cameraContext"
93
+ :reason reason})
94
+ (invalidate-stale! reason)))
95
+
96
+ :reset
97
+ (fn []
98
+ (clear-timeout! publish-timer)
99
+ (clear-timeout! stale-timer)
100
+ (reset! pending-fact nil)
101
+ (reset! queue []))
102
+
103
+ :queue
104
+ (fn []
105
+ @queue)
106
+
107
+ :dispose
108
+ (fn []
109
+ (reset! disposed? true)
110
+ (clear-timeout! publish-timer)
111
+ (clear-timeout! stale-timer)
112
+ (reset! pending-fact nil)
113
+ (reset! queue []))})))
@@ -0,0 +1,121 @@
1
+ (ns polymer.camera-context.state
2
+ (:require [polymer.camera-context.domain :as domain]))
3
+
4
+ ;; Camera Context state is the local ledger of stable runtime facts. The agency
5
+ ;; may observe many camera updates, but only scheduler-approved facts become
6
+ ;; published stream data for peer agencies.
7
+
8
+ (def default-config
9
+ {:coalesceMs 16
10
+ :staleAfterMs 1000
11
+ :epsilon domain/default-epsilon
12
+ :yawWeight domain/default-yaw-weight
13
+ :pitchWeight domain/default-pitch-weight})
14
+
15
+ (defn normalize-config
16
+ [config]
17
+ (let [input (merge default-config (domain/data-map config))]
18
+ {:coalesceMs (int (domain/clamp 0 1000
19
+ (domain/number-or (:coalesceMs input)
20
+ (:coalesceMs default-config))))
21
+ :staleAfterMs (int (domain/clamp 0 60000
22
+ (domain/number-or (:staleAfterMs input)
23
+ (:staleAfterMs default-config))))
24
+ :epsilon (domain/clamp 1.0e-9 1
25
+ (domain/number-or (:epsilon input)
26
+ (:epsilon default-config)))
27
+ :yawWeight (domain/clamp 0 1
28
+ (domain/number-or (:yawWeight input)
29
+ (:yawWeight default-config)))
30
+ :pitchWeight (domain/clamp 0 1
31
+ (domain/number-or (:pitchWeight input)
32
+ (:pitchWeight default-config)))}))
33
+
34
+ (defn default-state
35
+ [config]
36
+ {:agency "cameraContext"
37
+ :status "idle"
38
+ :cameraPosition nil
39
+ :targetPosition nil
40
+ :modelQuaternion domain/identity-quaternion
41
+ :relativeOffset domain/zero-offset
42
+ :lastUpdatedAt nil
43
+ :lastPublishedAt nil
44
+ :lastFact nil
45
+ :lastPlan nil
46
+ :lastInvalidation nil
47
+ :stale false
48
+ :invalidated false
49
+ :updateCount 0
50
+ :publishedCount 0
51
+ :config (normalize-config config)})
52
+
53
+ (defn config->state
54
+ [config]
55
+ (default-state config))
56
+
57
+ (defn visible-state
58
+ [state]
59
+ (clj->js state))
60
+
61
+ (defn configure
62
+ [state config]
63
+ (assoc state :config (normalize-config (merge (:config state)
64
+ (domain/data-map config)))))
65
+
66
+ (defn reset-state
67
+ [state]
68
+ ;; Reset clears camera facts and counters while preserving the normalized
69
+ ;; runtime cadence supplied when this agency was created or configured.
70
+ (default-state (:config state)))
71
+
72
+ (defn record-plan
73
+ [state plan]
74
+ (assoc state :lastPlan plan))
75
+
76
+ (defn record-camera-update
77
+ [state fact]
78
+ (-> state
79
+ (assoc :status "pending"
80
+ :cameraPosition (:cameraPosition fact)
81
+ :targetPosition (:targetPosition fact)
82
+ :modelQuaternion (:modelQuaternion fact)
83
+ :relativeOffset (:relativeOffset fact)
84
+ :lastUpdatedAt (:observedAt fact)
85
+ :stale false
86
+ :invalidated false)
87
+ (update :updateCount inc)))
88
+
89
+ (defn record-published
90
+ [state fact published-at]
91
+ (-> state
92
+ (assoc :status "fresh"
93
+ :lastFact (assoc fact :publishedAt published-at)
94
+ :lastPublishedAt published-at
95
+ :stale false
96
+ :invalidated false)
97
+ (update :publishedCount inc)))
98
+
99
+ (defn stale-fact
100
+ [state invalidated-at reason]
101
+ {:kind "camera.stale"
102
+ :agency "cameraContext"
103
+ :status "stale"
104
+ :reason reason
105
+ :invalidatedAt invalidated-at
106
+ :lastPublishedAt (:lastPublishedAt state)
107
+ :lastObservedAt (:lastUpdatedAt state)
108
+ :lastRelativeOffset (:relativeOffset state)})
109
+
110
+ (defn can-invalidate?
111
+ [state]
112
+ (and (:lastPublishedAt state) (not (:stale state))))
113
+
114
+ (defn record-stale
115
+ [state invalidated-at reason]
116
+ (let [fact (stale-fact state invalidated-at reason)]
117
+ (assoc state
118
+ :status "stale"
119
+ :stale true
120
+ :invalidated true
121
+ :lastInvalidation fact)))