Case study
Land2Build — Generated Floor Plans
A construction planner that turns a plot and a sentence into a walkable 3D house. The interesting decision is what the language model is not allowed to do: it reads the brief, but the floor plan is generated by a deterministic algorithm — which means the layout can be tested, and it is.
React/TypeScript/Three.js/React Three Fiber/Dexie / IndexedDB/Leaflet/Vitest/Vite
7
House types
standard through courtyard and L-shaped
35
Invariant checks
randomised runs, every house type
0
Backend services
3D, storage and maps all in-browser
3
External APIs
Nebius, Open-Meteo, Nominatim
01 Context
A plot, a sentence, a house.
Someone planning a house on a plot in India wants three things early and cheaply: what can I fit here, roughly what will it cost, and when can we pour concrete without the monsoon undoing it.
Each of those is normally a separate professional and a separate week. Land2Build takes a plot — located on a map, with a size and a foundation type — and a sentence describing the building, and answers all three in the browser: a walkable 3D model, a bill of materials in rupees, and a weather view for the build season.
There is no server anywhere in that. Projects live in IndexedDB, the 3D runs on the GPU through Three.js, and the only things it reaches out to are a geocoder, a weather API and a language model.
02 The line
The model does not draw it.
The obvious way to build “AI architecture” in 2026 is to send the brief to an image model and return a render. It demos extremely well. It is also useless for the actual question, because a picture of a house has no dimensions you can measure, no rooms you can count, and no guarantee that the bedroom it drew fits on the plot.
So the model here has one narrow job: read a sentence like “modern two-storey house with a flat roof” and return a typed configuration — width, length, floors, roof type, wall material, design style. That is a parsing problem, which is what language models are genuinely good at.
Everything downstream of that config is ordinary deterministic code. The model never sees the geometry, never places a room and never decides a dimension. It converts English into a struct, and then it is done.
03 Generation
Splitting a rectangle into rooms.
Floor plans are generated by binary space partitioning — the same idea roguelikes use to lay out dungeons, pointed at a house. Start with the building footprint as one rectangle, then recursively split it.
Left unconstrained that produces nonsense: corridors two metres long and forty centimetres wide. Three rules keep it habitable. A rectangle more than 1.25× wider than it is tall must split vertically, and vice versa, so proportions stay sane. The split point is random but confined to the middle fifth, so no child is a sliver. And recursion stops when another cut would breach the minimum room size.
The result is a set of rooms, each tagged with a purpose — living, kitchen, bedroom, bathroom, dining, hallway, utility — and with which of its four sides face outward. That last flag is what later decides where windows can go and where the front door belongs.
Seven house types drive different partitioning: a courtyard plan reserves its centre, an L-shape carves a corner, a duplex splits into two independent units.
04 Three dimensions
Walls that know what they are.
Extruding a plan into 3D is not just raising rectangles. Each wall needs to know whether it is an exterior or interior wall, whether it carries a window, and whether it holds the front door — which is exactly the information the generator attached to each room.
So walls are built from that metadata rather than modelled by hand: an exterior wall with a window gets a glazed opening, the room flagged with the entrance gets a framed door, and interior walls stay solid. Dimension lines and room labels are drawn from the same numbers, so the annotation cannot drift from the geometry — they are the same source.
05 Proof
You can test a plan. You cannot test a picture.
This is the payoff, and the reason the line in chapter two is drawn where it is. Because the plan is data produced by an algorithm, it has properties that can be asserted — and a randomised generator is exactly the kind of code that needs them.
- Inside the footprint — No room extends past the plot boundary
- No overlaps — Two rooms may share a wall but never occupy the same space
- Positive area — Every room has real width and length, never a degenerate sliver
- Unique identity — Every room carries an id nothing else uses
Those four properties are checked across twenty-five randomised runs for each of the seven house types — thirty-five test cases that would catch a partitioning bug the moment it appeared, including the intermittent kind that only shows up on one random seed in fifty.
None of that is available to an image model. You cannot assert that a diffusion output has non-overlapping rooms, because it does not have rooms — it has pixels that resemble them. Choosing an algorithm over a model cost some visual flair and bought the ability to prove the thing is correct.
7
House types
standard through courtyard and L-shaped
35
Invariant checks
randomised runs, every house type
0
Backend services
3D, storage and maps all in-browser
3
External APIs
Nebius, Open-Meteo, Nominatim
06 Retrospective
The costs are still a guess.
The cost estimate does not follow its own rule. Having argued that the model should not produce numbers, I let it produce the entire bill of materials — quantities, unit prices, totals. It returns plausible Indian market figures and I cannot verify any of them, which is precisely the failure mode I designed the geometry to avoid. It should be a rate table and a quantity takeoff derived from the plan the generator already produced; the model should only explain the result.
The plans have no circulation model. Partitioning guarantees rooms do not overlap, not that you can walk between them sensibly. A bedroom can end up reachable only through a bathroom, because nothing in the algorithm knows what a doorway is for. Adding adjacency rules to the partition step is the obvious next move.
Accounts are a local demo and now say so. Sign-up writes to IndexedDB with no server behind it. Passwords are hashed with PBKDF2 rather than stored, which fixes the worst of it, but it is a demo boundary and the README states that plainly instead of implying otherwise.
The source is open.
Every figure here is checkable against the repository, including the tests. Happy to talk through the partitioning algorithm or where the deterministic line should sit in a tool like this.