Skip to content

feat(core): declarative world-space objects + editor bridge object commands - #48

Merged
hblee12294 merged 2 commits into
mainfrom
feat/objects
Jul 25, 2026
Merged

feat(core): declarative world-space objects + editor bridge object commands#48
hblee12294 merged 2 commits into
mainfrom
feat/objects

Conversation

@hblee12294

Copy link
Copy Markdown
Member

objects?: ObjectConfig[] — engine-managed 3D props in the main scene, the world-space sibling of elements[].

The capability

"objects": [
  { "id": "p0",
    "asset": { "kind": "primitive", "shape": "knot", "color": "#ffb03a" },
    "transform": { "x": 1, "y": 0.5, "z": -2, "ry": 40, "scale": 0.6 } },
  { "id": "m0", "asset": { "kind": "gltf", "key": "https://…/model.glb" } }
]
  • Assets: parametric primitives (cube | sphere | torus | knot; standard material with color/metalness/roughness, or unlit: true for a light-free basic material) and GLB models by URL — bbox-normalized at load so transform.scale is asset-independent, with per-instance cloned materials. A gltf asset auto-detects the GLTFLoader addon (objects are data — function-string keyword scanning can't see them).
  • Transforms are raw world units; apps map their own layout conventions onto world units before authoring the config.
  • Each object is an ObjectInstance { config, root, props, dispose } in an id-keyed Map exposed as ctx.objects and on the result. The props bag is the single animatable/editable surface — the render loop mirrors it onto the mesh every frame, so tweens targeting ctx.objects.get(id).props and editor overrides compose exactly like element props.
  • Lighting is the author's choice: standard materials expect lights from createContent; unlit renders without any.

Bridge (protocol 2 → 3)

  • SET_OBJECT_PROPS { id, props } — ephemeral overrides for gesture-time preview; cleared by LOAD (durable state stays in the config).
  • OBJECT_HIT_TEST { x, y, requestId }OBJECT_HIT_RESULT { requestId, id | null } — a main-camera raycast; nearest hit wins (real 3D depth, unlike overlay elements), child hits walk up to the registered root.

Compatibility

Fully additive: configs without objects compile byte-identically (pinned by test); no config version bump. 135 tests pass (7 new: codegen, unlit, GLTFLoader auto-detect + non-detect, context/result exposure, editor/non-editor bridge surface). Changeset: minor.

…mmands

objects?: ObjectConfig[] — engine-managed 3D props in the main scene, the
world-space sibling of elements[]:

- Assets: parametric primitives (cube/sphere/torus/knot; standard material
  with color/metalness/roughness, or unlit basic material) and GLB models by
  URL. GLB roots are bbox-normalized at load (largest dimension = 1 world
  unit) so transform.scale is asset-independent; per-instance cloned
  materials. A gltf asset auto-detects the GLTFLoader addon — objects are
  data, so keyword scanning of function strings can't see them.
- Transforms are raw world units (x/y/z, rx/ry/rz degrees, uniform scale);
  apps map their own layout conventions onto world units before authoring.
- Each object becomes an ObjectInstance { config, root, props, dispose } in
  an id-keyed Map exposed as ctx.objects and on the result. The props bag is
  the animatable/editable surface: the render loop mirrors it onto the mesh
  every frame (__syncObjects), so tweens targeting props and editor overrides
  compose the same way element props do.
- Bridge protocol 3: SET_OBJECT_PROPS (ephemeral overrides, cleared by LOAD —
  durable state stays in the config) and OBJECT_HIT_TEST (main-camera raycast,
  nearest object id; child hits walk up to the registered root).
- Lighting stays the author's choice: standard materials expect lights from
  createContent; unlit: true renders without any.

Fully additive: configs without objects compile byte-identically (pinned by
test); no config version bump needed. 135 tests pass (7 new).
…casts with it

Real-browser verification of the objects bridge caught this: the editor api
reads __current.camera for the object raycast, but the result only exposed
overlayCamera — every OBJECT_HIT_TEST returned null while rendering worked
perfectly. Expose camera alongside the other introspection handles and pin
it in the codegen test.

Verified end-to-end in headless Chrome against the built package: lit knot /
unlit cube / GLB duck all render (pixel checks), hit-tests return the right
ids (GLB child hits walk to the root), empty space misses, and
SET_OBJECT_PROPS moves an object so the old spot misses and the new spot
hits. 136 tests.
@hblee12294

Copy link
Copy Markdown
Member Author

Real-browser verification (headless Chrome against the built package — compiled config booted through the editor render template):

  • ✅ lit knot / unlit cube / GLB duck all render (pixel checks at their screen positions; background clean)
  • OBJECT_HIT_TEST returns the right id for each — including GLB child-mesh hits walking up to the registered root
  • ✅ empty space returns null
  • SET_OBJECT_PROPS moves an object: the old position stops hitting, the new one hits

This pass caught one real bug the unit tests couldn't: the editor api raycasts with __current.camera, but the result only exposed overlayCamera — every object hit-test returned null while rendering worked. Fixed in ce557fd (camera now rides the result's introspection handles, pinned by test).

@hblee12294
hblee12294 merged commit b7b0e7d into main Jul 25, 2026
3 checks passed
@hblee12294
hblee12294 deleted the feat/objects branch July 25, 2026 04:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant