← All articles

Floorcraft: A Floor Plan You Can Talk To, Where the AI Never Draws

August 31, 2026 · Artificial Intelligence, CAD, Computational Geometry, Constraint Solvers, MCP, Cloudflare Workers

Decades ago, CAD software blew my mind: vector diagrams with real dimensions that never went blurry, no matter how far you zoomed.

Lately I have been working on getting AI to understand floor plans. Then it hit me — the fun version is the opposite. What if I could just type “create a floor plan with a kitchen and dining room, a family room and an office” and get a real, editable drawing? And then keep going: “increase the kitchen by 20%.”

That became Floorcraft. The whole thing rests on one rule: the AI never draws anything.

Try Floorcraft live or browse the source code on GitHub.

A two-bedroom plan generated by Floorcraft, with dimensioned rooms, doors, windows and street frontage

The tell

That claim is easy to make and easy to fake, so here is the tell. Ask for a kitchen that cannot work and you get “Kitchen cannot fit in 4×5 with the required appliance clearances; suggest 4×6 or 5×5” — not a plan with a wall through the fridge. No model wrote that sentence. A solver did, because the model was never holding the geometry.

Ask a model for coordinates and you get plausible nonsense: walls that miss each other, numbers that drift 40mm and leave a gap in a corner. So Floorcraft does not ask. What follows is the accounting — including where the design strains.

The model does not see the drawing

The obvious build hands the model the plan and asks for a new one. That fails immediately, because a wall graph is hundreds of coordinates and every one of them is a chance to be slightly wrong.

So the model never receives the wall graph. It gets a digest: the room list with programs and approximate areas, which rooms touch which, which face the exterior, and the layout tree. That is it — about 600 tokens for a twenty-room level.

That number is the whole architecture. A 600-token input fits a model running on your laptop’s own silicon, which is why the on-device tier is real rather than a demo that quietly needs a frontier API.

The model can only say fourteen things

What comes back is neither a drawing nor prose. It is a patch: operations from a closed vocabulary — addRoom, resizeRoom, swapRooms, setSplit, setDimension, and nine more. Fourteen, plus a few for levels. Anything conversational travels in a separate narration field, where it cannot contaminate the structured part.

The vocabulary being closed is the point. There is no operation for “put a wall here,” so there is no way to put a wall in a silly place. The worst a bad response can do is ask for a room that does not fit, and that has a defined answer.

Every patch is validated against the schema and the solver’s preconditions before it touches the document. On failure the error text goes back into the prompt and the model retries — twice, because one retry left ordinary requests dying on the on-device model’s first bad parse. You only learn that from real turns. After two failures the plan is left untouched, with a plain-language explanation, because a patch that was never asked for cannot be repaired into a good one.

Adding a capability costs exactly three edits: schema, reducer, prompt fixture. If a feature needs a fourth, the architecture is wrong — and I would rather find that out at the schema.

The fast path skips the model entirely

Before any provider is invoked, the utterance goes through two deterministic passes.

First, a dimension parser: “kitchen is 4×5 feet,” “living room at least 300 sq ft.” Those are extracted, converted to canonical integer millimetres, and turned into operations directly. They are facts, not suggestions; there is no reason to pay a model to re-read a number the user already typed.

Second, an intent matcher on whatever remains: rename, delete, swap, resize by percentage, change units, add a room of a known program.

The two compose. “Make the kitchen 5×6 feet and add a pantry” pins the kitchen deterministically and asks the model only to place the pantry, with the handled fragment stripped so it cannot be applied twice. Plenty of turns never reach a model at all: instant, free, offline.

The solver is where correctness lives

Rooms are not stored as coordinates. A level is a slicing tree — recursive horizontal and vertical cuts of the outer boundary — that the solver evaluates into rectangles. The structure guarantees what would otherwise need checking: no overlaps, no gaps, no zero-area rooms, for any valid tree. Not validated after the fact. Unrepresentable.

On top of it runs a refinement pass with a strict priority order. Per-program minimums and grid-snapped wall centerlines are required; circulation widths are strong; plumbing alignment — kitchens, baths and laundries preferring a shared wet wall — is medium; requested area ratios are weak and the first thing to give. When the required constraints cannot be satisfied, the solver returns a structured failure naming the conflicting rooms. That is the fridge answer above.

