@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.
- package/NOTICE.md +1 -7
- package/PRIVACY.md +16 -7
- package/README.md +65 -20
- package/apps/desktop/README.md +14 -130
- package/apps/desktop/application/context.ts +6 -9
- package/apps/desktop/application/default-registry.ts +26 -10
- package/apps/desktop/application/operation.ts +14 -5
- package/apps/desktop/application/operations/index.ts +4 -1
- package/apps/desktop/application/operations/spatial-behavior.ts +151 -0
- package/apps/desktop/application/operations/spatial-direction.ts +222 -0
- package/apps/desktop/application/operations/spatial-rendered-audit.ts +174 -0
- package/apps/desktop/application/operations/spatial-review.ts +194 -0
- package/apps/desktop/application/operations/spatial-scene.ts +25 -1
- package/apps/desktop/application/registry.ts +2 -2
- package/apps/desktop/application/spatial-asset-admission.ts +201 -0
- package/apps/desktop/application/spatial-assets.ts +160 -31
- package/apps/desktop/application/spatial-render.ts +217 -20
- package/apps/desktop/application/spatial-rendered-audit.ts +273 -0
- package/apps/desktop/application/spatial-review.ts +361 -0
- package/apps/desktop/application/spatial-spz.ts +48 -3
- package/apps/desktop/application/spatial-world-import.ts +26 -5
- package/apps/desktop/application/spatial-world-metadata.ts +105 -0
- package/apps/desktop/cli/args.ts +528 -97
- package/apps/desktop/cli/capability-manifest.ts +374 -0
- package/apps/desktop/cli/cinema-renderer.ts +608 -0
- package/apps/desktop/cli/command-host-resources.ts +31 -2
- package/apps/desktop/cli/commands.ts +1215 -70
- package/apps/desktop/cli/gateway-review-provider.ts +334 -0
- package/apps/desktop/cli/help.ts +183 -23
- package/apps/desktop/cli/html-scene.ts +139 -28
- package/apps/desktop/cli/io.ts +2 -1
- package/apps/desktop/cli/main.ts +26 -50
- package/apps/desktop/cli/menubar.ts +244 -0
- package/apps/desktop/cli/music-analysis-service.ts +61 -0
- package/apps/desktop/cli/portable-surface.ts +18 -5
- package/apps/desktop/cli/spatial-asset-service.ts +124 -0
- package/apps/desktop/cli/spatial-design-service.ts +237 -0
- package/apps/desktop/cli/spatial-generate-service.ts +370 -0
- package/apps/desktop/cli/spatial-scene-service.ts +268 -4
- package/apps/desktop/cli/workflow-code.ts +1 -1
- package/apps/desktop/code/application-node-planner.ts +0 -47
- package/apps/desktop/code/host-source-layout.ts +46 -0
- package/apps/desktop/code/public.ts +0 -1
- package/apps/desktop/code/runtime-identity.ts +50 -8
- package/apps/desktop/code/semantic-builder.ts +24 -43
- package/apps/desktop/code/source-bundle.ts +23 -6
- package/apps/desktop/code/source-typecheck.ts +131 -43
- package/apps/desktop/code/worker-client.ts +14 -2
- package/apps/desktop/contracts/cinema.ts +736 -0
- package/apps/desktop/contracts/index.ts +1 -0
- package/apps/desktop/contracts/spatial-asset.ts +16 -0
- package/apps/desktop/contracts/spatial-world.ts +26 -3
- package/apps/desktop/core/cinema-plan.ts +1320 -0
- package/apps/desktop/core/index.ts +1 -0
- package/apps/desktop/core/music-analysis.ts +124 -1
- package/apps/desktop/core/storage.ts +75 -0
- package/apps/desktop/dist/cli/main.js +737 -291
- package/apps/desktop/html-overlay/audio-reactivity.ts +127 -0
- package/apps/desktop/html-overlay/index.ts +9 -0
- package/apps/desktop/html-overlay/runtime.ts +2 -0
- package/apps/desktop/html-overlay/scene.ts +29 -0
- package/apps/desktop/html-overlay/spatial.ts +619 -57
- package/apps/desktop/workflows/cinematic-world.ts +127 -0
- package/apps/desktop/workflows/index.ts +6 -0
- package/dist/cli.js +32 -434
- package/dist/code/advanced.js +1 -269
- package/dist/code/index.js +16 -2239
- package/dist/generate.js +1 -23
- package/dist/host-resources.js +1 -25
- package/dist/index-0bbesn4m.js +35 -0
- package/dist/index-296qhpf2.js +3 -0
- package/dist/index-3cxf2c58.js +3 -0
- package/dist/index-77fjfg9f.js +3 -0
- package/dist/index-80a36bc0.js +3 -0
- package/dist/index-a3vvs3rf.js +3 -0
- package/dist/index-b3geqczn.js +5 -0
- package/dist/index-cgpkyjqa.js +3 -0
- package/dist/index-f0ptv8dm.js +7 -0
- package/dist/index-grk47tgs.js +3 -0
- package/dist/index-qry58nj2.js +10 -0
- package/dist/index.js +1 -240
- package/dist/operations.js +1 -28
- package/dist/skill-install-gx7afxsd.js +2 -0
- package/dist/vectorize/worker.js +1 -141
- package/dist/workflow.js +1 -15
- package/docs/README.md +5 -1
- package/docs/studio.md +1 -1
- package/package.json +11 -24
- package/skills/slopcamera/SKILL.md +14 -1
- package/skills/slopcamera/references/diagrams.md +5 -14
- package/skills/slopcamera/references/directed-scenes.md +22 -2
- package/skills/slopcamera/references/gateway-media.md +15 -1
- package/skills/slopcamera/references/image-galleries.md +166 -0
- package/skills/slopcamera/references/install.md +2 -2
- package/skills/slopcamera/references/music-video.md +23 -3
- package/skills/slopcamera/references/parametric-design.md +14 -0
- package/skills/slopcamera/references/reference-led-3d.md +1 -1
- package/skills/slopcamera/references/scene-building.md +32 -0
- package/skills/slopcamera/references/social-collage-banners.md +204 -0
- package/skills/slopcamera/references/support.md +25 -0
- package/skills/slopcamera/references/web-media-excerpts.md +120 -0
- package/skills/slopcamera/references/workflows-sdk.md +3 -3
- package/skills/slopcamera/scripts/compose-social-collage-banner.ts +728 -0
- package/src/capability-manifest.ts +361 -0
- package/src/cli.ts +250 -102
- package/src/code/canonical-json.ts +9 -0
- package/src/generate.ts +2 -2
- package/src/icon.ts +1111 -0
- package/src/image-gallery.ts +1049 -0
- package/src/index.ts +11 -23
- package/src/mcp/boundary.ts +11 -11
- package/src/mcp/index.ts +3 -0
- package/src/mcp/server.ts +2 -1
- package/src/mcp/tools.ts +1642 -130
- package/src/mcp/types.ts +19 -0
- package/src/operations.ts +412 -0
- package/src/portable-capability-manifest.ts +67 -0
- package/src/scene-gallery.ts +184 -0
- package/src/spatial-scene/asset-admission.ts +183 -0
- package/src/spatial-scene/audit-rendered.ts +692 -0
- package/src/spatial-scene/audit.ts +617 -0
- package/src/spatial-scene/behavior-audit.ts +252 -0
- package/src/spatial-scene/behavior-bake.ts +233 -0
- package/src/spatial-scene/behavior-fns.ts +585 -0
- package/src/spatial-scene/behavior-gallery.ts +109 -0
- package/src/spatial-scene/behavior-stdlib.ts +323 -0
- package/src/spatial-scene/behavior-trace.ts +325 -0
- package/src/spatial-scene/behavior.ts +618 -0
- package/src/spatial-scene/build.ts +632 -0
- package/src/spatial-scene/camera-rig.ts +125 -0
- package/src/spatial-scene/camera-track.ts +4 -2
- package/src/spatial-scene/character.ts +124 -0
- package/src/spatial-scene/contracts.ts +75 -7
- package/src/spatial-scene/design-templates.ts +351 -0
- package/src/spatial-scene/design.ts +327 -0
- package/src/spatial-scene/direction-compile.ts +584 -0
- package/src/spatial-scene/direction.ts +122 -0
- package/src/spatial-scene/effects.ts +149 -0
- package/src/spatial-scene/evaluate.ts +109 -29
- package/src/spatial-scene/gallery.ts +277 -0
- package/src/spatial-scene/generate.ts +239 -0
- package/src/spatial-scene/geometry-native.ts +165 -0
- package/src/spatial-scene/geometry.ts +1216 -0
- package/src/spatial-scene/gltf.ts +600 -77
- package/src/spatial-scene/identity.ts +55 -2
- package/src/spatial-scene/index.ts +36 -0
- package/src/spatial-scene/inspect.ts +35 -6
- package/src/spatial-scene/material-lighting.ts +609 -0
- package/src/spatial-scene/math.ts +21 -4
- package/src/spatial-scene/motion-evidence.ts +88 -0
- package/src/spatial-scene/parametric.ts +765 -0
- package/src/spatial-scene/particle-preparation.ts +183 -0
- package/src/spatial-scene/particle.ts +195 -0
- package/src/spatial-scene/patch.ts +109 -15
- package/src/spatial-scene/performance.ts +1613 -0
- package/src/spatial-scene/probe.ts +137 -0
- package/src/spatial-scene/recipe-pack.ts +86 -0
- package/src/spatial-scene/render-effects.ts +240 -0
- package/src/spatial-scene/review.ts +639 -0
- package/src/spatial-scene/simulation-bake.ts +206 -0
- package/src/spatial-scene/simulation.ts +187 -0
- package/src/spatial-scene/solve.ts +366 -0
- package/src/spatial-scene/temporal-audit.ts +354 -0
- package/src/support-completion.ts +16 -0
- package/src/support.ts +42 -0
- package/src/version.ts +1 -1
- package/apps/desktop/application/operations/recording/index.ts +0 -5
- package/apps/desktop/application/operations/recording/pause.ts +0 -34
- package/apps/desktop/application/operations/recording/resume.ts +0 -34
- package/apps/desktop/application/operations/recording/shared.ts +0 -199
- package/apps/desktop/application/operations/recording/start.ts +0 -169
- package/apps/desktop/application/operations/recording/stop.ts +0 -34
- package/apps/desktop/capture/protocol.ts +0 -689
- package/apps/desktop/cli/capture-bundle.ts +0 -1117
- package/apps/desktop/cli/recording-controller.ts +0 -1567
- package/apps/desktop/cli/recording-daemon.ts +0 -931
- package/dist/index-42zsesc1.js +0 -2257
- package/dist/index-7308egqr.js +0 -48
- package/dist/index-8txs6fkn.js +0 -712
- package/dist/index-mcy8z0br.js +0 -192
- package/dist/index-p63wavx0.js +0 -2029
- package/dist/index-r7gdhmsp.js +0 -424
- package/dist/index-rvghzt7b.js +0 -2013
- package/dist/index-sh6xbav6.js +0 -1219
- package/dist/index-z1w83f81.js +0 -4
- package/dist/index-z7239b4h.js +0 -2363
- package/dist/skill-install-dh0ntqba.js +0 -10
- 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
|
+
}
|