Phase 4 follow-on: registry-driven floor-plan rendering + shelf port

Adds the floor-plan side of the three-checkbox composition model
documented in wiki/architecture/node-definitions.md. Mirrors the 3D
side (def.geometry → <GeometrySystem> → <ParametricNodeRenderer>) but
emits SVG primitives instead of three.js Object3Ds, and runs inside
the floor-plan panel.

Type-side (packages/core/src/registry/types.ts):
 - New FloorplanGeometry tagged union covering path / polygon /
   polyline / rect / circle / line / group. FloorplanStyle props map
   straight to SVG attributes. Coordinates are level-local meters;
   rotations are radians (three.js convention).
 - New `def.floorplan?: (node, ctx) => FloorplanGeometry | null` field
   on NodeDefinition, independent of `geometry` and `renderer`. Re-
   exported via packages/core/src/registry/index.ts.

Runtime (packages/editor):
 - <FloorplanGeometryRenderer> walks the FloorplanGeometry tree and
   emits the matching React-SVG elements. Pure data → DOM; no per-kind
   logic.
 - <FloorplanRegistryLayer> reads the active levelId, walks the level
   subtree, looks up each node's def.floorplan, builds a
   GeometryContext, calls the builder, and renders the output via
   FloorplanGeometryRenderer.
 - Mounted in floorplan-panel.tsx just before <FloorplanMarqueeLayer>
   so registry-driven kinds layer above legacy inline content.

Shelf migration (proof port):
 - New nodes/src/shelf/floorplan.ts — buildShelfFloorplan(node) emits
   a group with the rotation/translation transform and a width × depth
   rectangle in the shelf's color. Brackets omitted (hidden under top
   board from above).
 - Wired to shelfDefinition.floorplan. Shelf now appears in the floor
   plan view for the first time (was missing from the legacy panel's
   inline switch).

Pattern proven; every future kind migrating in Phase 5 follows the
same shape: a pure (node, ctx) => FloorplanGeometry function. As kinds
register their floor-plan builders, the corresponding inline branches
in floorplan-panel.tsx become dead code and can be deleted in the
same PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-05-15 10:34:42 -04:00
co-authored by Claude Opus 4.7
parent 3f3818f3b0
commit ac36297e7e
7 changed files with 385 additions and 7 deletions
+3
View File
@@ -22,6 +22,9 @@ export type {
CuttableConfig,
DragAction,
EditorCtx,
FloorplanGeometry,
FloorplanPoint,
FloorplanStyle,
GeometryContext,
HostableConfig,
IconRef,
+67
View File
@@ -26,6 +26,58 @@ export type GeometryContext = {
parent: AnyNode | null
}
// ─── FloorplanGeometry ───────────────────────────────────────────────
//
// Output shape for `def.floorplan(node, ctx)`. The floor-plan panel
// converts these primitives to React-SVG elements via a generic renderer
// — kinds never touch SVG nodes directly. Coordinates are level-local
// meters; the panel handles world→SVG transform via its viewBox.
//
// Visual styling lives in the geometry so an AI-authored kind can pick
// its own colors without needing to know about CSS / theme tokens. The
// renderer maps these directly to SVG attributes.
export type FloorplanPoint = readonly [x: number, y: number]
export type FloorplanStyle = {
stroke?: string
fill?: string
strokeWidth?: number
strokeDasharray?: string
opacity?: number
}
export type FloorplanGeometry =
| ({ kind: 'path'; d: string } & FloorplanStyle)
| ({ kind: 'polygon'; points: readonly FloorplanPoint[] } & FloorplanStyle)
| ({
kind: 'polyline'
points: readonly FloorplanPoint[]
} & FloorplanStyle)
| ({
kind: 'rect'
x: number
y: number
width: number
height: number
rx?: number
ry?: number
} & FloorplanStyle)
| ({ kind: 'circle'; cx: number; cy: number; r: number } & FloorplanStyle)
| ({
kind: 'line'
x1: number
y1: number
x2: number
y2: number
} & FloorplanStyle)
| {
kind: 'group'
children: FloorplanGeometry[]
/** Optional transform applied to all children. Rotation in radians. */
transform?: { translate?: FloorplanPoint; rotate?: number }
}
// ─── Plugin manifest ─────────────────────────────────────────────────
export type Plugin = {
@@ -76,6 +128,21 @@ export type NodeDefinition<S extends ZodObject<any>> = {
* work (animations, named-mesh material poking).
*/
geometry?: (node: z.infer<S>, ctx: GeometryContext) => Object3D
/**
* Pure 2D builder for floor-plan rendering. Mirrors `geometry` but emits
* plain `FloorplanGeometry` data (SVG-renderable) rather than three.js
* Object3D. Coordinates are level-local meters — the floor-plan panel
* applies the world→SVG transform.
*
* Returns `null` when the kind shouldn't appear in floor plan (e.g. an
* invisible utility node, or a kind that's 3D-only). Kinds that need
* floor-plan rendering but no 3D mesh set `floorplan` without `geometry`.
*
* See `wiki/architecture/node-definitions.md` ("floor-plan rendering"
* section) and Phase 5 of the registry plan for the migration plan off
* the legacy `floorplan-panel.tsx` monolith.
*/
floorplan?: (node: z.infer<S>, ctx: GeometryContext) => FloorplanGeometry | null
system?: SystemContribution
tool?: LazyComponent
affordances?: Affordance<z.infer<S>>[]