wiki(tools): clarify useLiveTransforms.position semantics for polygon kinds

Two regressions in the Stage D port surfaced the same gap: the wiki
already said "the mesh offset must mirror the useLiveTransforms entry"
but didn't spell out that for polygon-based kinds (slab / fence /
ceiling / wall), the value to mirror is the DELTA (the group's local
position) — not the world location of the polygon's center.

Expanded the live-drag rule with a position-based vs polygon-based
split and an explicit note that ParametricNodeRenderer reconciles the
group's position via React on every Zustand notification, so the two
writes must agree to the exact value or they fight every frame.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-05-18 09:47:39 -04:00
co-authored by Claude Opus 4.7
parent 82c9b5e1df
commit c3e255b58a
+3 -1
View File
@@ -60,7 +60,9 @@ export function MyTool() {
- **Tools mutate `useScene` for committed changes and `useLiveTransforms` for ephemeral drag state.** A tool's end-of-interaction write (click-to-commit, release-to-commit) goes to `useScene` and is captured in undo history. Per-mouse-move previews go to `useLiveTransforms` so history and subscribers aren't spammed. - **Tools mutate `useScene` for committed changes and `useLiveTransforms` for ephemeral drag state.** A tool's end-of-interaction write (click-to-commit, release-to-commit) goes to `useScene` and is captured in undo history. Per-mouse-move previews go to `useLiveTransforms` so history and subscribers aren't spammed.
- **Live-drag exception for direct mesh transforms.** During an active drag a tool may apply a transform offset directly to `sceneRegistry.nodes.get(id).position`/`rotation`/`scale` *when and only when* the same offset is mirrored into `useLiveTransforms` for that node. This exception exists because the 3D renderers don't reconcile `useLiveTransforms` onto `mesh.position` yet; once a `LiveTransformSystem` does that, this exception goes away. Conditions: - **Live-drag exception for direct mesh transforms.** During an active drag a tool may apply a transform offset directly to `sceneRegistry.nodes.get(id).position`/`rotation`/`scale` *when and only when* the same offset is mirrored into `useLiveTransforms` for that node. This exception exists because the 3D renderers don't reconcile `useLiveTransforms` onto `mesh.position` yet; once a `LiveTransformSystem` does that, this exception goes away. Conditions:
- The mesh offset must mirror the `useLiveTransforms` entry (same delta on both), so anything reading `useLiveTransforms` sees the same preview as the 3D view. - The mesh offset must mirror the `useLiveTransforms` entry (**same exact value**, not "same conceptual translation"), so anything reading `useLiveTransforms` sees the same preview as the 3D view. `ParametricNodeRenderer` (used for every kind that ships `def.geometry`) binds `<group position={liveTransform.position}>` via React — every Zustand notification re-renders and reconciles the group's position back to whatever value `useLiveTransforms` holds. If `mesh.position.set(delta)` and `useLiveTransforms.set({ position: someOtherValue })` disagree, the two writes fight every frame and the user sees jitter during the drag.
- **For position-based kinds (spawn / item / column)**: the `position` field on the node IS the group's local-frame position, so `useLiveTransforms.position` should hold the live world position of the node (matches the eventual `scene.update`).
- **For polygon-based kinds (slab / fence / ceiling / wall)**: the node has no `position` field — the canonical group position is `[0,0,0]` with geometry built in level-local coords. `useLiveTransforms.position` must hold the **delta** the tool wants to translate by (`[deltaX, 0, deltaZ]`), not the world location of the polygon's center. The cursor sphere position (which IS the translated polygon center) is tracked separately via React `useState`, not `useLiveTransforms`.
- The offset must be cleared on tool unmount, cancel, *and* commit — both `mesh.position.set(0, 0, 0)` and `useLiveTransforms.clear(id)`. - The offset must be cleared on tool unmount, cancel, *and* commit — both `mesh.position.set(0, 0, 0)` and `useLiveTransforms.clear(id)`.
- The tool must not generate or mutate geometry in this path — only transform writes. Geometry generation still belongs in a core system. - The tool must not generate or mutate geometry in this path — only transform writes. Geometry generation still belongs in a core system.
- **No business logic in tools** — delegate geometry/constraint rules to core systems. - **No business logic in tools** — delegate geometry/constraint rules to core systems.