@hraness/slopcamera 3.2.7 → 3.3.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 (188) hide show
  1. package/NOTICE.md +1 -7
  2. package/PRIVACY.md +16 -7
  3. package/README.md +65 -20
  4. package/apps/desktop/README.md +14 -130
  5. package/apps/desktop/application/context.ts +6 -9
  6. package/apps/desktop/application/default-registry.ts +26 -10
  7. package/apps/desktop/application/operation.ts +14 -5
  8. package/apps/desktop/application/operations/index.ts +4 -1
  9. package/apps/desktop/application/operations/spatial-behavior.ts +151 -0
  10. package/apps/desktop/application/operations/spatial-direction.ts +222 -0
  11. package/apps/desktop/application/operations/spatial-rendered-audit.ts +174 -0
  12. package/apps/desktop/application/operations/spatial-review.ts +194 -0
  13. package/apps/desktop/application/operations/spatial-scene.ts +25 -1
  14. package/apps/desktop/application/registry.ts +2 -2
  15. package/apps/desktop/application/spatial-asset-admission.ts +201 -0
  16. package/apps/desktop/application/spatial-assets.ts +160 -31
  17. package/apps/desktop/application/spatial-render.ts +217 -20
  18. package/apps/desktop/application/spatial-rendered-audit.ts +273 -0
  19. package/apps/desktop/application/spatial-review.ts +361 -0
  20. package/apps/desktop/application/spatial-spz.ts +48 -3
  21. package/apps/desktop/application/spatial-world-import.ts +26 -5
  22. package/apps/desktop/application/spatial-world-metadata.ts +105 -0
  23. package/apps/desktop/cli/args.ts +528 -97
  24. package/apps/desktop/cli/capability-manifest.ts +374 -0
  25. package/apps/desktop/cli/cinema-renderer.ts +608 -0
  26. package/apps/desktop/cli/command-host-resources.ts +31 -2
  27. package/apps/desktop/cli/commands.ts +1215 -70
  28. package/apps/desktop/cli/gateway-review-provider.ts +334 -0
  29. package/apps/desktop/cli/help.ts +183 -23
  30. package/apps/desktop/cli/html-scene.ts +139 -28
  31. package/apps/desktop/cli/io.ts +2 -1
  32. package/apps/desktop/cli/main.ts +26 -50
  33. package/apps/desktop/cli/menubar.ts +244 -0
  34. package/apps/desktop/cli/music-analysis-service.ts +61 -0
  35. package/apps/desktop/cli/portable-surface.ts +18 -5
  36. package/apps/desktop/cli/spatial-asset-service.ts +124 -0
  37. package/apps/desktop/cli/spatial-design-service.ts +237 -0
  38. package/apps/desktop/cli/spatial-generate-service.ts +370 -0
  39. package/apps/desktop/cli/spatial-scene-service.ts +268 -4
  40. package/apps/desktop/cli/workflow-code.ts +1 -1
  41. package/apps/desktop/code/application-node-planner.ts +0 -47
  42. package/apps/desktop/code/host-source-layout.ts +46 -0
  43. package/apps/desktop/code/public.ts +0 -1
  44. package/apps/desktop/code/runtime-identity.ts +50 -8
  45. package/apps/desktop/code/semantic-builder.ts +24 -43
  46. package/apps/desktop/code/source-bundle.ts +23 -6
  47. package/apps/desktop/code/source-typecheck.ts +131 -43
  48. package/apps/desktop/code/worker-client.ts +14 -2
  49. package/apps/desktop/contracts/cinema.ts +736 -0
  50. package/apps/desktop/contracts/index.ts +1 -0
  51. package/apps/desktop/contracts/spatial-asset.ts +16 -0
  52. package/apps/desktop/contracts/spatial-world.ts +26 -3
  53. package/apps/desktop/core/cinema-plan.ts +1320 -0
  54. package/apps/desktop/core/index.ts +1 -0
  55. package/apps/desktop/core/music-analysis.ts +124 -1
  56. package/apps/desktop/core/storage.ts +75 -0
  57. package/apps/desktop/dist/cli/main.js +737 -291
  58. package/apps/desktop/html-overlay/audio-reactivity.ts +127 -0
  59. package/apps/desktop/html-overlay/index.ts +9 -0
  60. package/apps/desktop/html-overlay/runtime.ts +2 -0
  61. package/apps/desktop/html-overlay/scene.ts +29 -0
  62. package/apps/desktop/html-overlay/spatial.ts +619 -57
  63. package/apps/desktop/workflows/cinematic-world.ts +127 -0
  64. package/apps/desktop/workflows/index.ts +6 -0
  65. package/dist/cli.js +32 -434
  66. package/dist/code/advanced.js +1 -269
  67. package/dist/code/index.js +16 -2239
  68. package/dist/generate.js +1 -23
  69. package/dist/host-resources.js +1 -25
  70. package/dist/index-0bbesn4m.js +35 -0
  71. package/dist/index-296qhpf2.js +3 -0
  72. package/dist/index-3cxf2c58.js +3 -0
  73. package/dist/index-77fjfg9f.js +3 -0
  74. package/dist/index-80a36bc0.js +3 -0
  75. package/dist/index-a3vvs3rf.js +3 -0
  76. package/dist/index-b3geqczn.js +5 -0
  77. package/dist/index-cgpkyjqa.js +3 -0
  78. package/dist/index-f0ptv8dm.js +7 -0
  79. package/dist/index-grk47tgs.js +3 -0
  80. package/dist/index-qry58nj2.js +10 -0
  81. package/dist/index.js +1 -240
  82. package/dist/operations.js +1 -28
  83. package/dist/skill-install-gx7afxsd.js +2 -0
  84. package/dist/vectorize/worker.js +1 -141
  85. package/dist/workflow.js +1 -15
  86. package/docs/README.md +5 -1
  87. package/docs/studio.md +1 -1
  88. package/package.json +11 -24
  89. package/skills/slopcamera/SKILL.md +14 -1
  90. package/skills/slopcamera/references/diagrams.md +5 -14
  91. package/skills/slopcamera/references/directed-scenes.md +22 -2
  92. package/skills/slopcamera/references/gateway-media.md +15 -1
  93. package/skills/slopcamera/references/image-galleries.md +166 -0
  94. package/skills/slopcamera/references/install.md +2 -2
  95. package/skills/slopcamera/references/music-video.md +23 -3
  96. package/skills/slopcamera/references/parametric-design.md +14 -0
  97. package/skills/slopcamera/references/reference-led-3d.md +1 -1
  98. package/skills/slopcamera/references/scene-building.md +32 -0
  99. package/skills/slopcamera/references/social-collage-banners.md +204 -0
  100. package/skills/slopcamera/references/support.md +25 -0
  101. package/skills/slopcamera/references/web-media-excerpts.md +120 -0
  102. package/skills/slopcamera/references/workflows-sdk.md +3 -3
  103. package/skills/slopcamera/scripts/compose-social-collage-banner.ts +728 -0
  104. package/src/capability-manifest.ts +361 -0
  105. package/src/cli.ts +250 -102
  106. package/src/code/canonical-json.ts +9 -0
  107. package/src/generate.ts +2 -2
  108. package/src/icon.ts +1111 -0
  109. package/src/image-gallery.ts +1049 -0
  110. package/src/index.ts +11 -23
  111. package/src/mcp/boundary.ts +11 -11
  112. package/src/mcp/index.ts +3 -0
  113. package/src/mcp/server.ts +2 -1
  114. package/src/mcp/tools.ts +1642 -130
  115. package/src/mcp/types.ts +19 -0
  116. package/src/operations.ts +412 -0
  117. package/src/portable-capability-manifest.ts +67 -0
  118. package/src/scene-gallery.ts +184 -0
  119. package/src/spatial-scene/asset-admission.ts +183 -0
  120. package/src/spatial-scene/audit-rendered.ts +692 -0
  121. package/src/spatial-scene/audit.ts +617 -0
  122. package/src/spatial-scene/behavior-audit.ts +252 -0
  123. package/src/spatial-scene/behavior-bake.ts +233 -0
  124. package/src/spatial-scene/behavior-fns.ts +585 -0
  125. package/src/spatial-scene/behavior-gallery.ts +109 -0
  126. package/src/spatial-scene/behavior-stdlib.ts +323 -0
  127. package/src/spatial-scene/behavior-trace.ts +325 -0
  128. package/src/spatial-scene/behavior.ts +618 -0
  129. package/src/spatial-scene/build.ts +632 -0
  130. package/src/spatial-scene/camera-rig.ts +125 -0
  131. package/src/spatial-scene/camera-track.ts +4 -2
  132. package/src/spatial-scene/character.ts +124 -0
  133. package/src/spatial-scene/contracts.ts +75 -7
  134. package/src/spatial-scene/design-templates.ts +351 -0
  135. package/src/spatial-scene/design.ts +327 -0
  136. package/src/spatial-scene/direction-compile.ts +584 -0
  137. package/src/spatial-scene/direction.ts +122 -0
  138. package/src/spatial-scene/effects.ts +149 -0
  139. package/src/spatial-scene/evaluate.ts +109 -29
  140. package/src/spatial-scene/gallery.ts +277 -0
  141. package/src/spatial-scene/generate.ts +239 -0
  142. package/src/spatial-scene/geometry-native.ts +165 -0
  143. package/src/spatial-scene/geometry.ts +1216 -0
  144. package/src/spatial-scene/gltf.ts +600 -77
  145. package/src/spatial-scene/identity.ts +55 -2
  146. package/src/spatial-scene/index.ts +36 -0
  147. package/src/spatial-scene/inspect.ts +35 -6
  148. package/src/spatial-scene/material-lighting.ts +609 -0
  149. package/src/spatial-scene/math.ts +21 -4
  150. package/src/spatial-scene/motion-evidence.ts +88 -0
  151. package/src/spatial-scene/parametric.ts +765 -0
  152. package/src/spatial-scene/particle-preparation.ts +183 -0
  153. package/src/spatial-scene/particle.ts +195 -0
  154. package/src/spatial-scene/patch.ts +109 -15
  155. package/src/spatial-scene/performance.ts +1613 -0
  156. package/src/spatial-scene/probe.ts +137 -0
  157. package/src/spatial-scene/recipe-pack.ts +86 -0
  158. package/src/spatial-scene/render-effects.ts +240 -0
  159. package/src/spatial-scene/review.ts +639 -0
  160. package/src/spatial-scene/simulation-bake.ts +206 -0
  161. package/src/spatial-scene/simulation.ts +187 -0
  162. package/src/spatial-scene/solve.ts +366 -0
  163. package/src/spatial-scene/temporal-audit.ts +354 -0
  164. package/src/support-completion.ts +16 -0
  165. package/src/support.ts +42 -0
  166. package/src/version.ts +1 -1
  167. package/apps/desktop/application/operations/recording/index.ts +0 -5
  168. package/apps/desktop/application/operations/recording/pause.ts +0 -34
  169. package/apps/desktop/application/operations/recording/resume.ts +0 -34
  170. package/apps/desktop/application/operations/recording/shared.ts +0 -199
  171. package/apps/desktop/application/operations/recording/start.ts +0 -169
  172. package/apps/desktop/application/operations/recording/stop.ts +0 -34
  173. package/apps/desktop/capture/protocol.ts +0 -689
  174. package/apps/desktop/cli/capture-bundle.ts +0 -1117
  175. package/apps/desktop/cli/recording-controller.ts +0 -1567
  176. package/apps/desktop/cli/recording-daemon.ts +0 -931
  177. package/dist/index-42zsesc1.js +0 -2257
  178. package/dist/index-7308egqr.js +0 -48
  179. package/dist/index-8txs6fkn.js +0 -712
  180. package/dist/index-mcy8z0br.js +0 -192
  181. package/dist/index-p63wavx0.js +0 -2029
  182. package/dist/index-r7gdhmsp.js +0 -424
  183. package/dist/index-rvghzt7b.js +0 -2013
  184. package/dist/index-sh6xbav6.js +0 -1219
  185. package/dist/index-z1w83f81.js +0 -4
  186. package/dist/index-z7239b4h.js +0 -2363
  187. package/dist/skill-install-dh0ntqba.js +0 -10
  188. package/src/desktop.ts +0 -253