The whole evaluation has a 16ms budget, one animation frame, because of what comes next.

Dragging a wall is the same operation as saying it

That 16ms is what lets you drag. A wall drag does not move geometry; it edits the ratio of one split and re-solves the level live. The canvas receives each cut line along with the range it may travel before a child room falls under its minimum, so an invalid drag is prevented rather than corrected. Pin a room to an exact dimension and its walls stop moving, with a lock indicator showing which constraint is refusing you.

The tree also knows its own limits. A drag that no arrangement of cuts can express — an L-shaped room, a courtyard, an interior core — detaches the level into freeform geometry, where node coordinates move directly. The generated layout is kept, so you can go back.

Because gestures and sentences both become patches against the same document, undo works identically across both. There is no “manual mode” the AI cannot see, and no AI action that stomps hand-work.

Once a plan is one well-defined document, features stop being features. Exports to SVG, PDF, DXF, JSON, IFC4 and glTF are readers of the same structure, so nothing is locked inside my app. Multi-storey is a list of levels, so stair-core alignment is a check across two graphs with a one-click fix when they drift. Raster import — tracing a scanned plan with computer vision — proposes candidate walls that face the same validation as everything else, after you calibrate scale from two known points.

Four tiers, and the release-blocker

Inference has four tiers: an on-device model, a free hosted pool, OpenRouter via OAuth, or your own Anthropic, OpenAI or Google key. Your own keys live in localStorage, are redacted in one shared error serializer rather than at each call site, and where the provider allows browser-origin requests the client calls it directly, removing my server from the trust path. Fallback is silent when a failure is transient and explicit when a quota is exhausted, because those mean very different things mid-drawing.

And the one I treated as release-blocking rather than a nicety: with every tier unavailable, the app must remain a fully functional editor. Chat greys out with an explanation. Nothing else changes. If losing the model cost you your drawing, the split between model and machine was never real.

The fifth answer: ship the app to their model

Every tier trades something. The on-device model is free but literal. The good models cost money — mine or yours. And pasting an API key is friction most people never push through.

Then the obvious thing hit me: everyone willing to bring a key already pays for an assistant, and that assistant already speaks MCP. So I stopped trying to bring a model to my app and shipped my app to their model.

The Floorcraft MCP server exposes the plan engine as tools: create_plan, apply_patch, validate_plan, render_svg, export_plan. It runs no model at all — zero inference, zero tokens on my bill. The agent does the fuzzy part, turning “a two-bed cottage, kitchen facing the street, bath off the hallway” into a structured room programme. The server does the exact part: geometry, constraints, validation, export.

What came back was better than any tier I had built. Frontier-model reasoning funded by a subscription the user already has. Messy paragraphs turned into coherent tool calls. Noticeably higher-quality plans than my own prompt loop produced. And it lives next to their other tools, so a brief can be read from a document, built into a plan, and dropped into an email in one thread.

Connecting takes about thirty seconds. In Claude web or desktop: Settings → Connectors → Add custom connector, then paste https://floorcraft.troche.workers.dev/mcp. From the command line: claude mcp add --transport http floorcraft https://floorcraft.troche.workers.dev/mcp. That is anonymous mode — full building, editing and export, nothing stored. To work on a saved plan instead, open it in the web app, hit Share, and append the edit link’s token to the connector URL as ?t=<token>; every edit the agent makes then appears live in your browser tab.

Same document. Same solver. Two front doors.

Where it strains

Two places, honestly.

The slicing tree is a strong constraint, and strong constraints have edges. Most homes are expressible as recursive cuts; the ones that are not hit the freeform escape hatch, where the guarantees I have been bragging about get weaker. Freeform geometry is checked, not structurally impossible to get wrong.

And deterministic parsing is a treadmill. Every phrasing I add is one fewer model call, but natural language does not converge — there is always another way to say “make it bigger.” The fast path is an optimization with a long tail, not a replacement for inference.

Neither changes the shape of the thing. The model reads intent; the engine owns the truth.

The lesson is not really about floor plans. It is about giving the model the one thing it is genuinely great at — understanding what a human meant — and letting a deterministic engine be exactly right about everything else.

Try Floorcraft and explore the code

Open Floorcraft or browse the source code on GitHub.