Skip to content
Felucca Online

Docs

Building editor

Decision #173. Built: the shared core (§ 6.1), the client's editor with its offline stage (#175, § 4.11), the furniture stock (#174, § 3.1), the server side and online wiring (#176, § 6.2–6.4) and town houses for sale (#177, § 1.2). How to test: § 11 (the editor offline) and § 12 (building online). Building is the game's first pillar (#77). Player houses on town plots already exist (#129, docs/buildings.md › Player houses): the plot holder picks one of the generator's designs and pays for it in materials. This document replaces that fixed pick with an editor that works like Build/Buy mode in The Sims. Construction itself stays slow and costs materials.

1. The idea

You draw a plan for your plot: the foundation and its shape, walls, floors, doors and windows, stairs, upper storeys, roofs, and the furniture. You can tear down part or all of what stands there now. You can start from a blank plot, from the house that stands there, or from a generated design. The editor shows the plan as it will stand: what you tear down is gone, what you add is drawn as the real thing, so you judge the house itself. A small line at the bottom says what the walls still cost after salvage, a discount for what the plan tears down: torn-down materials only lower the bill, they never come back to you. You commit the plan, and the plot becomes a construction site. You or anyone else carries the missing materials there; they are mined, chopped, crafted or bought at the town's exchange (#121). When the last material for the shell (foundation, walls, floors, stairs, roofs) arrives, the shell is raised at once: the torn-down parts crumble and the new ones stand, with the furniture of the plan. Furniture never travels in a bag: every colonist has one furniture stock for all towns, which crafting and buying fill, and the editor's catalog shows how many of each piece you have (§ 3.1). Pieces the plan uses but the stock lacks appear on their spot the moment you make or buy them. Later, missing pieces can post buy requests on an exchange (§ 8).

1.1 The lot

