@scenoco-three/core 0.1.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.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -0
  3. package/dist/components/Follow.d.ts +15 -0
  4. package/dist/components/Follow.js +41 -0
  5. package/dist/components/LookAt.d.ts +11 -0
  6. package/dist/components/LookAt.js +30 -0
  7. package/dist/components/Oscillator.d.ts +13 -0
  8. package/dist/components/Oscillator.js +42 -0
  9. package/dist/components/Rotator.d.ts +5 -0
  10. package/dist/components/Rotator.js +21 -0
  11. package/dist/core/Component.d.ts +60 -0
  12. package/dist/core/Component.js +63 -0
  13. package/dist/core/Engine.d.ts +169 -0
  14. package/dist/core/Engine.js +468 -0
  15. package/dist/core/NodeRegistry.d.ts +99 -0
  16. package/dist/core/NodeRegistry.js +87 -0
  17. package/dist/core/Registry.d.ts +55 -0
  18. package/dist/core/Registry.js +28 -0
  19. package/dist/core/System.d.ts +33 -0
  20. package/dist/core/System.js +23 -0
  21. package/dist/core/decorators.d.ts +50 -0
  22. package/dist/core/decorators.js +142 -0
  23. package/dist/dsl/PrefabRef.d.ts +36 -0
  24. package/dist/dsl/PrefabRef.js +33 -0
  25. package/dist/dsl/bundle-format.d.ts +74 -0
  26. package/dist/dsl/bundle-format.js +41 -0
  27. package/dist/dsl/bundle-shared.d.ts +18 -0
  28. package/dist/dsl/bundle-shared.js +33 -0
  29. package/dist/dsl/compiled.d.ts +64 -0
  30. package/dist/dsl/compiled.js +1 -0
  31. package/dist/dsl/deserialize.d.ts +9 -0
  32. package/dist/dsl/deserialize.js +125 -0
  33. package/dist/dsl/instantiate.d.ts +40 -0
  34. package/dist/dsl/instantiate.js +111 -0
  35. package/dist/dsl/nodes.d.ts +1 -0
  36. package/dist/dsl/nodes.js +10 -0
  37. package/dist/dsl/types.d.ts +45 -0
  38. package/dist/dsl/types.js +120 -0
  39. package/dist/index.d.ts +13 -0
  40. package/dist/index.js +17 -0
  41. package/dist/internal.d.ts +7 -0
  42. package/dist/internal.js +7 -0
  43. package/dist/nodes/base.d.ts +31 -0
  44. package/dist/nodes/base.js +27 -0
  45. package/dist/nodes/geometry/BoxGeometry.d.ts +13 -0
  46. package/dist/nodes/geometry/BoxGeometry.js +44 -0
  47. package/dist/nodes/geometry/CylinderGeometry.d.ts +11 -0
  48. package/dist/nodes/geometry/CylinderGeometry.js +36 -0
  49. package/dist/nodes/geometry/PlaneGeometry.d.ts +11 -0
  50. package/dist/nodes/geometry/PlaneGeometry.js +36 -0
  51. package/dist/nodes/geometry/SphereGeometry.d.ts +10 -0
  52. package/dist/nodes/geometry/SphereGeometry.js +32 -0
  53. package/dist/nodes/geometry/TorusGeometry.d.ts +11 -0
  54. package/dist/nodes/geometry/TorusGeometry.js +36 -0
  55. package/dist/nodes/index.d.ts +36 -0
  56. package/dist/nodes/index.js +46 -0
  57. package/dist/nodes/manifest.d.ts +18 -0
  58. package/dist/nodes/manifest.js +64 -0
  59. package/dist/nodes/material/LineBasicMaterial.d.ts +11 -0
  60. package/dist/nodes/material/LineBasicMaterial.js +41 -0
  61. package/dist/nodes/material/MeshBasicMaterial.d.ts +11 -0
  62. package/dist/nodes/material/MeshBasicMaterial.js +41 -0
  63. package/dist/nodes/material/MeshStandardMaterial.d.ts +14 -0
  64. package/dist/nodes/material/MeshStandardMaterial.js +56 -0
  65. package/dist/nodes/material/PointsMaterial.d.ts +12 -0
  66. package/dist/nodes/material/PointsMaterial.js +46 -0
  67. package/dist/nodes/material/SpriteMaterial.d.ts +11 -0
  68. package/dist/nodes/material/SpriteMaterial.js +41 -0
  69. package/dist/nodes/object3d/AmbientLight.d.ts +7 -0
  70. package/dist/nodes/object3d/AmbientLight.js +26 -0
  71. package/dist/nodes/object3d/ArrayCamera.d.ts +5 -0
  72. package/dist/nodes/object3d/ArrayCamera.js +20 -0
  73. package/dist/nodes/object3d/BatchedMesh.d.ts +7 -0
  74. package/dist/nodes/object3d/BatchedMesh.js +31 -0
  75. package/dist/nodes/object3d/Bone.d.ts +5 -0
  76. package/dist/nodes/object3d/Bone.js +18 -0
  77. package/dist/nodes/object3d/DirectionalLight.d.ts +7 -0
  78. package/dist/nodes/object3d/DirectionalLight.js +29 -0
  79. package/dist/nodes/object3d/Group.d.ts +5 -0
  80. package/dist/nodes/object3d/Group.js +18 -0
  81. package/dist/nodes/object3d/HemisphereLight.d.ts +8 -0
  82. package/dist/nodes/object3d/HemisphereLight.js +30 -0
  83. package/dist/nodes/object3d/InstancedMesh.d.ts +6 -0
  84. package/dist/nodes/object3d/InstancedMesh.js +28 -0
  85. package/dist/nodes/object3d/LOD.d.ts +5 -0
  86. package/dist/nodes/object3d/LOD.js +20 -0
  87. package/dist/nodes/object3d/Line.d.ts +5 -0
  88. package/dist/nodes/object3d/Line.js +21 -0
  89. package/dist/nodes/object3d/LineLoop.d.ts +5 -0
  90. package/dist/nodes/object3d/LineLoop.js +21 -0
  91. package/dist/nodes/object3d/LineSegments.d.ts +5 -0
  92. package/dist/nodes/object3d/LineSegments.js +21 -0
  93. package/dist/nodes/object3d/Mesh.d.ts +5 -0
  94. package/dist/nodes/object3d/Mesh.js +21 -0
  95. package/dist/nodes/object3d/Object3D.d.ts +5 -0
  96. package/dist/nodes/object3d/Object3D.js +18 -0
  97. package/dist/nodes/object3d/OrthographicCamera.d.ts +11 -0
  98. package/dist/nodes/object3d/OrthographicCamera.js +42 -0
  99. package/dist/nodes/object3d/PerspectiveCamera.d.ts +8 -0
  100. package/dist/nodes/object3d/PerspectiveCamera.js +31 -0
  101. package/dist/nodes/object3d/PointLight.d.ts +9 -0
  102. package/dist/nodes/object3d/PointLight.js +34 -0
  103. package/dist/nodes/object3d/Points.d.ts +5 -0
  104. package/dist/nodes/object3d/Points.js +21 -0
  105. package/dist/nodes/object3d/RectAreaLight.d.ts +9 -0
  106. package/dist/nodes/object3d/RectAreaLight.js +34 -0
  107. package/dist/nodes/object3d/Scene.d.ts +5 -0
  108. package/dist/nodes/object3d/Scene.js +22 -0
  109. package/dist/nodes/object3d/SkinnedMesh.d.ts +5 -0
  110. package/dist/nodes/object3d/SkinnedMesh.js +23 -0
  111. package/dist/nodes/object3d/SpotLight.d.ts +11 -0
  112. package/dist/nodes/object3d/SpotLight.js +42 -0
  113. package/dist/nodes/object3d/Sprite.d.ts +5 -0
  114. package/dist/nodes/object3d/Sprite.js +21 -0
  115. package/dist/nodes/setting/Background.d.ts +6 -0
  116. package/dist/nodes/setting/Background.js +22 -0
  117. package/dist/nodes/setting/Fog.d.ts +8 -0
  118. package/dist/nodes/setting/Fog.js +30 -0
  119. package/dist/nodes/setting/FogExp2.d.ts +7 -0
  120. package/dist/nodes/setting/FogExp2.js +27 -0
  121. package/dist/nodes/targets.d.ts +5 -0
  122. package/dist/nodes/targets.js +5 -0
  123. package/package.json +42 -0
