Blueprint Designer#
The Blueprint Designer is a god's-eye map editor: a pan/zoom canvas where each room is a box and each connector is a door between two rooms. It's a graph-level view of the game's map, deliberately shallow — it never touches what's inside a room (that's the Room Editor's territory: hotspots, walkable areas, regions). Its whole job is authoring which rooms exist and how they connect, then materializing that graph into real room files on demand.
You reach it from the Hub (dev server running). The toolbar has three tools — Select, Room (drop a box), Connect (draw a link, with ida/vuelta/bidi direction buttons and a pared/libre connector-kind switch) — plus Undo/Redo, Auto-arrange, Fit, a diagram Scale selector (1:16 / 1:8 / 2:8 / 4:8), and the two save actions: Save Design and Run Scaffolding.
Source of truth stays with the rooms#
projects/<id>/blueprint.json is layout only — box positions, connector routing hints,
and (for not-yet-created rooms) a pending "stub" description. It never becomes the source of
truth for room content: the actual hotspots, walkable areas, and regions always live in
projects/<id>/rooms/<slug>.js, exactly where the Room Editor writes them. Opening the
Blueprint Designer reads every room file, derives one box per room and one connector per
existing warp region, and merges that with whatever positions were last saved — a room with
no saved position lands on an auto-grid. This makes the tool safe to abandon mid-design:
worst case you lose some box arrangement, never room content.
A door can also be authored without a warp region — as a hotspot reaction, an onEnter
chain, or a warp inside a watcher or cutscene. Those are real exits, and the diagram counts them
in the status line and lists them per room in the properties panel (each one naming where it
lives), but it does not draw them as connectors. A connector here is region-backed — Run
Scaffolding upserts and deletes regions by stable id — so a drawn line with no region behind it
would offer a "remove connection" that couldn't work. Reach for the Room
Editor to edit those, or draw a connector here and scaffold it to get a real
warp zone alongside.
Stubs: boxes at real scale#
A box for a room that doesn't exist yet is a stub. Stubs are drawn as a literal scale
model of the room's real pixel dimensions, at a fixed 1:8 ratio (adjustable via the
toolbar's Scale selector, which only changes the diagram's zoom, not the room) — a
1920×1080 room draws as a 240×135 box. Dragging a stub's resize handle edits its real
realW/realH directly, gated by two scrollX/scrollY toggles (off by default: a stub
starts as exactly one screen, and you opt in per axis before it can grow into a scrolling
room). A stub's default height comes from the project's playable area — roomViewport.h
when the game reserves a GUI strip, or the full resolution height for a GUI-less/full-screen
game — with a per-stub guiVisible override for mixed projects (e.g. full-screen cutscene
rooms in an otherwise GUI'd game).
Once a stub is materialized (see Run Scaffolding below), its box locks: the room's .js
is now the source of truth for its dimensions, so resizing the box is purely cosmetic and no
longer writes anything. Its two scroll checkboxes go read-only for the same reason, and they
report what the room file actually says — X is on unless the room turned it off, Y is off
unless the room turned it on, exactly as the engine reads them.
When a room is bigger than the window its camera can reach, the panel says so in red: this room is 1024px tall and 276px never show. The diagram is where that's obvious — the box is visibly taller than the screen it sits next to — and Run Scaffolding can fix it (below).
Connector kinds: pared vs libre#
A connector between two rooms is either:
- pared ("wall", the default) — anchors to whichever side of the box you drag across
(nearest of up/down/left/right), at whatever point along that wall you released. This is a
real door: it generates a domino pair of regions — a warp region flush to the wall on
the source room, and an entrance region just inside the destination room — wired with a
WARPTOROOM:<room>|<entrance>token and matching facing. - libre ("free") — no wall, no domino. You drop the anchor at any point inside the box and the door pair gets placed there instead, floor-trigger style. This is the shape SCUMM floor warps actually take (a room-wide "walk off this edge" trigger with no wall to hug), and it's also how you'd model a pseudo-room like an "outside" node that every room can warp to without a literal shared wall.
Direction is independent of kind: ida (A→B one-way), vuelta (B→A one-way), or bidi (both directions, generating both dominoes). When the editor derives connectors from existing warps on load, it classifies each one automatically — a warp whose polygon hugs a wall (within roughly 12% of an edge) reads as pared at that wall position; anything more central reads as libre at its true point. This replaced an earlier "nearest wall" guess that mis-anchored floor-trigger warps sitting in the middle of very wide rooms.
Orthogonal routing#
Connectors route in right angles rather than a diagonal line: each end exits perpendicular to its anchor (the wall's normal for pared, the dominant axis toward the other room for libre), then the two stubs join with at most two bends — a middle jog if both exits run the same axis, a single elbow if they're mixed. The route recomputes live as you drag either box, and you can also grab a connector's midpoint handle and drag it to bend the route through a manual waypoint — pared anchors keep their fixed wall exit regardless, but a libre anchor re-aims itself toward wherever you dragged. Obstacle avoidance (routing around other boxes) is not implemented — routes can cross unrelated boxes; only the corner count is minimized.
Save Design vs Run Scaffolding#
This is the load-bearing split in the tool:
- Save Design writes only
blueprint.json— box positions, scroll-axis hints, stub descriptions, connector kind/mode/anchors. It never touches a room.jsfile. You can design freely, save constantly, and nothing on disk outside the layout file changes. - Run Scaffolding is the explicit, confirm-gated action that actually materializes the
design: it writes a real
.jsfor every stub, and generates the domino (or free-point) regions for every connector not yet backed by a warp. It opens a summary modal first — naming exactly which rooms will be created and how many existing rooms will be touched — before anything is written.
The contract that makes this safe to run repeatedly: existing rooms are only ever appended to, never rewritten. Connecting a new stub to a room that already exists adds that room's entrance/warp region (an upsert by stable id) without disturbing anything else already authored there — its hotspots, walkable areas, other regions all survive untouched. The two opt-in checkboxes below are the only exceptions, and each says so on its own label: they change one field in the rooms they name, and they start unchecked. Scaffolding a design twice is idempotent: already-materialized stubs and already-generated connectors are skipped, so re-running only picks up what's new since the last pass.
Walkable-prototype opt-in#
Run Scaffolding's confirm dialog has an unchecked-by-default checkbox: generate prototype walkable areas. When on, any room touched by this scaffold pass that has no existing walkable area gets one simple axis-aligned rectangle — a default lower-middle floor band, grown to also cover the bounding box of every door region (warp and entrance) the pass just added — so the room is walkable end-to-end (you can spawn on the entrance and reach every exit) without hand-drawing a polygon first. It's a starting point meant to be refined in the Room Editor, not a final walkable area. The check is per room and non-destructive: a room that already has a floor (even a single triangle) is left completely alone and the helper returns nothing for it, so re-running scaffolding with the checkbox on never overwrites hand-authored walkable geometry.
Doors that would land off the floor#
A door region is a trigger the player has to walk into, so a door with no walkable floor under it is inert — it looks authored and does nothing. Scaffolding anchors a pared door to the wall you dragged across, and a wall is exactly where a floor usually isn't.
So the confirmation dialog names them in amber before you commit: 4 door(s) will land outside their room's walkable area, so they can never be stepped on, with the room, the region id and the coordinates. It only warns — it never touches the floor. Growing someone's hand-drawn walkable area to swallow a door is precisely the rewrite the append-only contract exists to forbid, so moving the door (or extending the floor) is a Room Editor decision. Rooms with no floor at all are left out of the warning: the prototype-walkable checkbox above already covers those, and it grows its rectangle over the door bounding boxes on purpose.
The check is a dry run of the real thing rather than a prediction: it asks the same door-placement code where the doors will land, and the same point-in-polygon test the running game uses whether the floor is under them — so what the dialog warns about is what actually gets written. A door counts as reachable if any corner or its centre touches the floor, because a partial overlap is still a door you can step into.
Camera-scroll opt-in#
The diagram knows something no other tool is positioned to notice: whether a room is bigger than the window its camera can ever reach. Vertical scroll is off unless a room turns it on, so a room taller than the playable area hides its far end for good — and that band is usually where the floor got painted.
Run Scaffolding's second unchecked-by-default checkbox offers the fix, naming every affected
room and how much it hides: turn on vertical camera scroll on 2 room(s)… QA Room 1 (276px).
Ticking it writes scrollY into those room files — the one field the runtime camera reads —
and the summary reports it on its own line. Afterwards the Room Editor's Validate goes quiet
about those rooms, because the diagram and the validator judge reachability with the same
shared model.
It is an offer, never automatic, and only for the vertical axis. Both restrictions are
deliberate: a tall background deliberately framed by the room's viewport origin is a real
technique, so turning scroll on by fiat would break a legitimate choice; and horizontal
scroll is already on by default, so a wide room with scrollX explicitly off is someone
saying they want it framed.
Workflow#
- Open the Blueprint Designer — existing rooms appear as locked, real-scale boxes automatically; existing warps appear as connectors.
- Use the Room tool to drop new stub boxes (existing-room picker or a brand-new stub, named on drop) and resize them to set their real dimensions (gated by scrollX/scrollY).
- Use the Connect tool to link boxes: pick a direction (ida/vuelta/bidi) and a kind (pared/libre), then drag from one box to another.
- Auto-arrange to un-pile a tangled layout (force-directed: separates overlapping boxes, pulls connected rooms closer), then Fit to frame everything.
- Save Design as often as you like — it's always safe, box-only.
- When ready to turn stubs and pending connectors into real content, Run Scaffolding, review the confirmation summary (and opt into prototype walkable areas if useful), and confirm.
- Open the newly-scaffolded rooms in the Room Editor to add real backgrounds, hotspots, and refine or replace the prototype floor.
Nothing here is real until Run Scaffolding. Dropping stubs, drawing connectors, even hitting Save Design — none of it writes a room file. The diagram is a proposal that lives entirely in
blueprint.jsonuntil you explicitly ask the tool to materialize it, and even then it only ever adds to what exists. If a session ends mid-design without a Save, the worst case is losing unsaved box positions — the moment something looks wrong after Scaffolding, the room files themselves are the place to check, since they're the only thing that was ever actually written.