Land is given out as in The Sims: a lot, the town plot you hold (#121 § 7, its box from the town plan, #103/#111). The lot is yours to arrange: the house may take any part of it, any shape, set back from the street or filling it, and the rest is your yard. Whatever stands on the lot now (a house, or nothing) is only the starting point of the plan. The yard itself (fences and gates, paths, beds, trees, garden props, wells) is a separate editor in v2 (garden-editor); in this version the yard keeps the town's ground and dressing.

1.2 Town houses are for sale

NPCs need only a few buildings in a town, so most town buildings are lots for sale with their house standing (user directive). The town keeps the buildings its townsfolk work in (#123): the bank with the exchange (the banker), the temple (the healer), the barracks for its guards and one inn; every other building (houses, cottages, manors, shops, workshops, smithies, warehouses, extra inns) comes with its plot as a lot. Buying it is holding that plot (#121 § 7); the house on it is yours to keep, live in or rebuild with the editor, its pieces counted for salvage like any house. The town's still-empty plots (#103: 10–20 %) stay lots without a house.

Built (#177): the bake lists every building's lot with its generator spec (world.json, docs/towns.md › Town houses for sale); a lot with a house costs the plot's stake plus the house's materials in coin, paid to the town treasury; the buyer gets a deed and moves in with it at the house, whose record (design STANDING, uo_world::plots::standing_view) replaces the baked building: the server's arena and every client skip the baked statics whose building box is the record's (plots::replaces, Content::replaced). Moving out leaves the house standing, vacant, for sale again. Shopkeepers and smiths of sold buildings work at stalls around the exchange. Details: docs/economy.md § 7. Once the house record holds a built plan (§ 6.2), a town house's first plan is blueprint::from_building of its regenerated building.

How to test (with the baked world in assets/world/felucca):

cargo test --release -p uo-server --test town_houses -- --nocapture
cargo test --release -p uo-world felucca_town_houses
cargo run --release -p uo-server -- --world assets/world/felucca

then, in a client on that server (everyone is a GM with the dev login, #146): .give ME gold_coin 20000, .tele X Y to a town's exchange (the server test prints one: town, lot, house and price), open the market (B) and its land tab: lots show their house (kind, size, storeys) and price; Buy one (a stake first: add liquidity to a pool), open the house window (J), walk to the house and Move in. The baked building is replaced by your house (locked, signed with your name, unfurnished); Move out and Release put it back on sale. The test picks the first town house in its exchange's region and prints its town, lot and price.

 edit (draft) ──commit──▶ project: deliver materials ──bill covered──▶ RAISE (instant)
      ▲                         │ cancel: deliveries come back               │
      └──── edit again ─────────┘                                           ▼
                                    missing furniture appears as soon as the stock gets it

2. What we take from The Sims

The Sims (2–4) Build and Buy modes are the reference for how editing should feel. What makes them work:

  • Drag to build. The wall tool draws a run of walls from where you press to where you let go and snaps to the grid. A diagonal drag makes a diagonal wall. Dragging with the delete modifier (Ctrl) removes walls along the same line. The room tool drags out a rectangle and makes four walls and a floor in one move.
  • Rooms are found, not declared. Any area closed by walls is a room. Shift+click fills the whole room with a floor or paints all its walls.
  • Openings snap to walls. Doors and windows only go on walls. They follow the mouse along the wall, cut their own hole, and show red where they can't go.
  • Levels. Page Up/Down moves the storey you edit. Floors above follow the walls below. Stairs connect two levels and check that both ends are clear.
  • Walls up / cutaway / walls down decides how much of the walls you see, so you can work inside.
  • Auto roof puts a roof over a storey. You can also draw roofs by hand and change their style.
  • Foundation. A building can stand on a raised foundation whose shape comes from the footprint.
  • Buy mode. A catalog by room and function. An object follows the cursor with a green footprint, or red where it doesn't fit, and turns with , . or by dragging. The eyedropper picks up what's under the cursor, the sledgehammer removes anything, and the hand moves it.
  • Always reversible. Unlimited undo/redo. A running cost is always on screen. Rooms and whole houses can be saved to and placed from a library.
  • Pre-made rooms and houses let you start from something good and change it.

What we do differently, and why:

The SimsFeluccaWhy
Building is instant and paid in simoleonsThe shell is raised at once, but only after its materials are delivered. Furniture comes from the colonist's furniture stock, filled by crafting and buyingBuilding drives the economy: it is the main demand for gathered and crafted goods (#121, #129)
The game pauses in build modeThe world keeps running: you can be attacked while you edit, and your house stays as it is until the raiseIt's an MMO; the plan is a draft until it is raised
A free camera that turnsFixed camera looking north-west (#14)You always see the south and east faces, so the editor has wall views (§ 4.6) instead of rotation
Any lot, any priceOnly plots you hold (#121 § 7), no more than MAX_STOREYSLand is stake; height is a town rule
Free wallpaper and paintWalls come in material sets (#102: stone, brick, wood, log, plaster, sandstone, marble, fieldstone)Each set is a baked pixel-art sprite set; its material decides the cost
Free-form furnitureFurniture is a grid of tiles and facings (#51)Pixel-art sprites are baked in 4 facings

3. The life of a plan

Draft. Editing is free and private: only the owner and co-owners see a plot's draft. The client keeps the draft and checks it as you go. It is saved to the server on its own (a short while after the last change, and when the editor closes), so you can close the editor, log out or switch computers and carry on. There is one draft per plot. Plans you want to keep go to your library (§ 4.10).

Commit. The owner commits the draft (CommitPlan). The server runs the same checks the editor ran (§ 5) and freezes the plan as the plot's project:

  • Diff. The plan is compared with what stands, piece by piece, by stable piece identity (§ 6.3). Each piece is kept, removed (torn down) or added. If a piece's material or variant changes, the old one is removed and a new one added. A kept piece keeps its damage.
  • Bill = the materials of the added pieces (plots::piece_cost, the same costs as #129).
  • Salvage = for every removed piece, SALVAGE (50 %) of its materials × the share of its hits it still has, rounded down per material. Salvage is fixed at commit: whatever happens to those pieces before the raise doesn't change it. Furniture is never salvaged: it goes back to the furniture stock (§ 3.1).
  • Due = bill − salvage, per material, never below zero. Salvage is only a discount (user directive): the salvage of a material covers only that material, and whatever the bill doesn't need (stone from a torn-down wall when the new walls are timber) is simply lost. Nobody gets materials back from tearing down.

The old building stands and works until the raise: you live in it, your door still locks, and your chest is still there while you gather. A plan that only tears down, or whose salvage covers the whole bill, is raised as soon as it is committed.

Delivery. As today (#129): a crate and scaffolding stand at the front of the plot, and anyone can hand in materials (Deliver), but only what is still due. The house window shows what is due, what has been delivered, and what you carry. Materials can come from the pools at the town's exchange (#121); a "buy what's missing" button at the exchange is a small later step.

Change or cancel. Committing again with a changed draft gives a new project. Delivered materials stay counted against the new bill, and any surplus goes into the crate. Cancelling (CancelProject) puts everything delivered into the crate. Nothing is lost either way.

Raise. When the due list is empty, the plot's region applies the whole diff in one tick:

  1. Removed pieces break into debris the way destroyed statics do (#66, fall mode, no hits), and new pieces appear behind a puff of dust.
  2. Characters and mobs standing on a tile that now blocks are moved to the nearest free tile: inside the building if they were inside, else outside.
  3. Furniture the plan no longer has goes back to the owner's furniture stock; furniture the plan adds is taken from it and stands at once. Pieces the stock lacks wait (§ 3.1).
  4. The secure chest is one element of the plan (exactly one, placed with the furniture tool but paid in materials on the bill like the shell). If the plan moves it, its contents move with it; if the plan rebuilds it, its contents go into the crate.
  5. The house record now holds the new built plan. Clients rebuild the pieces from it, as they do with ObjectUpdate::building today.

Furniture-only plans. A plan that changes only furniture has nothing due, so it is raised at commit: rearranging a finished house is edit, commit, done.

3.1 The furniture stock

Furniture is good for one thing, furnishing a house, so it never sits in a backpack:

  • Every character has a furniture stock: a count per furniture item (plots::FURNITURE, items 901–914, Carpentry's stool and table). It is one stock for every town: nothing is hauled between towns or onto a site. It weighs nothing and is never looted from a corpse.
  • Crafting a piece of furniture puts it straight into the crafter's stock; buying one (the mainland store in Aquais today, players later) does too, and it can be placed at once.
  • The editor's furniture catalog shows, for every piece, how many are available to this plan: the stock, plus the pieces standing in this house now (a plan may move or keep them), minus what the draft already uses. A draft may use more than is available; the extra pieces show as missing in the catalog (the count goes red) and are the plan's furniture order.
  • At the raise, available pieces stand and missing ones wait. A waiting piece appears on its spot by itself as soon as the owner's stock gets one (crafted or bought), on any storey, since nobody has to walk there. Furniture removed by a plan, or broken off it, goes back to the stock or is gone, as #66 breaks it.
  • The plan draws on the owner's stock. Co-owners giving pieces from their own stock comes later.
  • Selling from the stock works wherever furniture is traded, in any town. That lets someone buy furniture in one town and sell it in another, moving value without carrying it. The loophole is accepted for now; the later fix is a lock on selling a piece for some days after it was bought (§ 8).
  • Furniture that lands in a bag some other way (loot, a GM's gift) moves into the stock on the next market sweep.

Built (#174). The stock is Character::furniture, a uo_proto::character::FurnitureStock (count, add, take, iter, total; serialized as a plain map {item id: count}, left out when empty). Which items are furniture is uo_proto::character::FURNITURE_ITEMS / is_furniture (a test in uo-world keeps it equal to plots::FURNITURE). It rides along with the character sheet (SheetView.sheet.furniture), so the owner gets it with the sheet and on every change; the SDK reads it with View::furniture / View::stocked, MCP agents in status, the client's EconMirror::furniture / furniture_count (what the editor's catalog reads) and the backpack window's Furniture stock block (count, sell 1 at an exchange in reach). On the server:

  • crafting a furniture item, buying one (the mainland store, or any town's pool with Swap), taking one back from a house (PickUpFurniture) or from a corpse puts it into the stock (Region::pack and the market's receive route furniture there); the crafter's mark is not kept, a piece is a piece;
  • SellFurniture { item, amount, limit } sells from the stock to the pool of the exchange within reach, any town, Aquais' crown pools included (a Swap sell of furniture takes from the stock too);
  • PlaceFurniture (#129) takes the piece from the stock (item; the old uid is ignored) until plan furniture replaces it;
  • a furniture item found in a bag moves into the stock when the character loads (Player::from_row) and on the market sweep every 2 s (a GM's .give, loot, a piece taken from a secure chest). Stolen pieces stay in the bag until their taint wears off, so the stock never launders plunder;
  • the transfer ledgers and the conservation audit count the stock with the backpack (Character::holdings, audit::tally_sheet), so moving between bag and stock is no transfer;
  • death takes only the possessions: the stock is never looted.

4. The editor

4.1 Opening and leaving

  • Who: the plot's holder (owner) and the house's co-owners. Co-owners can edit and save the draft; only the owner can commit.
  • Where: within EDIT_RANGE 32 tiles of the plot (the online client streams statics within 32 tiles, #88). Opened from the house window (J) with Plan on a held plot or on a house you co-own.
  • The game goes on. Your character stays where it was and can be attacked. The editor closes itself if you get into a fight (#92's fight window), and the draft is kept.
  • Controls are the editor's while it is open: game keys and clicks are not passed through, as in the asset review (#105). Arrows still pan and the wheel still zooms. Esc leaves the current tool, then the editor.
  • Camera: centred on the plot when the editor opens and kept within the plot plus a margin. Roofs are hidden while you edit unless you are on the roof tool (§ 4.6).

4.2 The screen

The editor looks and works like a build mode (The Sims 1, UO's house customisation tool): big pictures, few words, plain dark panels (#179; pixel-art stone, brick and wood frames were tried and dropped: the user prefers simple, clean panels). While it is open the game's own interface (HUD, windows, names and bars over mobiles, chat) is hidden; it comes back when the editor closes. The connection banner and hover tips stay.

                  ┌──────────────────────────────────────────────────────────────┐
                  │ [↶][↷] | [plans][lib] | [board]70 [ingot]5 [stone]306 ... | ok | [✎][✔][✕] │
                  └──────────────────────────────────────────────────────────────┘
┌──────┐
│[1][8]│
│[2][B]│          the plot, drawn as the plan will stand
│ ...  │          (grid and plot outline only while a tool is active)
│[7]   │
└──────┘    ┌──┐
            │▭ │ ┌───────┐ ┌──────────────────────────────────────────────────┐
┌──┐        │▣ │ │ piece │ │ < [pic][pic][pic][pic][pic][pic][pic][pic]   > │
│▲ │        │□ │ │       │ │ < [pic][pic][pic][pic][pic][pic][pic][pic][…]> │
│1 │        │⌂ │ └───────┘ │                                                  │
│▼ │        └──┘ [⟲] [⟳]   └──────────────────────────────────────────────────┘
└──┘
  • Tool rail (left): one pixel-art icon per tool, in the ability icons' style (#151: 24 x 24 drawn twice their size, 1 px outline, frame, dark glass): Foundation, Room, Walls, Floors, Openings, Stairs, Roof, Furniture, then Material brush, Eyedropper, Sledgehammer. The key is printed in each icon's corner; the pointer on an icon names the tool and how it works. In a short window the rail goes on in a second column.
  • Along the bottom, small panels side by side, bottom-aligned: the storeys (level arrows with the storey between them, 2 of 3), the four views stacked (§ 4.6, the active one lit), the piece in hand drawn large in its current facing and material with the turn left and turn right buttons under it (dead for pieces that don't turn; the pointer on the picture names it, Bed, facing east), and the catalog, which takes the rest of the width. How the active tool works is one outlined line over the world just above the catalog row (Click to place | the count is what you have left ...), not inside a panel.
  • Catalog: the active tool's choices as cards with a picture of the real game pieces (§ 4.2.1): wall sets as a wall corner, the wall kinds (full, low, crenellation), floors as a patch of floor, doors, windows, arches and slits in a wall, stairs as a flight, roofs as a hip roof, the footings, the secure chest and every piece of furniture with the count left in its corner (red when the plan uses more than there are, § 3.1). No titles and no row labels: the pictures say what they are, and the pointer on a card names it. Wall it in and Auto roof sit first in their tool's row.
  • Rows scroll, never wrap: each kind of card is one line. When it doesn't fit, the wheel or a two-finger swipe over it scrolls it sideways (either axis), and the arrows at its ends page it by most of its width (it glides there); an arrow dims where the row can't go further. Each row keeps where it was scrolled to, by its label, while the editor is open.
  • Top bar: undo and redo, templates and library, the cost bar (what the shell still needs: each material as its baked item picture, item_<key> of #170, and the count, § 4.7; the pointer names the material, a click opens the breakdown; Nothing to build when nothing is due), the problem sign with its count (§ 4.8; No problems in green otherwise), save, Commit (dead until there are no problems, and for co-owners) and close. The editor's notes (Draft saved., a refusal) show for a few seconds as an outlined line under the bar.
  • Templates open as a grid of pictures of the whole houses (every storey and the roofs).
  • The look follows the game's UI (#59): smoked-glass panels, hairline brass, colour as signal.

4.2.1 Pictures of the pieces

No catalog picture is drawn by hand. Each card is a tiny plan (a corner of walls of the set, a 2 x 2 floor, a door in one wall tile, a flight of stairs, a 3 x 2 hip roof, a 2 x 2 foundation, a piece of furniture in a facing) turned into statics by the same plan_pieces the lot is drawn with, and composited on the CPU from the baked sprites (assets/sprites/<name>/: albedo, normal, height): the nearest texel wins by its baked height (the renderer's depth), lit by its normal map in the game's calm toon bands, with the darkened one-pixel outline (#35). Pictures are made off the main thread, cached by what they show and their box, and enlarged by whole numbers (or enlarged to the next whole number and area-averaged down to the box). A changed sprite shows in the editor without anyone redrawing an icon. Code: editor/thumbs.rs.

The editor's own icons (tools, steps, views, commit, templates, library, save, close, problems) are pixel art in assets/ui/icons/editor.png + .json, drawn as code in tools/pixel/icons/editor/editor.px.js with the ability icons' kit (pxl skill) and packed by python3 tools/pixel/icons/editor/pack.py. Frames say what a button is: tools steel, steps (undo, redo, turn, storeys) pewter with rivets, views copper with cut corners, Commit gold.

4.3 The grid

The editor builds on the generator's grid (#101), so every plan can be drawn with the existing pieces:

  • Tiles hold floors, stairs, furniture and diagonal walls (diag_a /, diag_b \, crossing the tile between the corner posts, #102).
  • Edges hold walls: the north edge H(x, y) and the west edge V(x, y) of every tile. An edge holds at most one piece: a wall, window, slit, doorway (with or without a door), low wall or crenellation. Corners and posts are never placed by hand: they follow from the walls, as in the generator (walls.rs).
  • Storeys are WALL_HEIGHT (48 px) apart (#60). Storey 0 stands on the foundation. A plan has at most MAX_STOREYS (4) storeys (a tower's crenellated top counts as one), plus roofs or roof terraces on top.
  • Coordinates are plot-local, (0, 0) at the plot's north-west tile, the same as the generator's building-local tiles.

4.4 Tools

Placing works the same way in every tool: left click or drag places, Ctrl + drag removes with the same tool (as in The Sims), and Shift applies to a whole room or run. The piece under the cursor is drawn as it will stand, with a thin footprint outline on the ground: green where it can go, red where it can't; a tooltip says why and what it costs (+6 walls · 12 stone).

Foundation (the first step). Draw the footprint anywhere on the lot (§ 1.1): drag rectangles to add, Ctrl + drag to cut out, and Shift + click a convex corner to cut it diagonally (the generator's cut corners, mirrored pairs encouraged as in #104). Choose the foundation type (stone footing, timber sill), which decides its material on the bill. Walls and floors of storey 0 stand only on the foundation, and anything off it is red. One button, Wall it in, puts walls of the current set around the whole outline, including diagonal walls on cut corners. For now the foundation sets the floor at level 1 like every floor (#60); raised plinths with steps up to the doors come later (§ 8), because they need multi-level movement and new sprites.

Room. Drag a rectangle: it gets four walls of the current set and a floor, and the foundation under it if storey 0 needs one. Walls a new room shares with a neighbour aren't doubled, because both rooms share the edge. This is the quickest way to block out a house.

Walls. Drag along a grid line for a straight run. A drag at 45° draws a run of diagonal walls (each tile gets a diag_a or diag_b). The catalog picks the set (material) and the kind: full wall, low wall (a parapet that blocks walking, not sight), or crenellation (towers and roof terraces of stone sets). The inside or outside role of a wall follows from where it stands, and the outside set and inside set can differ, as in the generator's styles.

Floors. Click or drag tiles. Shift + click fills the room under the cursor. The catalog has the floor sprites (wood light/dark, plank, flagstone, cobble, marble and the rest) and rugs. A floor on an upper storey has to be supported: it lies over a room below, or at most CANTILEVER 1 tile beyond the outer walls below (a balcony).

Openings. Choose a door, window, arch (an open doorway), or arrow slit, and move it along the walls. It snaps to an edge with a full wall and never goes on a corner. A window in a diagonal wall uses diag_*_window. Doors swing (#136) and the front door locks (#129). An exterior door is one between a room and the outside. A plan needs at least one, and the first one placed is the front door (the sign and the lock), which you can change with a right click.

Stairs. Click a tile and the stair runs 4 tiles straight up away from you (rising north or west, as the generator's step_s/step_e; the other two directions come with new sprites). It needs a free approach tile at the bottom and a floor tile for the landing on the storey above. The storey above gets the stairwell hole on its own. A red footprint names what's in the way.

Roof. Auto roof covers the top of every wing: it cuts each storey's top outline into the fewest rectangles and puts a hip roof on each (hip_roof, the eaves at the wall top, as the generator's roofed). A stairwell counts as floor: the storey above covers the well it rises from, and the top storey's roof covers the well it arrives in. You can also draw roof rectangles by hand: drag a hip roof or a roof terrace (a flat top with a low-wall parapet and a door from the storey above, #101). The roof material comes from the catalog (thatch, shingle, slate, tile; #101's styles). Leaving a building without a roof is allowed.

Furniture (Buy). A catalog of every furniture item (plots::FURNITURE, items 901–914, Carpentry's stool and table) with how many are available (§ 3.1; red when the draft uses more than the stock has), and the secure chest (required, exactly one, paid in materials). Each entry shows the item, its size and who makes it. A piece follows the cursor with its footprint and turns with R or , .; pieces with a back (beds, bookcases, fireplaces) want a wall behind them. A blocking piece can't close a path, a door, a stair approach or a landing (Structure::connected, #129). Furnish room (on a room) fills it from the generator's room templates (furnish.rs) for the room kind you name. That's a quick start, which you then edit. Furniture that isn't an item yet (paintings, plants, wall mounts) shows in the catalog, greyed out with "not made yet", so the list shows what crafting still has to add.

Material brush. Repaint a wall run (Shift: the whole room's walls, the inside or outside face), a floor, or a roof with another material. In the diff this is a remove plus an add.

Eyedropper (E) takes the tool and option of whatever is under the cursor. Sledgehammer (K) removes anything under the cursor, and Ctrl + drag removes over an area.

Templates. The generator's designs for this plot (plots::designs: every kind that fits, in three variants) and the generator's own room shapes. Picking one replaces the draft, but you can undo it. This is how #129's design list lives on.

4.5 Keys

KeyIn the editor
1–8Foundation, Room, Walls, Floors, Openings, Stairs, Roof, Furniture
E / KEyedropper / Sledgehammer
Left click / dragPlace (draw, fill, put)
Ctrl + dragRemove with the current tool
ShiftThe whole room (floors, brush), the whole outline (foundation)
R or . / ,Turn the piece in hand right / left (furniture, stairs); also the turn buttons
Right clickCancel the current drag or piece; on a door: make it the front door
Page Up / Page Down, ] / [Storey up / down (also the level arrows)
Tab / Shift+TabNext / previous view: walls down, cutaway, walls up, roof view (§ 4.6)
Ctrl+Z / Ctrl+Shift+ZUndo / redo
Ctrl+SSave the draft now
Arrows, wheelPan, zoom (as in the game)
EscLeave the tool, then the editor
BMaterial brush (built, #175)
Space (held)What stands now instead of the plan
JOffline stage only: open the editor again after closing it

4.6 What you see

  • The plan as it will stand. Torn-down pieces are simply gone, and new pieces are the real sprites, lit, shadowed and occluded like everything else, with no tint, so the house can be judged as it will look (user directive). This is only on your screen: the editor hides the house's statics that the draft removes and spawns the draft's pieces as local statics; the world and other players still see what stands.
  • Before and after. Hold Space to see what stands now instead of the plan.
  • Validity shows only at the cursor (the footprint outline, § 4.4), in the problem list, and as an outline on a problem's pieces while you click through the list. Missing furniture is drawn like the rest; the catalog shows that it's missing.
  • Storeys. Storeys above the one you edit are hidden, storeys below are drawn, and the active storey's grid is drawn at its floor height.
  • Views (Tab, or the four view buttons), because the camera never turns; they are The Sims' build-mode views:
    • walls down: every wall of the active storey as its low twin;
    • cutaway (the default): walls of the active storey whose room lies behind them as seen from the camera (south and east faces of rooms) are drawn as their low twins (*_low_*, which every wall set has since #102), the back walls at full height; the windows, doorways and slits of lowered walls are outlined where they stand in the full wall (windows blue, doorways gold, slits violet) so you still see where they are;
    • walls up: walls at full height;
    • roof view: the whole house as it will stand, every storey with full walls and the roofs, whatever storey is active.
  • Roofs are hidden in the editor except on the roof tool and in the roof view. On the roof tool the active storey's roofs are shown, and H (#96) still cycles them.
  • The plot: the plot outline, the neighbours' buildings in grey, the street side marked, and the tiles where the front door is visible to the camera (south and east sides) marked along the edge.
  • Rooms: a faint colour per detected room with its name and size, and a red outline on rooms that can't be reached.

4.7 Cost bar

The cost is not a panel; it is one small bar in the top bar (user directive): each material as its item picture and a count ([board] 55 [stone] 30 [iron ingot] 1; #179 replaced the text line Boards 55 · Stone 30 · Iron ingots 1), the materials the shell still needs once salvage (and, during a project, what's been delivered) is taken off. It is computed on the client with the same shared functions the server uses (§ 5). A click opens a small breakdown per material (bill, salvage, delivered, due) and the piece counts against the limits (MAX_PIECES 3000, MAX_FURNITURE 1024, #129); the house window shows the same during delivery. Furniture is not on this line: its count is in the catalog (§ 3.1).

4.8 Problems and warnings

Problems block Commit. Each one is listed and framed on click:

  • a piece outside the plot, or a wall/floor of storey 0 off the foundation;
  • an unsupported upper floor or wall (nothing below within CANTILEVER);
  • no exterior door; floor that furniture cuts off from an exterior door (storey 0) or from a stair landing (upper storeys); a secure chest nobody can walk up to;
  • a door, window or slit not on a full wall, or on a corner;
  • a stair without its approach or landing, or crossing a wall;
  • blocking furniture closing a path, door, approach or landing; furniture overlapping;
  • not exactly one secure chest;
  • over MAX_STOREYS, MAX_PIECES or MAX_FURNITURE.

Warnings don't block:

  • the only exterior doors are on the north or west side (the camera can't see them, #101);
  • a room with no window or door to the outside (dark);
  • a storey without a roof or terrace above it;
  • dead space: floor the walls, stairs and stairwells themselves close off (the strip between a tower's wall and its stair run), or a room with no way in at all;
  • furniture planned that nobody can make yet;
  • furniture the stock lacks (it waits for the stock, § 3.1).

4.9 Undo and saving

Every edit is one step on an unlimited undo stack: a drag counts as one step, and so do Wall it in, Furnish room and a template. The stack lives as long as the editor is open. Saving the draft (automatic or Ctrl+S) never touches the undo stack.

4.10 Library

Save to library keeps the draft (or a selected area of whole rooms) as a named blueprint in the player's own library: a client-side file for now, on the account later. Place from library drops it on the plot, following the cursor, to move and turn (R) before you put it down. Sharing and selling blueprints, so that players can work as architects, comes later (§ 8).

4.11 As built (#175)

The client's editor lives in crates/uo-client/src/editor/ behind the contract editor/api.rs (OpenEditor / EditorUpdate in, EditorEvent out, EditorState.open): edit.rs is what each tool does to a plan (pure, tested), input.rs the keys, picking and drags, view.rs the plan drawn as it will stand, ui.rs the panels (#179: icons, pictures, views), thumbs.rs the catalog's pictures, offline.rs the --build-editor stage. Where it differs from the design above:

  • Drawing. The draft's pieces (plots::plan_pieces, furniture and the chest) are spawned as ordinary statics and rebuilt incrementally by piece identity and sprite; the lot's standing statics that the built plan accounts for are hidden by (tile, sprite), whoever drew them (offline stage, online houses, a town's baked building). The stroke under the cursor is applied to a copy of the draft and that copy is drawn, so a wall run, a room, a door or a piece of furniture shows as the real sprites while you aim; the only colour is the thin green/red footprint (gizmo lines) and a tooltip by the cursor with the reason or what it adds (+6 walls, 12 Stone).
  • Foundation. The footing is drawn (assets/manifest.d/footings.toml, made by tools/blender/models/roofs_stairs_floors/footing.py): footing_<stone|timber> is a slab under every foundation tile one level below the floors (rough flags; joists over packed earth), so it shows where no floor is laid; footing_<m>_edge is a plinth in the tile outside every outer side of the footprint, turned away from it so it hugs the wall there (stone: two courses about 4 px high, 2-3 texels proud of the wall face; timber: a sole plate and a sill beam), left out across doorways and along cut corners; footing_<m>_corner closes it around convex corners. blueprint::pieces emits them (layers FootingEdge, FootingCorner + the facing); only the slab is priced. While a tool is active on storey 1 the editor also outlines the footprint on the ground in the footing's colour. Raised plinths with steps up to the doors still need multi-level movement (§ 8).
  • Views. Cutaway and walls down draw the active storey's walls as Low edges of a view copy of the plan, so corners and posts follow (_low_corner, _low_post); doorways become gaps in those views; diagonal walls keep their height (they have no low twin). The roof view draws the plan whole (every storey, every roof).
  • Storeys and roofs. Storeys above the active one, their stairs and furniture are left out; roofs are drawn only on the roof tool (those up to the active storey's top) and in the roof view. Page Up goes one storey above the plan's top, where the Room tool starts the next storey.
  • Tools. Every tool of § 4.4 works on click or drag with Ctrl removing and Shift for rooms and corners. A stroke refuses only what can never stand (off the lot, an opening on no wall, a stair through a wall or with no floor above to land on, furniture off the floor, on stairs, doorways or other furniture); everything else is left to the problem list. Not built yet: Furnish room, right click to choose the front door (the plan has no front-door field: the first exterior door is the front), the brush's inside/outside face choice, placing a library blueprint by the cursor (it replaces the draft; one bigger than the lot shows its overhang as problems), wall views for diagonals, pictures of library blueprints (their list is still words).
  • Cost bar. Each material still due as its baked item picture and the count (the text line Board 48, Stone 197 of #175 is gone). A click opens the breakdown (bill, salvage, brought, due, pieces and furniture against their limits); the server's ledger replaces the local one there once it sends one (EditorUpdate::Ledger).
  • Input. While open, the editor copies the keys right after Bevy's input and text fields and takes them from everyone else (and the right button); the character's controls are off (controls::Scope); the left button reaches only the editor and the panels.

5. Rules shared by editor and server

Every rule here is a pure function in shared code (uo-buildings). The editor shows exactly what the server will accept, and the server never trusts the client's draft:

  • Blueprint::check(&plot) -> Report with problems and warnings (§ 4.8);
  • Blueprint::rooms(storey): rooms found by flood fill between walls;
  • Blueprint::pieces() -> Vec<Piece>: every sprite, with corners, posts, stairwell holes and roofs derived the way the generator derives them;
  • diff(built, draft) -> Diff { kept, removed, added } by piece identity;
  • bill(&Diff), salvage(&Diff, hits), due(bill, salvage, delivered).

6. Data model

6.1 The plan (built: the shared core)

A plan is data, not a seed: uo_proto::plan::Plan (serde; every list kept sorted by its mutators, so equal plans serialise alike):

pub struct Plan {
    pub version: u16,
    pub w: i32, pub h: i32,              // the lot (≤ MAX_LOT 64 a side)
    pub foundation: Vec<(i32, i32)>,     // storey-0 footprint, (x, y) sorted by (y, x)
    pub footing: Footing,                // Stone | Timber
    pub storeys: Vec<StoreyPlan>,        // ≤ MAX_STOREYS (4: a tower's crenellated top counts)
    pub stairs: Vec<StairPlan>,          // storey, bottom tile, east?, set (wood | stone)
    pub roofs: Vec<RoofPlan>,            // hip roof over a rect, eaves on top of storey-1, set
    pub furniture: Vec<PlannedItem>,     // item, x, y, storey, facing (from the furniture stock)
    pub chest: Option<ChestPlan>,        // the secure chest (required once anything is built)
}
pub struct StoreyPlan {
    pub floors: Vec<FloorPlan>,          // x, y, floor sprite
    pub edges: Vec<EdgePlan>,            // x, y, north?, kind (Wall | Window | Slit | Doorway | Low | Crenel), set, door
    pub diagonals: Vec<DiagonalPlan>,    // x, y, a (/) or b (\), window, set
}

with set_floor, set_edge, set_diagonal, set_foundation, storey_mut, trim, and Ledger { bill, salvage, delivered, due }. Constants: MAX_STOREYS 4, CANTILEVER 1, SALVAGE_PCT 50, MAX_LOT 64.

The rules are uo_buildings::blueprint (pure):

  • catalog: WALL_SETS, FLOORS, DOORS, ROOF_SETS, STAIR_SETS, CRENEL_SETS;
  • pieces(&plan) -> Vec<PlanPiece { id: PieceId, piece: Piece, hidden }>: every sprite with its identity (storey, layer, x, y) (layers: footing (hidden, priced only), floor, north/west edge, door, corner, post, diagonal, stair step and blocks, roof); corners and posts follow from the edges through the generator's walls::pieces, stairwell holes are cut from the floor above;
  • walk(&plan, s) (floor, blocking edges, solid and reserved tiles of a storey), rooms, footprint;
  • check(&plan, &Rules) -> Report { problems, warnings } (Issue { code, storey, at, text }): everything in § 4.8;
  • auto_roof(&plan, set): hip roofs over every storey's top, fewest rectangles, terraces left open;
  • diff(built, draft) and ledger(&diff, cost, hits, delivered);
  • from_building(&Building, lot_w, lot_h, offset).

World glue in uo_world::plots: cost(sprite) (now also footing_* and the chest), plan_rules(), plan_pieces(&plan) (structure + the secure chest as a priced piece), plan_ledger(built, draft, hits, delivered), templates(style, front, w, h, seed).

Proof: every generated building of ten kinds × four styles × two fronts × three seeds turns into a plan whose pieces are exactly the generator's (sprite, tile, height) and which passes the checks; every template on three lot sizes passes and has a bill; the checks catch each problem of § 4.8 on hand-made cases; the ledger keeps salvage a discount only.

  • Blueprint::from_building(&Building) turns any generated building into a plan. The templates use it. A town house bought with its lot (§ 1.2) works the same way: its building is regenerated from the town plan's spec, becomes the lot's first built plan, and the baked statics of that building (statics.bin keeps a building index per piece, #103) are hidden by a server patch, so from then on the house is a record like any player house.
  • Sprite names come from the same tables the generator uses (style.rs materials, walls.rs), so a plan and a generated building of the same shape give the same pieces.

6.2 House record (built, #176)

HouseView (#129, on the wire) is now a lot with a plan:

  • x, y, w, h: the lot (the plot's area, at most MAX_LOT a side); kind, style, seed and design stay for the sign and templates (design is CUSTOM_DESIGN for a drawn plan);
  • plan: the built plan;
  • keys: the index of every drawn piece (§ 6.3);
  • project: Option<Ledger>: a committed plan's bill, frozen salvage, delivered and due;
  • crate_full: the site crate holds something.

stage, working_until, delivered and furniture are gone: a project is either waiting for materials or raised. The server's HouseRecord (the object row's blob, never sent) adds the private draft, the committed project plan, the secure chest, the site crate's items, furniture owed back to the owner's stock (returns), and a version (HOUSE_VERSION 1; #129's v0 records are not read, #178). The character record carries the furniture stock (Character::furniture: FurnitureStock, a count per item; § 3.1, #174), sent to its owner with the sheet; planned furniture is taken from it and given back to it by the region that holds the house while the owner is there.

Waiting furniture is not a field: it is the plan's furniture whose index is 0. The furniture stock lives on the character (decision #174, its own branch); until it lands the server's seam (stock_take/stock_give in uo-server/src/sim/region/build.rs) uses the furniture items in the owner's backpack, and fills waiting pieces once a second while the owner is in the house's region. The draft is stored with the record and sent only to owners (GetDraft). A lot with nothing built is a record with an empty built plan, made by its first save.

6.3 Stable piece identity (built, #176)

A piece's identity is its PieceId (storey, layer, x, y) (structure) or plots::furniture_ident (item, storey, facing, x, y) (furniture). The keys stay 12-bit indices under the record (record + index), kept per house in HouseView::keys, aligned with plots::slots(plan) (structure pieces, then furniture, each sorted by identity, so the order never depends on how the plan was drawn and survives moving the plan). plots::rekey turns the built plan's keys into the new plan's: a piece of the same identity and sprite keeps its index (and its damage), new pieces take free indices (structure 1..=3000, furniture 3072..), never one freed in the same raise. The secure chest keeps its own key (3011); a moved chest is a new chest (paid again, whole) whose contents follow it.

6.4 Protocol (built, #176)

Appended to Command (no version bump): SaveDraft { town, plot, plan }, GetDraft { town, plot }, CommitPlan { town, plot } (commits the saved draft), CancelProject { town, plot }, all answered with ReplyData::Plan(PlanReply) (the lot, the built plan, the draft, the project plan and its ledger; for a save the draft's ledger against what stands and its problems; for a commit whether it was raised). Commission { design: CUSTOM_DESIGN } takes a permit for a plan of one's own; BreakGround with a design's permit opens the lot and commits the design; Deliver takes only what is due; Demolish commits an empty plan and gives the lot up once nothing is left on it. PlaceFurniture/PickUpFurniture are gone: furniture is in the plan. SellFurniture belongs to the furniture stock (#174). Plot commands go to the region that owns the plot's record (the one it knows of, else the lot's middle's), handing the player over when they stand in its arena (#136). The SDK's builder bot plans a template and builds it; MCP tools plan_permit, plan_templates, plan_check, plan_save, plan_commit, plan_deliver give agents the same, with the editor's checks before anything is sent.

7. What this changes in #129

  • Commissioning one of a fixed list at the exchange → editing a plan at the plot. The design list becomes the editor's templates. The permit (item 900) stays the proof of the right to build, and its design field becomes "custom".
  • Four timed stages → the shell is raised at once when its materials are in. Scaffolding and piles still mark a site while it waits.
  • Demolition with no materials back stays: tearing down is part of a plan, and its salvage only discounts the plan's new parts. Demolish stays as "commit an empty plan" (then Release plot as before).
  • Furniture leaves the backpack: one furniture stock per character for every town, placed only through the plan (which reaches upper storeys); free placement from the backpack goes.
  • Houses raised under #129 are not converted: no stored data existed (#178).

8. Later

  • Buy requests: an unfilled order line posts a buy request on an exchange order book for crafted items (the town pools only trade fungible goods, #121). It needs that order book first.
  • "Buy what's missing" for materials at the town's pools, in one click from the house window.
  • A lock on selling furniture for some days after buying it, to close the loophole of moving value between towns through the stock (§ 3.1).
  • Co-owners furnishing a house from their own stock.
  • Push/pull walls and moving whole rooms with their contents (The Sims 4's room editing).
  • Raised foundations and plinths with steps; stairs in all four directions; gable roofs and valley pieces (asset requests).
  • Shared, sold and rated blueprints: architects as a player role.
  • The yard (v2): fences, gates, paths, garden beds, trees and garden props on the rest of the lot, as its own editor (garden-editor).
  • Building outside towns (claims) and the lord's buildings: player-building-design, #77.
  • Upkeep and decay: house-upkeep.
  • Walking on upper storeys: multi-level-movement. Until then, upper storeys are built, drawn and furnished through the order, but nobody can climb to them.

9. Implementation plan

  1. Blueprint core (uo-buildings::blueprint, pure, tests): the data model, from_building, pieces, rooms, check, diff, bill/salvage/due, stable slots. Done when every generator design round-trips (from_building → pieces gives the same pieces), the checks catch each problem in § 4.8 on hand-made cases, and diff/salvage are tested.
  2. Editor in the client, offline: uo-client --build-editor [--plot w×h] on an empty stage like the building gallery (#101): the tools of § 4.4, the plan drawn as it will stand, wall views, the cost line, problems, undo, save/load to a local file, templates. Done when a house can be drawn from foundation to furniture with the mouse alone and every generator template can be loaded and edited.
  3. Online (built, #176): the record fields, draft storage, CommitPlan, salvage, deliveries against the due list, the instant raise (diff applied in one tick, characters moved), the editor opened from the house window. The server tests cover the raise, a rebuild with salvage and a reload from stored rows; a paid project not yet raised (a crash between delivery and raise) is raised by the next sweep.
  4. Furniture stock: the stock on the character, crafting and buying into it, counts in the catalog, waiting pieces filled as the stock grows, #129 furniture items moved into stocks. Built (#174): everything but the counts in the editor's catalog (step 2 reads EconMirror::furniture) and filling waiting pieces (step 3 takes them from the owner's stock with FurnitureStock::take, booking the move on both ledgers).
  5. Later items (§ 8).

10. Open questions (defaults chosen)

  • Salvage 50 % of materials × remaining hits, as a discount only (surplus is lost). Higher makes rebuilding cheap, lower makes it a sink. Tunable as SALVAGE.
  • The old house stands until the raise (rather than being torn down at commit). Kinder to players, and the raise becomes one dramatic moment.
  • Doors on any side are allowed, with a warning on north/west (the generator's rule, #101, stays a rule for generated buildings only).
  • Any wall set in any town. Realm styles (#101) only shape the templates; towns may restrict materials later by vote (#121 § 7).
  • MAX_STOREYS 4 everywhere for now (the generator's towers have three storeys and a crenellated top); per town tier later.
  • What a lot with a house costs (§ 1.2): the plot's stake alone, or the stake plus the house's materials in coin to the town treasury. Default: stake plus materials, so a standing house is never cheaper than building the same one. Built (#177).
  • NPC jobs in sold buildings (§ 1.2): shopkeepers and smiths whose building is sold move to stalls on the square or into the town-kept buildings. Default: market stalls. Built (#177).
  • Releasing a town house's lot (#177): the house stays, vacant, back on sale as it stands (rather than being torn down or reverting to the baked building).

11. How to test the editor offline

The editor runs without a server on an empty, levelled lot beside the player (#175):

cargo run --release -p uo-client -- --build-editor

Options: --lot 22x16 (tiles, at most 64 a side; default 16x12), --template inn (start the draft from a generated design: house, cottage, inn, shop, smithy, workshop, warehouse, manor, farmhouse, tower, as far as it fits the lot), --lot-style karrak|aelwyn|free|isles (templates and default materials), --plan path/house.json (the draft file; default <config>/build-editor/draft.json, where <config> is the settings folder of docs/settings.md). The draft saves itself 2 s after the last change, on Ctrl+S and on close. Commit raises the plan on the spot: it becomes the built plan (<name>.built.json beside the draft) and stands as ordinary statics, and the editor opens again on it, so the next edit shows its salvage on the cost line. Close with Esc; J opens it again. Blueprints saved from the Library go to <config>/blueprints/.

What to try and look for:

  1. Room (2): drag a rectangle on the lot: walls, floor and foundation appear as you drag, as real sprites. Rooms dragged side by side share their wall.
  2. Openings (5): pick a door, click a wall: the doorway and the door replace the wall. The problem counter drops needs a door to the outside. A window on a diagonal wall works too.
  3. Foundation (1): drag to add, Ctrl + drag to cut out, Shift + click a convex corner to cut it diagonally, then Wall it in.
  4. Walls (3): drag along a grid line; drag at 45 degrees for a diagonal run; pick Low wall or Crenellation (stone sets).
  5. Storeys: Page Up, then Room on storey 2 over the room below (one tile of overhang is a balcony); Stairs (6) need that floor to land on (R turns them to rise west).
  6. Roof (7): Auto roof, or drag hip roofs; roofs show on this tool and in the roof view.
  7. Furniture (8): the chest first (it is required), then pieces: the count left is in each card's corner (red when the plan uses more than there are). The turn buttons beside the big picture, or R and , ., turn the piece; the picture turns with it.
  8. Views: the four buttons at the bottom left (or Tab / Shift+Tab): walls down, cutaway, walls up, roof view. The level arrows (or Page Up / Page Down, ] / [) change storeys. Space held shows what stands; Ctrl+Z / Ctrl+Shift+Z undo and redo; E eyedropper, K sledgehammer, B material brush.
  9. Every catalog card is a picture of the real pieces; the pointer on a card or an icon names it.
  10. The cost line (top bar) and its breakdown; the problem sign lists the problems and frames a problem's tile on the lot when one is clicked; Commit stays dead while there are problems.
  11. Templates (top bar) shows a picture of every design that fits the lot.

Captures for docs and reviews: UO_FREEZE_TIME=1 and the hook UO_EDITOR="tool=furniture,item=bed,facing=east,view=down|cut|up|roof,storey=2,popup=problems|templates,focus=0,space,hover=6.5;7.5" (hover stands for the cursor while it is off the window; UO_EDITOR_PAGE=Furniture pages that catalog row on once, as its arrow would). A window --screenshot goes black when the window isn't focused (#69); --record renders offscreen with the panels, so record two seconds and take the last frame:

UO_FREEZE_TIME=1 UO_EDITOR="tool=roof,storey=2" cargo run --release -p uo-client -- --build-editor --lot 22x16 --template inn --lot-style karrak --plan /tmp/inn.json --width 1600 --height 1000 --zoom 3 --record inn.mp4 --seconds 2
ffmpeg -sseof -0.1 -i inn.mp4 -frames:v 1 inn.png

--headless captures only the scene. Examples: docs/screenshots/building-editor-furniture.png (a house template, the furniture tool with a bed turned east), building-editor-problems.png (a two-storey inn on storey 2, walls tool, the problem list), building-editor-roof.png (the inn in the roof view), building-editor-templates.png (the templates as pictures), building-editor-scrolling.png (1280 x 720: the furniture tool, the rail in two columns).

12. How to test building online

Two terminals, one server and one client. The baked world must carry the town-house lots (#177): bake it with this checkout first (cargo run --release -p uo-atlas -- --no-tiles --out /tmp/atlas --bake assets/world/felucca, about 35 s), then:

UO_DEV_PURSE=20000 cargo run --release -p uo-server -- --world assets/world/felucca
cargo run --release -p uo-client -- --server ws://127.0.0.1:7777 --name Builder
  • UO_DEV_PURSE=20000 lands new colonists with that much coin (plots, stakes and materials are bought at the exchange). Without a baked world (--world), the server plays a generated map whose towns have plots too.
  • The dev login makes everyone a GM (#146): backquote opens the console. Useful lines: .tele X Y (go to a plot), .give Builder stone 200, .give Builder board 200, .give Builder iron_ingot 20, .give Builder nails 40, .give Builder round_table 1 (furniture for the stock), .help for the rest.
  • What to click: walk to a town's exchange, open the market (B), stake liquidity on the Stake tab until the plot price is covered, claim a free plot in the list of plots under the Stake tab, Build → Permit for my own plan. Walk to the plot, J → Plan: the editor opens on the lot (start from a template). Save; Commit (owner only). J shows the project's ledger: bring what is due and Deliver what I carry (anyone can). The last delivery raises the house at once. Plan again to rebuild: kept pieces keep their damage, torn-down ones count as salvage. Furniture in the plan stands from your furniture stock (#174: crafted or bought furniture goes straight there; .give Builder round_table 1 lands in it too) and waits on its spot until you have it.
  • The bot: cargo run --release -p uo-loadtest -- swarm --bots 1 --mix builder=1 --world assets/world/felucca (with the server above): it stakes, claims a plot, takes a permit, saves the lot's cheapest template, commits it, buys and delivers what is due until the house stands, locks it, stashes coin, and plans any furniture it carries. UO_BOT_TRACE=builder traces it.
  • Agents: cargo run -p uo-mcp -- --server ws://127.0.0.1:7777 --name Ann exposes plan_permit, plan_templates, plan_check, plan_save, plan_commit, plan_deliver.
  • Proof without a client: cargo test -p uo-server --test houses (the rules, one region driven directly) and cargo test -p uo-server --test economy -- a_builder (the builder bot against a real node raises a house in about two minutes).