@compilr-dev/sdk 0.29.8 → 0.31.0

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.
@@ -0,0 +1,277 @@
1
+ /**
2
+ * 3D scene contract — the `scene` canvas type (3d-canvas-spec §2).
3
+ *
4
+ * A scene is VALIDATED JSON DATA, never code: a list of primitives in metres. Agents write
5
+ * it through the scene tools; the host draws it (three.js in Desktop's renderer). This module
6
+ * is the contract both sides bind to — types, limits, validation, normalisation and the few
7
+ * pieces of geometry (lift, bounds, fit) that must be ONE function everywhere.
8
+ *
9
+ * ⚠️ Renderer-safe on purpose: no imports at all. Desktop's renderer imports this through
10
+ * `@compilr-dev/sdk/canvas`; one node import here breaks its bundle (see
11
+ * `tests/canvas-subpath.test.ts`).
12
+ */
13
+ export declare const SCENE_VERSION = 1;
14
+ export type Vec3 = [number, number, number];
15
+ export type Vec2 = [number, number];
16
+ export type SceneObjectType = 'box' | 'sphere' | 'cylinder' | 'cone' | 'torus' | 'extrude' | 'wedge' | 'group';
17
+ export declare const SCENE_OBJECT_TYPES: readonly SceneObjectType[];
18
+ /** The direction an extrude extends along (§17). */
19
+ export type ExtrudeAxis = 'x' | 'y' | 'z';
20
+ export declare const SCENE_EXTRUDE_AXES: readonly ExtrudeAxis[];
21
+ export interface SceneMaterial {
22
+ /** 0..1, default 0.78 */
23
+ roughness?: number;
24
+ /** 0..1, default 0.02 */
25
+ metalness?: number;
26
+ /** 0.05..1, default 1 (transparent when < 1) */
27
+ opacity?: number;
28
+ }
29
+ export interface SceneObjectBase {
30
+ /** Unique in the scene; /^[a-z0-9][a-z0-9_-]{0,47}$/ */
31
+ id: string;
32
+ /** Display name, ≤ 80 chars; the UI title-cases the id when absent. */
33
+ name?: string;
34
+ type: SceneObjectType;
35
+ /** Metres. y is the BOTTOM of the object: y = 0 sits on the floor (in the parent's frame). */
36
+ position: Vec3;
37
+ /** Degrees, Euler XYZ (three.js default order). Default [0, 0, 0]. */
38
+ rotation?: Vec3;
39
+ /** '#RRGGBB'. Filled by the writer from the neutral palette when absent. */
40
+ color?: string;
41
+ /**
42
+ * Parent object id — position/rotation are relative to the parent's frame. A `group` is the
43
+ * intended container, but ANY object may be a parent (P1 scenes parent to shapes; kept for
44
+ * backward compatibility). No cycles, depth ≤ SCENE_MAX_GROUP_DEPTH.
45
+ */
46
+ group?: string;
47
+ material?: SceneMaterial;
48
+ /** User-only lock: the inspector cannot move/remove it; agents still can (and are warned). */
49
+ locked?: boolean;
50
+ }
51
+ export interface BoxObject extends SceneObjectBase {
52
+ type: 'box';
53
+ /** w, h, d */
54
+ size: Vec3;
55
+ }
56
+ export interface SphereObject extends SceneObjectBase {
57
+ type: 'sphere';
58
+ radius: number;
59
+ }
60
+ /**
61
+ * `segments` (integer 3–64, optional): a faceted prism instead of a smooth round one. Omitted =
62
+ * smooth. Vertex k sits at angle θ = 2πk/segments with x = radius·sin θ, z = radius·cos θ
63
+ * (three.js CylinderGeometry/ConeGeometry, thetaStart 0) — so vertex 0 is on +z, and `radius`
64
+ * is the CIRCUMradius. A 4-segment cone is a pyramid with corners on the ±x/±z axes (rotate 45°
65
+ * about Y for sides parallel to the axes; its base side is radius·√2).
66
+ */
67
+ export interface CylinderObject extends SceneObjectBase {
68
+ type: 'cylinder';
69
+ radius: number;
70
+ height: number;
71
+ segments?: number;
72
+ }
73
+ export interface ConeObject extends SceneObjectBase {
74
+ type: 'cone';
75
+ radius: number;
76
+ height: number;
77
+ segments?: number;
78
+ }
79
+ /** Lies flat (ring in the x–z plane). radius = ring radius, tube = tube radius. */
80
+ export interface TorusObject extends SceneObjectBase {
81
+ type: 'torus';
82
+ radius: number;
83
+ tube: number;
84
+ }
85
+ /**
86
+ * An outline extruded by `height` along `axis` (default 'y'). Points are local to `position`;
87
+ * which world axes a point [a, b] maps to depends on the axis:
88
+ *
89
+ * | axis | point [a, b] is | the outline lies in | extends along |
90
+ * |------|-----------------|---------------------|----------------|
91
+ * | 'y' | [x, z] (plan) | the floor (x–z) | +y, 0 → height |
92
+ * | 'z' | [x, y] (side) | the x–y plane | +z, 0 → height |
93
+ * | 'x' | [z, y] (side) | the z–y plane | +x, 0 → height |
94
+ *
95
+ * For 'x'/'z' the SECOND coordinate is up (y) and the first is the horizontal one — a side
96
+ * profile (a gable, a roof section) pushed sideways. y-bottom rule: the mesh is lifted by
97
+ * −min(b) for 'x'/'z' (0 for 'y'), so the lowest point of the outline sits at `position.y`.
98
+ * The rotation pivot is the outline's [0, 0] at the start of the extrusion (lifted likewise).
99
+ */
100
+ export interface ExtrudeObject extends SceneObjectBase {
101
+ type: 'extrude';
102
+ points: Vec2[];
103
+ height: number;
104
+ axis?: ExtrudeAxis;
105
+ }
106
+ /**
107
+ * A triangular prism (roofs): `size` [w, h, d]. The triangle is in the x–y plane and the ridge
108
+ * runs along z. Mesh-local (centred like a box, lift h/2): base corners (−w/2, −h/2) and
109
+ * (w/2, −h/2), apex (−w/2 + ridge·w, h/2); the prism spans z ∈ [−d/2, d/2]. `ridge` 0–1,
110
+ * default 0.5 (centred apex; 0 or 1 = a lean-to with a vertical side).
111
+ */
112
+ export interface WedgeObject extends SceneObjectBase {
113
+ type: 'wedge';
114
+ size: Vec3;
115
+ ridge?: number;
116
+ }
117
+ /**
118
+ * A transform node — no geometry, no size, no colour/material (rejected: colour its children).
119
+ * Children (`group: "<this id>"`) are positioned in its frame: its `position` is their origin
120
+ * (a group has no height, so its lift is 0 and child y = 0 is the group's y) and its rotation
121
+ * pivots there. Its bounds are the union of its descendants' (none while it is empty).
122
+ */
123
+ export interface GroupObject extends SceneObjectBase {
124
+ type: 'group';
125
+ color?: never;
126
+ material?: never;
127
+ }
128
+ export type SceneObject = BoxObject | SphereObject | CylinderObject | ConeObject | TorusObject | ExtrudeObject | WedgeObject | GroupObject;
129
+ export interface SceneCamera {
130
+ position: Vec3;
131
+ target: Vec3;
132
+ }
133
+ export type SceneLight = {
134
+ type: 'sun';
135
+ position: Vec3;
136
+ intensity?: number;
137
+ castShadow?: boolean;
138
+ } | {
139
+ type: 'ambient';
140
+ intensity?: number;
141
+ };
142
+ export interface SceneFile {
143
+ version: 1;
144
+ name: string;
145
+ /** Only metres in v1. */
146
+ units: 'm';
147
+ /** Missing → the viewer fits the camera to the objects. */
148
+ camera?: SceneCamera;
149
+ /** Missing → the default studio light. */
150
+ lights?: SceneLight[];
151
+ objects: SceneObject[];
152
+ }
153
+ export interface SceneBounds {
154
+ min: Vec3;
155
+ max: Vec3;
156
+ }
157
+ export declare const SCENE_MAX_OBJECTS = 500;
158
+ export declare const SCENE_MAX_BYTES: number;
159
+ /** Max objects in one scene_add_object batch (Q-1). */
160
+ export declare const SCENE_MAX_BATCH = 50;
161
+ export declare const SCENE_MAX_COORD = 1000;
162
+ export declare const SCENE_SIZE_MIN = 0.01;
163
+ export declare const SCENE_SIZE_MAX = 500;
164
+ export declare const SCENE_RADIUS_MIN = 0.01;
165
+ export declare const SCENE_RADIUS_MAX = 250;
166
+ export declare const SCENE_TUBE_MIN = 0.005;
167
+ /** `segments` on cone/cylinder: integer range (§17). */
168
+ export declare const SCENE_SEGMENTS_MIN = 3;
169
+ export declare const SCENE_SEGMENTS_MAX = 64;
170
+ /** wedge `ridge` default: apex centred across the width. */
171
+ export declare const SCENE_WEDGE_RIDGE_DEFAULT = 0.5;
172
+ export declare const SCENE_POINTS_MIN = 3;
173
+ export declare const SCENE_POINTS_MAX = 256;
174
+ export declare const SCENE_MAX_LIGHTS = 4;
175
+ export declare const SCENE_MAX_SHADOW_LIGHTS = 2;
176
+ export declare const SCENE_LIGHT_INTENSITY_MAX = 10;
177
+ export declare const SCENE_MAX_GROUP_DEPTH = 8;
178
+ export declare const SCENE_NAME_MAX = 80;
179
+ export declare const SCENE_OPACITY_MIN = 0.05;
180
+ export declare const SCENE_ID_PATTERN: RegExp;
181
+ /** Object colours never read as state (README) — a neutral palette, filled by insertion index. */
182
+ export declare const SCENE_NEUTRAL_PALETTE: readonly string[];
183
+ /** Accessible names for the palette swatches, same order (Q-10). */
184
+ export declare const SCENE_PALETTE_NAMES: readonly string[];
185
+ export declare const SCENE_DEFAULT_MATERIAL: Readonly<Required<SceneMaterial>>;
186
+ /** The reference camera, used for an empty scene. */
187
+ export declare const SCENE_DEFAULT_CAMERA: Readonly<SceneCamera>;
188
+ /** Vertical field of view of the viewer camera, degrees. */
189
+ export declare const SCENE_CAMERA_FOV = 38;
190
+ /** A new, empty scene. */
191
+ export declare function emptyScene(name: string): SceneFile;
192
+ /**
193
+ * Defaults for a freshly added shape of each type (the reference's fallbacks), for the
194
+ * inspector's "Add a shape" menu. Position is the origin.
195
+ */
196
+ export declare function defaultObjectFor(type: SceneObjectType, id: string): SceneObject;
197
+ /** The stored form: pretty-printed, because agents read and cite it (§4.1). */
198
+ export declare function serializeScene(scene: SceneFile): string;
199
+ /** `slug(title).scene.json` — the display file name and the JSON export's default name. */
200
+ export declare function sceneFileName(title: string): string;
201
+ /** UTF-8 byte length without TextEncoder (keeps this module free of DOM/node typings). */
202
+ export declare function utf8Bytes(s: string): number;
203
+ export type SceneValidationResult = {
204
+ ok: true;
205
+ scene: SceneFile;
206
+ } | {
207
+ ok: false;
208
+ errors: string[];
209
+ };
210
+ /** Short, stable number rendering for messages and summaries. */
211
+ export declare function fmtNum(n: number): string;
212
+ export declare function fmtVec(v: readonly number[]): string;
213
+ /** Drop a closing duplicate of the first point (§2.3: dropped, not rejected). */
214
+ export declare function openOutline(points: Vec2[]): Vec2[];
215
+ /**
216
+ * Is the (open) outline a simple polygon? O(n²) segment test — ExtrudeGeometry's
217
+ * triangulation silently breaks on self-intersection. Returns the first offending edge pair.
218
+ */
219
+ export declare function findSelfIntersection(points: Vec2[]): [number, number] | null;
220
+ /**
221
+ * Validate an untrusted scene (agent JSON, a stored row, an import). Every error names the
222
+ * field path and what is allowed. On success the returned scene is a deep copy of the input
223
+ * (not yet normalised — see `normalizeScene`).
224
+ */
225
+ export declare function validateScene(input: unknown): SceneValidationResult;
226
+ /** '#abc' / '#aabbcc' → '#AABBCC'. Assumes a valid hex (validated first). */
227
+ export declare function normalizeColor(hex: string): string;
228
+ /** Degrees → (−180, 180]. */
229
+ export declare function normalizeAngle(deg: number): number;
230
+ /**
231
+ * Fill what the writer owns: upper-case #RRGGBB colours, the neutral default colour by
232
+ * insertion index (so the stored file is explicit and agent-readable), rotations wrapped to
233
+ * (−180, 180], closing duplicate points dropped. Never fills camera/lights — their absence
234
+ * means "fit" / "studio light". Pure; returns a new scene.
235
+ */
236
+ export declare function normalizeScene(scene: SceneFile): SceneFile;
237
+ /**
238
+ * Half-height from the object's BOTTOM (its `position.y`) to its geometric centre, where the
239
+ * mesh sits. extrude along y is 0: its geometry already starts at the floor; along x/z it is
240
+ * −min(profile y), so the profile's lowest point sits at position.y. A group has no height: 0.
241
+ * ONE function — the viewer, the fit and the GLB export must agree.
242
+ */
243
+ export declare function liftFor(obj: SceneObject): number;
244
+ /**
245
+ * World matrix of each object's mesh (row-major 4×4), keyed by id, with group parenting
246
+ * applied. A `group` has no mesh and lift 0, so its entry is its frame: T(position) · R,
247
+ * rotating about its position. Assumes a validated scene (no cycles).
248
+ */
249
+ export declare function sceneWorldMatrices(scene: SceneFile): Map<string, number[]>;
250
+ /**
251
+ * World-space AABB of all objects (conservative for rotated shapes). null when nothing has
252
+ * geometry — an empty scene, or one holding only empty groups (the camera then uses the
253
+ * reference default, as for an empty scene).
254
+ */
255
+ export declare function sceneBounds(scene: SceneFile): SceneBounds | null;
256
+ /**
257
+ * World AABB of one object and everything grouped under it — for a `group`, the union of its
258
+ * descendants (null while it holds no geometry); for a shape, its own mesh plus any children.
259
+ * null for an unknown id. (The viewer outlines a selected group's whole subtree with this.)
260
+ */
261
+ export declare function sceneObjectBounds(scene: SceneFile, id: string): SceneBounds | null;
262
+ /** The reference view direction ([7, 5.5, 8], normalised). */
263
+ export declare const SCENE_CAMERA_DIRECTION: Readonly<Vec3>;
264
+ /**
265
+ * The camera used when a scene has none: look at the bounds' centre from the reference
266
+ * direction, at the distance that fits the bounding sphere in the vertical FOV, × 1.2.
267
+ * null bounds (empty scene) → the reference default.
268
+ */
269
+ export declare function fitCamera(bounds: SceneBounds | null, fovDeg?: number): SceneCamera;
270
+ /** The camera to view a scene with: its own, or the fit. */
271
+ export declare function sceneCamera(scene: SceneFile): SceneCamera;
272
+ /** One line: "12 objects (2 groups), bounds 6.2 × 4 × 2.7 m" (W × D × H). */
273
+ export declare function describeScene(scene: SceneFile): string;
274
+ /** The ids of an object's descendants (children, grandchildren, …), in scene order. */
275
+ export declare function descendantsOf(scene: SceneFile, id: string): string[];
276
+ /** Turn a display name (or type) into an id candidate: lowercase slug, ≤ 48 chars. */
277
+ export declare function slugifySceneId(text: string): string;