PR #320 changed SiteNode.children from embedded BuildingNode/ItemNode objects to flat string[] IDs. PR #325 updated the runtime call sites but missed the three scene templates, which still mutated the site node's children array to embed full building objects after building the flat dict. This caused AnyNode.safeParse to fail for site_empty, site_2br, and site_garden in bun test --cwd packages/mcp. Remove the obsolete mutation blocks; each template already initialises site.children with the correct string id (e.g. ['building_empty']). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
255 lines
5.9 KiB
TypeScript
255 lines
5.9 KiB
TypeScript
import type { SceneGraph } from '@pascal-app/core/clone-scene-graph'
|
||
import type { AnyNode, AnyNodeId } from '@pascal-app/core/schema'
|
||
|
||
/**
|
||
* 40 m² studio apartment — a single open room with one window, one front door
|
||
* and a single "Living/Kitchen" zone. Used as a starting point for small unit
|
||
* briefs. Deterministic ids (`site_empty`, `building_empty`, `level_0`, etc.)
|
||
* are regenerated by the MCP tool via `cloneSceneGraph` before applying.
|
||
*/
|
||
|
||
// Footprint: 8 m × 5 m = 40 m² (centered at origin).
|
||
// Walls traverse the boundary counter-clockwise (right-handed XZ plane).
|
||
const W = 4 // half-width
|
||
const D = 2.5 // half-depth
|
||
|
||
type StudioNodes = {
|
||
site: AnyNode
|
||
building: AnyNode
|
||
level: AnyNode
|
||
walls: AnyNode[]
|
||
zone: AnyNode
|
||
door: AnyNode
|
||
window: AnyNode
|
||
}
|
||
|
||
function buildNodes(): StudioNodes {
|
||
const site: AnyNode = {
|
||
object: 'node',
|
||
id: 'site_empty' as AnyNodeId,
|
||
type: 'site',
|
||
parentId: null,
|
||
visible: true,
|
||
metadata: {},
|
||
polygon: {
|
||
type: 'polygon',
|
||
points: [
|
||
[-15, -15],
|
||
[15, -15],
|
||
[15, 15],
|
||
[-15, 15],
|
||
],
|
||
},
|
||
children: ['building_empty' as AnyNodeId],
|
||
} as unknown as AnyNode
|
||
|
||
const building: AnyNode = {
|
||
object: 'node',
|
||
id: 'building_empty' as AnyNodeId,
|
||
type: 'building',
|
||
parentId: 'site_empty' as AnyNodeId,
|
||
visible: true,
|
||
metadata: {},
|
||
position: [0, 0, 0],
|
||
rotation: [0, 0, 0],
|
||
children: ['level_0' as AnyNodeId],
|
||
} as unknown as AnyNode
|
||
|
||
// Walls with deterministic ids; child ids are listed below after doors/windows
|
||
// are created, so we fill this array after computing them.
|
||
const wallIds = ['wall_n', 'wall_e', 'wall_s', 'wall_w'] as const
|
||
|
||
// South wall carries the front door; west wall carries the window.
|
||
const door: AnyNode = {
|
||
object: 'node',
|
||
id: 'door_front' as AnyNodeId,
|
||
type: 'door',
|
||
parentId: 'wall_s' as AnyNodeId,
|
||
visible: true,
|
||
metadata: {},
|
||
wallId: 'wall_s',
|
||
position: [0, 1.05, 0],
|
||
rotation: [0, 0, 0],
|
||
width: 0.9,
|
||
height: 2.1,
|
||
frameThickness: 0.05,
|
||
frameDepth: 0.07,
|
||
threshold: true,
|
||
thresholdHeight: 0.02,
|
||
hingesSide: 'left',
|
||
swingDirection: 'inward',
|
||
segments: [
|
||
{
|
||
type: 'panel',
|
||
heightRatio: 0.4,
|
||
columnRatios: [1],
|
||
dividerThickness: 0.03,
|
||
panelDepth: 0.01,
|
||
panelInset: 0.04,
|
||
},
|
||
{
|
||
type: 'panel',
|
||
heightRatio: 0.6,
|
||
columnRatios: [1],
|
||
dividerThickness: 0.03,
|
||
panelDepth: 0.01,
|
||
panelInset: 0.04,
|
||
},
|
||
],
|
||
handle: true,
|
||
handleHeight: 1.05,
|
||
handleSide: 'right',
|
||
contentPadding: [0.04, 0.04],
|
||
doorCloser: false,
|
||
panicBar: false,
|
||
panicBarHeight: 1.0,
|
||
} as unknown as AnyNode
|
||
|
||
const windowNode: AnyNode = {
|
||
object: 'node',
|
||
id: 'window_w' as AnyNodeId,
|
||
type: 'window',
|
||
parentId: 'wall_w' as AnyNodeId,
|
||
visible: true,
|
||
metadata: {},
|
||
wallId: 'wall_w',
|
||
position: [0, 1.2, 0],
|
||
rotation: [0, 0, 0],
|
||
width: 1.5,
|
||
height: 1.2,
|
||
frameThickness: 0.05,
|
||
frameDepth: 0.07,
|
||
columnRatios: [1],
|
||
rowRatios: [1],
|
||
columnDividerThickness: 0.03,
|
||
rowDividerThickness: 0.03,
|
||
sill: true,
|
||
sillDepth: 0.08,
|
||
sillThickness: 0.03,
|
||
} as unknown as AnyNode
|
||
|
||
const wallNorth: AnyNode = {
|
||
object: 'node',
|
||
id: 'wall_n' as AnyNodeId,
|
||
type: 'wall',
|
||
parentId: 'level_0' as AnyNodeId,
|
||
visible: true,
|
||
metadata: {},
|
||
children: [],
|
||
thickness: 0.1,
|
||
height: 2.5,
|
||
start: [-W, -D],
|
||
end: [W, -D],
|
||
frontSide: 'unknown',
|
||
backSide: 'unknown',
|
||
} as unknown as AnyNode
|
||
|
||
const wallEast: AnyNode = {
|
||
object: 'node',
|
||
id: 'wall_e' as AnyNodeId,
|
||
type: 'wall',
|
||
parentId: 'level_0' as AnyNodeId,
|
||
visible: true,
|
||
metadata: {},
|
||
children: [],
|
||
thickness: 0.1,
|
||
height: 2.5,
|
||
start: [W, -D],
|
||
end: [W, D],
|
||
frontSide: 'unknown',
|
||
backSide: 'unknown',
|
||
} as unknown as AnyNode
|
||
|
||
const wallSouth: AnyNode = {
|
||
object: 'node',
|
||
id: 'wall_s' as AnyNodeId,
|
||
type: 'wall',
|
||
parentId: 'level_0' as AnyNodeId,
|
||
visible: true,
|
||
metadata: {},
|
||
children: ['door_front'],
|
||
thickness: 0.1,
|
||
height: 2.5,
|
||
start: [W, D],
|
||
end: [-W, D],
|
||
frontSide: 'unknown',
|
||
backSide: 'unknown',
|
||
} as unknown as AnyNode
|
||
|
||
const wallWest: AnyNode = {
|
||
object: 'node',
|
||
id: 'wall_w' as AnyNodeId,
|
||
type: 'wall',
|
||
parentId: 'level_0' as AnyNodeId,
|
||
visible: true,
|
||
metadata: {},
|
||
children: ['window_w'],
|
||
thickness: 0.1,
|
||
height: 2.5,
|
||
start: [-W, D],
|
||
end: [-W, -D],
|
||
frontSide: 'unknown',
|
||
backSide: 'unknown',
|
||
} as unknown as AnyNode
|
||
|
||
const zone: AnyNode = {
|
||
object: 'node',
|
||
id: 'zone_living' as AnyNodeId,
|
||
type: 'zone',
|
||
parentId: 'level_0' as AnyNodeId,
|
||
visible: true,
|
||
metadata: {},
|
||
name: 'Living / Kitchen',
|
||
color: '#60a5fa',
|
||
polygon: [
|
||
[-W, -D],
|
||
[W, -D],
|
||
[W, D],
|
||
[-W, D],
|
||
],
|
||
} as unknown as AnyNode
|
||
|
||
const level: AnyNode = {
|
||
object: 'node',
|
||
id: 'level_0' as AnyNodeId,
|
||
type: 'level',
|
||
parentId: 'building_empty' as AnyNodeId,
|
||
visible: true,
|
||
metadata: {},
|
||
level: 0,
|
||
children: [...wallIds, 'zone_living'] as AnyNodeId[],
|
||
} as unknown as AnyNode
|
||
|
||
return {
|
||
site,
|
||
building,
|
||
level,
|
||
walls: [wallNorth, wallEast, wallSouth, wallWest],
|
||
zone,
|
||
door,
|
||
window: windowNode,
|
||
}
|
||
}
|
||
|
||
function buildTemplate(): SceneGraph {
|
||
const n = buildNodes()
|
||
const nodes: Record<AnyNodeId, AnyNode> = {}
|
||
for (const node of [n.site, n.building, n.level, ...n.walls, n.zone, n.door, n.window]) {
|
||
nodes[node.id as AnyNodeId] = node
|
||
}
|
||
|
||
return {
|
||
nodes,
|
||
rootNodeIds: ['site_empty'] as AnyNodeId[],
|
||
}
|
||
}
|
||
|
||
export const template: SceneGraph = buildTemplate()
|
||
|
||
export const metadata = {
|
||
id: 'empty-studio',
|
||
name: 'Empty studio',
|
||
description:
|
||
'40 m² single-room studio apartment: 4 walls, 1 living/kitchen zone, 1 window, 1 front door.',
|
||
} as const
|