From c3e255b58ab025224845bae16b7b75c80a842e06 Mon Sep 17 00:00:00 2001 From: Wassim SAMAD Date: Mon, 18 May 2026 09:47:39 -0400 Subject: [PATCH] wiki(tools): clarify useLiveTransforms.position semantics for polygon kinds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- wiki/architecture/tools.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wiki/architecture/tools.md b/wiki/architecture/tools.md index e933979f..a1a20f7d 100644 --- a/wiki/architecture/tools.md +++ b/wiki/architecture/tools.md @@ -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. - **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 `` 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 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.