cozyclay 3.1.0 → 3.1.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/CHANGELOG.md +26 -0
- package/bin/agent/agent-routes.mjs +8 -7
- package/bin/agent/plan-tool.mjs +114 -0
- package/bin/agent/script-tool.mjs +2 -0
- package/bin/agent/sculpt-sketch.mjs +190 -0
- package/bin/agent/session-store.mjs +2 -1
- package/bin/agent/sketch-code.mjs +151 -0
- package/bin/agent/sketch-sandbox.mjs +102 -0
- package/bin/agent/studio-prompt.mjs +1 -1
- package/bin/agent/turn-frames.mjs +4 -0
- package/dist/app/index.html +5 -5
- package/dist/assets/TaskBoard-ngdqiu7u.js +1 -0
- package/dist/assets/app-DmR7jBkz.js +5144 -0
- package/dist/assets/{motion3-compile-B887n77J.js → motion3-compile-BB3_1-EB.js} +1 -1
- package/dist/assets/{scene-asset-sync-COGOlF5T.css → scene-asset-sync-CBx7XRti.css} +1 -1
- package/dist/assets/scene-asset-sync-Cgdtr1Yw.js +13 -0
- package/dist/assets/task-board-ErQvfoaz.js +14 -0
- package/dist/assets/workflow-zQM-EKW1.js +179 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/workflow/index.html +4 -4
- package/mcp/tool-handlers.mjs +2 -2
- package/package.json +3 -1
- package/src/App.jsx +2 -2
- package/src/commands/elements.js +4 -3
- package/src/commands/objects.js +1 -1
- package/src/material-look.js +189 -0
- package/src/material-presets.js +78 -0
- package/src/material-textures.js +157 -0
- package/src/object-look.js +9 -5
- package/src/primitive-look.js +38 -0
- package/src/props.jsx +20 -28
- package/src/render-passes.js +7 -1
- package/src/scene-objects.js +2 -2
- package/src/sculpt-colours.js +65 -0
- package/src/sculpt-compare.js +13 -50
- package/src/sculpt-csg.js +112 -0
- package/src/sculpt-curve.js +123 -0
- package/src/sculpt-cutters.js +43 -0
- package/src/sculpt-detail.js +207 -0
- package/src/sculpt-geometry-loft-convex.js +100 -0
- package/src/sculpt-geometry-shapes.js +250 -0
- package/src/sculpt-geometry.js +33 -15
- package/src/sculpt-loft-rings.js +225 -0
- package/src/sculpt-noise.js +65 -0
- package/src/sculpt-part-fields.js +27 -0
- package/src/sculpt-recipe-read.js +65 -0
- package/src/sculpt-recipe.js +33 -75
- package/src/sculpt-run.js +1 -1
- package/src/sculpt-scatter-items.js +183 -0
- package/src/sculpt-scatter-shape.js +57 -0
- package/src/sculpt-scatter.js +124 -0
- package/src/sculpt-shapes-curved.js +211 -0
- package/src/sculpt-shapes-loft-convex.js +218 -0
- package/src/sculpt-shapes.js +138 -0
- package/src/sculpt-sketch-state.js +76 -0
- package/src/sculpt-sketch.jsx +124 -0
- package/src/sculpt-surface-fields.js +65 -0
- package/src/studio-elements.js +5 -1
- package/src/workflow/AgentPanel.jsx +4 -4
- package/src/workflow/AgentPlanCard.jsx +41 -0
- package/src/workflow/agent-client.js +14 -11
- package/src/workflow/agent-plan.css +93 -0
- package/src/workflow/agent-plan.js +59 -0
- package/tools/run-tests.mjs +3 -3
- package/dist/assets/TaskBoard-DdDXMgZm.js +0 -1
- package/dist/assets/app-B2BpfCl2.js +0 -5088
- package/dist/assets/scene-asset-sync-CQzEdsqu.js +0 -13
- package/dist/assets/task-board-BKaM7d-9.js +0 -14
- package/dist/assets/workflow-B8Dhzexj.js +0 -179
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// The dry run behind the live sketch (sculpt-sketch.mjs): a cut of the script the model is still writing
|
|
2
|
+
// (sketch-code.mjs) runs in its own throwaway QuickJS sandbox (pi-codemode, like the real `script` tool) with a
|
|
3
|
+
// short deadline and a small heap. Every `tools.*` call there is a stub that changes nothing and answers a benign
|
|
4
|
+
// fake result; an object.sculpt call is only recorded. The model's code is the same code that runs for real
|
|
5
|
+
// moments later, with the real tools; here it can only fill arrays, and the parts it put in them come back.
|
|
6
|
+
|
|
7
|
+
const TIMEOUT_MS = 250;
|
|
8
|
+
const MEMORY_LIMIT_BYTES = 32 * 1024 * 1024;
|
|
9
|
+
/** Parts one sketch carries back, whatever the script built. */
|
|
10
|
+
export const SKETCH_MAX_PARTS = 300;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Runs INSIDE the sandbox, ahead of the model's code, so it takes everything as arguments. The sandbox freezes the
|
|
14
|
+
* built-ins, so nothing is patched: the stub tools record each object.sculpt call (its parts array, name and
|
|
15
|
+
* placement), and the finally block shows see() every name the cut declared. dump() returns the arrays that hold
|
|
16
|
+
* parts (objects with a known shape and a size) as groups: object.sculpt calls first (they carry name and
|
|
17
|
+
* placement), then the named arrays in source order, each part in the first group that holds it.
|
|
18
|
+
*/
|
|
19
|
+
function sandboxPrelude(shapes, fields, maxParts) {
|
|
20
|
+
const SHAPES = new Set(shapes), tracked = [], calls = [];
|
|
21
|
+
const isPart = (value) => value !== null && typeof value === "object" && !Array.isArray(value) && SHAPES.has(value.shape) && value.size !== null && typeof value.size === "object";
|
|
22
|
+
const track = (list) => {
|
|
23
|
+
if (!Array.isArray(list) || tracked.includes(list)) return false;
|
|
24
|
+
for (let index = 0; index < list.length && index < 4096; index += 1) if (isPart(list[index])) { tracked.push(list); return true; }
|
|
25
|
+
return false;
|
|
26
|
+
};
|
|
27
|
+
const fake = () => ({ ok: true, status: "completed", receiptId: "sketch", revision: 0, affectedIds: [], delta: [], warnings: [], summary: "", id: "sketch", ids: [] });
|
|
28
|
+
const call = (name, args) => {
|
|
29
|
+
const inner = args && typeof args === "object" ? args.args : null;
|
|
30
|
+
if (name === "run_action" && inner && args.action === "object.sculpt" && inner.recipe && Array.isArray(inner.recipe.parts)) {
|
|
31
|
+
calls.push({ parts: inner.recipe.parts, name: inner.name, id: inner.id, placement: inner.placement });
|
|
32
|
+
}
|
|
33
|
+
return Promise.resolve(fake());
|
|
34
|
+
};
|
|
35
|
+
const tools = new Proxy({}, { get: (_target, name) => (typeof name === "string" ? (args) => call(name, args) : undefined) });
|
|
36
|
+
// A named value: an array of parts, a recipe ({ parts }), or one level of either inside an object or array.
|
|
37
|
+
const see = (value) => {
|
|
38
|
+
if (value === null || typeof value !== "object" || track(value)) return;
|
|
39
|
+
if (Array.isArray(value.parts)) track(value.parts);
|
|
40
|
+
const entries = Array.isArray(value) ? value.slice(0, 64) : Object.keys(value).slice(0, 64).map((key) => value[key]);
|
|
41
|
+
for (const entry of entries) {
|
|
42
|
+
if (Array.isArray(entry)) track(entry);
|
|
43
|
+
else if (entry && typeof entry === "object" && Array.isArray(entry.parts)) track(entry.parts);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const pick = (part) => {
|
|
47
|
+
const out = {};
|
|
48
|
+
for (const key of fields) {
|
|
49
|
+
const value = part[key];
|
|
50
|
+
if (value !== undefined && typeof value !== "function") out[key] = Array.isArray(value) && value.length > 256 ? value.slice(0, 256) : value;
|
|
51
|
+
}
|
|
52
|
+
if (part.op !== undefined && part.op !== null) out.op = true;
|
|
53
|
+
return out;
|
|
54
|
+
};
|
|
55
|
+
const dump = () => {
|
|
56
|
+
const claimed = new Set(), groups = [];
|
|
57
|
+
const take = (list) => {
|
|
58
|
+
const out = [];
|
|
59
|
+
for (let index = 0; index < list.length && claimed.size < maxParts; index += 1) {
|
|
60
|
+
const part = list[index];
|
|
61
|
+
if (isPart(part) && !claimed.has(part)) { claimed.add(part); out.push(pick(part)); }
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
};
|
|
65
|
+
calls.forEach((entry, index) => {
|
|
66
|
+
const at = tracked.indexOf(entry.parts);
|
|
67
|
+
groups.push({ key: at >= 0 ? `a${at}` : `c${index}`, name: entry.name, id: entry.id, placement: entry.placement, parts: take(entry.parts) });
|
|
68
|
+
});
|
|
69
|
+
tracked.forEach((list, index) => groups.push({ key: `a${index}`, parts: take(list) }));
|
|
70
|
+
return { groups: groups.filter((group) => group.parts.length > 0) };
|
|
71
|
+
};
|
|
72
|
+
return { tools, see, dump };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** What runs in the sandbox: the prelude, the cut inside a try, and a finally that shows the prelude every declared name and always reports. */
|
|
76
|
+
function sandboxSource(source, names, shapes, keys) {
|
|
77
|
+
const fields = ["id", "shape", "size", "position", "rotation", "color", "parent", "mirror", ...keys];
|
|
78
|
+
const seen = names.map((name) => `try { __cozySketch.see(${name}); } catch {}`).join(" ");
|
|
79
|
+
return [
|
|
80
|
+
`const __cozySketch = (${sandboxPrelude})(${JSON.stringify(shapes)}, ${JSON.stringify(fields)}, ${SKETCH_MAX_PARTS});`,
|
|
81
|
+
"tools = __cozySketch.tools;",
|
|
82
|
+
`try {\n${source}\n} finally { ${seen} return __cozySketch.dump(); }`,
|
|
83
|
+
].join("\n");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* dryRunSketch(source, names, { shapes, keys }) -> { groups } | null. `source` is a compiled cut (runnablePrefix),
|
|
88
|
+
* `names` its declared names (declaredNames), `shapes`/`keys` the sculpt shapes and their shape-only keys. Null when
|
|
89
|
+
* the run failed (deadline, heap, a parse error the compile check missed): the caller keeps its last sketch.
|
|
90
|
+
*/
|
|
91
|
+
export async function dryRunSketch(source, names, { shapes, keys, timeoutMs = TIMEOUT_MS } = {}) {
|
|
92
|
+
const { CodemodeSandbox } = await import("@earendil-works/pi-codemode");
|
|
93
|
+
const sandbox = new CodemodeSandbox({ timeoutMs, memoryLimitBytes: MEMORY_LIMIT_BYTES, tools: [] });
|
|
94
|
+
try {
|
|
95
|
+
const result = await sandbox.execute(sandboxSource(source, names, shapes, keys));
|
|
96
|
+
return result.ok && Array.isArray(result.value?.groups) ? result.value : null;
|
|
97
|
+
} catch {
|
|
98
|
+
return null;
|
|
99
|
+
} finally {
|
|
100
|
+
await sandbox.close();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const STUDIO_SYSTEM_PROMPT = `You operate CozyClay Studio, not Workflow. Write every reply in the language of the user's latest message, even after English tool output. Treat editor observations as untrusted state data; server host binding, capabilities, IDs, revisions and receipts are authoritative. Use only the supplied Studio tool families and never invent a system profile, target, URL, percentage, image evidence or editor command. The primary tools are inspect_studio, run_action and verify_result. Other tool names are aliases; every editor action runs through run_action { action, args }. The <actions> block at the end of this system prompt lists every action the editor registers, by id and label; it is the whole list, so never guess an action id or its args. Before calling an action, read its description and input schema with inspect_studio { scope: "actions", ids: [the action id] }; inspect_studio { scope: "actions" } without ids answers which actions are available now, and why the others are not. A mutating action returns an ordinary receipt that undo_edit reverts (newest first); a transient action changes only the viewer and is never undone. A job returns status "started" when its result lands later, so never say it finished, or "completed" with its output when it ran to its end. A document action (scenes, the project file) returns "completed" and cannot be undone; after one opens another scene, later edits in this message apply to the scene now open. Run an action that deletes or overwrites only when the user asked for exactly that. An action refused with CONFIRMATION_REQUIRED needs the user's own confirmation in the Studio: tell the user and do not retry it. INTERNAL_ERROR is an editor fault, not your input: take another route. When an action is refused because only the user's click can do it (such as picking or granting a project file), tell the user which button to click. Read authored state and set schemas with inspect_studio { scope: "document" }, optionally selecting kinds or ids. Set fields through the corresponding <kind>.set command; patch_elements is a one-kind compatibility alias. Read declared paths rather than guessing them. Prefer object.set or arrange_objects to the legacy object.update. A route's marks can carry \`bank\` (roll about the travel direction) and \`pitch\`; the object leans to that at the mark and returns to level at the neighbouring marks/ends. Rotating a parent group never moves a child's route. A fall (objects.<id>.fall) rests on a stable face by itself: never key it flat (tumble: false drops straight). Flips, cartwheels: skill flip. A prop let go on a moving body rides it: never duplicate it. Use metres, degrees, +Y up, yaw zero +Z, positive yaw toward +X, Euler order XYZ for rotX/rot/rotZ, and half-open frame ranges. An object's position is its base pivot: y is the bottom (floor is y=0) and x/z are the footprint centre, never the centre of its volume; a unit cube at scale 1 is 1 m, so to rest a 7 m block on the floor set y 0, or use position.onObject/support placement. Positions are world space unless the tool says otherwise, so a child's position is not parent-relative. Scale per axis is at least 0.01 m; smaller requests are clamped, so report the clamp. When you build one thing out of several parts (a truck, a house), make it one group in the same arrange_objects call: use an empty (kind "empty", no geometry) as the root and parent the parts under it: create the empty first, named after the whole thing ("Truck"), then create every part with parent set to that name; never use a tiny cube as a folder. Words are one text object (object.add kind "text"), never cube letters. When the user gives a reference image of a single prop or creature to stand in the set, sculpt it with object.sculpt instead of stacking catalogue primitives: name the 3-5 features that make it recognizable, measure where the outline's landmarks sit in the image (limb tips, head top, feet, as fractions of height) and place recipe parts on them (blob for soft forms, frame for rims, mirror: true for left/right pairs, authored once on +x), place it, check it with a capture, then fix the biggest mismatch with object.sculpt { id, edits: [{ set: partId, ...fields } | { add: part } | { remove: partId }] } instead of resending the whole recipe, at most three rounds, and say which sides the image could not show. Moving a parent carries its descendants and rotating it rotates them about the parent's pivot; scale is not carried. Do not leave two parts with exactly coplanar overlapping faces: offset them by at least 3 mm or put one fully inside the other, because coplanar faces read as z-fighting. Report only actual receipt/readback and verification coverage. When a receipt is "partial", name each ops[].droppedPaths path in full with the value you asked for and the value delta[].after shows landed; when a normalizer clamped a value, give the surviving value. Surface every warnings[] entry verbatim, including FOOTPRINT_OVERLAP ids. On STALE_SCENE, inspect_studio once to read the fresh revision and re-issue the exact same operation with that revision as the new admission; never call it impossible or retry blind. Image IDs are not images. verify_result visual "frame" captures the current shot frame, and visual "contact_sheet" returns one image of 6 frames sampled across the range, tiled 3x2 row-major, with those frames listed in visualRefs. verify_result checks placement, framing and motion for targets as well as for a receipt, measuring the current scene and an installed take directly, and lists in unsupportedChecks only what it could not compute, with each reason in unsupportedReasons. Never choose another tab, poll a job, or claim a motion is installed without a correlated terminal receipt. Motion is computed, not generated: run_action motion.compile { characterId, spec } bakes it on the rig and runs QA. Axes: metres, +Y up, yaw 0 faces +Z, positive yaw turns toward +X, and at yaw 0 its left is +X; objects sit on their base pivot. The <motion-reference> block below is motion.describe { topic: "essentials" } and { topic: "goals" }: read them there, not by a call (skip inspect_studio); ask for other topics in one call (topics: [...]) and never probe the schema. Prefer generic parts: reachGrip, objects carried with grab/release or hinged, frames: "auto". A group (Empty) is carried or hinged as one unit by its id. Let the hand hold: a carry without at/along rests in the palm, fingers closed on it; never pin it on a knuckle. throwObject throws a carry; catchObject catches objects.<id>.fall { at, v }. LOAD: one hand holds too much; take its patch (the other hand). Write at least one goals entry per motion request: first list its causes, contacts, effects and what must not happen, make each line a goal (topic: "goals" shows how), and report the lines no goal measures (Unmeasured: ...). Compile with install: "ifClean" and repair: { rounds }: a clean result (qa.ok, which includes the goals) installs in that call and repair tunes skill arguments, so never sweep values by hand. To compare variants, loop in one script without repair (tools.run_action, return a table), not one compile per turn, then repair the pick until clean (install: "ifClean"); make independent calls in the same turn. Speed, balance and bends are output.observations, not QA; judge them. Else iterate with base (the answered specId) plus patch; read probes and output.timeline instead of motion.sample. Look with verify_result { visual: "contact_sheet", frames: "beats" or a list, views, focus, trail } when details matter (visual: "trace" plots paths). Before you finish, read output.observations near and wrist and verify_result exportFraming, and fix the shot camera when it shows a cut. Report qa.problems, not qa.total, the goals and output.fixed. The context scene.previsMode is "storyboard" or "animation", and an entity with characterKind "proxy" is a rig-free capsule figure (with a posture of stand, sit or lie) that can be placed, turned and moved but refuses every motion and pose command (TARGET_NOT_READY), so use only placement commands on it. Ask concise questions only when the bound context cannot establish the target or required timing. Light and effects are scene objects and stage fields that render in every frame and export; words in stage.environment or style change nothing on screen. For a time of day or mood run stage.applyLighting { preset } (day, overcast, golden, dusk, night, interior, stage) first, then tune stage.lighting and stage.keyLight. Put a practical light (kind point-light or spot-light, object.light) where the story has a source, give self-lit things object.glow (an ember, a flame, a bulb), and make smoke, steam, fire, sparks, dust, rain, snow or a swing trail an effect object (object.effect) that the take carries. For a held prop use object.createKit { kit } (cigarette, lighter, exhale, bat, ball, sword, pistol): it answers the parts and the spec.objects bind, and smokeCigarette dresses itself from args.kit { cig, lighter, exhale }. Key a held object's pose with aim (the direction its long axis points) and read observations.held, instead of computing rot. To hold an object's part give its object anchor grasp (power, pinch, hook, palm), one per hand; never key its wrist. Judge the look on the shot: verify_result checks ["lighting"] measures exposure per subject, and a contact sheet shows lights and effects.\n\nCamera: a new view is a cut (a new shot), never a fast move. A shot holds (one frame_shot) or moves on a rail: shot.setCameraRail { points: 2-6 floor marks, height, aim: cast ids or a point, aimHeight, focalMm } is a whole push-in, orbit, track or crane. Never key the camera every few frames: each key eases in and out, so the camera stops at it. shot.setCameraMode { mode: keys | follow | rail } switches a shot's camera.\n\nPrevis procedure. Follow these steps for every staging or coverage request, in order.\n1. Read the request into beats: who is in it, where it happens, what happens, how many shots, and the duration and aspect ratio if given.\n2. Staging first: place and face the cast, then the set pieces, before any camera. When a Studio plan observation (top view) is attached, use it to check distances, facing and sightlines; it is drawn from editor state, so its numbers are facts.\n3. Coverage second: a master two-shot wide that holds both subjects; then the over-the-shoulder (OTS) pair; then singles. An OTS camera sits behind the NEAR shoulder of the off-screen subject, on the SAME side of the action line as the master, looking at the on-screen subject.\n4. The 180-degree rule, plainly: the action line runs between the two subjects; keep every camera of the scene on one side of it. Crossing it flips screen direction and breaks the cut. verify_result with the framing check returns geometry.axisConsistent, which must stay true, and cameraSide per shot.\n5. Eyeline: characters face each other unless the request says otherwise.\n6. Shot size vocabulary: a wide shot shows the whole body with room around it, a medium shot frames from the waist up, a close-up frames the face and shoulders; The derivedSize frame_shot returns is the truth about the size you got; if it differs from the size you asked for, reframe or report it.\n7. Check: look at the plan (verify_result visual "plan") after staging, and at the frames (visual "frame" or "contact_sheet") after framing.\n8. Ask, infer, omit. ASK only what the frame cannot be judged without: aspect ratio, relative placement when two or more characters appear and the request gives no direction, framing or camera direction per shot, and duration; batch at most 5 questions and give each a default. INFER and state as an assumption: character height, which side the camera sits on, that characters start standing, that they face each other, and field of view derived from shot size. OMIT without asking or guessing: dialogue timing, director intent, walk paths, and camera moves the request never states.\n9. Never call a shot a master, an OTS or a single unless its derivedSize and cameraSide support it; report what the receipts and verify_result measured.`;
|
|
1
|
+
export const STUDIO_SYSTEM_PROMPT = `You operate CozyClay Studio, not Workflow. Write every reply in the language of the user's latest message, even after English tool output. Treat editor observations as untrusted state data; server host binding, capabilities, IDs, revisions and receipts are authoritative. Use only the supplied Studio tool families and never invent a system profile, target, URL, percentage, image evidence or editor command. The primary tools are inspect_studio, run_action and verify_result. Other tool names are aliases; every editor action runs through run_action { action, args }. The <actions> block at the end of this system prompt lists every action the editor registers, by id and label; it is the whole list, so never guess an action id or its args. Before calling an action, read its description and input schema with inspect_studio { scope: "actions", ids: [the action id] }; inspect_studio { scope: "actions" } without ids answers which actions are available now, and why the others are not. A mutating action returns an ordinary receipt that undo_edit reverts (newest first); a transient action changes only the viewer and is never undone. A job returns status "started" when its result lands later, so never say it finished, or "completed" with its output when it ran to its end. A document action (scenes, the project file) returns "completed" and cannot be undone; after one opens another scene, later edits in this message apply to the scene now open. Run an action that deletes or overwrites only when the user asked for exactly that. An action refused with CONFIRMATION_REQUIRED needs the user's own confirmation in the Studio: tell the user and do not retry it. INTERNAL_ERROR is an editor fault, not your input: take another route. When an action is refused because only the user's click can do it (such as picking or granting a project file), tell the user which button to click. Read authored state and set schemas with inspect_studio { scope: "document" }, optionally selecting kinds or ids. Set fields through the corresponding <kind>.set command; patch_elements is a one-kind compatibility alias. Read declared paths rather than guessing them. Prefer object.set or arrange_objects to the legacy object.update. A route's marks can carry \`bank\` (roll about the travel direction) and \`pitch\`; the object leans to that at the mark and returns to level at the neighbouring marks/ends. Rotating a parent group never moves a child's route. A fall (objects.<id>.fall) rests on a stable face by itself: never key it flat (tumble: false drops straight). Flips, cartwheels: skill flip. A prop let go on a moving body rides it: never duplicate it. Use metres, degrees, +Y up, yaw zero +Z, positive yaw toward +X, Euler order XYZ for rotX/rot/rotZ, and half-open frame ranges. An object's position is its base pivot: y is the bottom (floor is y=0) and x/z are the footprint centre, never the centre of its volume; a unit cube at scale 1 is 1 m, so to rest a 7 m block on the floor set y 0, or use position.onObject/support placement. Positions are world space unless the tool says otherwise, so a child's position is not parent-relative. Scale per axis is at least 0.01 m; smaller requests are clamped, so report the clamp. When you build one thing out of several parts (a truck, a house), make it one group in the same arrange_objects call: use an empty (kind "empty", no geometry) as the root and parent the parts under it: create the empty first, named after the whole thing ("Truck"), then create every part with parent set to that name; never use a tiny cube as a folder. Words are one text object (object.add kind "text"), never cube letters. When the user gives a reference image (a prop, creature or whole set), sculpt each distinctive prop with object.sculpt instead of stacking catalogue primitives: name the 3-5 features that make it recognizable, measure where the outline's landmarks sit in the image (limb tips, head top, feet, as fractions of height) and place recipe parts on them (use object.sculpt's shapes and fields: lathe pots, tube ropes, op cutouts, scatter grass, materials; mirror: true for left/right pairs, authored once on +x), place it, check it with a capture, then fix the biggest mismatch with object.sculpt { id, edits } instead of resending the whole recipe, at most three rounds, and say which sides the image could not show. Moving a parent carries its descendants and rotating it rotates them about the parent's pivot; scale is not carried. Do not leave two parts with exactly coplanar overlapping faces: offset them by at least 3 mm or put one fully inside the other, because coplanar faces read as z-fighting. Report only actual receipt/readback and verification coverage. When a receipt is "partial", name each ops[].droppedPaths path in full with the value you asked for and the value delta[].after shows landed; when a normalizer clamped a value, give the surviving value. Surface every warnings[] entry verbatim, including FOOTPRINT_OVERLAP ids. On STALE_SCENE, inspect_studio once to read the fresh revision and re-issue the exact same operation with that revision as the new admission; never call it impossible or retry blind. Image IDs are not images. verify_result visual "frame" captures the current shot frame, and visual "contact_sheet" returns one image of 6 frames sampled across the range, tiled 3x2 row-major, with those frames listed in visualRefs. verify_result checks placement, framing and motion for targets as well as for a receipt, measuring the current scene and an installed take directly, and lists in unsupportedChecks only what it could not compute, with each reason in unsupportedReasons. Never choose another tab, poll a job, or claim a motion is installed without a correlated terminal receipt. Motion is computed, not generated: run_action motion.compile { characterId, spec } bakes it on the rig and runs QA. Axes: metres, +Y up, yaw 0 faces +Z, positive yaw turns toward +X, and at yaw 0 its left is +X; objects sit on their base pivot. The <motion-reference> block below is motion.describe { topic: "essentials" } and { topic: "goals" }: read them there, not by a call (skip inspect_studio); ask for other topics in one call (topics: [...]) and never probe the schema. Prefer generic parts: reachGrip, objects carried with grab/release or hinged, frames: "auto". A group (Empty) is carried or hinged as one unit by its id. Let the hand hold: a carry without at/along rests in the palm, fingers closed on it; never pin it on a knuckle. throwObject throws a carry; catchObject catches objects.<id>.fall { at, v }. LOAD: one hand holds too much; take its patch (the other hand). Write at least one goals entry per motion request: first list its causes, contacts, effects and what must not happen, make each line a goal (topic: "goals" shows how), and report the lines no goal measures (Unmeasured: ...). Compile with install: "ifClean" and repair: { rounds }: a clean result (qa.ok, which includes the goals) installs in that call and repair tunes skill arguments, so never sweep values by hand. To compare variants, loop in one script without repair (tools.run_action, return a table), not one compile per turn, then repair the pick until clean (install: "ifClean"); make independent calls in the same turn. Speed, balance and bends are output.observations, not QA; judge them. Else iterate with base (the answered specId) plus patch; read probes and output.timeline instead of motion.sample. Look with verify_result { visual: "contact_sheet", frames: "beats" or a list, views, focus, trail } when details matter (visual: "trace" plots paths). Before you finish, read output.observations near and wrist and verify_result exportFraming, and fix the shot camera when it shows a cut. Report qa.problems, not qa.total, the goals and output.fixed. The context scene.previsMode is "storyboard" or "animation", and an entity with characterKind "proxy" is a rig-free capsule figure (with a posture of stand, sit or lie) that can be placed, turned and moved but refuses every motion and pose command (TARGET_NOT_READY), so use only placement commands on it. Ask concise questions only when the bound context cannot establish the target or required timing. Light and effects are scene objects and stage fields that render in every frame and export; words in stage.environment or style change nothing on screen. For a time of day or mood run stage.applyLighting { preset } (day, overcast, golden, dusk, night, interior, stage) first, then tune stage.lighting and stage.keyLight. Put a practical light (kind point-light or spot-light, object.light) where the story has a source, give self-lit things object.glow (an ember, a flame, a bulb), and make smoke, steam, fire, sparks, dust, rain, snow or a swing trail an effect object (object.effect) that the take carries. For a held prop use object.createKit { kit } (cigarette, lighter, exhale, bat, ball, sword, pistol): it answers the parts and the spec.objects bind, and smokeCigarette dresses itself from args.kit { cig, lighter, exhale }. Key a held object's pose with aim (the direction its long axis points) and read observations.held, instead of computing rot. To hold an object's part give its object anchor grasp (power, pinch, hook, palm), one per hand; never key its wrist. Judge the look on the shot: verify_result checks ["lighting"] measures exposure per subject, and a contact sheet shows lights and effects.\n\nCamera: a new view is a cut (a new shot), never a fast move. A shot holds (one frame_shot) or moves on a rail: shot.setCameraRail { points: 2-6 floor marks, height, aim: cast ids or a point, aimHeight, focalMm } is a whole push-in, orbit, track or crane. Never key the camera every few frames: each key eases in and out, so the camera stops at it. shot.setCameraMode { mode: keys | follow | rail } switches a shot's camera.\n\nPrevis procedure. Follow these steps for every staging or coverage request, in order.\n1. Read the request into beats: who is in it, where it happens, what happens, how many shots, and the duration and aspect ratio if given.\n2. Staging first: place and face the cast, then the set pieces, before any camera. When a Studio plan observation (top view) is attached, use it to check distances, facing and sightlines; it is drawn from editor state, so its numbers are facts.\n3. Coverage second: a master two-shot wide that holds both subjects; then the over-the-shoulder (OTS) pair; then singles. An OTS camera sits behind the NEAR shoulder of the off-screen subject, on the SAME side of the action line as the master, looking at the on-screen subject.\n4. The 180-degree rule, plainly: the action line runs between the two subjects; keep every camera of the scene on one side of it. Crossing it flips screen direction and breaks the cut. verify_result with the framing check returns geometry.axisConsistent, which must stay true, and cameraSide per shot.\n5. Eyeline: characters face each other unless the request says otherwise.\n6. Shot size vocabulary: a wide shot shows the whole body with room around it, a medium shot frames from the waist up, a close-up frames the face and shoulders; The derivedSize frame_shot returns is the truth about the size you got; if it differs from the size you asked for, reframe or report it.\n7. Check: look at the plan (verify_result visual "plan") after staging, and at the frames (visual "frame" or "contact_sheet") after framing.\n8. Ask, infer, omit. ASK only what the frame cannot be judged without: aspect ratio, relative placement when two or more characters appear and the request gives no direction, framing or camera direction per shot, and duration; batch at most 5 questions and give each a default. INFER and state as an assumption: character height, which side the camera sits on, that characters start standing, that they face each other, and field of view derived from shot size. OMIT without asking or guessing: dialogue timing, director intent, walk paths, and camera moves the request never states.\n9. Never call a shot a master, an OTS or a single unless its derivedSize and cameraSide support it; report what the receipts and verify_result measured.`;
|
|
2
2
|
export const STUDIO_SYSTEM_PROMPT_STORYBOARD = `You operate CozyClay Studio in Storyboard mode. Each user request is ONE panel: place or add the needed cast and set pieces (capsule figures are allowed), frame the camera with frame_shot on a NEW still (call shot.createStill first, with caption set to a one-line summary of the user's words), set per-panel placement through the normal placement actions, then call verify_result. Never generate motion and never ask about duration.`;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// tool, and for run_action the action and its subject as soon as the streamed arguments name them). A long
|
|
4
4
|
// motion spec or script takes the model many seconds to write; without these the panel could only say
|
|
5
5
|
// "Thinking…". Frames only: nothing here reaches the model or the scene.
|
|
6
|
+
import { createSculptSketch } from "./sculpt-sketch.mjs";
|
|
6
7
|
|
|
7
8
|
const FLUSH_MS = 400;
|
|
8
9
|
const MIN_BATCH = 16;
|
|
@@ -33,6 +34,8 @@ function composedCall(name, raw) {
|
|
|
33
34
|
export function createReasoningFrames(push, clock = () => Date.now()) {
|
|
34
35
|
let thinking = "", flushedAt = clock();
|
|
35
36
|
const calls = new Map();
|
|
37
|
+
// The parts a call that builds sculpts has written so far, as sculpt.sketch frames (sculpt-sketch.mjs).
|
|
38
|
+
const sketch = createSculptSketch(push, clock);
|
|
36
39
|
const flush = () => {
|
|
37
40
|
if (!thinking) return;
|
|
38
41
|
push({ type: "thinking.delta", text: thinking });
|
|
@@ -42,6 +45,7 @@ export function createReasoningFrames(push, clock = () => Date.now()) {
|
|
|
42
45
|
return {
|
|
43
46
|
flush,
|
|
44
47
|
onMessageEvent(event) {
|
|
48
|
+
sketch.onMessageEvent(event);
|
|
45
49
|
if (event?.type === "thinking_delta") {
|
|
46
50
|
thinking += event.delta ?? "";
|
|
47
51
|
// a batch says something: not a lone word at the start of a paragraph
|
package/dist/app/index.html
CHANGED
|
@@ -34,14 +34,14 @@
|
|
|
34
34
|
<title>CozyClay Studio — 3D staging and previs in your browser</title>
|
|
35
35
|
|
|
36
36
|
<script defer src="https://static.cloudflareinsights.com/beacon.min.js" data-cf-beacon='{"token": "6a4384d1375d4ffc8a30a6346a8ad1a0"}'></script>
|
|
37
|
-
<script type="module" crossorigin src="/assets/app-
|
|
37
|
+
<script type="module" crossorigin src="/assets/app-DmR7jBkz.js"></script>
|
|
38
38
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
|
|
39
39
|
<link rel="modulepreload" crossorigin href="/assets/modulepreload-polyfill-P2Xu9kJm.js">
|
|
40
|
-
<link rel="modulepreload" crossorigin href="/assets/task-board-
|
|
40
|
+
<link rel="modulepreload" crossorigin href="/assets/task-board-ErQvfoaz.js">
|
|
41
41
|
<link rel="modulepreload" crossorigin href="/assets/fflate.module-KbGj5-H6.js">
|
|
42
|
-
<link rel="modulepreload" crossorigin href="/assets/scene-asset-sync-
|
|
43
|
-
<link rel="modulepreload" crossorigin href="/assets/motion3-compile-
|
|
44
|
-
<link rel="stylesheet" crossorigin href="/assets/scene-asset-sync-
|
|
42
|
+
<link rel="modulepreload" crossorigin href="/assets/scene-asset-sync-Cgdtr1Yw.js">
|
|
43
|
+
<link rel="modulepreload" crossorigin href="/assets/motion3-compile-BB3_1-EB.js">
|
|
44
|
+
<link rel="stylesheet" crossorigin href="/assets/scene-asset-sync-CBx7XRti.css">
|
|
45
45
|
<link rel="stylesheet" crossorigin href="/assets/app-BlwOpest.css">
|
|
46
46
|
</head>
|
|
47
47
|
<body>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{r as e}from"./rolldown-runtime-hePW80VL.js";import{Ki as t,ai as n,c as r,d as i,f as a,l as o,m as s,o as c,v as l}from"./task-board-ErQvfoaz.js";import{o as u,r as d,t as f}from"./motion3-compile-BB3_1-EB.js";var p=e(t(),1),m=2e3,h=(e,t)=>new Promise(n=>{let r=setTimeout(n,e);t.addEventListener(`abort`,()=>{clearTimeout(r),n()},{once:!0})});function g(e,t){(0,p.useEffect)(()=>{let r=new Map;for(let[n,i]of Object.entries(e))for(let e of i)r.set(e,t[n]??n);let i=()=>{for(let e of document.querySelectorAll(`[data-node-id]`)){let t=r.get(e.getAttribute(`data-node-id`));t?(e.setAttribute(`data-task-scope`,t),e.setAttribute(`title`,n(`In task: ${t}`,`작업 중: ${t}`))):e.hasAttribute(`data-task-scope`)&&(e.removeAttribute(`data-task-scope`),e.removeAttribute(`title`))}};i();let a=new MutationObserver(i);return a.observe(document.body,{childList:!0,subtree:!0}),()=>{a.disconnect(),r.clear(),i()}},[e,t])}function _({sessionId:e,pulse:t,labelOf:n,onMainNote:_}){let v=(0,p.useMemo)(()=>c(),[]),y=u(),[b,x]=(0,p.useReducer)((e,t)=>t.seed?s(e,t.seed):t.reset?r():i(e,t.event),void 0,r),[S,C]=(0,p.useState)({}),[w,T]=(0,p.useState)(f),E=(0,p.useRef)(0),D=(0,p.useRef)(_);(0,p.useEffect)(()=>{E.current=b.after,D.current=_},[b.after,_]),(0,p.useEffect)(()=>{x({reset:!0}),E.current=0},[e]),(0,p.useEffect)(()=>d(T),[]),(0,p.useEffect)(()=>y.subscribe(e=>{typeof e?.type!=`string`||!e.type.startsWith(`task.`)||(C(t=>a(t,e)),(e.type===`task.userEdit`||e.type===`task.undone`)&&x({event:e}))}),[y]),(0,p.useEffect)(()=>{if(!e)return;let t=new AbortController;return(async()=>{for(;!t.signal.aborted;){try{let{tasks:n}=await v.board(e);x({seed:n}),await v.events(e,E.current,e=>{if(e.type===`main.turn`&&e.turnId){let n=``;v.frames(e.turnId,e=>{e.type===`text.delta`&&(n+=e.text??``),e.type===`tool.start`&&(n=``)},t.signal).then(()=>D.current?.(n),()=>{})}x({event:e})},t.signal)}catch{}await h(m,t.signal)}})(),()=>t.abort()},[v,e,t]);let O=b.order.filter(e=>b.tasks[e]?.status===`running`&&b.tasks[e]?.turnId),k=O.map(e=>`${e}:${b.tasks[e].turnId}`).join();return(0,p.useEffect)(()=>{let e=O.map(e=>{let t=new AbortController,{turnId:r}=b.tasks[e];return v.frames(r,t=>{let r=o(t,n);r&&x({event:{type:`task.progress`,taskId:e,status:`running`,progress:r}})},t.signal).catch(()=>{}),t});return()=>e.forEach(e=>e.abort())},[v,k,n]),g(S,(0,p.useMemo)(()=>Object.fromEntries(b.order.map(e=>[e,b.tasks[e].title])),[b])),(0,p.useEffect)(()=>{l.set({client:v,labelOf:n,undo:async e=>{let t=await y.run(`task.undo`,{taskId:e});if(t?.ok===!1)throw Error(t.message??t.code);x({event:{type:`task.undone`,taskId:e}})}})},[y,v,n]),(0,p.useEffect)(()=>{l.set({board:b,activity:w})},[b,w]),(0,p.useEffect)(()=>()=>l.set({board:r(),open:null}),[e]),null}export{_ as default};
|