@@ -0,0 +1,125 @@
1
+ import { isScalarType } from '../core/Registry.js';
2
+ import { ComponentFlag, NodeFlag, } from './bundle-format.js';
3
+ import { attachmentAttrEntries, componentPropEntries, nodeAttrEntries, popcount } from './bundle-shared.js';
4
+ /**
5
+ * Decode a bundle into a CompiledScene. This is the runtime's only "loader" —
6
+ * it contains no XML and no parser, just array indexing against the registry
7
+ * schema. The redundant back-pointers (`node.parent`, `component.node`) are
8
+ * reconstructed from the children/components arrays rather than stored.
9
+ */
10
+ export function deserializeBundle(bundle) {
11
+ const [, strings, asset] = bundle;
12
+ const [nodeData, attachmentData, componentData, root, prefabData] = asset;
13
+ const attachments = attachmentData.map((a) => decodeTag(a, strings));
14
+ const components = componentData.map((c) => decodeComponent(c, strings));
15
+ const nodes = nodeData.map((n) => decodeNode(n, strings));
16
+ // Embedded prefabs are self-contained sub-bundles; decode each recursively.
17
+ const prefabs = prefabData?.map((p) => deserializeBundle(p));
18
+ // Rebuild parent pointers and component ownership from the forward arrays.
19
+ nodes.forEach((node, i) => {
20
+ for (const child of node.children)
21
+ nodes[child].parent = i;
22
+ for (const c of node.components)
23
+ components[c].node = i;
24
+ });
25
+ return { root, nodes, attachments, components, ...(prefabs ? { prefabs } : {}) };
26
+ }
27
+ function decodeTag(data, strings) {
28
+ const tag = strings[data[0]];
29
+ const mask = data[1];
30
+ return { tag, attrs: decodeAttrs(attachmentAttrEntries(tag), mask, data.slice(2), strings) };
31
+ }
32
+ function decodeNode(data, strings) {
33
+ const tag = strings[data[0]];
34
+ const flags = data[1];
35
+ const mask = data[2];
36
+ const entries = nodeAttrEntries(tag);
37
+ let i = 3;
38
+ const attrCount = popcount(mask);
39
+ const attrs = decodeAttrs(entries, mask, data.slice(i, i + attrCount), strings);
40
+ i += attrCount;
41
+ const node = {
42
+ tag,
43
+ parent: -1,
44
+ children: [],
45
+ transform: identityTransform(),
46
+ attrs,
47
+ attachments: [],
48
+ components: [],
49
+ };
50
+ if (flags & NodeFlag.Id)
51
+ node.id = strings[data[i++]];
52
+ if (flags & NodeFlag.Transform)
53
+ node.transform = decodeTransform(data[i++]);
54
+ if (flags & NodeFlag.Attachments)
55
+ node.attachments = data[i++];
56
+ if (flags & NodeFlag.Components)
57
+ node.components = data[i++];
58
+ if (flags & NodeFlag.Children)
59
+ node.children = data[i++];
60
+ return node;
61
+ }
62
+ function decodeComponent(data, strings) {
63
+ const type = strings[data[0]];
64
+ const flags = data[1];
65
+ const mask = data[2];
66
+ const props = {};
67
+ let i = 3;
68
+ let bit = 0;
69
+ for (const [name, meta] of componentPropEntries(type)) {
70
+ if (mask & (1 << bit)) {
71
+ props[name] = decodePropValue(data[i++], meta.type, strings);
72
+ }
73
+ bit++;
74
+ }
75
+ const component = { type, node: -1, props };
76
+ if (flags & ComponentFlag.Id)
77
+ component.id = strings[data[i++]];
78
+ return component;
79
+ }
80
+ // ---- value helpers --------------------------------------------------------
81
+ function decodeAttrs(entries, mask, values, strings) {
82
+ const out = {};
83
+ let vi = 0;
84
+ let bit = 0;
85
+ for (const [name, meta] of entries) {
86
+ if (mask & (1 << bit)) {
87
+ const raw = values[vi++];
88
+ out[name] = meta.type === 'string' ? strings[raw] : raw;
89
+ }
90
+ else {
91
+ out[name] = cloneDefault(meta.default);
92
+ }
93
+ bit++;
94
+ }
95
+ return out;
96
+ }
97
+ function decodePropValue(raw, type, strings) {
98
+ // A prefab prop stores a plain index into the scene's prefabs[] pool; the
99
+ // registered type disambiguates it from node/component refs.
100
+ if (type === 'prefab')
101
+ return { prefab: raw };
102
+ if (!isScalarType(type)) {
103
+ const i = raw;
104
+ return i < 0 ? { node: ~i } : { component: i };
105
+ }
106
+ if (type === 'string')
107
+ return strings[raw];
108
+ return raw;
109
+ }
110
+ function decodeTransform(arr) {
111
+ return {
112
+ position: arr.slice(0, 3),
113
+ rotation: arr.slice(3, 6),
114
+ scale: arr.slice(6, 9),
115
+ visible: arr[9] === 1,
116
+ };
117
+ }
118
+ function identityTransform() {
119
+ return { position: [0, 0, 0], rotation: [0, 0, 0], scale: [1, 1, 1], visible: true };
120
+ }
121
+ function cloneDefault(value) {
122
+ if (value === undefined)
123
+ return 0;
124
+ return Array.isArray(value) ? [...value] : value;
125
+ }
@@ -0,0 +1,40 @@
1
+ import { type Object3D, Scene, type WebGLRenderer } from 'three';
2
+ import type { Component } from '../core/Component.js';
3
+ import type { CompiledScene } from './compiled.js';
4
+ import { type AttachmentInstance } from './nodes.js';
5
+ import { type SpawnFn } from './PrefabRef.js';
6
+ /**
7
+ * The live result of instantiating a CompiledScene: a Three.js subtree, bare
8
+ * component instances (lifecycle not yet started — the Engine owns that), and
9
+ * the live attachment instances (which the engine retains for `findAttachment`).
10
+ * Arrays are index-aligned with the CompiledScene so references resolve by index.
11
+ */
12
+ export interface InstantiatedScene {
13
+ /** Object for the root <Scene> node; its children are the scene contents. */
14
+ root: Object3D;
15
+ /** Index-aligned with CompiledScene.components. */
16
+ components: {
17
+ instance: Component;
18
+ node: Object3D;
19
+ }[];
20
+ /** One per pooled attachment (caching → shared geometry/material). */
21
+ attachments: AttachmentInstance[];
22
+ }
23
+ /** Live scene/renderer context an attachment may configure (settings need it). */
24
+ export interface InstantiateContext {
25
+ scene?: Scene;
26
+ renderer?: WebGLRenderer;
27
+ /**
28
+ * How a `prefab` prop spawns its subtree (the Engine passes `engine.instantiate`).
29
+ * Absent when instantiating standalone (pure tests) — a PrefabRef.spawn() then
30
+ * throws a clear error rather than silently no-op.
31
+ */
32
+ spawn?: SpawnFn;
33
+ }
34
+ /**
35
+ * Build live Three.js objects, attachments, and component instances from a
36
+ * compiled scene. Attachments are pooled (one instance per entry → shared
37
+ * resources); component references are wired by index in a second pass, so they
38
+ * are valid before any component lifecycle runs.
39
+ */
40
+ export declare function instantiateScene(compiled: CompiledScene, ctx?: InstantiateContext): InstantiatedScene;
@@ -0,0 +1,111 @@
1
+ import { Color, Euler, Scene, Vector2, Vector3 } from 'three';
2
+ import { Registry } from '../core/Registry.js';
3
+ import { getAttachmentDef, getNodeDef } from './nodes.js';
4
+ import { PrefabRef } from './PrefabRef.js';
5
+ /**
6
+ * Build live Three.js objects, attachments, and component instances from a
7
+ * compiled scene. Attachments are pooled (one instance per entry → shared
8
+ * resources); component references are wired by index in a second pass, so they
9
+ * are valid before any component lifecycle runs.
10
+ */
11
+ export function instantiateScene(compiled, ctx = {}) {
12
+ // A scene is needed by scene-targeted attachments (fog/background); default to
13
+ // a throwaway when instantiating standalone (prefabs/tests use node attachments).
14
+ const scene = ctx.scene ?? new Scene();
15
+ const renderer = ctx.renderer;
16
+ // One instance per pooled attachment entry, so caching shares the resource.
17
+ const attachments = compiled.attachments.map((a) => getAttachmentDef(a.tag).make(a.attrs));
18
+ const objects = compiled.nodes.map((node) => buildObject(node));
19
+ // Link the scene graph.
20
+ compiled.nodes.forEach((node, i) => {
21
+ for (const childIndex of node.children)
22
+ objects[i].add(objects[childIndex]);
23
+ });
24
+ // Apply attachments (geometry/material on the node, fog/background on the scene).
25
+ compiled.nodes.forEach((node, i) => {
26
+ for (const ai of node.attachments)
27
+ attachments[ai].attach({ node: objects[i], scene, ...(renderer ? { renderer } : {}) });
28
+ });
29
+ // Create component instances and set their scalar props (refs come next).
30
+ const instances = compiled.components.map((c) => {
31
+ const entry = Registry.getComponent(c.type);
32
+ const instance = new entry.ctor();
33
+ instance.node = objects[c.node];
34
+ if (c.id !== undefined)
35
+ instance.id = c.id;
36
+ setScalarProps(instance, c, entry.meta.properties);
37
+ return instance;
38
+ });
39
+ // Wire references now that every object and instance exists.
40
+ const spawn = ctx.spawn ?? unboundSpawn;
41
+ compiled.components.forEach((c, i) => {
42
+ const props = Registry.getComponent(c.type).meta.properties;
43
+ for (const [attr, value] of Object.entries(c.props)) {
44
+ if (!isRef(value))
45
+ continue;
46
+ const meta = props.get(attr);
47
+ if (!meta)
48
+ continue;
49
+ const target = instances[i];
50
+ if ('prefab' in value) {
51
+ target[attr] = new PrefabRef(compiled.prefabs[value.prefab], spawn, instances[i].node);
52
+ }
53
+ else {
54
+ target[attr] = 'node' in value ? objects[value.node] : instances[value.component];
55
+ }
56
+ }
57
+ });
58
+ return {
59
+ root: objects[compiled.root],
60
+ components: instances.map((instance, i) => ({ instance, node: objects[compiled.components[i].node] })),
61
+ attachments,
62
+ };
63
+ }
64
+ function buildObject(node) {
65
+ const object = getNodeDef(node.tag).build(node.attrs);
66
+ // The DSL `id` maps to Three.js's Object3D.name (Object3D.id is a reserved
67
+ // numeric auto-id), so name-based lookups keep working.
68
+ if (node.id !== undefined)
69
+ object.name = node.id;
70
+ const { position, rotation, scale, visible } = node.transform;
71
+ object.position.fromArray(position);
72
+ object.rotation.set(rotation[0], rotation[1], rotation[2]);
73
+ object.scale.fromArray(scale);
74
+ object.visible = visible;
75
+ return object;
76
+ }
77
+ function setScalarProps(instance, compiled, props) {
78
+ const target = instance;
79
+ for (const [attr, value] of Object.entries(compiled.props)) {
80
+ if (isRef(value))
81
+ continue;
82
+ const meta = props.get(attr);
83
+ if (!meta)
84
+ continue;
85
+ target[attr] = toRuntimeValue(meta.type, value);
86
+ }
87
+ }
88
+ /** Convert a compiled scalar into the Three.js-typed value a component field expects. */
89
+ function toRuntimeValue(type, value) {
90
+ switch (type) {
91
+ case 'vec2':
92
+ return new Vector2().fromArray(value);
93
+ case 'vec3':
94
+ return new Vector3().fromArray(value);
95
+ case 'euler': {
96
+ const e = value;
97
+ return new Euler(e[0], e[1], e[2]);
98
+ }
99
+ case 'color':
100
+ return new Color(value);
101
+ default:
102
+ return value; // float/int/bool/string
103
+ }
104
+ }
105
+ function isRef(value) {
106
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
107
+ }
108
+ /** Fallback for a prefab prop instantiated without an engine (pure/standalone use). */
109
+ const unboundSpawn = () => {
110
+ throw new Error('PrefabRef.spawn() needs an engine — instantiate the scene via Engine.loadScene/instantiate.');
111
+ };
@@ -0,0 +1 @@
1
+ export * from '../core/NodeRegistry.js';
@@ -0,0 +1,10 @@
1
+ // Compatibility shim. The node system moved to an extensible registry in
2
+ // `core/NodeRegistry.ts`, with built-ins split one-per-file under `nodes/**`.
3
+ // This module re-exports the registry surface (so existing `from './nodes.js'`
4
+ // import sites keep working).
5
+ //
6
+ // It deliberately does NOT import the built-in barrel: keeping the runtime entry
7
+ // node-module-free is what lets bundlers tree-shake unused node tags. Built-ins
8
+ // register on demand — the Vite plugin emits a usage-based import per used tag
9
+ // (see nodes/manifest.ts), or import `@scenoco-three/core/nodes` to register all.
10
+ export * from '../core/NodeRegistry.js';
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Canonical wire formats for SceNoCo attribute values — the single place that
3
+ * defines how each scalar/math property is written in XML, parsed into a
4
+ * JSON-serializable compiled value, serialized back, and described in XSD.
5
+ *
6
+ * Compiled representation (what flows through compile → instantiate, and what
7
+ * ships as JSON in production builds) is deliberately plain and fast:
8
+ * float/int → number
9
+ * bool → boolean
10
+ * string → string
11
+ * vec2/vec3 → number[] (length 2 / 3)
12
+ * euler → number[] in RADIANS (XML is degrees; converted at parse time)
13
+ * color → number (0xRRGGBB)
14
+ *
15
+ * Reference types (`node` and typed component references) are not scalar codecs;
16
+ * they need the scene-wide name/id tables and are resolved by the compiler.
17
+ */
18
+ export type ScalarType = 'float' | 'int' | 'bool' | 'string' | 'vec2' | 'vec3' | 'euler' | 'color';
19
+ export type CompiledScalar = number | boolean | string | number[];
20
+ export interface ParseOk<T> {
21
+ ok: true;
22
+ value: T;
23
+ }
24
+ export interface ParseErr {
25
+ ok: false;
26
+ /** LLM-actionable: says what was expected and shows a valid example. */
27
+ message: string;
28
+ }
29
+ export type ParseResult<T> = ParseOk<T> | ParseErr;
30
+ export interface XsdType {
31
+ /** Underlying XSD simple type. */
32
+ base: string;
33
+ /** Optional restriction pattern for string-encoded math types. */
34
+ pattern?: string;
35
+ }
36
+ export interface TypeCodec<T extends CompiledScalar = CompiledScalar> {
37
+ /** Human label used in error messages, e.g. "a vec3". */
38
+ label: string;
39
+ /** A valid example value, shown in errors and docs. */
40
+ example: string;
41
+ parse(raw: string): ParseResult<T>;
42
+ serialize(value: T): string;
43
+ xsd: XsdType;
44
+ }
45
+ export declare const codecs: Record<ScalarType, TypeCodec>;
@@ -0,0 +1,120 @@
1
+ import { Color } from 'three';
2
+ const ok = (value) => ({ ok: true, value });
3
+ const err = (message) => ({ ok: false, message });
4
+ // A single finite number: 1, -2.5, .5, 3e2, +4. Used for runtime parsing (JS regex).
5
+ const NUM_SRC = '[-+]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][-+]?\\d+)?';
6
+ const NUM_RE = new RegExp(`^${NUM_SRC}$`);
7
+ // XML-Schema-flavored equivalents for the generated XSD: patterns are implicitly
8
+ // anchored and forbid the `(?:…)` non-capturing group, so we use plain groups.
9
+ const XSD_NUM = '[+-]?(\\d+(\\.\\d*)?|\\.\\d+)([eE][+-]?\\d+)?';
10
+ const xsdVecPattern = (n) => `\\s*${XSD_NUM}(\\s+${XSD_NUM}){${n - 1}}\\s*`;
11
+ function parseFiniteNumber(raw) {
12
+ const t = raw.trim();
13
+ if (!NUM_RE.test(t))
14
+ return err(`expected a number (e.g. "1.5"), got "${raw}"`);
15
+ const n = Number(t);
16
+ return Number.isFinite(n) ? ok(n) : err(`expected a finite number, got "${raw}"`);
17
+ }
18
+ function parseVec(raw, n, label) {
19
+ const parts = raw.trim().split(/\s+/);
20
+ if (parts.length !== n) {
21
+ return err(`expected ${label} — ${n} space-separated numbers (e.g. "${vecExample(n)}"), got "${raw}"`);
22
+ }
23
+ const out = [];
24
+ for (const p of parts) {
25
+ if (!NUM_RE.test(p)) {
26
+ return err(`expected ${label} — ${n} space-separated numbers (e.g. "${vecExample(n)}"), got "${raw}"`);
27
+ }
28
+ out.push(Number(p));
29
+ }
30
+ return ok(out);
31
+ }
32
+ const vecExample = (n) => (n === 2 ? '0 1' : '0 1.5 -3');
33
+ const DEG2RAD = Math.PI / 180;
34
+ const RAD2DEG = 180 / Math.PI;
35
+ const HEX_RE = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
36
+ export const codecs = {
37
+ float: {
38
+ label: 'a number',
39
+ example: '1.5',
40
+ parse: parseFiniteNumber,
41
+ serialize: (v) => String(v),
42
+ xsd: { base: 'xs:float' },
43
+ },
44
+ int: {
45
+ label: 'an integer',
46
+ example: '32',
47
+ parse: (raw) => {
48
+ const r = parseFiniteNumber(raw);
49
+ if (!r.ok)
50
+ return r;
51
+ return Number.isInteger(r.value) ? r : err(`expected an integer (e.g. "32"), got "${raw}"`);
52
+ },
53
+ serialize: (v) => String(v),
54
+ xsd: { base: 'xs:int' },
55
+ },
56
+ bool: {
57
+ label: 'a boolean',
58
+ example: 'true',
59
+ parse: (raw) => {
60
+ const t = raw.trim();
61
+ if (t === 'true')
62
+ return ok(true);
63
+ if (t === 'false')
64
+ return ok(false);
65
+ return err(`expected "true" or "false", got "${raw}"`);
66
+ },
67
+ serialize: (v) => String(v),
68
+ xsd: { base: 'xs:boolean' },
69
+ },
70
+ string: {
71
+ label: 'a string',
72
+ example: 'hello',
73
+ parse: (raw) => ok(raw),
74
+ serialize: (v) => String(v),
75
+ xsd: { base: 'xs:string' },
76
+ },
77
+ vec2: {
78
+ label: 'a vec2',
79
+ example: '0 1',
80
+ parse: (raw) => parseVec(raw, 2, 'a vec2'),
81
+ serialize: (v) => v.join(' '),
82
+ xsd: { base: 'xs:string', pattern: xsdVecPattern(2) },
83
+ },
84
+ vec3: {
85
+ label: 'a vec3',
86
+ example: '0 1.5 -3',
87
+ parse: (raw) => parseVec(raw, 3, 'a vec3'),
88
+ serialize: (v) => v.join(' '),
89
+ xsd: { base: 'xs:string', pattern: xsdVecPattern(3) },
90
+ },
91
+ euler: {
92
+ // XML carries degrees (Unity/Blender/A-Frame convention); compiled is radians.
93
+ label: 'an euler (degrees)',
94
+ example: '0 90 0',
95
+ parse: (raw) => {
96
+ const r = parseVec(raw, 3, 'an euler (degrees)');
97
+ return r.ok ? ok(r.value.map((d) => d * DEG2RAD)) : r;
98
+ },
99
+ serialize: (v) => v.map((rad) => round(rad * RAD2DEG)).join(' '),
100
+ xsd: { base: 'xs:string', pattern: xsdVecPattern(3) },
101
+ },
102
+ color: {
103
+ label: 'a color',
104
+ example: '#ffcc00',
105
+ parse: (raw) => {
106
+ const t = raw.trim();
107
+ if (HEX_RE.test(t))
108
+ return ok(new Color(t).getHex());
109
+ if (t.toLowerCase() in Color.NAMES)
110
+ return ok(new Color(t.toLowerCase()).getHex());
111
+ return err(`expected a hex color (e.g. "#ffcc00") or a CSS color name (e.g. "tomato"), got "${raw}"`);
112
+ },
113
+ serialize: (v) => `#${v.toString(16).padStart(6, '0')}`,
114
+ xsd: { base: 'xs:string', pattern: `#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})|[a-zA-Z]+` },
115
+ },
116
+ };
117
+ // Trim floating-point dust when serializing degrees back from radians.
118
+ function round(n) {
119
+ return Math.round(n * 1e6) / 1e6;
120
+ }
@@ -0,0 +1,13 @@
1
+ export { Engine, type EngineOptions } from './core/Engine.js';
2
+ export { Component } from './core/Component.js';
3
+ export { System } from './core/System.js';
4
+ export { Registry, isScalarType, type PropType, type PropertyMeta, type ComponentMeta, type ComponentCtor, type RegisteredComponent, } from './core/Registry.js';
5
+ export { component, property, node, attachment, system, type ComponentOptions, type NodeOptions, type AttachmentOptions, } from './core/decorators.js';
6
+ export { Object3DNode, Attachment } from './nodes/base.js';
7
+ export { getNodeDef, nodeTags, allNodeDefs, registerNode, unregisterNode, getAttachmentDef, attachmentTags, allAttachmentDefs, registerAttachment, unregisterAttachment, type NodeDef, type NodeAttr, type NodeKind, type ChildRules, type AttachmentDef, type AttachTarget, type AttachContext, } from './dsl/nodes.js';
8
+ export { deserializeBundle } from './dsl/deserialize.js';
9
+ export { BUNDLE_VERSION, bundleKind, type Bundle, type BundleAsset, type BundleKind, } from './dsl/bundle-format.js';
10
+ export { instantiateScene, type InstantiatedScene } from './dsl/instantiate.js';
11
+ export { PrefabRef, type SpawnOptions, type SpawnFn } from './dsl/PrefabRef.js';
12
+ export type { SceneInstance } from './core/Engine.js';
13
+ export type { CompiledScene, CompiledNode, CompiledComponent, CompiledAsset, CompiledRef, CompiledValue, CompiledTransform, } from './dsl/compiled.js';
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ // `@scenoco-three/core` — the runtime entry point. Contains no XML parser,
2
+ // validator, or compiler: it loads pre-built bundles only.
3
+ export { Engine } from './core/Engine.js';
4
+ export { Component } from './core/Component.js';
5
+ export { System } from './core/System.js';
6
+ export { Registry, isScalarType, } from './core/Registry.js';
7
+ // One authoring idiom: @property fields + a class decorator per role.
8
+ export { component, property, node, attachment, system, } from './core/decorators.js';
9
+ export { Object3DNode, Attachment } from './nodes/base.js';
10
+ // Tag registry — built-in tags self-register on import; projects add their own
11
+ // the same way (the extensible counterpart to @component).
12
+ export { getNodeDef, nodeTags, allNodeDefs, registerNode, unregisterNode, getAttachmentDef, attachmentTags, allAttachmentDefs, registerAttachment, unregisterAttachment, } from './dsl/nodes.js';
13
+ // Bundle: the optimized format the runtime loads.
14
+ export { deserializeBundle } from './dsl/deserialize.js';
15
+ export { BUNDLE_VERSION, bundleKind, } from './dsl/bundle-format.js';
16
+ export { instantiateScene } from './dsl/instantiate.js';
17
+ export { PrefabRef } from './dsl/PrefabRef.js';
@@ -0,0 +1,7 @@
1
+ export { Registry, isScalarType, type ComponentCtor, type PropertyMeta, type PropType } from './core/Registry.js';
2
+ export { getNodeDef, nodeTags, allNodeDefs, isSpatial, unregisterNode, getAttachmentDef, attachmentTags, allAttachmentDefs, attachmentsForNode, attachmentAcceptsNode, unregisterAttachment, allSystemDefs, TRANSFORM_ATTRS, type NodeDef, type NodeAttr, type NodeKind, type ChildRules, type AttachmentDef, type AttachTarget, type AttachClass, type AttachContext, type SystemDef, } from './dsl/nodes.js';
3
+ export { builtinNodeModules, builtinNodeModule } from './nodes/manifest.js';
4
+ export { BUNDLE_VERSION, ComponentFlag, NodeFlag, type Bundle, type BundleAsset, type BundleComponent, type BundleKind, type BundleNode, type BundleTag, type BundleValue, } from './dsl/bundle-format.js';
5
+ export { componentPropEntries, nodeAttrEntries, attachmentAttrEntries, popcount, valueEquals, } from './dsl/bundle-shared.js';
6
+ export type { CompiledScene, CompiledNode, CompiledComponent, CompiledAsset, CompiledRef, CompiledValue, CompiledTransform, } from './dsl/compiled.js';
7
+ export type { CompiledScalar, ScalarType } from './dsl/types.js';
@@ -0,0 +1,7 @@
1
+ // Internal bridge exports for build-time tooling packages.
2
+ // Not intended as the primary runtime API surface.
3
+ export { Registry, isScalarType } from './core/Registry.js';
4
+ export { getNodeDef, nodeTags, allNodeDefs, isSpatial, unregisterNode, getAttachmentDef, attachmentTags, allAttachmentDefs, attachmentsForNode, attachmentAcceptsNode, unregisterAttachment, allSystemDefs, TRANSFORM_ATTRS, } from './dsl/nodes.js';
5
+ export { builtinNodeModules, builtinNodeModule } from './nodes/manifest.js';
6
+ export { BUNDLE_VERSION, ComponentFlag, NodeFlag, } from './dsl/bundle-format.js';
7
+ export { componentPropEntries, nodeAttrEntries, attachmentAttrEntries, popcount, valueEquals, } from './dsl/bundle-shared.js';
@@ -0,0 +1,31 @@
1
+ import type { Object3D } from 'three';
2
+ import type { AttachContext } from '../core/NodeRegistry.js';
3
+ /**
4
+ * Base classes for the two tag kinds that live in the scene tree — the factory
5
+ * counterpart to `Component`. A tag is a class: its XML attributes are
6
+ * `@property` fields, a class decorator (`@node`/`@attachment`) declares its kind
7
+ * and registers it, and the body reads the fields with full typing.
8
+ *
9
+ * @node({ name: 'Mesh', children: { … } })
10
+ * class MeshNode extends Object3DNode { build() { return new Mesh(); } }
11
+ *
12
+ * @attachment({ target: Mesh, group: 'geometry', required: true })
13
+ * class BoxGeometry extends Attachment<Mesh> {
14
+ * @property({ type: 'float', default: 1 }) width = 1;
15
+ * attach(ctx) { ctx.node.geometry = new BoxGeometry(this.width); }
16
+ * }
17
+ */
18
+ /** A scene-graph node (Object3D family + the Scene root). Builds the bare object. */
19
+ export declare abstract class Object3DNode {
20
+ abstract build(): Object3D;
21
+ }
22
+ /**
23
+ * A leaf that attaches to and configures a node — geometry/material on a mesh,
24
+ * fog/background on the scene. `TNode` types the host: `Attachment<Mesh>` gives
25
+ * `ctx.node` typed as `Mesh`. An attachment may cache the resource it builds on
26
+ * `this`, so the compiler's content-dedup (one def instance shared across nodes)
27
+ * yields one shared geometry/material.
28
+ */
29
+ export declare abstract class Attachment<TNode extends Object3D = Object3D> {
30
+ abstract attach(ctx: AttachContext<TNode>): void;
31
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Base classes for the two tag kinds that live in the scene tree — the factory
3
+ * counterpart to `Component`. A tag is a class: its XML attributes are
4
+ * `@property` fields, a class decorator (`@node`/`@attachment`) declares its kind
5
+ * and registers it, and the body reads the fields with full typing.
6
+ *
7
+ * @node({ name: 'Mesh', children: { … } })
8
+ * class MeshNode extends Object3DNode { build() { return new Mesh(); } }
9
+ *
10
+ * @attachment({ target: Mesh, group: 'geometry', required: true })
11
+ * class BoxGeometry extends Attachment<Mesh> {
12
+ * @property({ type: 'float', default: 1 }) width = 1;
13
+ * attach(ctx) { ctx.node.geometry = new BoxGeometry(this.width); }
14
+ * }
15
+ */
16
+ /** A scene-graph node (Object3D family + the Scene root). Builds the bare object. */
17
+ export class Object3DNode {
18
+ }
19
+ /**
20
+ * A leaf that attaches to and configures a node — geometry/material on a mesh,
21
+ * fog/background on the scene. `TNode` types the host: `Attachment<Mesh>` gives
22
+ * `ctx.node` typed as `Mesh`. An attachment may cache the resource it builds on
23
+ * `this`, so the compiler's content-dedup (one def instance shared across nodes)
24
+ * yields one shared geometry/material.
25
+ */
26
+ export class Attachment {
27
+ }
@@ -0,0 +1,13 @@
1
+ import { type Mesh } from 'three';
2
+ import { Attachment } from '../base.js';
3
+ import type { AttachContext } from '../../core/NodeRegistry.js';
4
+ export declare class BoxGeometryNode extends Attachment<Mesh> {
5
+ width: number;
6
+ height: number;
7
+ depth: number;
8
+ widthSegments: number;
9
+ heightSegments: number;
10
+ depthSegments: number;
11
+ private cached?;
12
+ attach(ctx: AttachContext<Mesh>): void;
13
+ }
@@ -0,0 +1,44 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { BoxGeometry } from 'three';
8
+ import { Attachment } from '../base.js';
9
+ import { attachment, property } from '../../core/decorators.js';
10
+ import { GEOMETRY_TARGETS } from '../targets.js';
11
+ let BoxGeometryNode = class BoxGeometryNode extends Attachment {
12
+ width = 1;
13
+ height = 1;
14
+ depth = 1;
15
+ widthSegments = 1;
16
+ heightSegments = 1;
17
+ depthSegments = 1;
18
+ cached;
19
+ attach(ctx) {
20
+ ctx.node.geometry = (this.cached ??= new BoxGeometry(this.width, this.height, this.depth, this.widthSegments, this.heightSegments, this.depthSegments));
21
+ }
22
+ };
23
+ __decorate([
24
+ property({ type: 'float', default: 1 })
25
+ ], BoxGeometryNode.prototype, "width", void 0);
26
+ __decorate([
27
+ property({ type: 'float', default: 1 })
28
+ ], BoxGeometryNode.prototype, "height", void 0);
29
+ __decorate([
30
+ property({ type: 'float', default: 1 })
31
+ ], BoxGeometryNode.prototype, "depth", void 0);
32
+ __decorate([
33
+ property({ type: 'int', default: 1 })
34
+ ], BoxGeometryNode.prototype, "widthSegments", void 0);
35
+ __decorate([
36
+ property({ type: 'int', default: 1 })
37
+ ], BoxGeometryNode.prototype, "heightSegments", void 0);
38
+ __decorate([
39
+ property({ type: 'int', default: 1 })
40
+ ], BoxGeometryNode.prototype, "depthSegments", void 0);
41
+ BoxGeometryNode = __decorate([
42
+ attachment({ name: 'BoxGeometry', target: GEOMETRY_TARGETS, group: 'geometry', required: true })
43
+ ], BoxGeometryNode);
44
+ export { BoxGeometryNode };
@@ -0,0 +1,11 @@
1
+ import { type Mesh } from 'three';
2
+ import { Attachment } from '../base.js';
3
+ import type { AttachContext } from '../../core/NodeRegistry.js';
4
+ export declare class CylinderGeometryNode extends Attachment<Mesh> {
5
+ radiusTop: number;
6
+ radiusBottom: number;
7
+ height: number;
8
+ radialSegments: number;
9
+ private cached?;
10
+ attach(ctx: AttachContext<Mesh>): void;
11
+ }