@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,354 @@
1
+ import { z } from "zod"
2
+
3
+ import { deepFreezeJson } from "../code/json-snapshot.js"
4
+ import {
5
+ EvaluatedSpatialSceneSchema,
6
+ SpatialCameraIdSchema,
7
+ SpatialDigestSchema,
8
+ SpatialEntityIdSchema,
9
+ SpatialTimeUsSchema,
10
+ type EvaluatedSpatialScene,
11
+ } from "./contracts.js"
12
+ import { parseSpatialValue, SpatialSceneError, spatialValueSha256 } from "./identity.js"
13
+
14
+ /**
15
+ * Temporal evidence audit for Phase 12.
16
+ *
17
+ * Consumes an ordered run of evaluated snapshots (derived state only — never
18
+ * renders) and reports exact sample/time evidence for flicker, transform
19
+ * discontinuity, planted-contact sliding, camera acceleration/jerk and angular
20
+ * velocity, exposure/focus jumps, and resource spikes. Findings reference the
21
+ * snapshot index and integer microsecond time at which the violation was
22
+ * measured so reviewers can re-evaluate the exact frame.
23
+ */
24
+
25
+ export const SPATIAL_TEMPORAL_AUDIT_LIMITS = Object.freeze({
26
+ samples: 256,
27
+ contacts: 64,
28
+ cutBoundaries: 64,
29
+ findings: 512,
30
+ })
31
+
32
+ export const SpatialTemporalContactSchema = z.strictObject({
33
+ entityId: SpatialEntityIdSchema,
34
+ groundY: z.number().finite().min(-1_000_000).max(1_000_000),
35
+ startUs: SpatialTimeUsSchema,
36
+ endUs: SpatialTimeUsSchema,
37
+ }).superRefine((contact, context) => {
38
+ if (contact.endUs <= contact.startUs) {
39
+ context.addIssue({ code: "custom", path: ["endUs"], message: "Contact windows must have positive duration." })
40
+ }
41
+ })
42
+ export type SpatialTemporalContact = Readonly<z.infer<typeof SpatialTemporalContactSchema>>
43
+
44
+ /** Companion file for ground-contact evidence supplied alongside a scene. */
45
+ export const SpatialTemporalContactsFileSchema = z.strictObject({
46
+ contacts: z.array(SpatialTemporalContactSchema).max(SPATIAL_TEMPORAL_AUDIT_LIMITS.contacts),
47
+ })
48
+ export type SpatialTemporalContactsFile = Readonly<z.infer<typeof SpatialTemporalContactsFileSchema>>
49
+
50
+ export const SpatialTemporalAuditOptionsSchema = z.strictObject({
51
+ contacts: z.array(SpatialTemporalContactSchema).max(SPATIAL_TEMPORAL_AUDIT_LIMITS.contacts).default([]),
52
+ cutBeforeUs: z.array(SpatialTimeUsSchema).max(SPATIAL_TEMPORAL_AUDIT_LIMITS.cutBoundaries).default([]),
53
+ maxPositionJumpM: z.number().finite().positive().max(1_000_000).default(5),
54
+ maxFootSlideMps: z.number().finite().positive().max(1_000).default(0.05),
55
+ maxCameraAccelerationMps2: z.number().finite().positive().max(1_000_000).default(50),
56
+ maxCameraJerkMps3: z.number().finite().positive().max(1_000_000_000).default(400),
57
+ maxAngularVelocityRadps: z.number().finite().positive().max(100_000).default(4),
58
+ maxExposureJumpEv: z.number().finite().positive().max(64).default(1),
59
+ maxFocusJumpM: z.number().finite().positive().max(1_000_000).default(0.5),
60
+ maxNewAssetsPerSample: z.number().int().min(1).max(1_024).default(8),
61
+ maxEntityCountDelta: z.number().int().min(1).max(1_024).default(16),
62
+ })
63
+ export type SpatialTemporalAuditOptions = Readonly<z.infer<typeof SpatialTemporalAuditOptionsSchema>>
64
+
65
+ export const SpatialTemporalFindingKindSchema = z.enum([
66
+ "visibility-flicker",
67
+ "transform-discontinuity",
68
+ "foot-slide",
69
+ "camera-acceleration",
70
+ "camera-jerk",
71
+ "camera-angular-velocity",
72
+ "exposure-jump",
73
+ "focus-jump",
74
+ "resource-spike",
75
+ ])
76
+ export type SpatialTemporalFindingKind = z.infer<typeof SpatialTemporalFindingKindSchema>
77
+
78
+ export const SpatialTemporalFindingSchema = z.strictObject({
79
+ kind: SpatialTemporalFindingKindSchema,
80
+ sampleIndex: z.number().int().min(0).max(SPATIAL_TEMPORAL_AUDIT_LIMITS.samples - 1),
81
+ timeUs: SpatialTimeUsSchema,
82
+ entityId: SpatialEntityIdSchema.optional(),
83
+ cameraId: SpatialCameraIdSchema.optional(),
84
+ measured: z.number().finite(),
85
+ limit: z.number().finite(),
86
+ detail: z.string().min(1).max(240),
87
+ })
88
+ export type SpatialTemporalFinding = Readonly<z.infer<typeof SpatialTemporalFindingSchema>>
89
+
90
+ export const SpatialTemporalAuditReportSchema = z.strictObject({
91
+ kind: z.literal("slopcamera.spatial-temporal-audit-report"),
92
+ schemaVersion: z.literal(1),
93
+ sceneSha256: SpatialDigestSchema,
94
+ cameraId: SpatialCameraIdSchema,
95
+ sampleCount: z.number().int().min(2).max(SPATIAL_TEMPORAL_AUDIT_LIMITS.samples),
96
+ firstTimeUs: SpatialTimeUsSchema,
97
+ lastTimeUs: SpatialTimeUsSchema,
98
+ findings: z.array(SpatialTemporalFindingSchema).max(SPATIAL_TEMPORAL_AUDIT_LIMITS.findings),
99
+ omittedFindings: z.number().int().min(0),
100
+ thresholds: SpatialTemporalAuditOptionsSchema.omit({ contacts: true, cutBeforeUs: true }),
101
+ contactCount: z.number().int().min(0).max(SPATIAL_TEMPORAL_AUDIT_LIMITS.contacts),
102
+ })
103
+ export type SpatialTemporalAuditReport = Readonly<z.infer<typeof SpatialTemporalAuditReportSchema>>
104
+
105
+ export function spatialTemporalAuditReportSha256(report: SpatialTemporalAuditReport): string {
106
+ return spatialValueSha256(report)
107
+ }
108
+
109
+ const auditInputSchema = z.strictObject({
110
+ snapshots: z.array(z.unknown()).min(2).max(SPATIAL_TEMPORAL_AUDIT_LIMITS.samples),
111
+ options: z.unknown().optional(),
112
+ })
113
+
114
+ const KIND_ORDER: Readonly<Record<SpatialTemporalFindingKind, number>> = {
115
+ "visibility-flicker": 0,
116
+ "transform-discontinuity": 1,
117
+ "foot-slide": 2,
118
+ "camera-acceleration": 3,
119
+ "camera-jerk": 4,
120
+ "camera-angular-velocity": 5,
121
+ "exposure-jump": 6,
122
+ "focus-jump": 7,
123
+ "resource-spike": 8,
124
+ }
125
+
126
+ const translation = (snapshot: EvaluatedSpatialScene, entityId: string): [number, number, number] | undefined => {
127
+ const evaluated = snapshot.entities.find((item) => item.entity.entityId === entityId)
128
+ if (evaluated === undefined) return undefined
129
+ return [evaluated.worldMatrix[12]!, evaluated.worldMatrix[13]!, evaluated.worldMatrix[14]!]
130
+ }
131
+
132
+ const distance = (a: readonly number[], b: readonly number[]): number =>
133
+ Math.hypot(a[0]! - b[0]!, a[1]! - b[1]!, a[2]! - b[2]!)
134
+
135
+ const horizontalDistance = (a: readonly number[], b: readonly number[]): number =>
136
+ Math.hypot(a[0]! - b[0]!, a[2]! - b[2]!)
137
+
138
+ const seconds = (later: EvaluatedSpatialScene, earlier: EvaluatedSpatialScene): number =>
139
+ (later.timeUs - earlier.timeUs) / 1_000_000
140
+
141
+ function quaternionAngleRadians(a: readonly number[], b: readonly number[]): number {
142
+ const dot = Math.min(1, Math.max(-1, Math.abs(a.reduce((sum, value, index) => sum + value * b[index]!, 0))))
143
+ return 2 * Math.acos(dot)
144
+ }
145
+
146
+ /**
147
+ * Audits one ordered run of snapshots. Snapshots must share one scene digest
148
+ * and camera identity and be strictly increasing in time; callers derive them
149
+ * with `evaluateSpatialScene` so evidence stays reproducible.
150
+ */
151
+ export function auditSpatialTemporalEvidence(input: unknown): SpatialTemporalAuditReport {
152
+ const value = parseSpatialValue(auditInputSchema, input, "temporal audit")
153
+ const snapshots = value.snapshots.map((snapshot) => parseSpatialValue(EvaluatedSpatialSceneSchema, snapshot, "snapshot"))
154
+ const options = parseSpatialValue(SpatialTemporalAuditOptionsSchema, value.options ?? {}, "temporal audit options")
155
+
156
+ const sceneSha256 = snapshots[0]!.sceneSha256
157
+ const cameraId = snapshots[0]!.camera.cameraId
158
+ for (const [index, snapshot] of snapshots.entries()) {
159
+ if (snapshot.sceneSha256 !== sceneSha256) {
160
+ throw new SpatialSceneError("invalid-data", `Snapshot ${index} belongs to a different scene digest.`, "snapshots")
161
+ }
162
+ if (snapshot.camera.cameraId !== cameraId) {
163
+ throw new SpatialSceneError("invalid-data", `Snapshot ${index} uses a different camera.`, "snapshots")
164
+ }
165
+ if (index > 0 && snapshot.timeUs <= snapshots[index - 1]!.timeUs) {
166
+ throw new SpatialSceneError("invalid-data", "Snapshots must be strictly increasing in time.", "snapshots")
167
+ }
168
+ }
169
+
170
+ const findings: SpatialTemporalFinding[] = []
171
+ const cutTimes = new Set(options.cutBeforeUs)
172
+ const isCutBoundary = (snapshot: EvaluatedSpatialScene): boolean => cutTimes.has(snapshot.timeUs)
173
+ const add = (finding: SpatialTemporalFinding): void => {
174
+ if (findings.length < SPATIAL_TEMPORAL_AUDIT_LIMITS.findings) findings.push(finding)
175
+ }
176
+
177
+ const entityIds = [...new Set(snapshots.flatMap((snapshot) => snapshot.entities.map((item) => item.entity.entityId)))].sort()
178
+
179
+ // Visibility flicker: an entity toggling hidden↔visible more than once in the run.
180
+ for (const entityId of entityIds) {
181
+ const states = snapshots.map((snapshot) => snapshot.entities.find((item) => item.entity.entityId === entityId)?.visible)
182
+ let flips = 0
183
+ let firstFlipIndex = -1
184
+ for (let index = 1; index < states.length; index += 1) {
185
+ if (states[index] !== undefined && states[index - 1] !== undefined && states[index] !== states[index - 1]) {
186
+ flips += 1
187
+ if (firstFlipIndex === -1) firstFlipIndex = index
188
+ }
189
+ }
190
+ if (flips >= 2) {
191
+ add({
192
+ kind: "visibility-flicker", sampleIndex: firstFlipIndex, timeUs: snapshots[firstFlipIndex]!.timeUs,
193
+ entityId, measured: flips, limit: 1,
194
+ detail: `Entity ${entityId} toggles visibility ${flips} times across the sampled run.`,
195
+ })
196
+ }
197
+ }
198
+
199
+ // Transform discontinuity and foot slide across consecutive samples.
200
+ for (const entityId of entityIds) {
201
+ for (let index = 1; index < snapshots.length; index += 1) {
202
+ const previous = translation(snapshots[index - 1]!, entityId)
203
+ const current = translation(snapshots[index]!, entityId)
204
+ if (previous === undefined || current === undefined) continue
205
+ if (!isCutBoundary(snapshots[index]!)) {
206
+ const jump = distance(previous, current)
207
+ if (jump > options.maxPositionJumpM) {
208
+ add({
209
+ kind: "transform-discontinuity", sampleIndex: index, timeUs: snapshots[index]!.timeUs,
210
+ entityId, measured: jump, limit: options.maxPositionJumpM,
211
+ detail: `Entity ${entityId} teleports ${jump.toFixed(3)}m between samples ${index - 1} and ${index} outside a declared cut.`,
212
+ })
213
+ }
214
+ }
215
+ const dt = seconds(snapshots[index]!, snapshots[index - 1]!)
216
+ if (dt <= 0) continue
217
+ for (const contact of options.contacts) {
218
+ if (contact.entityId !== entityId) continue
219
+ const inWindow = snapshots[index - 1]!.timeUs >= contact.startUs && snapshots[index]!.timeUs <= contact.endUs
220
+ if (!inWindow) continue
221
+ const slideMps = horizontalDistance(previous, current) / dt
222
+ if (slideMps > options.maxFootSlideMps) {
223
+ add({
224
+ kind: "foot-slide", sampleIndex: index, timeUs: snapshots[index]!.timeUs,
225
+ entityId, measured: slideMps, limit: options.maxFootSlideMps,
226
+ detail: `Entity ${entityId} slides ${slideMps.toFixed(3)}m/s horizontally while planted near groundY ${contact.groundY} during [${contact.startUs}, ${contact.endUs}]us.`,
227
+ })
228
+ }
229
+ }
230
+ }
231
+ }
232
+
233
+ // Camera dynamics: position acceleration/jerk and angular velocity.
234
+ for (let index = 1; index < snapshots.length; index += 1) {
235
+ const current = snapshots[index]!
236
+ const previous = snapshots[index - 1]!
237
+ const dt = seconds(current, previous)
238
+ if (dt > 0) {
239
+ const angular = quaternionAngleRadians(current.camera.pose.rotation, previous.camera.pose.rotation) / dt
240
+ if (angular > options.maxAngularVelocityRadps) {
241
+ add({
242
+ kind: "camera-angular-velocity", sampleIndex: index, timeUs: current.timeUs,
243
+ cameraId, measured: angular, limit: options.maxAngularVelocityRadps,
244
+ detail: `Camera rotates at ${angular.toFixed(3)}rad/s between samples ${index - 1} and ${index}.`,
245
+ })
246
+ }
247
+ const focusNow = current.camera.lens?.focusDistanceM
248
+ const focusBefore = previous.camera.lens?.focusDistanceM
249
+ if (focusNow !== undefined && focusBefore !== undefined && Math.abs(focusNow - focusBefore) > options.maxFocusJumpM) {
250
+ add({
251
+ kind: "focus-jump", sampleIndex: index, timeUs: current.timeUs,
252
+ cameraId, measured: Math.abs(focusNow - focusBefore), limit: options.maxFocusJumpM,
253
+ detail: `Focus distance jumps ${Math.abs(focusNow - focusBefore).toFixed(3)}m between samples ${index - 1} and ${index}.`,
254
+ })
255
+ }
256
+ const evNow = current.camera.lens?.exposureEv
257
+ const evBefore = previous.camera.lens?.exposureEv
258
+ if (evNow !== undefined && evBefore !== undefined && Math.abs(evNow - evBefore) > options.maxExposureJumpEv) {
259
+ add({
260
+ kind: "exposure-jump", sampleIndex: index, timeUs: current.timeUs,
261
+ cameraId, measured: Math.abs(evNow - evBefore), limit: options.maxExposureJumpEv,
262
+ detail: `Exposure jumps ${Math.abs(evNow - evBefore).toFixed(3)}EV between samples ${index - 1} and ${index}.`,
263
+ })
264
+ }
265
+ }
266
+ if (index >= 2) {
267
+ const a = snapshots[index - 2]!, b = snapshots[index - 1]!
268
+ const dt0 = seconds(b, a), dt1 = seconds(current, b)
269
+ if (dt0 > 0 && dt1 > 0) {
270
+ const acceleration = Math.hypot(...([0, 1, 2].map((axis) =>
271
+ (current.camera.pose.position[axis]! - b.camera.pose.position[axis]!) / dt1
272
+ - (b.camera.pose.position[axis]! - a.camera.pose.position[axis]!) / dt0,
273
+ ) as [number, number, number])) / ((dt0 + dt1) / 2)
274
+ if (acceleration > options.maxCameraAccelerationMps2) {
275
+ add({
276
+ kind: "camera-acceleration", sampleIndex: index, timeUs: current.timeUs,
277
+ cameraId, measured: acceleration, limit: options.maxCameraAccelerationMps2,
278
+ detail: `Camera accelerates at ${acceleration.toFixed(3)}m/s² at sample ${index}.`,
279
+ })
280
+ }
281
+ }
282
+ }
283
+ if (index >= 3) {
284
+ const a = snapshots[index - 3]!, b = snapshots[index - 2]!, c = snapshots[index - 1]!
285
+ const dta = seconds(b, a), dtb = seconds(c, b), dtc = seconds(current, c)
286
+ if (dta > 0 && dtb > 0 && dtc > 0) {
287
+ // Window accelerations use the same centered-difference convention as
288
+ // the camera-acceleration check; jerk is their time difference.
289
+ const accel = (p1: EvaluatedSpatialScene, p0: EvaluatedSpatialScene, q1: EvaluatedSpatialScene, q0: EvaluatedSpatialScene, d1: number, d0: number): [number, number, number] =>
290
+ [0, 1, 2].map((axis) => (
291
+ (p1.camera.pose.position[axis]! - p0.camera.pose.position[axis]!) / d1
292
+ - (q1.camera.pose.position[axis]! - q0.camera.pose.position[axis]!) / d0
293
+ ) / ((d0 + d1) / 2)) as [number, number, number]
294
+ const a0 = accel(c, b, b, a, dtb, dta)
295
+ const a1 = accel(current, c, c, b, dtc, dtb)
296
+ const jerk = Math.hypot(a1[0] - a0[0], a1[1] - a0[1], a1[2] - a0[2]) / ((dtb + dtc) / 2)
297
+ if (jerk > options.maxCameraJerkMps3) {
298
+ add({
299
+ kind: "camera-jerk", sampleIndex: index, timeUs: current.timeUs,
300
+ cameraId, measured: jerk, limit: options.maxCameraJerkMps3,
301
+ detail: `Camera jerk reaches ${jerk.toFixed(3)}m/s³ at sample ${index}.`,
302
+ })
303
+ }
304
+ }
305
+ }
306
+ }
307
+
308
+ // Resource spikes: new asset digests appearing and entity-count swings per sample.
309
+ for (let index = 1; index < snapshots.length; index += 1) {
310
+ const previous = snapshots[index - 1]!
311
+ const current = snapshots[index]!
312
+ const previousAssets = new Set(previous.assets.map((asset) => asset.assetId))
313
+ const newAssets = current.assets.filter((asset) => !previousAssets.has(asset.assetId)).length
314
+ if (newAssets > options.maxNewAssetsPerSample) {
315
+ add({
316
+ kind: "resource-spike", sampleIndex: index, timeUs: current.timeUs,
317
+ measured: newAssets, limit: options.maxNewAssetsPerSample,
318
+ detail: `${newAssets} assets enter the scene between samples ${index - 1} and ${index}.`,
319
+ })
320
+ }
321
+ const entityDelta = Math.abs(current.entities.length - previous.entities.length)
322
+ if (entityDelta > options.maxEntityCountDelta) {
323
+ add({
324
+ kind: "resource-spike", sampleIndex: index, timeUs: current.timeUs,
325
+ measured: entityDelta, limit: options.maxEntityCountDelta,
326
+ detail: `Entity count changes by ${entityDelta} between samples ${index - 1} and ${index}.`,
327
+ })
328
+ }
329
+ }
330
+
331
+ const all = findings.slice()
332
+ all.sort((a, b) =>
333
+ a.sampleIndex - b.sampleIndex
334
+ || KIND_ORDER[a.kind] - KIND_ORDER[b.kind]
335
+ || (a.entityId ?? "").localeCompare(b.entityId ?? "")
336
+ || (a.cameraId ?? "").localeCompare(b.cameraId ?? ""),
337
+ )
338
+ const returned = all.slice(0, SPATIAL_TEMPORAL_AUDIT_LIMITS.findings)
339
+ const { contacts: _contacts, cutBeforeUs: _cutBeforeUs, ...thresholds } = options
340
+
341
+ return deepFreezeJson(SpatialTemporalAuditReportSchema.parse({
342
+ kind: "slopcamera.spatial-temporal-audit-report",
343
+ schemaVersion: 1,
344
+ sceneSha256,
345
+ cameraId,
346
+ sampleCount: snapshots.length,
347
+ firstTimeUs: snapshots[0]!.timeUs,
348
+ lastTimeUs: snapshots[snapshots.length - 1]!.timeUs,
349
+ findings: returned,
350
+ omittedFindings: all.length - returned.length,
351
+ thresholds,
352
+ contactCount: options.contacts.length,
353
+ }))
354
+ }
@@ -0,0 +1,16 @@
1
+ /** Content-free notification after useful output and owned cleanup complete. */
2
+ export type UsefulResultObserver = () => void | Promise<void>
3
+
4
+ export function reportUsefulResult(observer: UsefulResultObserver | undefined): void {
5
+ try {
6
+ if (observer !== undefined) void Promise.resolve(observer()).catch(() => undefined)
7
+ } catch {
8
+ // Optional presentation cannot change a completed operation's outcome.
9
+ }
10
+ }
11
+
12
+ export function quietSupportChildEnvironment(
13
+ env: Readonly<Record<string, string | undefined>>,
14
+ ): Record<string, string | undefined> {
15
+ return { ...env, HRANESS_SUPPORT_AUDIENCE: "off", HRANESS_SUPPORT_EMAIL: "off" }
16
+ }
package/src/support.ts ADDED
@@ -0,0 +1,42 @@
1
+ import {
2
+ maybeShowSupportInvitation,
3
+ runSupportCommand,
4
+ type SupportCommandOptions,
5
+ } from "@hraness/support-foundation/node"
6
+
7
+ export const supportProfile = {
8
+ id: "slopcamera",
9
+ name: "Slopcamera",
10
+ valueProposition: "Support ongoing development of local visual tools for agents.",
11
+ updates: false,
12
+ } as const
13
+
14
+ /** Called only by the real process entrypoint; imported CLI and SDK calls stay inert. */
15
+ export function standaloneSupportEnvironment(): Readonly<Record<string, string | undefined>> {
16
+ const env = { ...process.env }
17
+ process.env.HRANESS_SUPPORT_AUDIENCE = "off"
18
+ process.env.HRANESS_SUPPORT_EMAIL = "off"
19
+ return env
20
+ }
21
+
22
+ export async function runProductSupportCommand(
23
+ args: readonly string[],
24
+ options: SupportCommandOptions = {},
25
+ ): Promise<number> {
26
+ const result = await runSupportCommand(supportProfile, args, {
27
+ command: ["slopcamera"], ...options, gitEmail: false,
28
+ })
29
+ if (result.stdout !== "") process.stdout.write(result.stdout)
30
+ if (result.stderr !== "") process.stderr.write(result.stderr)
31
+ return result.exitCode
32
+ }
33
+
34
+ export async function showProductSupportInvitation(options: SupportCommandOptions = {}): Promise<void> {
35
+ try {
36
+ await maybeShowSupportInvitation(supportProfile, {
37
+ usefulResult: true, command: ["slopcamera"], ...options, gitEmail: false,
38
+ })
39
+ } catch {
40
+ // Optional support must not alter the completed task or its stdout.
41
+ }
42
+ }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const SLOPCAMERA_VERSION = "3.2.7" as const
1
+ export const SLOPCAMERA_VERSION = "3.3.1" as const
@@ -1,5 +0,0 @@
1
- export * from "./pause";
2
- export * from "./resume";
3
- export * from "./shared";
4
- export * from "./start";
5
- export * from "./stop";
@@ -1,34 +0,0 @@
1
- import type { OperationDefinition } from "../../operation";
2
- import {
3
- EmptyRecordingOperationInputSchema,
4
- type EmptyRecordingOperationInput,
5
- executeRecordingControl,
6
- RecordingOperationOutputSchema,
7
- type RecordingOperationOutput,
8
- recordingTransitionPolicy,
9
- summarizeRecordingControl,
10
- } from "./shared";
11
-
12
- export const RecordingPauseInputSchema = EmptyRecordingOperationInputSchema;
13
- export const RecordingPauseOutputSchema = RecordingOperationOutputSchema;
14
- export type RecordingPauseInput = EmptyRecordingOperationInput;
15
- export type RecordingPauseOutput = RecordingOperationOutput;
16
-
17
- export const recordingPauseOperationDefinition: OperationDefinition<
18
- "recording.pause",
19
- RecordingPauseInput,
20
- RecordingPauseOutput
21
- > = {
22
- inputSchema: RecordingPauseInputSchema,
23
- inputSchemaId: "slopcamera.operation.recording.pause.input/v1",
24
- kind: "recording.pause",
25
- lifecycle: {
26
- kind: "live-control",
27
- execute: async context => await executeRecordingControl(context, "pause"),
28
- },
29
- outputSchema: RecordingPauseOutputSchema,
30
- outputSchemaId: "slopcamera.operation.recording.pause.output/v1",
31
- policy: recordingTransitionPolicy,
32
- summarize: output => summarizeRecordingControl("recording.pause", output),
33
- version: 1,
34
- };
@@ -1,34 +0,0 @@
1
- import type { OperationDefinition } from "../../operation";
2
- import {
3
- EmptyRecordingOperationInputSchema,
4
- type EmptyRecordingOperationInput,
5
- executeRecordingControl,
6
- RecordingOperationOutputSchema,
7
- type RecordingOperationOutput,
8
- recordingTransitionPolicy,
9
- summarizeRecordingControl,
10
- } from "./shared";
11
-
12
- export const RecordingResumeInputSchema = EmptyRecordingOperationInputSchema;
13
- export const RecordingResumeOutputSchema = RecordingOperationOutputSchema;
14
- export type RecordingResumeInput = EmptyRecordingOperationInput;
15
- export type RecordingResumeOutput = RecordingOperationOutput;
16
-
17
- export const recordingResumeOperationDefinition: OperationDefinition<
18
- "recording.resume",
19
- RecordingResumeInput,
20
- RecordingResumeOutput
21
- > = {
22
- inputSchema: RecordingResumeInputSchema,
23
- inputSchemaId: "slopcamera.operation.recording.resume.input/v1",
24
- kind: "recording.resume",
25
- lifecycle: {
26
- kind: "live-control",
27
- execute: async context => await executeRecordingControl(context, "resume"),
28
- },
29
- outputSchema: RecordingResumeOutputSchema,
30
- outputSchemaId: "slopcamera.operation.recording.resume.output/v1",
31
- policy: recordingTransitionPolicy,
32
- summarize: output => summarizeRecordingControl("recording.resume", output),
33
- version: 1,
34
- };