@hraness/slopcamera 3.3.1 → 3.3.3
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/README.md +3 -3
- package/apps/desktop/application/operation.ts +1 -1
- package/apps/desktop/cli/commands.ts +1 -1
- package/apps/desktop/cli/spatial-scene-service.ts +6 -3
- package/apps/desktop/dist/cli/main.js +220 -220
- package/dist/cli.js +1 -1
- package/dist/code/index.js +4 -4
- package/dist/{index-f0ptv8dm.js → index-3e1pcxb5.js} +2 -2
- package/dist/index-z1kd1x8j.js +3 -0
- package/dist/index.js +1 -1
- package/docs/studio.md +12 -0
- package/package.json +1 -1
- package/skills/slopcamera/SKILL.md +1 -0
- package/skills/slopcamera/references/directed-scenes.md +26 -0
- package/skills/slopcamera/references/install.md +1 -1
- package/skills/slopcamera/references/music-video.md +4 -0
- package/skills/slopcamera/references/native-studio.md +19 -0
- package/skills/slopcamera/references/parametric-design.md +5 -1
- package/skills/slopcamera/references/scene-building.md +143 -1
- package/src/mcp/tools.ts +6 -1
- package/src/spatial-scene/behavior-bake.ts +2 -3
- package/src/spatial-scene/behavior-gallery.ts +2 -3
- package/src/spatial-scene/behavior.ts +2 -3
- package/src/spatial-scene/geometry.ts +88 -35
- package/src/spatial-scene/parametric.ts +42 -15
- package/src/spatial-scene/performance.ts +19 -19
- package/src/version.ts +1 -1
- package/dist/index-cgpkyjqa.js +0 -3
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS,SLOPCAMERA_PORTABLE_CAPABILITY_VERSION,SLOPCAMERA_VERSION,STUDIO_LIMITS,SlopcameraCapabilityCommandSchema,SlopcameraCapabilityEffectSchema,SlopcameraCapabilityManifestSchema,SlopcameraCapabilityModuleDeclarationSchema,SlopcameraCapabilityModuleSchema,SlopcameraCapabilityOperationSchema,SlopcameraCapabilityPreparationSchema,SlopcameraCapabilityProfileSchema,SlopcameraCapabilityQualificationSchema,SlopcameraCapabilityResourceSchema,SlopcameraCapabilityResumeSchema,SlopcameraCapabilityToolSchema,SlopcameraCapabilityWorkflowSchema,SlopcameraMcpToolRuntime,SlopcameraSceneVariantsSchema,StudioCapabilityNameSchema,StudioDigestSchema,StudioEngineOptionsSchema,StudioEngineSchema,StudioExecutionProfileSchema,StudioJobSchema,StudioOutputArtifactSchema,StudioOutputFormatSchema,StudioOutputInterpretationSchema,StudioOutputRoleSchema,StudioOutputSpecSchema,StudioPathSchema,StudioPlanSchema,StudioReceiptSchema,StudioRenderSchema,StudioRuntimeIdentitySchema,StudioSourceBundleSchema,StudioSourceSpaceSchema,WorkspaceBoundary,WorkspaceBoundaryError,artifactSummary,checkDiagramFile,compileSlopcameraCapabilityManifest,createPortableSlopcameraCapabilityManifest,defineSlopcameraCapabilityModule,diagramApi,inspectStudioBundle,inspectStudioPlan,mcpMaximumRenderedPixels,mcpMaximumScale,mcpSourceByteLimit,parseSlopcameraCapabilityManifest,parseSlopcameraSceneVariants,parseStudioJob,parseStudioPlan,parseStudioReceipt,parseStudioRuntimeIdentity,parseStudioSourceBundle,planSlopcameraSceneGallery,planStudioJob,readDiagramFile,renderDiagramFile,runMcpServer,slopcameraApi,slopcameraMcpProtocolVersion,slopcameraMcpServerName,slopcameraMcpTools,slopcameraPortableCapabilityManifest,slopcameraSceneGalleryLimits,studioJobSha256,studioOutputPath,studioRuntimeSha256,studioSourceBundleSha256,summarizeSceneVariantPatch,validateStudioReceipt}from"./index-
|
|
2
|
+
import{SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS,SLOPCAMERA_PORTABLE_CAPABILITY_VERSION,SLOPCAMERA_VERSION,STUDIO_LIMITS,SlopcameraCapabilityCommandSchema,SlopcameraCapabilityEffectSchema,SlopcameraCapabilityManifestSchema,SlopcameraCapabilityModuleDeclarationSchema,SlopcameraCapabilityModuleSchema,SlopcameraCapabilityOperationSchema,SlopcameraCapabilityPreparationSchema,SlopcameraCapabilityProfileSchema,SlopcameraCapabilityQualificationSchema,SlopcameraCapabilityResourceSchema,SlopcameraCapabilityResumeSchema,SlopcameraCapabilityToolSchema,SlopcameraCapabilityWorkflowSchema,SlopcameraMcpToolRuntime,SlopcameraSceneVariantsSchema,StudioCapabilityNameSchema,StudioDigestSchema,StudioEngineOptionsSchema,StudioEngineSchema,StudioExecutionProfileSchema,StudioJobSchema,StudioOutputArtifactSchema,StudioOutputFormatSchema,StudioOutputInterpretationSchema,StudioOutputRoleSchema,StudioOutputSpecSchema,StudioPathSchema,StudioPlanSchema,StudioReceiptSchema,StudioRenderSchema,StudioRuntimeIdentitySchema,StudioSourceBundleSchema,StudioSourceSpaceSchema,WorkspaceBoundary,WorkspaceBoundaryError,artifactSummary,checkDiagramFile,compileSlopcameraCapabilityManifest,createPortableSlopcameraCapabilityManifest,defineSlopcameraCapabilityModule,diagramApi,inspectStudioBundle,inspectStudioPlan,mcpMaximumRenderedPixels,mcpMaximumScale,mcpSourceByteLimit,parseSlopcameraCapabilityManifest,parseSlopcameraSceneVariants,parseStudioJob,parseStudioPlan,parseStudioReceipt,parseStudioRuntimeIdentity,parseStudioSourceBundle,planSlopcameraSceneGallery,planStudioJob,readDiagramFile,renderDiagramFile,runMcpServer,slopcameraApi,slopcameraMcpProtocolVersion,slopcameraMcpServerName,slopcameraMcpTools,slopcameraPortableCapabilityManifest,slopcameraSceneGalleryLimits,studioJobSha256,studioOutputPath,studioRuntimeSha256,studioSourceBundleSha256,summarizeSceneVariantPatch,validateStudioReceipt}from"./index-3e1pcxb5.js";import{bundledSkillPath,installSkill}from"./index-grk47tgs.js";import"./index-z1kd1x8j.js";import"./index-3cxf2c58.js";import{SlopcameraWorkflowError,defineSlopcameraWorkflow,runSlopcameraWorkflow}from"./index-296qhpf2.js";import{DiagramValidationError,SlopcameraOperationError,StackLayoutError,builtInIcons,composeSlopcameraImageGallery,critiqueIconRaster,executeSlopcameraOperation,executeSlopcameraOperationWithLease,extractIconLineArt,galleryCandidateId,galleryPromptFor,generateSlopcameraIcon,generateSlopcameraImageGallery,iconPromptFor,iconVisualGateProblems,isSlopcameraOperationCode,lintDiagram,parseDiagramSource,parseDiagramSpec,parseSlopcameraGalleryVary,parseSlopcameraOperationInput,planSlopcameraGallery,renderPng,renderSvg,resolveDiagramSource,resolveEdge,resolveStackLayout,searchSlopcameraOperations,serializeTldr,slopcameraGalleryAxes,slopcameraGalleryKinds,slopcameraGalleryLimits,slopcameraIconCritiqueDefaultModel,slopcameraIconCritiqueTimeoutMs,slopcameraIconDefaultInk,slopcameraIconDefaultRounds,slopcameraIconMaximumRounds,slopcameraIconPanel,slopcameraIconSubjectMaximumBytes,slopcameraOperationCodes,slopcameraOperationHostResourceClaims,slopcameraOperationRegistry,stackLayoutDefaults,withSlopcameraOperationHostAdmission}from"./index-0bbesn4m.js";import{VTRACER_VERSION,VectorizeError,vectorizeDefaultLimits,vectorizeHardLimits,vectorizeImage,vectorizeProfileNames,vtracerReleases}from"./index-qry58nj2.js";import{SlopcameraCloudError,createFixedGatewayFetch,generateSlopcameraImage,generateSlopcameraImageFile,resolveSlopcameraGatewayCredential,slopcameraGatewayApiBaseUrl,slopcameraGatewayCredentialStatus,slopcameraImageModels,slopcameraMaximumPromptBytes,slopcameraMaximumRawImageBytes,slopcameraResponseMediaTypes}from"./index-77fjfg9f.js";import{HOST_RESOURCE_MAX_WAIT_MILLISECONDS,HostResourceError,createDefaultHostResourceCoordinator,createHostResourceCoordinator,createProcessLocalHostResourceCoordinator,defaultSlopcameraHostResourceProfile,defaultSlopcameraHostResourceStateRoot,normalizeHostResourceClaims,normalizeHostResourceProfile,slopcameraHostResourceNames}from"./index-b3geqczn.js";import"./index-80a36bc0.js";export{withSlopcameraOperationHostAdmission,vtracerReleases,vectorizeProfileNames,vectorizeImage,vectorizeHardLimits,vectorizeDefaultLimits,validateStudioReceipt,summarizeSceneVariantPatch,studioSourceBundleSha256,studioRuntimeSha256,studioOutputPath,studioJobSha256,stackLayoutDefaults,slopcameraSceneGalleryLimits,slopcameraResponseMediaTypes,slopcameraPortableCapabilityManifest,slopcameraOperationRegistry,slopcameraOperationHostResourceClaims,slopcameraOperationCodes,slopcameraMcpTools,slopcameraMcpServerName,slopcameraMcpProtocolVersion,slopcameraMaximumRawImageBytes,slopcameraMaximumPromptBytes,slopcameraImageModels,slopcameraIconSubjectMaximumBytes,slopcameraIconPanel,slopcameraIconMaximumRounds,slopcameraIconDefaultRounds,slopcameraIconDefaultInk,slopcameraIconCritiqueTimeoutMs,slopcameraIconCritiqueDefaultModel,slopcameraHostResourceNames,slopcameraGatewayCredentialStatus,slopcameraGatewayApiBaseUrl,slopcameraGalleryLimits,slopcameraGalleryKinds,slopcameraGalleryAxes,slopcameraApi,serializeTldr,searchSlopcameraOperations,runSlopcameraWorkflow,runMcpServer,resolveStackLayout,resolveSlopcameraGatewayCredential,resolveEdge,resolveDiagramSource,renderSvg,renderPng,renderDiagramFile,readDiagramFile,planStudioJob,planSlopcameraSceneGallery,planSlopcameraGallery,parseStudioSourceBundle,parseStudioRuntimeIdentity,parseStudioReceipt,parseStudioPlan,parseStudioJob,parseSlopcameraSceneVariants,parseSlopcameraOperationInput,parseSlopcameraGalleryVary,parseSlopcameraCapabilityManifest,parseDiagramSpec,parseDiagramSource,normalizeHostResourceProfile,normalizeHostResourceClaims,mcpSourceByteLimit,mcpMaximumScale,mcpMaximumRenderedPixels,lintDiagram,isSlopcameraOperationCode,installSkill,inspectStudioPlan,inspectStudioBundle,iconVisualGateProblems,iconPromptFor,generateSlopcameraImageGallery,generateSlopcameraImageFile,generateSlopcameraImage,generateSlopcameraIcon,galleryPromptFor,galleryCandidateId,extractIconLineArt,executeSlopcameraOperationWithLease,executeSlopcameraOperation,diagramApi,defineSlopcameraWorkflow,defineSlopcameraCapabilityModule,defaultSlopcameraHostResourceStateRoot,defaultSlopcameraHostResourceProfile,critiqueIconRaster,createProcessLocalHostResourceCoordinator,createPortableSlopcameraCapabilityManifest,createHostResourceCoordinator,createFixedGatewayFetch,createDefaultHostResourceCoordinator,composeSlopcameraImageGallery,compileSlopcameraCapabilityManifest,checkDiagramFile,bundledSkillPath,builtInIcons,artifactSummary,WorkspaceBoundaryError,WorkspaceBoundary,VectorizeError,VTRACER_VERSION,StudioSourceSpaceSchema,StudioSourceBundleSchema,StudioRuntimeIdentitySchema,StudioRenderSchema,StudioReceiptSchema,StudioPlanSchema,StudioPathSchema,StudioOutputSpecSchema,StudioOutputRoleSchema,StudioOutputInterpretationSchema,StudioOutputFormatSchema,StudioOutputArtifactSchema,StudioJobSchema,StudioExecutionProfileSchema,StudioEngineSchema,StudioEngineOptionsSchema,StudioDigestSchema,StudioCapabilityNameSchema,StackLayoutError,SlopcameraWorkflowError,SlopcameraSceneVariantsSchema,SlopcameraOperationError,SlopcameraMcpToolRuntime,SlopcameraCloudError,SlopcameraCapabilityWorkflowSchema,SlopcameraCapabilityToolSchema,SlopcameraCapabilityResumeSchema,SlopcameraCapabilityResourceSchema,SlopcameraCapabilityQualificationSchema,SlopcameraCapabilityProfileSchema,SlopcameraCapabilityPreparationSchema,SlopcameraCapabilityOperationSchema,SlopcameraCapabilityModuleSchema,SlopcameraCapabilityModuleDeclarationSchema,SlopcameraCapabilityManifestSchema,SlopcameraCapabilityEffectSchema,SlopcameraCapabilityCommandSchema,STUDIO_LIMITS,SLOPCAMERA_VERSION,SLOPCAMERA_PORTABLE_CAPABILITY_VERSION,SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS,HostResourceError,HOST_RESOURCE_MAX_WAIT_MILLISECONDS,DiagramValidationError};
|
package/docs/studio.md
CHANGED
|
@@ -22,6 +22,10 @@ The scaffold creates `scene.py`, any explicit helpers, `source.json` and `job.js
|
|
|
22
22
|
|
|
23
23
|
The default Blender product job requests Cycles GPU rendering. An unavailable GPU fails clearly. Select `device: "cpu"` in a new job when CPU rendering is intended; no fallback is implicit. Initial Metal shader compilation can make the first render substantially slower than later frames.
|
|
24
24
|
|
|
25
|
+
`studio probe` discovers devices without rendering a frame. A successful probe
|
|
26
|
+
does not qualify shader compilation or the scene's render. Use a bounded render
|
|
27
|
+
with the selected engine and device before committing to a long job.
|
|
28
|
+
|
|
25
29
|
Other starters:
|
|
26
30
|
|
|
27
31
|
```sh
|
|
@@ -210,6 +214,14 @@ slopcamera studio reconcile <studio-id> --json
|
|
|
210
214
|
|
|
211
215
|
Inspection rechecks source and physical output hashes. Reconciliation can restore a missing receipt only when a closed successful process completion and an unchanged output-validation checkpoint exist. Earlier interrupted execution remains ambiguous and is never automatically resubmitted. Choose a new job ID for an explicitly revised attempt after custody is settled.
|
|
212
216
|
|
|
217
|
+
For an unexpected Blender exit, correlate that attempt's retained logs with the
|
|
218
|
+
operating system's crash report before changing settings. A macOS report in
|
|
219
|
+
`MetalKernelPipeline::compile` while the main thread waits in
|
|
220
|
+
`BlenderSession::render` identifies a render failure; it does not prove an
|
|
221
|
+
out-of-memory condition or a specific shader-cache defect. A bounded CPU or EEVEE
|
|
222
|
+
comparison needs a new, explicitly configured job after custody is settled. Its
|
|
223
|
+
success does not qualify Cycles/Metal, and its frames need separate visual review.
|
|
224
|
+
|
|
213
225
|
The supervisor bounds combined logs, the job deadline and termination/pipe-drain grace. It monitors output and scratch budgets, retains failure evidence and tracks native process groups. Live scans tolerate bounded disappearance of temporary cache entries; final output, source and scratch scans remain strict. Budget-monitor and validation failures retain a safe failed host stage separately from authored-process logs, without storing foreign exception text. A machine-wide durable activity marker survives CLI death; unresolved custody blocks later native dispatch. Detached sessions from authored source are unsupported. Do not delete an unresolved marker to bypass admission; inspect and resolve the actual process ownership first.
|
|
214
226
|
|
|
215
227
|
The native profile is trusted current-user execution without an OS sandbox. Private roots, clean environment variables, hashes and process supervision provide operational control and provenance. They do not confine arbitrary Python, installed plugins or native libraries. Keep unreviewed downloaded source inert until execution is explicitly authorized.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hraness/slopcamera",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.3",
|
|
4
4
|
"description": "A local visual studio for coding agents: author scenes, combine generated and recorded media, and export images, diagrams, animation, and video from retained sources.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -30,6 +30,7 @@ Slopcamera installs from its verified release archive or from source. Historical
|
|
|
30
30
|
| Editable Three/Spark scene, saved world, named-part edits or shared camera | [Directed scenes](references/directed-scenes.md) |
|
|
31
31
|
| Semantic direction, galleries, bounded effects, temporal audit or the cinematic-world recipe pack | [Directed scenes](references/directed-scenes.md#direct-cinematic-worlds) |
|
|
32
32
|
| Author a scene from code, generate procedural entities, or audit framing | [Scene building](references/scene-building.md) |
|
|
33
|
+
| Refine an existing scene, organic interactions, contact, staging, or final movie quality | [Scene refinement](references/scene-building.md#refine-an-existing-scene) |
|
|
33
34
|
| Parametric architecture, furniture, dimensions, repeated structures or editable design variants | [Parametric design](references/parametric-design.md) |
|
|
34
35
|
| Blender, rigs, bakes, CAD, shared native assets or production frames | [Native studio](references/native-studio.md) |
|
|
35
36
|
| Manim lesson, narrated explanation or mathematical animation | [Educational video](references/educational-video.md) |
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Use `slopcamera scene --help` to check that the installed CLI includes the directed-scene foundation. The initial renderer uses Three.js and supports explicit scene JSON, semantic edits, calibrated cameras, native media surfaces, contact sheets, and transparent video.
|
|
4
4
|
|
|
5
|
+
Use [scene refinement](scene-building.md#refine-an-existing-scene) for a shot-by-shot craft pass, subtle
|
|
6
|
+
behavior design, actual-rig contact checks, and final encoded-frame review.
|
|
7
|
+
|
|
5
8
|
To author a scene from code — builder helpers, procedural generator modules, glTF admission, or geometric audit — read [scene building](scene-building.md) first. Start interactive work with `slopcamera scene init scene.json --json`, then `slopcamera scene inspect scene.json --json`. Retain the original source. Use its stable entity IDs and exact `sceneSha256` in typed patches; save each edit with `--output` to a new file. Inspect `editableControls` before changing generated parts or imported materials. Do not replace named scene parts with opaque regenerated source merely to change one color or camera.
|
|
6
9
|
|
|
7
10
|
Use `slopcamera scene plan scene.json --request request.json --json` before rendering. A frame request is:
|
|
@@ -49,6 +52,29 @@ Four standard-library organisms are available for composition: locomotion FSM (`
|
|
|
49
52
|
|
|
50
53
|
The authoring loop: (1) assemble a behavior doc from stdlib organisms with scene-bound args, (2) check it with `scene behavior check`, (3) bake it with `scene behavior bake`, (4) audit the trace with `scene behavior audit`, (5) plan gallery variants and review. Identical behavior doc + seed + scene produce byte-identical bakes. The `seed` interface input auto-binds from `behavior.seed` for gallery diversity. A channel map (`--channel-map`) binds emitted channels to clip runs, attach/release pairs, or trajectory waypoints for performance directives. Behaviors never execute source, contact providers, or access the filesystem; organisms are data.
|
|
51
54
|
|
|
55
|
+
For SDK authoring, normalize the scene with `parseSpatialScene` and bind the
|
|
56
|
+
behavior using `spatialSceneSha256`. These public helpers validate references and
|
|
57
|
+
normalize entity order so saved SDK scenes and the CLI share the same identity.
|
|
58
|
+
Check, bake, and gallery use that canonical identity. A behavior bound to a raw,
|
|
59
|
+
noncanonical scene hash remains stale: explicitly rebind it to the inspected
|
|
60
|
+
scene and bake again, preserving the earlier documents and receipts.
|
|
61
|
+
|
|
62
|
+
The CLI check and bake still wait for host CPU and local-I/O admission. A concurrent
|
|
63
|
+
render can reserve the CPU pool. A timeout with no output does not establish that
|
|
64
|
+
the behavior program failed; preserve the attempt's outcome and distinguish
|
|
65
|
+
resource waiting from a returned validation error before retrying.
|
|
66
|
+
|
|
67
|
+
When compiling mapped directives with `compileSpatialPerformance`, use the
|
|
68
|
+
exported `SPATIAL_PERFORMANCE_COMPILER_ID` for the plan's `compilerVersion`.
|
|
69
|
+
Compiler `slopcamera.spatial-performance-compiler@v3` resolves prop ownership in
|
|
70
|
+
time order. A later attach can take ownership after a release. At the same
|
|
71
|
+
timestamp, releases precede attaches, so an immediate handoff keeps the new
|
|
72
|
+
attachment; among simultaneous attaches, the last authored attach wins. When the
|
|
73
|
+
latest attachment's interval ends, an older overlapping attachment does not
|
|
74
|
+
resume. Retained `@v2` plans require an explicit version update and a new compile
|
|
75
|
+
and audit. Preserve old takes and receipts as evidence rather than relabeling
|
|
76
|
+
them with the new compiler identity.
|
|
77
|
+
|
|
52
78
|
The `cinematic-world` built-in workflow composes exactly these operations for one admitted scene. Its input is an inert `slopcamera.spatial-recipe-pack` — one scene digest, one direction document, bounded gallery axes, up to 4 named preview render requests, optional declared effects, and optional temporal-audit inputs — wrapped with the scene document and its repository path. Plan it with `slopcamera workflows plan cinematic-world --input input.json --json`; run it with `workflows run`. The workflow never selects or promotes a candidate: registration uses `scene project add-candidate <project-id> --input <request.json>`, and selection uses `scene project select-candidate <project-id> --input <request.json>`, both after review. A recipe pack is data — it cannot register executors, source paths beyond the declared render source, permissions, secrets, or URLs.
|
|
53
79
|
|
|
54
80
|
## Direct short generated clips
|
|
@@ -14,7 +14,7 @@ command -v slopcamera
|
|
|
14
14
|
|
|
15
15
|
A restricted shell can omit package-manager paths. Check known host installation paths before declaring a tool unavailable. If Bun is genuinely absent, follow its official [installation guide](https://bun.sh/docs/installation) within the user’s authorized setup scope. Do not switch package managers or pipe an unreviewed installer into a shell.
|
|
16
16
|
|
|
17
|
-
Slopcamera installs from its verified release archive: `bun add --global https://github.com/hraness/slopcamera/releases/download/v3.2
|
|
17
|
+
Slopcamera installs from its verified release archive: `bun add --global https://github.com/hraness/slopcamera/releases/download/v3.3.2/hraness-slopcamera-3.3.2.tgz`, then `slopcamera doctor --json`. Building from source is the contributor path. Historical Atet archives do not install the renamed CLI; never substitute `slopcamera` into an old archive URL.
|
|
18
18
|
|
|
19
19
|
Use an existing compatible source build when available. Otherwise follow the [complete source-install guide](https://github.com/hraness/slopcamera/blob/main/docs/how-to/use-current-source.md): clone into a new directory, record its exact commit, install locked dependencies without lifecycle scripts, build the SDK and source CLI, then define the shell command against that checkout. A clone or skill installation alone does not install the executable. Do not alter an existing active checkout to satisfy this path.
|
|
20
20
|
|
|
@@ -5,6 +5,10 @@ including dancing characters, changing landscapes, and musical light accents.
|
|
|
5
5
|
The export retains the source and creates an ordinary editable project with
|
|
6
6
|
separate scene video and original music.
|
|
7
7
|
|
|
8
|
+
For an existing composition, use [scene refinement](scene-building.md#refine-an-existing-scene) to review
|
|
9
|
+
each shot, author subtle interactions, check contact, and inspect the encoded
|
|
10
|
+
delivery. Keep the scene's deliberate style and original media intact.
|
|
11
|
+
|
|
8
12
|
## Confirm the input and local capability
|
|
9
13
|
|
|
10
14
|
Use the user's selected track and supplied tempo. Establish an explicit duration,
|
|
@@ -20,6 +20,25 @@ A failed or interrupted job is never automatically rerun. `studio inspect` reche
|
|
|
20
20
|
|
|
21
21
|
See the shipped `docs/studio.md` and checked `examples/studio/` for source conventions, color/alpha rules, native caveats and the local `.studio.run()` workflow operation. Source/runtime identities are observed provenance, not a complete hash of every plugin, font, library or ambient read.
|
|
22
22
|
|
|
23
|
+
### Diagnose a Blender render crash
|
|
24
|
+
|
|
25
|
+
`studio probe` discovers devices and checks runtime capabilities; it does not
|
|
26
|
+
render a frame or prove that Cycles shader compilation succeeds. Qualify the
|
|
27
|
+
selected engine and device with a bounded render before a long job.
|
|
28
|
+
|
|
29
|
+
When Blender quits unexpectedly, inspect the exact studio attempt and its retained
|
|
30
|
+
logs, then correlate the process and time with the operating system's crash
|
|
31
|
+
report. On macOS, a `MetalKernelPipeline::compile` stack and a main thread waiting
|
|
32
|
+
in `BlenderSession::render` identify failure during rendering. They do not establish
|
|
33
|
+
an out-of-memory condition, a harmless shutdown, or a particular cache defect.
|
|
34
|
+
A successful Chrome/Three.js render supplies no evidence about Blender's recovery.
|
|
35
|
+
|
|
36
|
+
After custody is settled, test a revised engine or device in a new, explicitly
|
|
37
|
+
configured job if needed. CPU or EEVEE success can narrow the failing path but
|
|
38
|
+
does not fix or qualify Cycles/Metal. Keep the original failure and inspect the
|
|
39
|
+
new frames before adopting that configuration. Do not clear shared caches, alter
|
|
40
|
+
another task's render, or repeatedly launch the failing job as a generic recovery.
|
|
41
|
+
|
|
23
42
|
## Share a native output without re-executing it
|
|
24
43
|
|
|
25
44
|
`studio asset <studio-id> --output-id <id> --asset-id <id> --representation native|encoded-video [--frame <index>] --json` rechecks one successful output and returns its spatial asset, binding and receipt. It never rerenders or silently transcodes. Preserve all three together. A frame selection uses the native job’s explicit frame index; an encoded-video selection requires a previously retained unambiguous encode. Unsupported GLB materials, skins or morphs fail before rendering. Keep the original native rig and export a deliberate compatible static derivative.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Build a revisable parametric design
|
|
2
2
|
|
|
3
|
-
Use
|
|
3
|
+
Use Slopcamera v3.3.1 or newer and `slopcamera scene design` for dimensions, repeated architectural parts, furniture, pavilions and façades that must regenerate from retained rules. This workflow uses Slopcamera's local mesh geometry and needs no additional modeling application.
|
|
4
4
|
|
|
5
5
|
1. Discover original studies with `slopcamera scene design catalog --json`. Start with `scene design init <new-directory> --template crescent-pavilion|spiral-stair|ribbed-tower|modular-bookshelf`.
|
|
6
6
|
2. Read the emitted `design.json` and inspect it with `scene design inspect`. Describe the user's intended shape in named parameters, derived scalar values, constraints and semantic geometry stages. Keep references to engineering knowledge separate from visual references. Do not infer structural validation from appearance.
|
|
@@ -12,3 +12,7 @@ Use current source and `slopcamera scene design` for dimensions, repeated archit
|
|
|
12
12
|
Place generated bundles below `artifacts/slopcamera/generated/`. Existing files conflict. An interrupted bundle without its final receipt is incomplete; inspect it and choose a fresh destination. The design compiler never executes source text. For native CAD operations or Blender/Cycles production shading, retain a native-studio source and use the explicit trusted-code workflow in [native studio](native-studio.md).
|
|
13
13
|
|
|
14
14
|
Detailed contracts and checked commands: [design reference](https://github.com/hraness/slopcamera/blob/main/docs/parametric-design.md) and [design guide](https://github.com/hraness/slopcamera/blob/main/docs/how-to/parametric-design.md). The portable SDK exports the design compiler and starter catalog from `@hraness/slopcamera/code`; file publication and rendering belong to the CLI.
|
|
15
|
+
|
|
16
|
+
For an existing film, compile and decode assets before playback, retain exact bytes and receipts, and preserve the host's camera and interaction timing. A custom renderer must apply primitive and entity transforms once and preserve the SDK's linear material colors. Cache geometry but clone it before changing UVs; use world-scale texture coordinates for consistent finishes. Review actual camera paths for obstructing members, supported roof details, aperture depth, prop contacts and coplanar floor flicker. Verify repeated seeks and inspect the final encoded movie, not only hero stills. See the [film integration guide](https://github.com/hraness/slopcamera/blob/main/docs/how-to/parametric-design.md#integrate-a-design-into-an-existing-film).
|
|
17
|
+
|
|
18
|
+
For wall openings, `center[1]` is sill height from the wall base; arch height includes its semicircular crown. Use v3.3.3 or newer for corrected openings and boolean surfaces. Boolean inputs must be closed and satisfy hull convexity; conservative geometry budgets can reject opening counts below the schema maximum. Split a complex façade into wall segments. Distant wall LOD and collision proxies remain solid boxes; do not use them as aperture-accurate interaction geometry.
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
# Build
|
|
1
|
+
# Build and refine authored scenes
|
|
2
|
+
|
|
3
|
+
For an existing composition, go to [Refine an existing scene](#refine-an-existing-scene)
|
|
4
|
+
for staging, interaction, contact, and final delivery review.
|
|
2
5
|
|
|
3
6
|
Prefer helpers over hand-written scene JSON. The `@hraness/slopcamera/code` export ships pure builders in `spatial-scene/build` that return validated v1 scene data; feed their output into entities, transforms, and animation channels, then parse the whole scene before writing it. Helpers never read files, execute source, or touch a renderer, and every result already satisfies the contract schemas. Use [directed scenes](directed-scenes.md) for rendering, patching, and project commands.
|
|
4
7
|
|
|
@@ -30,3 +33,142 @@ Prefer helpers over hand-written scene JSON. The `@hraness/slopcamera/code` expo
|
|
|
30
33
|
## Admit a local glTF asset
|
|
31
34
|
|
|
32
35
|
`slopcamera scene asset admit model.glb --source-root <dir> --output manifest.json --json` validates a local GLB 2.0 against the closed profile, stores it content-addressed, derives model-space and scene-space bounds into a sibling facts manifest, and emits the mesh entity plus ready `add-asset`/`add-entity` patch operations. The source path must stay inside `--source-root`; unsupported GLB features reject rather than degrade. Apply the returned operations through `scene patch`, then feed the admission document itself to `scene audit --asset-bounds`.
|
|
36
|
+
|
|
37
|
+
Downloaded props use this same workflow. Require a self-contained GLB; any textures must be embedded PNG/JPEG. Loose glTF bundles, external textures, compressed meshes, and unsupported attributes or extensions reject. Admission defaults to a gray entity material. To preserve the model's materials and textures, set `geometry.materialMode: "source"` on the returned mesh in its `add-entity` operation before applying the patch, then inspect a rendered frame. Check each model's license and admission result; see the [local asset guide](https://github.com/hraness/slopcamera/blob/main/docs/spatial-scenes.md#admit-a-local-gltf-asset).
|
|
38
|
+
|
|
39
|
+
## Refine an existing scene
|
|
40
|
+
|
|
41
|
+
Use this pass when an existing HTML, Three.js, or native scene needs better
|
|
42
|
+
staging, character interaction, materials, and motion. Start with its retained
|
|
43
|
+
source and a playable baseline. Finish with reviewed frames from the new encoded
|
|
44
|
+
movie, the source that produced it, and specific remaining limitations.
|
|
45
|
+
|
|
46
|
+
### Establish the version and shot list
|
|
47
|
+
|
|
48
|
+
Check `slopcamera --version` and the relevant command help against the executable
|
|
49
|
+
you will use. A source checkout, installed CLI, and installed skill can differ.
|
|
50
|
+
Inspect the matching package or release before concluding that a feature is
|
|
51
|
+
missing. Native ALGAL behavior authoring uses `scene behavior check|bake|audit`;
|
|
52
|
+
read [directed scenes](directed-scenes.md#author-character-behaviors) for its
|
|
53
|
+
contracts.
|
|
54
|
+
|
|
55
|
+
Record each shot's time range, intended action, camera, principal objects, and
|
|
56
|
+
visible defects. Preserve deliberate stylistic choices and identity constraints.
|
|
57
|
+
Capture comparable baseline and revised frames at the same scene times. When
|
|
58
|
+
adding a shot, update duration, cuts, captions, end fades, frame count, and review
|
|
59
|
+
sample lists together.
|
|
60
|
+
|
|
61
|
+
Assign independent scene or object groups to separate workers when useful. Keep
|
|
62
|
+
one owner for shared rigs, camera conventions, render settings, generated source,
|
|
63
|
+
and the final browser session. Join the changes before the aggregate review.
|
|
64
|
+
|
|
65
|
+
### Repair what the camera reveals
|
|
66
|
+
|
|
67
|
+
Review the whole shot before adding detail. Use this order to make each pass
|
|
68
|
+
address the remaining visible problem:
|
|
69
|
+
|
|
70
|
+
| Area | Review and repair |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| Staging | Make the principal action readable at delivery size. Check silhouettes, feet, faces, sight lines, and foreground occlusion throughout the camera move. Extend sets far enough to cover every intended view. |
|
|
73
|
+
| Contact | Check soles against the floor, fingers against handles, carried objects against hands, and tools against their target surfaces. An object origin is rarely its contact point. |
|
|
74
|
+
| Construction | Give prominent objects plausible thickness, supports, seams, and connections. Inspect stairs, railings, glazing, roofs, furniture, and machine housings for floating or intersecting parts. |
|
|
75
|
+
| Materials | Distinguish metal, glass, painted surfaces, fabric, and foliage through their response to light. Keep roughness and small variations consistent with scale; uniform gloss and repeated bright windows can flatten a scene. |
|
|
76
|
+
| Lighting | Establish a readable key light, restrained fill, and motivated practical lights. Check contact shadows, highlight clipping, bloom, depth-of-field focus, and dark detail in the encoded result. |
|
|
77
|
+
| Motion | Give actions a cause, preparation, contact, follow-through, and rest. Review full motion when a pose depends on a preceding event. |
|
|
78
|
+
|
|
79
|
+
Fix a contact or camera defect before covering it with bloom, grain, motion, or
|
|
80
|
+
extra props. Concentrate detail where the camera can resolve it. Numerical
|
|
81
|
+
geometry checks can catch penetration; they cannot establish that a face is
|
|
82
|
+
visible, a gesture reads well, or a material looks convincing.
|
|
83
|
+
|
|
84
|
+
### Make behavior serve an observable action
|
|
85
|
+
|
|
86
|
+
Choose a few useful reactions: a commuter notices a sign, a listener looks toward
|
|
87
|
+
the speaker, or two characters pass an object. Define the event sequence before
|
|
88
|
+
varying timing. A handoff might use notice, pause, reach, contact, shared grip,
|
|
89
|
+
release, and acknowledgment. Assign explicit prop ownership intervals and
|
|
90
|
+
document which actor controls the shared phase.
|
|
91
|
+
|
|
92
|
+
Use the native ALGAL interaction organism for phase sequencing, then bind its
|
|
93
|
+
emitted channels to clips, attach/release events, or trajectory waypoints through
|
|
94
|
+
a channel map. Retain the behavior document, scene binding, channel map, bake,
|
|
95
|
+
and audit. Repeat the bake with identical inputs to check determinism. Change
|
|
96
|
+
the seed deliberately when reviewing variants. Inspect advisory findings in the
|
|
97
|
+
context of the channel's meaning; repeated event payloads can be intentional.
|
|
98
|
+
|
|
99
|
+
The behavior trace establishes event timing. Its mapped performance still needs
|
|
100
|
+
the right rig, clip sources, transforms, and contact solution. Measure gaze toward
|
|
101
|
+
the actual target in the character's coordinate frame. Preserve a resting arm
|
|
102
|
+
when only the other arm participates. Use small delays and bounded pose changes
|
|
103
|
+
to separate reactions without giving every object continuous motion.
|
|
104
|
+
|
|
105
|
+
Direct within the visible asset's limits. A mathematically exact gaze can turn a
|
|
106
|
+
photographic face map into an unreadable profile. Review face visibility during
|
|
107
|
+
the gesture and use a restrained glance or nod when that better communicates the
|
|
108
|
+
intended acknowledgment.
|
|
109
|
+
|
|
110
|
+
When adapting a procedural rig, document any proxy skeleton and the conversion
|
|
111
|
+
between local and world transforms. Test the visible rig separately. An audit of
|
|
112
|
+
a proxy does not establish the visible character's foot placement, grip, or
|
|
113
|
+
whole-body physical validity.
|
|
114
|
+
|
|
115
|
+
### Verify contact and time before the full render
|
|
116
|
+
|
|
117
|
+
Evaluate each frame from its absolute timestamp. Reset transforms and state that
|
|
118
|
+
can otherwise survive a seek. Keep behavior baking outside the frame callback;
|
|
119
|
+
sample the retained result during playback. Test a timestamp, a later timestamp,
|
|
120
|
+
and the original timestamp again, including ownership and pose endpoints.
|
|
121
|
+
|
|
122
|
+
For each important contact, declare the measured surfaces or anchors and a
|
|
123
|
+
tolerance in scene units. Sample the actual loaded rig through the interaction,
|
|
124
|
+
including frames immediately before and after attach, release, impact, and
|
|
125
|
+
planting events. Check finite transforms, grip error, floor clearance, ownership,
|
|
126
|
+
and prop orientation. For moving crowds or vehicles, check geometry extents and
|
|
127
|
+
intermediate times as well as center distances. Report sampling density and
|
|
128
|
+
tolerances; sampled clearance is not continuous collision proof.
|
|
129
|
+
|
|
130
|
+
Require finite measurements and actual samples for every required contact state.
|
|
131
|
+
Missing contact data must fail validation rather than default to zero error.
|
|
132
|
+
Ensure each reported contact and replay check contributes to the final pass/fail
|
|
133
|
+
result.
|
|
134
|
+
|
|
135
|
+
Render a short preview containing the entire difficult action. For an HTML scene,
|
|
136
|
+
author a bounded preview request and map its local clock to the intended source
|
|
137
|
+
time in the authored scene; preserve the full-scene request. For a directed scene,
|
|
138
|
+
use the supported explicit render range. Do not assume a project-specific capture
|
|
139
|
+
flag is a Slopcamera CLI option.
|
|
140
|
+
|
|
141
|
+
Review every shot at its start, middle, and end, then inspect event boundaries,
|
|
142
|
+
cuts, and changed camera coverage. A contact sheet reveals composition and
|
|
143
|
+
continuity; a motion preview reveals timing, sliding, snapping, and occlusion.
|
|
144
|
+
Use both when the change affects interaction. Record defects by shot, object,
|
|
145
|
+
time, and visible consequence so another pass can reproduce them.
|
|
146
|
+
|
|
147
|
+
### Verify the encoded movie
|
|
148
|
+
|
|
149
|
+
Run the supported [HTML music-video](music-video.md), [directed-scene](directed-scenes.md),
|
|
150
|
+
or [native studio](native-studio.md) render workflow from the converged source.
|
|
151
|
+
Retain its source and runtime receipts. A completed frame loop does not establish
|
|
152
|
+
successful encoding or browser cleanup; resolve any reported lifecycle failure
|
|
153
|
+
before calling the render successful.
|
|
154
|
+
|
|
155
|
+
Probe the delivery for dimensions, rational frame rate, decoded frame count,
|
|
156
|
+
duration, color metadata, and expected audio streams. Derive expected frames from
|
|
157
|
+
the selected workflow's timing rules. Decode review frames from this movie,
|
|
158
|
+
including cuts, the final frame, and the contact phases. If an authored event
|
|
159
|
+
falls between frame times, record the selected frame index and its actual time.
|
|
160
|
+
Review color against the source frames; metadata alone does not prove the correct
|
|
161
|
+
RGB-to-YUV conversion. Distinguish intentional fades from missing or black frames.
|
|
162
|
+
|
|
163
|
+
Keep the previous delivery and hash both files when verifying replacement. A
|
|
164
|
+
different hash proves different bytes, so also compare the intended visible
|
|
165
|
+
changes. Deliver the new movie with a compact scene review, retained evidence,
|
|
166
|
+
and the limitations that remain visible. Avoid a quality or physics claim that
|
|
167
|
+
the review did not establish.
|
|
168
|
+
|
|
169
|
+
For a custom capture optimization, compare decoded pixels against the supported
|
|
170
|
+
capture path at representative lit frames, including transparent edges and
|
|
171
|
+
post-processing. An opening black frame is insufficient evidence. Keep the
|
|
172
|
+
ordinary capture path when equality is required and the comparison differs.
|
|
173
|
+
Never bypass browser signature checks, disable its sandbox, or signal unrelated
|
|
174
|
+
processes to make a render finish.
|
package/src/mcp/tools.ts
CHANGED
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
import {
|
|
46
46
|
spatialBehaviorFnSignatures,
|
|
47
47
|
} from "../spatial-scene/behavior-fns.js"
|
|
48
|
+
import { SpatialBehaviorBakeSchema } from "../spatial-scene/behavior-trace.js"
|
|
48
49
|
import {
|
|
49
50
|
SPATIAL_SCENE_LIMITS,
|
|
50
51
|
type SpatialSceneV1,
|
|
@@ -2410,7 +2411,11 @@ export class SlopcameraMcpToolRuntime {
|
|
|
2410
2411
|
options: ParsedSceneBehaviorAuditArguments,
|
|
2411
2412
|
): Promise<McpToolResult> {
|
|
2412
2413
|
const bake = await loadJson(this.boundary, options.bake, "Behavior bake source")
|
|
2413
|
-
const
|
|
2414
|
+
const document = parseSpatialValue(SpatialBehaviorBakeSchema, bake.value, "behavior bake")
|
|
2415
|
+
const report = auditSpatialBehaviorTrace({
|
|
2416
|
+
behaviorSha256: document.behaviorSha256, emittedSha256: document.receipt.emittedSha256,
|
|
2417
|
+
emitted: document.emitted, rangeUs: document.rangeUs,
|
|
2418
|
+
})
|
|
2414
2419
|
const findings = boundedSlice(report.findings, mcpMaximumReturnedFindings)
|
|
2415
2420
|
const summary = {
|
|
2416
2421
|
findingCount: report.findings.length,
|
|
@@ -27,8 +27,7 @@ import {
|
|
|
27
27
|
type SpatialBehaviorChannelMap,
|
|
28
28
|
type SpatialBehaviorEmitted,
|
|
29
29
|
} from "./behavior-trace.js"
|
|
30
|
-
import {
|
|
31
|
-
import { parseSpatialValue, spatialValueSha256, SpatialSceneError } from "./identity.js"
|
|
30
|
+
import { parseSpatialScene, parseSpatialValue, spatialValueSha256, SpatialSceneError } from "./identity.js"
|
|
32
31
|
|
|
33
32
|
/**
|
|
34
33
|
* Deterministic behavior bake: an admitted `slopcamera.spatial-behavior`
|
|
@@ -105,7 +104,7 @@ const entryRunArgs = (behavior: SpatialBehavior, manifest: OrganismManifest): Re
|
|
|
105
104
|
export async function bakeSpatialBehavior(input: unknown): Promise<SpatialBehaviorBakeResult> {
|
|
106
105
|
const options = parseSpatialValue(bakeOptionsSchema, input, "behavior bake")
|
|
107
106
|
const behavior = parseSpatialBehavior(options.behavior)
|
|
108
|
-
const scene =
|
|
107
|
+
const scene = parseSpatialScene(options.scene)
|
|
109
108
|
const sceneSha256 = spatialValueSha256(scene)
|
|
110
109
|
const signatures = spatialBehaviorFnSignatures()
|
|
111
110
|
const check = checkSpatialBehavior({ behavior, scene }, signatures)
|
|
@@ -13,9 +13,8 @@ import {
|
|
|
13
13
|
} from "./behavior-trace.js"
|
|
14
14
|
import {
|
|
15
15
|
SpatialDigestSchema,
|
|
16
|
-
SpatialSceneV1Schema,
|
|
17
16
|
} from "./contracts.js"
|
|
18
|
-
import { parseSpatialValue, spatialValueSha256 } from "./identity.js"
|
|
17
|
+
import { parseSpatialScene, parseSpatialValue, spatialValueSha256 } from "./identity.js"
|
|
19
18
|
|
|
20
19
|
/**
|
|
21
20
|
* Seeded behavior galleries: one admitted behavior document baked under
|
|
@@ -69,7 +68,7 @@ const candidateId = (parameter: string, behaviorSha256: string): string =>
|
|
|
69
68
|
export async function planSpatialBehaviorGallery(input: unknown): Promise<SpatialBehaviorGalleryPlan> {
|
|
70
69
|
const options = parseSpatialValue(galleryOptionsSchema, input, "behavior gallery")
|
|
71
70
|
const behavior = parseSpatialBehavior(options.behavior)
|
|
72
|
-
const scene =
|
|
71
|
+
const scene = parseSpatialScene(options.scene)
|
|
73
72
|
const channelMap = options.channelMap === undefined ? undefined : parseSpatialBehaviorChannelMap(options.channelMap)
|
|
74
73
|
|
|
75
74
|
const seen = new Set<string>()
|
|
@@ -4,11 +4,10 @@ import { boundedCanonicalJsonSha256 } from "../code/canonical-json.js"
|
|
|
4
4
|
import { deepFreezeJson } from "../code/json-snapshot.js"
|
|
5
5
|
import {
|
|
6
6
|
SpatialDigestSchema,
|
|
7
|
-
SpatialSceneV1Schema,
|
|
8
7
|
SpatialTimeUsSchema,
|
|
9
8
|
type SpatialSceneV1,
|
|
10
9
|
} from "./contracts.js"
|
|
11
|
-
import { parseSpatialValue, spatialValueSha256 } from "./identity.js"
|
|
10
|
+
import { parseSpatialScene, parseSpatialValue, spatialValueSha256 } from "./identity.js"
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* Behavior documents bind an ALGAL organism closure to one scene entity.
|
|
@@ -392,7 +391,7 @@ export function checkSpatialBehavior(
|
|
|
392
391
|
): SpatialBehaviorCheckReport {
|
|
393
392
|
const options = parseSpatialValue(checkOptionsSchema, input, "behavior check")
|
|
394
393
|
const behavior = parseSpatialBehavior(options.behavior)
|
|
395
|
-
const scene =
|
|
394
|
+
const scene = parseSpatialScene(options.scene)
|
|
396
395
|
const sceneSha256 = spatialValueSha256(scene)
|
|
397
396
|
const findings: SpatialBehaviorCheckFinding[] = []
|
|
398
397
|
const finding = (code: SpatialBehaviorFindingCode, severity: "error" | "warning", detail: string, referenceId?: string): void => {
|
|
@@ -44,6 +44,8 @@ export const SPATIAL_GEOMETRY_LIMITS = Object.freeze({
|
|
|
44
44
|
|
|
45
45
|
export const SPATIAL_GEOMETRY_GRAPH_KIND = "slopcamera.spatial-geometry-graph" as const
|
|
46
46
|
export const SPATIAL_GEOMETRY_PROFILE = "slopcamera.parametric-geometry-v1" as const
|
|
47
|
+
/** CSG algorithm identity is separate from the retained GLB/facts format. */
|
|
48
|
+
export const SPATIAL_GEOMETRY_BOOLEAN_COMPILER = "slopcamera.geometry-boolean-v2" as const
|
|
47
49
|
|
|
48
50
|
const finite = z.number().finite().min(-SPATIAL_GEOMETRY_LIMITS.coordinate).max(SPATIAL_GEOMETRY_LIMITS.coordinate)
|
|
49
51
|
const positive = z.number().finite().min(1e-9).max(SPATIAL_GEOMETRY_LIMITS.coordinate)
|
|
@@ -120,6 +122,7 @@ export interface SpatialGeometryEvaluation {
|
|
|
120
122
|
readonly mesh: SpatialGeometryMesh
|
|
121
123
|
readonly estimate: SpatialGeometryEstimate
|
|
122
124
|
readonly graphSha256: string
|
|
125
|
+
readonly booleanCompiler?: typeof SPATIAL_GEOMETRY_BOOLEAN_COMPILER
|
|
123
126
|
}
|
|
124
127
|
|
|
125
128
|
function fail(message: string, path = "geometry"): never {
|
|
@@ -747,6 +750,13 @@ function partitionTriangles(input: Builder, planes: readonly Plane[]): Fragment[
|
|
|
747
750
|
for (const plane of planes) {
|
|
748
751
|
const next: Fragment[] = []
|
|
749
752
|
for (const fragment of fragments) {
|
|
753
|
+
const signed = fragment.positions.map(point => plane.normal[0] * point[0] + plane.normal[1] * point[1] + plane.normal[2] * point[2] - plane.d)
|
|
754
|
+
// A one-sided or coplanar polygon is already a single cell. Clipping
|
|
755
|
+
// it into both inclusive half-spaces duplicates coplanar surfaces.
|
|
756
|
+
if (!signed.some(value => value < -PLANE_EPS) || !signed.some(value => value > PLANE_EPS)) {
|
|
757
|
+
next.push(fragment)
|
|
758
|
+
continue
|
|
759
|
+
}
|
|
750
760
|
const inner = clipFragment(fragment, plane, true)
|
|
751
761
|
const outer = clipFragment(fragment, plane, false)
|
|
752
762
|
if (inner !== null) next.push(inner)
|
|
@@ -759,25 +769,59 @@ function partitionTriangles(input: Builder, planes: readonly Plane[]): Fragment[
|
|
|
759
769
|
return result
|
|
760
770
|
}
|
|
761
771
|
|
|
762
|
-
|
|
763
|
-
function fragmentInside(planes: readonly Plane[], fragment: Fragment): boolean {
|
|
772
|
+
function fragmentCentroid(fragment: Fragment): Vec3 {
|
|
764
773
|
const count = fragment.positions.length
|
|
765
|
-
|
|
774
|
+
return [
|
|
766
775
|
fragment.positions.reduce((sum, p) => sum + p[0], 0) / count,
|
|
767
776
|
fragment.positions.reduce((sum, p) => sum + p[1], 0) / count,
|
|
768
777
|
fragment.positions.reduce((sum, p) => sum + p[2], 0) / count,
|
|
769
778
|
]
|
|
770
|
-
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function fragmentNormal(fragment: Fragment): Vec3 | null {
|
|
782
|
+
const origin = fragment.positions[0]!, normal = [0, 0, 0]
|
|
783
|
+
for (let index = 1; index + 1 < fragment.positions.length; index++) {
|
|
784
|
+
const a = fragment.positions[index]!, b = fragment.positions[index + 1]!
|
|
785
|
+
const u = [a[0] - origin[0], a[1] - origin[1], a[2] - origin[2]], v = [b[0] - origin[0], b[1] - origin[1], b[2] - origin[2]]
|
|
786
|
+
normal[0] = normal[0]! + u[1]! * v[2]! - u[2]! * v[1]!
|
|
787
|
+
normal[1] = normal[1]! + u[2]! * v[0]! - u[0]! * v[2]!
|
|
788
|
+
normal[2] = normal[2]! + u[0]! * v[1]! - u[1]! * v[0]!
|
|
789
|
+
}
|
|
790
|
+
const length = Math.hypot(...normal)
|
|
791
|
+
return length < 1e-12 ? null : [normal[0]! / length, normal[1]! / length, normal[2]! / length]
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function planeDistance(plane: Plane, point: Vec3): number {
|
|
795
|
+
return plane.normal[0] * point[0] + plane.normal[1] * point[1] + plane.normal[2] * point[2] - plane.d
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/** Symbolic infinitesimal occupancy on each side avoids scale-dependent probe distances. */
|
|
799
|
+
function insideHullSide(planes: readonly Plane[], point: Vec3, normal: Vec3, side: number): boolean {
|
|
800
|
+
return planes.every(plane => {
|
|
801
|
+
const signed = planeDistance(plane, point)
|
|
802
|
+
if (signed > PLANE_EPS) return false
|
|
803
|
+
if (signed < -PLANE_EPS) return true
|
|
804
|
+
return side * (plane.normal[0] * normal[0] + plane.normal[1] * normal[1] + plane.normal[2] * normal[2]) <= PLANE_EPS
|
|
805
|
+
})
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function boundsTouch(a: Bounds, b: Bounds): boolean {
|
|
809
|
+
return [0, 1, 2].every(axis => a.min[axis]! <= b.max[axis]! + PLANE_EPS && b.min[axis]! <= a.max[axis]! + PLANE_EPS)
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function builderBounds(input: Builder): Bounds {
|
|
813
|
+
const low: number[] = [Infinity, Infinity, Infinity], high: number[] = [-Infinity, -Infinity, -Infinity]
|
|
814
|
+
for (let i = 0; i < input.positions.length; i++) {
|
|
815
|
+
const axis = i % 3, value = input.positions[i]!
|
|
816
|
+
low[axis] = Math.min(low[axis]!, value); high[axis] = Math.max(high[axis]!, value)
|
|
817
|
+
}
|
|
818
|
+
return { min: low as unknown as Vec3, max: high as unknown as Vec3 }
|
|
771
819
|
}
|
|
772
820
|
|
|
773
821
|
function emitFragments(b: Builder, fragments: readonly Fragment[], flip: boolean): void {
|
|
774
822
|
for (const fragment of fragments) {
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
const a = fragment.positions[index]!, c = fragment.positions[(index + 1) % fragment.positions.length]!
|
|
778
|
-
z += a[0] * c[1] - a[1] * c[0]
|
|
779
|
-
}
|
|
780
|
-
const unit: Vec3 = z < 0 ? [0, 0, -1] : [0, 0, 1]
|
|
823
|
+
const unit = fragmentNormal(fragment)
|
|
824
|
+
if (unit === null) continue
|
|
781
825
|
const oriented: Vec3 = flip ? [-unit[0], -unit[1], -unit[2]] : unit
|
|
782
826
|
const indices = fragment.positions.map((position, index) => vertex(b, position, oriented, fragment.uvs[index]!))
|
|
783
827
|
for (let index = 1; index + 1 < indices.length; index++) {
|
|
@@ -801,30 +845,31 @@ function booleanMesh(input: Extract<SpatialGeometryNode, { kind: "boolean" }>, m
|
|
|
801
845
|
fail("Boolean inputs exceed the bounded simple-input triangle budget.", `${path}.${index}`)
|
|
802
846
|
}
|
|
803
847
|
}
|
|
804
|
-
const
|
|
805
|
-
|
|
806
|
-
assertHull(a, planesA, path)
|
|
807
|
-
cutters.forEach((cutter, index) => assertHull(cutter, planesC[index]!, path))
|
|
848
|
+
const inputs = [a, ...cutters], hulls = inputs.map(mesh => uniquePlanes(mesh, path)), bounds = inputs.map(builderBounds)
|
|
849
|
+
inputs.forEach((mesh, index) => assertHull(mesh, hulls[index]!, path))
|
|
808
850
|
const output = builder()
|
|
809
|
-
|
|
810
|
-
const
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
851
|
+
for (const [owner, mesh] of inputs.entries()) {
|
|
852
|
+
const planes = hulls.flatMap((hull, index) => index !== owner && boundsTouch(bounds[owner]!, bounds[index]!) ? hull : [])
|
|
853
|
+
const outward: Fragment[] = [], reversed: Fragment[] = []
|
|
854
|
+
for (const fragment of partitionTriangles(mesh, planes)) {
|
|
855
|
+
const normal = fragmentNormal(fragment)
|
|
856
|
+
if (normal === null) continue
|
|
857
|
+
const point = fragmentCentroid(fragment)
|
|
858
|
+
const occupied = (side: number): boolean => {
|
|
859
|
+
const inside = hulls.map(hull => insideHullSide(hull, point, normal, side))
|
|
860
|
+
return input.operation === "difference" ? inside[0]! && !inside.slice(1).some(Boolean)
|
|
861
|
+
: input.operation === "intersection" ? inside.every(Boolean) : inside.some(Boolean)
|
|
862
|
+
}
|
|
863
|
+
const positive = occupied(1), negative = occupied(-1)
|
|
864
|
+
if (positive === negative) continue
|
|
865
|
+
// Coincident boundary faces belong to the earliest source hull. Their
|
|
866
|
+
// occupancy is shared; retaining both would double area and cause z-fighting.
|
|
867
|
+
const duplicate = hulls.slice(0, owner).some(hull => hull.every(plane => planeDistance(plane, point) <= PLANE_EPS)
|
|
868
|
+
&& hull.some(plane => Math.abs(planeDistance(plane, point)) <= PLANE_EPS
|
|
869
|
+
&& Math.abs(plane.normal[0] * normal[0] + plane.normal[1] * normal[1] + plane.normal[2] * normal[2]) > 1 - 1e-7))
|
|
870
|
+
if (!duplicate) (positive ? reversed : outward).push(fragment)
|
|
827
871
|
}
|
|
872
|
+
emitFragments(output, outward, false); emitFragments(output, reversed, true)
|
|
828
873
|
}
|
|
829
874
|
output.uvsSet = [a, ...cutters].some(mesh => mesh.uvsSet)
|
|
830
875
|
if (output.indices.length === 0) fail("Boolean result is empty; the inputs do not overlap as the operation requires.", path)
|
|
@@ -1010,8 +1055,15 @@ function estimateNode(node: SpatialGeometryNode, meshEstimates: ReadonlyMap<stri
|
|
|
1010
1055
|
case "boolean": {
|
|
1011
1056
|
const a = mesh(node.a)
|
|
1012
1057
|
const cutters = node.operation === "difference" ? node.cutters.map(mesh) : [mesh(node.b)]
|
|
1013
|
-
const
|
|
1014
|
-
|
|
1058
|
+
const inputs = [a, ...cutters]
|
|
1059
|
+
// In one source triangle, n plane intersections form at most
|
|
1060
|
+
// 1+n(n+1)/2 cells. Each proper split adds at most two fan triangles,
|
|
1061
|
+
// hence 1+n(n+1) triangles per input triangle, including all cap faces.
|
|
1062
|
+
// Disjoint input bounds cannot cut each other's source triangles.
|
|
1063
|
+
const triangles = inputs.reduce((total, source, index) => {
|
|
1064
|
+
const planes = inputs.reduce((sum, other, otherIndex) => sum + (otherIndex !== index && boundsTouch(source.bounds, other.bounds) ? other.planes : 0), 0)
|
|
1065
|
+
return total + source.triangles * (1 + planes * (planes + 1))
|
|
1066
|
+
}, 0)
|
|
1015
1067
|
const other = cutters[0]!
|
|
1016
1068
|
const bounds = node.operation === "intersection"
|
|
1017
1069
|
? { min: [Math.max(a.bounds.min[0], other.bounds.min[0]), Math.max(a.bounds.min[1], other.bounds.min[1]), Math.max(a.bounds.min[2], other.bounds.min[2])] as unknown as Vec3,
|
|
@@ -1099,7 +1151,8 @@ export function evaluateSpatialGeometry(input: unknown): SpatialGeometryEvaluati
|
|
|
1099
1151
|
if (mesh.vertices > estimate.vertices || mesh.triangles > estimate.triangles) {
|
|
1100
1152
|
fail("Evaluated mesh exceeded its static budget estimate.", "geometry.output")
|
|
1101
1153
|
}
|
|
1102
|
-
return deepFreezeJson({ mesh, estimate, graphSha256: spatialValueSha256({ domain: "slopcamera.spatial-geometry-graph.v1", graph })
|
|
1154
|
+
return deepFreezeJson({ mesh, estimate, graphSha256: spatialValueSha256({ domain: "slopcamera.spatial-geometry-graph.v1", graph }),
|
|
1155
|
+
...(graph.nodes.some(node => node.kind === "boolean") ? { booleanCompiler: SPATIAL_GEOMETRY_BOOLEAN_COMPILER } : {}) })
|
|
1103
1156
|
}
|
|
1104
1157
|
|
|
1105
1158
|
// ---------------------------------------------------------------------------
|