Files
editor/packages/mcp/src/templates/empty-studio.ts
T
Jaafar El HarouchiandClaude Sonnet 4.6 3e8b972b8f fix(mcp): templates use string IDs for SiteNode.children
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>
2026-05-21 15:32:12 +01:00

255 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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