@@ -0,0 +1,323 @@
1
+ import { deepFreezeJson } from "../code/json-snapshot.js"
2
+ import { behaviorOrganismSha256, type SpatialBehaviorOrganism } from "./behavior.js"
3
+
4
+ /**
5
+ * Standard-library organisms that compose the closed behavior-fn catalog
6
+ * into common character-behavior patterns. Each organism is a documented
7
+ * manifest that agents embed in `slopcamera.spatial-behavior` documents;
8
+ * seed convention auto-binds `behavior.seed` at bake so seeded kernels
9
+ * produce deterministic diversity across gallery variants.
10
+ *
11
+ * The four organisms:
12
+ * - `locomotionFsm` — state-machine locomotion (idle/walk/run/etc.) over
13
+ * a tick window with seeded chance guards and configurable min-dwell.
14
+ * - `expressionLayer` — blink/gaze/mood autonomous expression over ticks.
15
+ * - `interactSequence` — linear phase sequencer for pickup/grasp/release
16
+ * interactions with emit-on-entry events.
17
+ * - `combinedBehavior` — top-level composition of the three sub-behaviors
18
+ * via pairwise append into one emitted trace.
19
+ *
20
+ * Each follows the `repeat`+`carry`+`window.advance` accumulation pattern:
21
+ * a sub-organism ticks one kernel over the head chunk of a carried window,
22
+ * appends fresh emissions to a carried accumulator, and advances the window
23
+ * until `done` — all within ALGAL's 16-round `maxRounds` bound.
24
+ */
25
+
26
+ // --------------------------------------------------- shared sub-organisms ---
27
+
28
+ /**
29
+ * Build one repeat-round sub-organism: advance the carried window, tick the
30
+ * kernel over the head chunk, append fresh emissions to the carried accumulator.
31
+ *
32
+ * The kernel's spec (machine/spec) is a non-carried pass-through; state and
33
+ * acc are carried outputs→inputs; the window advances via `rest`→`win`.
34
+ */
35
+ interface AccTickPorts {
36
+ readonly spec: string
37
+ readonly state: string
38
+ readonly window: string
39
+ readonly next: string
40
+ readonly emitted: string
41
+ }
42
+
43
+ const makeAccTickOrganism = (name: string, kernelFn: string, ports: AccTickPorts): SpatialBehaviorOrganism => {
44
+ const inputOutputs: Record<string, { type: "json" }> = {
45
+ [ports.state]: { type: "json" },
46
+ acc: { type: "json" },
47
+ win: { type: "json" },
48
+ [ports.spec]: { type: "json" },
49
+ step: { type: "json" },
50
+ }
51
+ return deepFreezeJson({
52
+ contract: "morphogen.organism.v1" as const,
53
+ key: `organism:acc-tick-${name}`,
54
+ name: `Acc tick ${name}`,
55
+ cells: [
56
+ { id: "in", kind: "input" as const, outputs: inputOutputs },
57
+ { id: "adv", kind: "fn" as const, fn: "window.advance.v1" },
58
+ { id: "tick", kind: "fn" as const, fn: kernelFn },
59
+ { id: "ap", kind: "fn" as const, fn: "emitted.append.v1" },
60
+ ],
61
+ edges: [
62
+ { from: { cell: "in", port: "win" }, to: { cell: "adv", port: "window" } },
63
+ { from: { cell: "in", port: "step" }, to: { cell: "adv", port: "step" } },
64
+ { from: { cell: "adv", port: "head" }, to: { cell: "tick", port: ports.window } },
65
+ { from: { cell: "in", port: ports.spec }, to: { cell: "tick", port: ports.spec } },
66
+ { from: { cell: "in", port: ports.state }, to: { cell: "tick", port: ports.state } },
67
+ { from: { cell: "in", port: "acc" }, to: { cell: "ap", port: "prior" } },
68
+ { from: { cell: "tick", port: ports.emitted }, to: { cell: "ap", port: "fresh" } },
69
+ ],
70
+ interface: {
71
+ inputs: {
72
+ [ports.state]: { cell: "in", port: ports.state },
73
+ acc: { cell: "in", port: "acc" },
74
+ win: { cell: "in", port: "win" },
75
+ [ports.spec]: { cell: "in", port: ports.spec },
76
+ step: { cell: "in", port: "step" },
77
+ },
78
+ outputs: {
79
+ next: { cell: "tick", port: ports.next },
80
+ acc: { cell: "ap", port: "emitted" },
81
+ rest: { cell: "adv", port: "rest" },
82
+ done: { cell: "adv", port: "done" },
83
+ },
84
+ },
85
+ })
86
+ }
87
+
88
+ /**
89
+ * Build the outer repeat-loop organism: carries `next→state`, `acc→acc`,
90
+ * `rest→win`; passes the spec and step constants; exposes `seed` for the
91
+ * document seed convention.
92
+ */
93
+ const makeAccLoopOrganism = (
94
+ name: string,
95
+ accTickDigest: string,
96
+ specPort: string,
97
+ statePort: string,
98
+ ): SpatialBehaviorOrganism => {
99
+ const loopInputOutputs: Record<string, { type: "json" }> = {
100
+ [statePort]: { type: "json" },
101
+ [specPort]: { type: "json" },
102
+ win: { type: "json" },
103
+ seed: { type: "json" },
104
+ }
105
+ const loopConstOutputs: Record<string, { type: "json"; value: unknown }> = {
106
+ step: { type: "json", value: 3 },
107
+ acc: { type: "json", value: [] },
108
+ }
109
+ return deepFreezeJson({
110
+ contract: "morphogen.organism.v1" as const,
111
+ key: `organism:acc-loop-${name}`,
112
+ name: `Acc loop ${name}`,
113
+ cells: [
114
+ { id: "in", kind: "input" as const, outputs: loopInputOutputs },
115
+ { id: "cfg", kind: "const" as const, outputs: loopConstOutputs },
116
+ {
117
+ id: "loop", kind: "repeat" as const,
118
+ manifest: accTickDigest,
119
+ maxRounds: 16,
120
+ carry: { next: statePort, acc: "acc", rest: "win" },
121
+ until: { output: "done", equals: "true" },
122
+ },
123
+ ],
124
+ edges: [
125
+ { from: { cell: "in", port: statePort }, to: { cell: "loop", port: statePort } },
126
+ { from: { cell: "in", port: specPort }, to: { cell: "loop", port: specPort } },
127
+ { from: { cell: "in", port: "win" }, to: { cell: "loop", port: "win" } },
128
+ { from: { cell: "cfg", port: "step" }, to: { cell: "loop", port: "step" } },
129
+ { from: { cell: "cfg", port: "acc" }, to: { cell: "loop", port: "acc" } },
130
+ ],
131
+ interface: {
132
+ inputs: {
133
+ [statePort]: { cell: "in", port: statePort },
134
+ [specPort]: { cell: "in", port: specPort },
135
+ win: { cell: "in", port: "win" },
136
+ seed: { cell: "in", port: "seed" },
137
+ },
138
+ outputs: { trace: { cell: "loop", port: "acc" } },
139
+ },
140
+ })
141
+ }
142
+
143
+ // ---------------------------------------------------------- locomotion fsm ---
144
+
145
+ const locomotionAccTick = makeAccTickOrganism("locomotion", "behavior.fsm.v1", {
146
+ spec: "machine", state: "state", window: "window", next: "next", emitted: "emitted",
147
+ })
148
+
149
+ const LOCOMOTION_ACC_TICK_DIGEST = behaviorOrganismSha256(locomotionAccTick)
150
+
151
+ const locomotionAccLoop = makeAccLoopOrganism("locomotion", LOCOMOTION_ACC_TICK_DIGEST, "machine", "state")
152
+
153
+ const LOCOMOTION_ACC_LOOP_DIGEST = behaviorOrganismSha256(locomotionAccLoop)
154
+
155
+ /**
156
+ * Locomotion FSM organism: a `repeat`+`carry` loop driving `behavior.fsm.v1`
157
+ * over chunked windows. Entry inputs: `machine` (FSM machine spec), `state`
158
+ * (initial FSM state), `win` (tick window), `seed` (document seed, auto-bound).
159
+ * Entry output: `trace` — accumulated emitted records for `<channel>` and
160
+ * `<channel>.transition` channels.
161
+ */
162
+ export const SPATIAL_BEHAVIOR_STDLIB_LOCOMOTION_FSM = deepFreezeJson({
163
+ organisms: {
164
+ [LOCOMOTION_ACC_TICK_DIGEST]: locomotionAccTick,
165
+ [LOCOMOTION_ACC_LOOP_DIGEST]: locomotionAccLoop,
166
+ },
167
+ entry: LOCOMOTION_ACC_LOOP_DIGEST,
168
+ /** Interface inputs the doc must supply via `args`. */
169
+ inputs: { state: "json" as const, machine: "json" as const, win: "json" as const, seed: "json" as const },
170
+ /** Interface outputs the bake surface extracts. */
171
+ outputs: { trace: "json" as const },
172
+ })
173
+
174
+ // ------------------------------------------------------ expression layer ---
175
+
176
+ const expressionAccTick = makeAccTickOrganism("expression", "behavior.expression.v1", {
177
+ spec: "spec", state: "state", window: "window", next: "next", emitted: "emitted",
178
+ })
179
+
180
+ const EXPRESSION_ACC_TICK_DIGEST = behaviorOrganismSha256(expressionAccTick)
181
+
182
+ const expressionAccLoop = makeAccLoopOrganism("expression", EXPRESSION_ACC_TICK_DIGEST, "spec", "state")
183
+
184
+ const EXPRESSION_ACC_LOOP_DIGEST = behaviorOrganismSha256(expressionAccLoop)
185
+
186
+ /**
187
+ * Expression layer organism: blink, gaze, and mood over chunked tick windows
188
+ * via `behavior.expression.v1`. Entry inputs: `spec` (expression config),
189
+ * `state` (initial expression state), `win` (tick window), `seed` (auto-bound).
190
+ */
191
+ export const SPATIAL_BEHAVIOR_STDLIB_EXPRESSION = deepFreezeJson({
192
+ organisms: {
193
+ [EXPRESSION_ACC_TICK_DIGEST]: expressionAccTick,
194
+ [EXPRESSION_ACC_LOOP_DIGEST]: expressionAccLoop,
195
+ },
196
+ entry: EXPRESSION_ACC_LOOP_DIGEST,
197
+ inputs: { state: "json" as const, spec: "json" as const, win: "json" as const, seed: "json" as const },
198
+ outputs: { trace: "json" as const },
199
+ })
200
+
201
+ // ------------------------------------------------- interaction sequence ---
202
+
203
+ const interactAccTick = makeAccTickOrganism("interact", "behavior.interact.v1", {
204
+ spec: "spec", state: "state", window: "window", next: "next", emitted: "emitted",
205
+ })
206
+
207
+ const INTERACT_ACC_TICK_DIGEST = behaviorOrganismSha256(interactAccTick)
208
+
209
+ const interactAccLoop = makeAccLoopOrganism("interact", INTERACT_ACC_TICK_DIGEST, "spec", "state")
210
+
211
+ const INTERACT_ACC_LOOP_DIGEST = behaviorOrganismSha256(interactAccLoop)
212
+
213
+ /**
214
+ * Interaction sequence organism: linear phase sequencer via
215
+ * `behavior.interact.v1`. Entry inputs: `spec` (interaction phases),
216
+ * `state` (initial phase state), `win` (tick window), `seed` (auto-bound).
217
+ */
218
+ export const SPATIAL_BEHAVIOR_STDLIB_INTERACT = deepFreezeJson({
219
+ organisms: {
220
+ [INTERACT_ACC_TICK_DIGEST]: interactAccTick,
221
+ [INTERACT_ACC_LOOP_DIGEST]: interactAccLoop,
222
+ },
223
+ entry: INTERACT_ACC_LOOP_DIGEST,
224
+ inputs: { state: "json" as const, spec: "json" as const, win: "json" as const, seed: "json" as const },
225
+ outputs: { trace: "json" as const },
226
+ })
227
+
228
+ // --------------------------------------------------- combined behavior ---
229
+
230
+ /**
231
+ * Combined behavior organism: composes locomotion, expression, and interaction
232
+ * sub-organisms into a single merged emitted trace. Each sub-organism runs
233
+ * its own `repeat`+`carry` loop; the combined organism chains pairwise
234
+ * `emitted.append` to concatenate the three traces into sorted order.
235
+ *
236
+ * Entry inputs: `loco-state`, `loco-machine`, `expr-state`, `expr-spec`,
237
+ * `interact-state`, `interact-spec`, `win` (shared tick window), `seed`.
238
+ * Entry output: `trace` — merged emitted records sorted by (tUs, channel).
239
+ */
240
+
241
+ const combinedOrganism: SpatialBehaviorOrganism = deepFreezeJson({
242
+ contract: "morphogen.organism.v1" as const,
243
+ key: "organism:combined-behavior",
244
+ name: "Combined behavior",
245
+ cells: [
246
+ {
247
+ id: "in", kind: "input" as const,
248
+ outputs: {
249
+ "loco-state": { type: "json" as const },
250
+ "loco-machine": { type: "json" as const },
251
+ "expr-state": { type: "json" as const },
252
+ "expr-spec": { type: "json" as const },
253
+ "interact-state": { type: "json" as const },
254
+ "interact-spec": { type: "json" as const },
255
+ win: { type: "json" as const },
256
+ seed: { type: "json" as const },
257
+ },
258
+ },
259
+ { id: "loco", kind: "organism" as const, manifest: LOCOMOTION_ACC_LOOP_DIGEST },
260
+ { id: "expr", kind: "organism" as const, manifest: EXPRESSION_ACC_LOOP_DIGEST },
261
+ { id: "interact", kind: "organism" as const, manifest: INTERACT_ACC_LOOP_DIGEST },
262
+ { id: "ap-le", kind: "fn" as const, fn: "emitted.append.v1" },
263
+ { id: "ap-lei", kind: "fn" as const, fn: "emitted.append.v1" },
264
+ ],
265
+ edges: [
266
+ // Locomotion sub
267
+ { from: { cell: "in", port: "loco-state" }, to: { cell: "loco", port: "state" } },
268
+ { from: { cell: "in", port: "loco-machine" }, to: { cell: "loco", port: "machine" } },
269
+ { from: { cell: "in", port: "win" }, to: { cell: "loco", port: "win" } },
270
+ { from: { cell: "in", port: "seed" }, to: { cell: "loco", port: "seed" } },
271
+ // Expression sub
272
+ { from: { cell: "in", port: "expr-state" }, to: { cell: "expr", port: "state" } },
273
+ { from: { cell: "in", port: "expr-spec" }, to: { cell: "expr", port: "spec" } },
274
+ { from: { cell: "in", port: "win" }, to: { cell: "expr", port: "win" } },
275
+ { from: { cell: "in", port: "seed" }, to: { cell: "expr", port: "seed" } },
276
+ // Interact sub
277
+ { from: { cell: "in", port: "interact-state" }, to: { cell: "interact", port: "state" } },
278
+ { from: { cell: "in", port: "interact-spec" }, to: { cell: "interact", port: "spec" } },
279
+ { from: { cell: "in", port: "win" }, to: { cell: "interact", port: "win" } },
280
+ { from: { cell: "in", port: "seed" }, to: { cell: "interact", port: "seed" } },
281
+ // Chain appends: loco+expr → ap-le, then ap-le+interact → ap-lei
282
+ { from: { cell: "loco", port: "trace" }, to: { cell: "ap-le", port: "prior" } },
283
+ { from: { cell: "expr", port: "trace" }, to: { cell: "ap-le", port: "fresh" } },
284
+ { from: { cell: "ap-le", port: "emitted" }, to: { cell: "ap-lei", port: "prior" } },
285
+ { from: { cell: "interact", port: "trace" }, to: { cell: "ap-lei", port: "fresh" } },
286
+ ],
287
+ interface: {
288
+ inputs: {
289
+ "loco-state": { cell: "in", port: "loco-state" },
290
+ "loco-machine": { cell: "in", port: "loco-machine" },
291
+ "expr-state": { cell: "in", port: "expr-state" },
292
+ "expr-spec": { cell: "in", port: "expr-spec" },
293
+ "interact-state": { cell: "in", port: "interact-state" },
294
+ "interact-spec": { cell: "in", port: "interact-spec" },
295
+ win: { cell: "in", port: "win" },
296
+ seed: { cell: "in", port: "seed" },
297
+ },
298
+ outputs: { trace: { cell: "ap-lei", port: "emitted" } },
299
+ },
300
+ })
301
+
302
+ const COMBINED_DIGEST = behaviorOrganismSha256(combinedOrganism)
303
+
304
+ export const SPATIAL_BEHAVIOR_STDLIB_COMBINED = deepFreezeJson({
305
+ organisms: {
306
+ ...SPATIAL_BEHAVIOR_STDLIB_LOCOMOTION_FSM.organisms,
307
+ ...SPATIAL_BEHAVIOR_STDLIB_EXPRESSION.organisms,
308
+ ...SPATIAL_BEHAVIOR_STDLIB_INTERACT.organisms,
309
+ [COMBINED_DIGEST]: combinedOrganism,
310
+ },
311
+ entry: COMBINED_DIGEST,
312
+ inputs: {
313
+ "loco-state": "json" as const,
314
+ "loco-machine": "json" as const,
315
+ "expr-state": "json" as const,
316
+ "expr-spec": "json" as const,
317
+ "interact-state": "json" as const,
318
+ "interact-spec": "json" as const,
319
+ win: "json" as const,
320
+ seed: "json" as const,
321
+ },
322
+ outputs: { trace: "json" as const },
323
+ })
@@ -0,0 +1,325 @@
1
+ import { z } from "zod"
2
+ import { HUMANOID_BONE_NAMES } from "./character.js"
3
+ import {
4
+ SPATIAL_SCENE_LIMITS,
5
+ SpatialDigestSchema,
6
+ SpatialQuaternionSchema,
7
+ SpatialTimeUsSchema,
8
+ SpatialTransformSchema,
9
+ SpatialVec3Schema,
10
+ } from "./contracts.js"
11
+ import { SpatialPerformanceDirectiveSchema, type SpatialPerformanceDirective } from "./performance.js"
12
+ import { deepFreezeJson } from "../code/json-snapshot.js"
13
+ import { parseSpatialValue, spatialValueSha256, SpatialSceneError } from "./identity.js"
14
+ import type { JsonValue } from "../code/contracts.js"
15
+ import type { SpatialBehaviorFn } from "./behavior-fns.js"
16
+
17
+ // ------------------------------------------------------------------ limits ---
18
+
19
+ export const SPATIAL_BEHAVIOR_TRACE_LIMITS = Object.freeze({
20
+ emitted: 16_384,
21
+ channels: 64,
22
+ clipChannels: 16,
23
+ clipValues: 64,
24
+ attachments: 8,
25
+ trajectories: 8,
26
+ directives: 4_096,
27
+ unresolvedIntents: 256,
28
+ })
29
+
30
+ // ----------------------------------------------------------------- emitted ---
31
+
32
+ const channelName = z.string().regex(/^[a-z][a-z0-9_.-]{0,62}$/u)
33
+
34
+ /** One emitted channel record — the vocabulary organisms surface at bake. */
35
+ export const SpatialBehaviorEmittedSchema = z.strictObject({
36
+ tUs: SpatialTimeUsSchema,
37
+ channel: channelName,
38
+ value: z.unknown(),
39
+ })
40
+ export type SpatialBehaviorEmitted = Readonly<z.infer<typeof SpatialBehaviorEmittedSchema>>
41
+
42
+ /** Validates one organism output value as a bounded emitted-record array. */
43
+ export function parseSpatialBehaviorEmissions(input: unknown, what: string): SpatialBehaviorEmitted[] {
44
+ return parseSpatialValue(z.array(SpatialBehaviorEmittedSchema).max(SPATIAL_BEHAVIOR_TRACE_LIMITS.emitted), input, what)
45
+ }
46
+
47
+ // -------------------------------------------------------------- channel map ---
48
+
49
+ const humanoidBone = z.enum(HUMANOID_BONE_NAMES)
50
+ const bindingChannel = channelName
51
+ const clipValue = z.string().min(1).max(64)
52
+
53
+ /**
54
+ * Host-supplied bindings from emitted channels to performance-directive
55
+ * assets. Clip digests, prop identities and attachment bones are
56
+ * scene-admission concerns, so they live outside the portable behavior doc:
57
+ * the map rides the bake request. Channels absent from the map stay
58
+ * trace-only; a mapped channel missing a sub-binding reports an unresolved
59
+ * intent rather than fabricating identity.
60
+ */
61
+ export const SpatialBehaviorChannelMapSchema = z.strictObject({
62
+ clips: z.record(
63
+ bindingChannel,
64
+ z.record(clipValue, z.strictObject({
65
+ clipDigest: SpatialDigestSchema,
66
+ durationUs: SpatialTimeUsSchema.refine((value) => value > 0, "Clip duration must be positive."),
67
+ })),
68
+ ).refine(
69
+ (clips) => Object.keys(clips).length <= SPATIAL_BEHAVIOR_TRACE_LIMITS.clipChannels
70
+ && Object.values(clips).every((values) => Object.keys(values).length <= SPATIAL_BEHAVIOR_TRACE_LIMITS.clipValues),
71
+ { message: `At most ${SPATIAL_BEHAVIOR_TRACE_LIMITS.clipChannels} clip channels with ${SPATIAL_BEHAVIOR_TRACE_LIMITS.clipValues} values each.` },
72
+ ).optional(),
73
+ attachments: z.array(z.strictObject({
74
+ attachChannel: bindingChannel,
75
+ releaseChannel: bindingChannel,
76
+ propId: z.string().min(1).max(128),
77
+ bone: humanoidBone,
78
+ localOffset: SpatialTransformSchema,
79
+ })).max(SPATIAL_BEHAVIOR_TRACE_LIMITS.attachments).optional(),
80
+ trajectories: z.array(bindingChannel).max(SPATIAL_BEHAVIOR_TRACE_LIMITS.trajectories).optional(),
81
+ }).superRefine((map, context) => {
82
+ const attachChannels = new Set<string>()
83
+ for (const [index, attachment] of (map.attachments ?? []).entries()) {
84
+ if (attachChannels.has(attachment.attachChannel)) {
85
+ context.addIssue({ code: "custom", path: ["attachments", index, "attachChannel"], message: `Attach channel ${attachment.attachChannel} is bound twice.` })
86
+ }
87
+ attachChannels.add(attachment.attachChannel)
88
+ }
89
+ for (const name of map.trajectories ?? []) {
90
+ if (map.clips !== undefined && name in map.clips) {
91
+ context.addIssue({ code: "custom", path: ["trajectories"], message: `Channel ${name} is bound as both clip and trajectory.` })
92
+ }
93
+ }
94
+ })
95
+ export type SpatialBehaviorChannelMap = Readonly<z.infer<typeof SpatialBehaviorChannelMapSchema>>
96
+
97
+ export function parseSpatialBehaviorChannelMap(input: unknown): SpatialBehaviorChannelMap {
98
+ return deepFreezeJson(parseSpatialValue(SpatialBehaviorChannelMapSchema, input, "behavior channel map"))
99
+ }
100
+
101
+ // ----------------------------------------------------------- unresolved ----
102
+
103
+ export const SpatialBehaviorUnresolvedIntentSchema = z.strictObject({
104
+ intentId: z.string().regex(/^prop_[a-f0-9]{16}$/u),
105
+ domain: z.literal("behavior"),
106
+ referenceId: z.string().min(1).max(128),
107
+ slot: z.string().min(1).max(64).regex(/^[a-z][a-z0-9-]*$/u),
108
+ detail: z.string().min(1).max(240),
109
+ })
110
+ export type SpatialBehaviorUnresolvedIntent = Readonly<z.infer<typeof SpatialBehaviorUnresolvedIntentSchema>>
111
+
112
+ const intentId = (domain: string, reference: string): string =>
113
+ `prop_${spatialValueSha256({ domain: `slopcamera.behavior-intent.${domain}`, reference }).slice(0, 16)}`
114
+
115
+ const unresolved = (domain: string, referenceId: string, slot: string, detail: string): SpatialBehaviorUnresolvedIntent =>
116
+ deepFreezeJson({ intentId: intentId(domain, `${referenceId}/${slot}`), domain: "behavior", referenceId, slot, detail })
117
+
118
+ // ------------------------------------------------------------ mapper --------
119
+
120
+ const emittedValueKey = (value: unknown): string => {
121
+ try {
122
+ return spatialValueSha256(value as JsonValue)
123
+ } catch {
124
+ return "\u0000unrepresentable"
125
+ }
126
+ }
127
+
128
+ const directiveId = (domain: string, parts: readonly (string | number)[]): string =>
129
+ `dir_${domain}_${spatialValueSha256({ domain: `slopcamera.behavior-directive.${domain}`, parts }).slice(0, 12)}`
130
+
131
+ export interface SpatialBehaviorDirectiveCompilation {
132
+ readonly directives: readonly SpatialPerformanceDirective[]
133
+ readonly unresolvedIntents: readonly SpatialBehaviorUnresolvedIntent[]
134
+ }
135
+
136
+ /**
137
+ * Maps a validated emitted trace to performance directives under a channel
138
+ * map. Clip channels emit contiguous same-value runs as clip directives;
139
+ * attach/release channels pair into prop bindings; trajectory channels
140
+ * accumulate waypoint values into one root-trajectory directive. Unmapped
141
+ * channels contribute nothing — their records stay trace evidence only.
142
+ */
143
+ export function compileSpatialBehaviorDirectives(input: {
144
+ readonly emitted: readonly SpatialBehaviorEmitted[]
145
+ readonly channelMap?: SpatialBehaviorChannelMap
146
+ readonly rangeUs: { readonly startUs: number; readonly endUs: number }
147
+ }): SpatialBehaviorDirectiveCompilation {
148
+ const { emitted, channelMap, rangeUs } = input
149
+ const directives: SpatialPerformanceDirective[] = []
150
+ const unresolvedIntents: SpatialBehaviorUnresolvedIntent[] = []
151
+ const map = channelMap ?? {}
152
+
153
+ const sorted = [...emitted].sort((a, b) => a.tUs - b.tUs || a.channel.localeCompare(b.channel))
154
+
155
+ const clipChannels = Object.keys(map.clips ?? {})
156
+ for (const channel of clipChannels) {
157
+ const bindings = map.clips![channel]!
158
+ const records = sorted.filter((record) => record.channel === channel)
159
+ if (records.length === 0) continue
160
+ let runStart = records[0]!.tUs
161
+ let runValue = emittedValueKey(records[0]!.value)
162
+ let runLabel = records[0]!.value
163
+ const flush = (endUs: number): void => {
164
+ const label = typeof runLabel === "string" ? runLabel : undefined
165
+ const binding = label === undefined ? undefined : bindings[label]
166
+ if (binding === undefined) {
167
+ unresolvedIntents.push(unresolved("clip", `${channel}:${label ?? "non-string"}`, "clip-binding",
168
+ `Channel ${channel} emitted ${label === undefined ? "a non-string value" : `value ${label}`} with no clip binding at ${runStart}us.`))
169
+ return
170
+ }
171
+ const intervalUs = endUs - runStart
172
+ if (intervalUs <= 0) return
173
+ directives.push(deepFreezeJson(SpatialPerformanceDirectiveSchema.parse({
174
+ directiveId: directiveId("clip", [channel, runStart, endUs]),
175
+ kind: "clip",
176
+ clipDigest: binding.clipDigest,
177
+ startUs: runStart,
178
+ endUs,
179
+ trimStartUs: 0,
180
+ trimEndUs: Math.min(intervalUs, binding.durationUs),
181
+ loop: intervalUs > binding.durationUs ? Math.ceil(intervalUs / binding.durationUs) : "once",
182
+ timeScale: 1,
183
+ })))
184
+ }
185
+ for (let index = 1; index < records.length; index += 1) {
186
+ const record = records[index]!
187
+ const key = emittedValueKey(record.value)
188
+ if (key !== runValue) {
189
+ flush(record.tUs)
190
+ runStart = record.tUs
191
+ runValue = key
192
+ runLabel = record.value
193
+ }
194
+ }
195
+ flush(rangeUs.endUs)
196
+ }
197
+
198
+ for (const attachment of map.attachments ?? []) {
199
+ const attachRecords = sorted.filter((record) => record.channel === attachment.attachChannel)
200
+ const releaseRecords = sorted.filter((record) => record.channel === attachment.releaseChannel)
201
+ if (attachRecords.length === 0 && releaseRecords.length === 0) continue
202
+ if (releaseRecords.length > attachRecords.length) {
203
+ unresolvedIntents.push(unresolved("attach", attachment.propId, "release-order",
204
+ `Channel ${attachment.releaseChannel} emits ${releaseRecords.length} releases against ${attachRecords.length} attaches on ${attachment.attachChannel}.`))
205
+ continue
206
+ }
207
+ for (const [index, attachRecord] of attachRecords.entries()) {
208
+ const releaseRecord = releaseRecords[index]
209
+ if (releaseRecord !== undefined && releaseRecord.tUs <= attachRecord.tUs) {
210
+ unresolvedIntents.push(unresolved("attach", attachment.propId, "release-order",
211
+ `Release on ${attachment.releaseChannel} at ${releaseRecord.tUs}us does not follow attach on ${attachment.attachChannel} at ${attachRecord.tUs}us.`))
212
+ continue
213
+ }
214
+ const endUs = releaseRecord !== undefined ? releaseRecord.tUs : rangeUs.endUs
215
+ directives.push(deepFreezeJson(SpatialPerformanceDirectiveSchema.parse({
216
+ directiveId: directiveId("attach", [attachment.propId, attachRecord.tUs]),
217
+ kind: "attach",
218
+ propId: attachment.propId,
219
+ bone: attachment.bone,
220
+ localOffset: attachment.localOffset,
221
+ startUs: attachRecord.tUs,
222
+ endUs,
223
+ })))
224
+ if (releaseRecord !== undefined) {
225
+ directives.push(deepFreezeJson(SpatialPerformanceDirectiveSchema.parse({
226
+ directiveId: directiveId("release", [attachment.propId, releaseRecord.tUs]),
227
+ kind: "release",
228
+ propId: attachment.propId,
229
+ startUs: releaseRecord.tUs,
230
+ })))
231
+ }
232
+ }
233
+ }
234
+
235
+ for (const channel of map.trajectories ?? []) {
236
+ const records = sorted.filter((record) => record.channel === channel)
237
+ if (records.length === 0) continue
238
+ const waypoints: { timeUs: number; position: readonly number[]; rotation: readonly number[] }[] = []
239
+ let malformed = 0
240
+ for (const record of records) {
241
+ const value = record.value
242
+ if (value === null || typeof value !== "object" || Array.isArray(value)) { malformed += 1; continue }
243
+ const waypoint = value as { position?: unknown; rotation?: unknown }
244
+ const position = SpatialVec3Schema.safeParse(waypoint.position)
245
+ const rotation = waypoint.rotation === undefined ? undefined : SpatialQuaternionSchema.safeParse(waypoint.rotation)
246
+ if (!position.success || (rotation !== undefined && !rotation.success)) { malformed += 1; continue }
247
+ waypoints.push({
248
+ timeUs: record.tUs,
249
+ position: position.data,
250
+ rotation: rotation === undefined ? [0, 0, 0, 1] : rotation.data,
251
+ })
252
+ }
253
+ if (malformed > 0) {
254
+ unresolvedIntents.push(unresolved("trajectory", channel, "waypoint-shape",
255
+ `Channel ${channel} emitted ${malformed} malformed waypoint records; waypoints need {position, rotation?}.`))
256
+ }
257
+ if (waypoints.length < 2) {
258
+ unresolvedIntents.push(unresolved("trajectory", channel, "waypoint-count",
259
+ `Channel ${channel} produced ${waypoints.length} valid waypoints; a root trajectory needs at least 2.`))
260
+ continue
261
+ }
262
+ directives.push(deepFreezeJson(SpatialPerformanceDirectiveSchema.parse({
263
+ directiveId: directiveId("trajectory", [channel, waypoints[0]!.timeUs, waypoints.length]),
264
+ kind: "root-trajectory",
265
+ waypoints,
266
+ })))
267
+ }
268
+
269
+ if (directives.length > SPATIAL_BEHAVIOR_TRACE_LIMITS.directives) {
270
+ throw new SpatialSceneError("invalid-data", `over-bound: channel map produced ${directives.length} directives beyond ${SPATIAL_BEHAVIOR_TRACE_LIMITS.directives}.`, "behavior-trace")
271
+ }
272
+ return deepFreezeJson({ directives, unresolvedIntents })
273
+ }
274
+
275
+ // ------------------------------------------------------------- bake ---------
276
+
277
+ export const SpatialBehaviorBakeReceiptSchema = z.strictObject({
278
+ kind: z.literal("slopcamera.spatial-behavior-bake-receipt"),
279
+ schemaVersion: z.literal(1),
280
+ behaviorSha256: SpatialDigestSchema,
281
+ sceneSha256: SpatialDigestSchema,
282
+ entry: z.string().regex(/^sha256:[a-f0-9]{64}$/u),
283
+ manifestDigest: z.string().regex(/^sha256:[a-f0-9]{64}$/u),
284
+ runDigest: z.string().regex(/^sha256:[a-f0-9]{64}$/u),
285
+ runtime: z.strictObject({ name: z.string().min(1).max(64), version: z.string().min(1).max(64) }),
286
+ fnCatalogSha256: SpatialDigestSchema,
287
+ emittedSha256: SpatialDigestSchema,
288
+ channelMapSha256: SpatialDigestSchema.optional(),
289
+ outcome: z.enum(["complete", "failed", "stuck"]),
290
+ work: z.strictObject({
291
+ steps: z.number().int().min(0).max(SPATIAL_SCENE_LIMITS.durationUs),
292
+ agentCalls: z.number().int().min(0).max(65_536),
293
+ units: z.number().int().min(0).max(100_000_000),
294
+ }),
295
+ })
296
+ export type SpatialBehaviorBakeReceipt = Readonly<z.infer<typeof SpatialBehaviorBakeReceiptSchema>>
297
+
298
+ export const SpatialBehaviorBakeSchema = z.strictObject({
299
+ kind: z.literal("slopcamera.spatial-behavior-bake"),
300
+ schemaVersion: z.literal(1),
301
+ behaviorSha256: SpatialDigestSchema,
302
+ sceneSha256: SpatialDigestSchema,
303
+ entry: z.string().regex(/^sha256:[a-f0-9]{64}$/u),
304
+ seed: z.number().int().min(0).max(4_294_967_295),
305
+ rangeUs: z.strictObject({ startUs: SpatialTimeUsSchema, endUs: SpatialTimeUsSchema }),
306
+ emitted: z.array(SpatialBehaviorEmittedSchema).max(SPATIAL_BEHAVIOR_TRACE_LIMITS.emitted),
307
+ directives: z.array(SpatialPerformanceDirectiveSchema).max(SPATIAL_BEHAVIOR_TRACE_LIMITS.directives),
308
+ unresolvedIntents: z.array(SpatialBehaviorUnresolvedIntentSchema).max(SPATIAL_BEHAVIOR_TRACE_LIMITS.unresolvedIntents),
309
+ receipt: SpatialBehaviorBakeReceiptSchema,
310
+ })
311
+ export type SpatialBehaviorBake = Readonly<z.infer<typeof SpatialBehaviorBakeSchema>>
312
+
313
+ export function spatialBehaviorBakeSha256(bake: SpatialBehaviorBake): string {
314
+ return spatialValueSha256(bake)
315
+ }
316
+
317
+ /** Digest binding the admitted fn catalog — names, port signatures, and modeled work costs. */
318
+ export function spatialBehaviorFnCatalogSha256(fns: ReadonlyMap<string, SpatialBehaviorFn>): string {
319
+ const projection: Record<string, unknown> = {}
320
+ for (const name of [...fns.keys()].sort()) {
321
+ const signature = fns.get(name)!.signature
322
+ projection[name] = { inputs: signature.inputs, outputs: signature.outputs, cost: signature.cost }
323
+ }
324
+ return spatialValueSha256({ domain: "slopcamera.behavior-fn-catalog.v1", fns: projection } as JsonValue & Record<string, unknown>)
325
+ }