DramaticShapeVoxelMod archival mirror · read-only
6887f5d951e1404f8fad8761889a91075e076a1b

updates to oak's lab

DramaticShape · 2026-08-01 · parent a140980b1d

updates to oak's lab

Changed files

CHANGELOG.md                  |  66 ++++++++++++++++++++++++++++++++++++
 README.md                     |   1 +
 data/voxel_heights.lua        |  90 ++++++++++++++++++++++++++++++++++++++++++++++++
 lib/BattleScene.lua           |  14 ++++++--
 lib/Buildings.lua             |  36 ++++++++++++++++----
 lib/TileShape.lua             |  18 ++++++++++
 lib/Voxel3D.lua               |   2 +-
 lib/VoxelGrid.lua             |  13 +++++++
 lib/Water.lua                 |   2 +-
 main.lua                      |  36 ++++++++++++++++++--
 tests/dramatic_shape_test.lua |  58 ++++++++++++++++++++++++++++++-
 tools/building_voxels.py      | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
 12 files changed, 426 insertions(+), 25 deletions(-)

Diff

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 21ab6e1..f9ea089 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -116,6 +116,72 @@
   ever lands half-way between two pixels and changing a wavelength moves the
   speed with it.
 
+- **AA, a new options row: OFF / 2X / 4X.** Everything else in this game is
+  flat art blitted at whole pixels. This mode's world is real geometry seen
+  through a perspective camera, and a polygon edge that lands at an angle
+  across the pixel grid is the one place where a hard stair-step is not a
+  stylistic choice -- a roof ridge, a ledge lip, a tree's silhouette against
+  the sky, the leaning card of a character. At the shallow rungs, where the
+  diorama reads most like a photograph of a model, they crawl as the camera
+  drifts.
+
+  The row is SUPERSAMPLING: the whole pass renders into a canvas larger than
+  the window and is folded back down at the end. The ladder is samples per
+  display pixel, so 2X is a canvas root-two wider and taller and 4X one
+  exactly twice the size -- an honest 2x2 box.
+
+  Two alternatives were tried against what this pass already is, and both
+  lost:
+
+  - **MSAA** would have taken the water with it. The reflections read the
+    frame's own depth buffer as a texture, and a multisampled depth
+    attachment is not something a fragment shader in this dialect can sample.
+    The row would have quietly switched the WATER row off.
+
+  - **An edge filter** (FXAA and its relatives) works from the finished
+    colour alone, so it would be guessing where the edges are out of one
+    sample per pixel -- inventing detail it never rendered, and unable to
+    tell a geometry edge from the boundary between two texels of a tileset.
+
+  Rendering larger has neither problem, and nothing in the frame had to be
+  taught about it: every pass already measures itself in the canvas it was
+  handed, so the sky's dither, the water's ray march, the shadow lookups and
+  the camera itself come out the same picture at a higher sample rate. It
+  antialiases the geometry, the alpha-cut outline of a sprite card, the
+  wireframe and the reflections at once, because none of them know it is
+  happening.
+
+  And it softens the ARTWORK with them, which is worth saying plainly. A
+  tileset texel out here is not a screen pixel, it is a quad in a perspective
+  view, and its boundary crosses the pixel grid at the same arbitrary angle a
+  roof ridge does -- so the fold averages across it exactly as it averages
+  across the ridge. That is what an honest extra sample says about that
+  pixel, and it is also the trade the row is: the diorama comes out smoother,
+  not sharper. Which is why it is a row and not something that is simply on.
+
+  Two things are quoted in DISPLAY pixels rather than canvas ones and are
+  multiplied up to match: the voxel wireframe's line width -- left alone it
+  would fold down to half a line, so turning the smoothing up would appear to
+  fade the grid out -- and the scale the overworld's FX closures draw at.
+
+  The fold is a shader rather than a scaled draw, because the void this pass
+  renders into is a transparent BLACK: averaging a straight-alpha edge against
+  it drags the colour toward black as well as toward transparent, and the
+  engine's composite then multiplies by that alpha a second time. Every
+  silhouette against the sky would have come out ringed with a dark fringe --
+  the exact artefact the row exists to remove. So the taps are premultiplied
+  before they are averaged and divided back out after.
+
+  The staged battle gets it too, on its own canvas: the arena is folded back
+  to the window's pixel size before the depth-of-field pass and the HUDs go
+  on, so the world is smoothed and the pics, panels and text box stay the
+  chunky GB art they are.
+
+  OFF by default, and **FULL neither sets it nor takes the row away** -- it
+  is the one row that is not a knob on the look but on what the look COSTS,
+  and only the player knows what their machine can carry. No hotkey, for the
+  same reason: it is set once, not flicked while walking.
+
 
 ### Changed
 
diff --git a/README.md b/README.md
index db18394..14e2cb8 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,7 @@ menu.
 | `8`, or the **3D-BTL** options row | ON / OFF — fight on the map instead of on a white field |
 | `9`, or the **WATER** options row | FULL / SKY / OFF — waves and reflections on water. **SKY** gives the surface its pixel-tall wave columns and puts the sky, the sun, the moon and the cast in them; **FULL** adds a screen-space ray march that also reflects the shoreline, the trees and the buildings standing behind it |
 | the **BACK SPRITES** options row | OFF / ON — keep your own Pokémon on the battle menu, seen from behind in its classic slot, instead of standing it on the map; the foe is still out there. Only on the menu while **3D-BTL** is on, because it decides nothing without it |
+| the **AA** options row | OFF / 2X / 4X — smooth the stair-stepped edges of the 3D world by rendering the diorama larger than the window and folding it back down. The ladder is samples per display pixel: 2X is a canvas root-two wider and taller, 4X one exactly twice the size. Every edge in the projected picture softens with the silhouettes — the tileset's own texels are quads in a perspective view and cross the pixel grid at the same arbitrary angles — so the diorama reads smoother rather than sharper. The most expensive row in the mod, so it is OFF by default and **FULL** leaves it alone |
 | the **DAYTIME** options row | SYNC / DAY / NIGHT / DUSK / DAWN / CYCLE — what time it is outdoors, on the diorama *and* on the flat 2D world; held at SYNC (and off the menu) while VOXEL is FULL |
 
 **3D-BTL** is on by default and is independent of **VOXEL**: battles draw
diff --git a/data/voxel_heights.lua b/data/voxel_heights.lua
index 4079a53..7ee4004 100644
--- a/data/voxel_heights.lua
+++ b/data/voxel_heights.lua
@@ -579,6 +579,12 @@ return {
       -- auto-extracting into standing prisms, and the ball/Pokedex
       -- sprites ride the table's authored height.
       table = { 41, 42, 57, 59, 78, 79 },
+      -- These tables are drawn 6px tall (3px slab edge over 3px base --
+      -- see the lab_table entry under `buildings`), not the default
+      -- table's 12: the override keeps the volume-built north tables
+      -- and the band-built starter table one height, and stands the
+      -- ball/Pokedex sprites exactly on the top face of both.
+      heights = { table = 6 },
     },
 
     -- Red's room and the Copycat's room (one tileset).  The detector reads
@@ -3231,5 +3237,89 @@ return {
         slab = 4, frontEave = 4, ledge = nil,
       },
     },
+
+    DOJO = {
+      -- F01: the starter-ball table in Oak's lab (one placement in the
+      -- game: OAKS_LAB cell 6,3) -- the first FURNITURE through the
+      -- band pipeline, and the first drawing whose plot is smaller than
+      -- its grid. Its 24 rows read: 0-15 the tabletop seen from above
+      -- (black rim, white highlight course, grey field); 16-18 the top
+      -- slab's own front edge, black/#555/black -- which is exactly
+      -- what the rim treatment paints, so slab = 3 and those rows fold
+      -- into the roof band instead of extruding under it; 19-21 the
+      -- base band, corner feet and the inset dark panel between them.
+      --
+      -- The legs stand on open FLOOR: the measured ground line lands
+      -- two rows short of the grid (see Buildings measure), and `depth`
+      -- keeps the plot to the blocked cell row -- the grid's third row
+      -- is the walkable cell the player faces the table from, matched
+      -- so the flat leg art is claimed off the floor, not so the model
+      -- stands on it. 16 top rows onto a 16px plot map 1:1: roofBack
+      -- covers the whole depth, nothing cycles, and roofCycle is
+      -- unreachable behind it. The Poke Ball sprites ride the `table`
+      -- pin's height (VoxelScene.groundAt reads the collision tile, not
+      -- this model), so the tileset entry above overrides that height
+      -- to the 6px this drawing actually stands.
+      {
+        id = "lab_table",
+        tiles = {
+          { 41, 59, 59, 59, 59, 42 },
+          { 78, 57, 57, 57, 57, 79 },
+          { 88, 89, 89, 89, 89, 90 },
+        },
+        roofRows = 19, roofBack = 16, roofFront = 0, roofCycle = { 2, 13 },
+        slab = 3, frontEave = 0, ledge = nil, depth = 2,
+      },
+      -- F02: the computer bank on the lab's west table (OAKS_LAB cell
+      -- 0,1) -- the one FACADE-ONLY template: rows 0-14 are machines
+      -- drawn face-on (monitor and keys, the centre console, the right
+      -- cabinet) over the lab-table fascia and base bands, and nothing
+      -- is drawn from above. roofRows = 0, so the whole drawing
+      -- extrudes -- the house facade treatment with no roof band; the
+      -- roof fields are inert and the recess pass sinks every framed
+      -- pane (screens, keys, the cabinet's inset face) into relief on
+      -- its own. Same drawing, same grid, stands in the Hall of Fame
+      -- on the GYM atlas -- registered there below.
+      {
+        id = "lab_computers",
+        tiles = {
+          { 91, 92, 93, 94 },
+          { 54, 55, 85, 95 },
+          { 88, 89, 89, 90 },
+        },
+        roofRows = 0, roofBack = 0, roofFront = 0, roofCycle = { 0, 0 },
+        slab = 0, frontEave = 0, ledge = nil, depth = 2,
+      },
+      -- F03: the empty north table beside it (OAKS_LAB cell 2,1): the
+      -- starter table's band table verbatim on a grid two tiles
+      -- narrower.
+      {
+        id = "lab_table_small",
+        tiles = {
+          { 41, 59, 59, 42 },
+          { 78, 57, 57, 79 },
+          { 88, 89, 89, 90 },
+        },
+        roofRows = 19, roofBack = 16, roofFront = 0, roofCycle = { 2, 13 },
+        slab = 3, frontEave = 0, ledge = nil, depth = 2,
+      },
+    },
+
+    GYM = {
+      -- F02 again: the Hall of Fame's recording machine is the lab's
+      -- computer bank drawing, tile for tile, on the GYM atlas (one
+      -- placement: HALL_OF_FAME cell 4,1). Same band table as the DOJO
+      -- entry above.
+      {
+        id = "lab_computers",
+        tiles = {
+          { 91, 92, 93, 94 },
+          { 54, 55, 85, 95 },
+          { 88, 89, 89, 90 },
+        },
+        roofRows = 0, roofBack = 0, roofFront = 0, roofCycle = { 0, 0 },
+        slab = 0, frontEave = 0, ledge = nil, depth = 2,
+      },
+    },
   },
 }
diff --git a/lib/BattleScene.lua b/lib/BattleScene.lua
index 170caac..3938f89 100644
--- a/lib/BattleScene.lua
+++ b/lib/BattleScene.lua
@@ -42,6 +42,7 @@ local BattleCam = V.require("BattleCam")
 local BattleBillboard = V.require("BattleBillboard")
 local VoxelGrid = V.require("VoxelGrid")
 local DayNight = V.require("DayNight")
+local AntiAlias = V.require("AntiAlias")
 local PaletteFX = require("src.render.PaletteFX")
 local Map = require("src.world.Map")
 
@@ -427,7 +428,16 @@ function BattleScene.render(state, arena, textures, token)
     -- its own canvas slot: this renders at the window's pixel size and the
     -- free-roam pass does too, but the two are alive at different moments
     -- and a shared slot would reallocate on every battle entry and exit
-    if not Voxel3D.beginScene(pw, ph, cx, cy, vw, vh, sky, "battle") then
+    --
+    -- AA, if the row asks for it, renders it larger still and folds it back
+    -- to pw x ph below (see AntiAlias). The framing is untouched by that:
+    -- the lens was widened by the window's RATIO to the letterbox and the
+    -- rig solved in the GB's own frame, so a bigger canvas is more samples
+    -- of the identical shot -- which is why the pins below still measure in
+    -- pw and ph, and why the HUDs and the depth of field, drawn onto the
+    -- folded canvas afterwards, stay the chunky GB art they are.
+    local rw, rh = AntiAlias.expand(pw, ph)
+    if not Voxel3D.beginScene(rw, rh, cx, cy, vw, vh, sky, "battle") then
       return
     end
     Voxel3D.draw(terrain, atlasFor(host), nil)
@@ -500,7 +510,7 @@ function BattleScene.render(state, arena, textures, token)
                    Mat4.translate(nb.ox, 0, nb.oy), fpull,
                    ShadowMap.snug(Mat4.translate(nb.ox, 0, nb.oy)))
     end
-    local canvas = Voxel3D.endScene()
+    local canvas = AntiAlias.resolve(Voxel3D.endScene(), pw, ph, "battle")
     if not canvas then return end
 
     local vp = Voxel3D.vp
diff --git a/lib/Buildings.lua b/lib/Buildings.lua
index 7bfc6e0..644cb09 100644
--- a/lib/Buildings.lua
+++ b/lib/Buildings.lua
@@ -190,7 +190,24 @@ local function measure(sp, t)
     top[x] = r
   end
 
-  local wallH = H - roofRows
+  -- The drawing's own ground line: the row after the last drawn one. A
+  -- building ends on the black threshold row it stands on (ground == H),
+  -- but furniture is drawn standing on open floor -- the lab table's
+  -- legs stop two rows short of its grid -- and extruding against H
+  -- would float it that far above its own plot.
+  local ground = roofRows
+  for sy = H - 1, roofRows, -1 do
+    local drawn = false
+    for sx = 0, W - 1 do
+      if sp.inside[sy * W + sx] then drawn = true break end
+    end
+    if drawn then
+      ground = sy + 1
+      break
+    end
+  end
+
+  local wallH = ground - roofRows
   local ytop = wallH - 1 + t.slab
 
   -- Side faces must not come out as slabs of outline black: where the
@@ -279,7 +296,12 @@ local function measure(sp, t)
   -- sprite taller than its footprint -- the tower's 16-row drawing
   -- stands on the 8 rows of it that are actually on the map, and D = H
   -- would have pushed its body 64px south into the town plaza.
-  return { top = top, ytop = ytop, D = #t.tiles * 8,
+  -- `depth` (in tile rows) names the plot when the grid runs PAST it
+  -- onto ground the drawing merely stands its legs on: the lab table's
+  -- third row is the walkable cell the player faces it from, and the
+  -- full-grid depth would stand the model in their path.
+  return { top = top, ytop = ytop, D = (t.depth or #t.tiles) * 8,
+           ground = ground,
            recess = recess, interior = interior, shadeTexel = shadeTexel }
 end
 
@@ -292,7 +314,7 @@ end
 local function model(sp, pr, t)
   local W, H, D = sp.W, sp.H, pr.D
   local slab, roofRows = t.slab, t.roofRows
-  local top, ytop = pr.top, pr.ytop
+  local top, ytop, ground = pr.top, pr.ytop, pr.ground
 
   -- The roof's drawn span. A sprite inset from its box (B03) leaves outer
   -- columns undrawn in the roof band; they carry no roof at all, and the
@@ -367,16 +389,18 @@ local function model(sp, pr, t)
 
     -- the awning: the band juts two voxels past the walls, front and back
     if ledge0 and (z == -2 or z == -1 or z == D or z == D + 1) then
-      local sy = H - 1 - y
+      local sy = ground - 1 - y
       if sy >= ledge0 and sy <= ledge1 and sp.inside[sy * W + x] then
         return sy * W + x
       end
       return nil
     end
 
-    -- the facade, extruded straight back over the footprint
+    -- the facade, extruded straight back over the footprint. Rows map
+    -- against the measured ground line, not the grid's last row: the two
+    -- differ only for furniture standing on open floor (see measure).
     if z < 0 or z >= D then return nil end
-    local sy = H - 1 - y
+    local sy = ground - 1 - y
     local i = sy * W + x
     if y == 0 and not sp.inside[i] and sy > 0 and sp.inside[i - W] then
       -- the drawing's last row is the ground the building stands on, so
diff --git a/lib/TileShape.lua b/lib/TileShape.lua
index 6cb65ce..f36f87d 100644
--- a/lib/TileShape.lua
+++ b/lib/TileShape.lua
@@ -295,6 +295,24 @@ function TileShape.forMap(map)
   if cache[id] then return cache[id] end
 
   local heights = TileShape.heights()
+  -- Per-tileset height overrides (a tileset entry's `heights`): the class
+  -- vocabulary is global but the drawings are not -- the DOJO lab tables
+  -- are drawn 6px tall where the default `table` is 12 -- and the height
+  -- a sprite RIDES at (VoxelScene.groundAt) must be the height the art
+  -- actually stands, or the starter balls float over their own table.
+  -- Same gate as the global list: known classes, numbers only.
+  do
+    local s = load()
+    local entry = s and s.tilesets and s.tilesets[id]
+    local over = entry and entry.heights
+    if type(over) == "table" then
+      for class, h in pairs(over) do
+        if type(h) == "number" and FALLBACK_HEIGHTS[class] then
+          heights[class] = h
+        end
+      end
+    end
+  end
   local authored = authoredGroups(id, heights)
   local count = math.floor((tileset.imageWidth or 128) / 8)
                 * math.floor((tileset.imageHeight or 48) / 8)
diff --git a/lib/Voxel3D.lua b/lib/Voxel3D.lua
index 471a688..54ef847 100644
--- a/lib/Voxel3D.lua
+++ b/lib/Voxel3D.lua
@@ -715,7 +715,7 @@ function Voxel3D.beginScene(w, h, cx, cy, vw, vh, sky, slot)
   pcall(sh.send, sh, "sunTexel", { texel, texel })
   if grid then
     pcall(sh.send, sh, "gridDark", VoxelGrid.DARK)
-    pcall(sh.send, sh, "gridWidth", VoxelGrid.WIDTH)
+    pcall(sh.send, sh, "gridWidth", VoxelGrid.width())
   end
   -- ordinary shading until the silhouette pass asks for otherwise. Sent
   -- every frame rather than once, because a scene that opened mid-ghost --
diff --git a/lib/VoxelGrid.lua b/lib/VoxelGrid.lua
index 1fb7ce7..b585072 100644
--- a/lib/VoxelGrid.lua
+++ b/lib/VoxelGrid.lua
@@ -44,6 +44,19 @@ VoxelGrid.DARK = 0.45
 -- 1.0 here is the one-pixel wireframe.
 VoxelGrid.WIDTH = 1.0
 
+-- The same width in the CANVAS pixels the shader measures in, which is what
+-- every sender of it actually wants.
+--
+-- The two are the same number until AA renders the pass larger than the
+-- window (see AntiAlias): there a canvas pixel is a fraction of a display
+-- one, and a width left at 1.0 would come out a half or a quarter of a line
+-- after the fold -- the wireframe fading as the smoothing goes up, which
+-- reads as one row breaking the other. Scaled, it stays a one-pixel seam and
+-- simply gains the antialiasing everything else in the frame just gained.
+function VoxelGrid.width()
+  return VoxelGrid.WIDTH * V.require("AntiAlias").factor()
+end
+
 -- where it persists and the rows that cycle it (see ModSetting)
 VoxelGrid.setting = ModSetting.new(VoxelGrid.KEY, VoxelGrid.LABEL,
                                    { false, true }, { "OFF", "ON" })
diff --git a/lib/Water.lua b/lib/Water.lua
index 9b7956c..db1db8e 100644
--- a/lib/Water.lua
+++ b/lib/Water.lua
@@ -1192,7 +1192,7 @@ function Water.begin(ctx)
   if ctx.grid then
     local VoxelGrid = V.require("VoxelGrid")
     send("gridDark", VoxelGrid.DARK)
-    send("gridWidth", VoxelGrid.WIDTH)
+    send("gridWidth", VoxelGrid.width())
   end
 
   Water.sendSky(sh, ctx)
diff --git a/main.lua b/main.lua
index 95ca933..6ca5f8a 100644
--- a/main.lua
+++ b/main.lua
@@ -82,6 +82,7 @@ local BattleExit = V.require("BattleExit")
 local DayNight = V.require("DayNight")
 local DayTint = V.require("DayTint")
 local Water = V.require("Water")
+local AntiAlias = V.require("AntiAlias")
 
 -- Forward declaration: the voxel pipeline's update hook (registered below)
 -- calls this, and it is defined further down with the settings it drives.
@@ -203,20 +204,34 @@ mod.content.render_pipelines:register("voxel", {
     -- a magnified low-res image, while the FX closures keep drawing in
     -- world-pixel units.
     local sw, sh = sceneSize(ctx)
-    local canvas = VoxelScene.render(ctx.state, sw, sh,
+    -- With AA on, the whole pass runs into a canvas BIGGER than the window
+    -- and is folded back down at the end (see AntiAlias).  Nothing between
+    -- these two lines knows: every pass in the frame measures itself in the
+    -- canvas it was handed, so the sky's dither, the water's march and the
+    -- camera itself all come out the same picture at a higher sample rate.
+    local rw, rh = AntiAlias.expand(sw, sh)
+    local canvas = VoxelScene.render(ctx.state, rw, rh,
                                      ctx.vw, ctx.vh, ctx.paletteFor)
     if not canvas then return nil end   -- fall back to the 2D path
     if Voxel3D.beginOverlay() then
+      -- the FX closures are ordinary 2D draws sized in DISPLAY pixels, and
+      -- they are drawing into the supersampled canvas alongside everything
+      -- else -- so the scale goes up with it, or the "!" bubble lands the
+      -- right place at half the size.  project() already answers in canvas
+      -- pixels, so only the scale needs saying.
       ctx.drawFx(function(wx, wy) return Voxel3D.project(wx, 0, wy) end,
-                 ctx.scale)
+                 ctx.scale * AntiAlias.factor())
       Voxel3D.endOverlay()
     end
-    return canvas
+    -- and back to the window's own size, which is what the engine composites
+    -- one canvas pixel to one display pixel.  A pass-through when AA is off.
+    return AntiAlias.resolve(canvas, sw, sh, "world")
   end,
 
   invalidate = function()
     Voxel3D.invalidate()
     OverworldBattle.invalidate()
+    AntiAlias.invalidate()
     ChunkMesher.invalidate()   -- no map id = every cached mesh
   end,
 })
@@ -361,6 +376,21 @@ local SETTINGS = {
     .. "let CYCLE run it -- ten minutes of sun, ten of moon, with the "
     .. "shadows, the sky and the light following -- or SYNC it to the "
     .. "clock on the wall, so Kanto's evening falls when yours does." },
+  -- Marked `full` for the opposite reason the battle rows are: this is not a
+  -- knob on the look at all, it is what the look COSTS. FULL is a preset for
+  -- the diorama, not a licence to spend four times the fill rate on the
+  -- machine it happens to be running on, so it neither sets this nor takes
+  -- the row away -- the player decides what their hardware can carry, from
+  -- inside FULL like anywhere else.
+  { AntiAlias.setting,
+    "Smooth the stair-stepped edges of the 3D world -- roof ridges, ledge "
+    .. "lips, a tree against the sky -- by rendering the diorama larger than "
+    .. "the window and folding it back down. Every edge in the picture "
+    .. "softens with them, the tileset's own texels included, so the diorama "
+    .. "reads smoother rather than sharper. 2X costs half again as many "
+    .. "pixels in each direction and 4X twice, which makes this the most "
+    .. "expensive row in the mod.",
+    full = true },
 }
 
 local schema = {}
diff --git a/tests/dramatic_shape_test.lua b/tests/dramatic_shape_test.lua
index 70b37fc..0ca9283 100644
--- a/tests/dramatic_shape_test.lua
+++ b/tests/dramatic_shape_test.lua
@@ -145,6 +145,9 @@ T.check(not fullIds["DRAMATIC_SHAPE:daytime"], "and DAYTIME")
 -- back sprite (or no staged fights at all) can still say so from inside FULL
 T.check(fullIds["DRAMATIC_SHAPE:battles"], "3D-BTL is still on the menu under FULL")
 T.check(fullIds["DRAMATIC_SHAPE:battleBack"], "and BACK SPRITES with it")
+-- and AA, for the opposite reason: it is not a knob on the look at all, it is
+-- what the look COSTS, and only the player knows what their machine can carry
+T.check(fullIds["DRAMATIC_SHAPE:aa"], "and AA, which FULL neither sets nor owns")
 
 -- DAYTIME is not only hidden under FULL, it is HELD at SYNC: the row cannot
 -- be reached while FULL owns it, so a value changed underneath (the mod
@@ -379,9 +382,11 @@ end
 Pipelines.setLevel("voxel", 2)
 local hookedRows = Runtime.call("ui.options.rows", function(_, r) return r end,
                                { data = Data }, { { id = "text_speed" } })
-T.eq(#hookedRows, 7, "the options hook added a row per setting")
+T.eq(#hookedRows, 8, "the options hook added a row per setting")
 local grid, curve, water = hookedRows[2], hookedRows[3], hookedRows[4]
 local battles, backRow, daytime = hookedRows[5], hookedRows[6], hookedRows[7]
+-- the AA row is hookedRows[8]; it is read in its own block below, because
+-- this chunk is one main function and has 200 local slots to spend
 T.eq(water.label, "WATER", "the water row carries its label")
 T.eq(water.value(), "FULL",
   "and defaults to FULL -- reflections are the point of having the row")
@@ -459,6 +464,57 @@ curve.step(settingGame, 1)
 curve.step(settingGame, 1)
 T.eq(curve.value(), "OFF", "the curve is left off for the rows below")
 
+-- ------- AA renders the pass larger and folds it back down
+--
+-- The ladder is SAMPLES per display pixel, so the canvas scale each rung asks
+-- for is its square root -- and the scale in force has to be readable after
+-- the fact, because two things are quoted in DISPLAY pixels and have to be
+-- multiplied up into the canvas the pass actually opened: the wireframe's
+-- line width and the FX overlay's sprite scale.
+do
+local AntiAlias = run.loader.exports.DRAMATIC_SHAPE.lib.require("AntiAlias")
+local VoxelGrid = run.loader.exports.DRAMATIC_SHAPE.lib.require("VoxelGrid")
+local aaGame = { save = { options = {} }, mods = { modOptions = {} } }
+local aa = hookedRows[8]
+T.eq(aa.label, "AA", "the anti-aliasing row carries its label")
+T.eq(aa.value(), "OFF",
+  "and starts off -- supersampling is a cost knob, and a mod must not spend "
+  .. "four times the fill rate of the machine it lands on unasked")
+
+T.eq(AntiAlias.samples(), 0, "AA is off by default")
+local w, h = AntiAlias.expand(320, 200)
+T.eq(w, 320, "an OFF row renders at the window's own width")
+T.eq(h, 200, "and its height")
+T.eq(AntiAlias.factor(), 1, "with nothing to multiply display pixels by")
+T.eq(VoxelGrid.width(), VoxelGrid.WIDTH,
+  "so the wireframe is the one-display-pixel line it has always been")
+
+aa.step(aaGame, 1)
+T.eq(aa.value(), "2X", "stepping the row climbs to two samples a pixel")
+T.eq(aaGame.save.options.modOptions.DRAMATIC_SHAPE.aa, 2,
+  "the sample count persists beside the other settings, not over them")
+w, h = AntiAlias.expand(320, 200)
+T.eq(w, 453, "two samples a pixel is a canvas root-two wider")
+T.eq(h, 283, "and root-two taller")
+T.check(math.abs(AntiAlias.factor() - 453 / 320) < 1e-9,
+  "and the factor is what it MEASURED, not what the row asked for")
+
+aa.step(aaGame, 1)
+T.eq(aa.value(), "4X", "and again to four")
+w, h = AntiAlias.expand(320, 200)
+T.eq(w, 640, "four samples a pixel is a canvas exactly twice the size")
+T.eq(h, 400, "in each direction, which is the 2x2 box the fold reads")
+T.eq(AntiAlias.factor(), 2, "with everything in display pixels doubled")
+T.eq(VoxelGrid.width(), VoxelGrid.WIDTH * 2,
+  "the wireframe among them -- a seam left at 1.0 would fold down to half a "
+  .. "line, so the smoothing row would appear to fade the grid row out")
+
+aa.step(aaGame, 1)
+T.eq(aa.value(), "OFF", "and the ladder wraps back to off")
+AntiAlias.expand(320, 200)
+T.eq(AntiAlias.factor(), 1, "leaving nothing behind for the next pass")
+end
+
 -- ------- the animated terrain atlas survives an engine without its seams
 --
 -- Regression: cycling palette modes with voxel mode on eventually killed
diff --git a/tools/building_voxels.py b/tools/building_voxels.py
index 0fb1b12..a38f8c5 100644
--- a/tools/building_voxels.py
+++ b/tools/building_voxels.py
@@ -547,6 +547,57 @@ TEMPLATES = {
         roof_rows=17, roof_back=5, roof_front=3, roof_cycle=(5, 9),
         slab=4, front_eave=4, ledge=None, tileset="forest",
     ),
+    # F01: the starter-ball table in Oak's lab -- the first FURNITURE
+    # through the pipeline, and the first drawing whose plot is smaller
+    # than its grid. Rows 0-15 are the tabletop seen from above; 16-18
+    # are the top slab's own front edge (black/#555/black -- exactly
+    # what the rim treatment paints, so slab=3 and those rows fold into
+    # the roof band instead of extruding); 19-21 are the base: corner
+    # feet and the inset dark panel between them. The legs stand on
+    # open FLOOR, so the measured ground line lands two rows short of
+    # the grid; `depth` keeps the plot to the blocked cell row -- the
+    # legs row of the grid is the walkable cell the player faces the
+    # table from, and D = len(tiles) would stand the model in their
+    # path. 16 top rows onto a 16px plot map 1:1: no cycling, and
+    # roof_cycle is unreachable behind roof_back=16.
+    "lab_table": dict(
+        tiles=[
+            [41, 59, 59, 59, 59, 42],
+            [78, 57, 57, 57, 57, 79],
+            [88, 89, 89, 89, 89, 90],
+        ],
+        roof_rows=19, roof_back=16, roof_front=0, roof_cycle=(2, 13),
+        slab=3, front_eave=0, ledge=None, tileset="gym", depth=2,
+    ),
+    # F02: the computer bank on Oak's lab's west table -- and the Hall
+    # of Fame's recording machine, the same drawing on the GYM atlas
+    # (one placement each; both registered in voxel_heights). The one
+    # FACADE-ONLY template: rows 0-14 are machines drawn face-on over
+    # the lab-table fascia and base bands, nothing is drawn from above,
+    # so roof_rows=0 and the whole drawing extrudes -- the house facade
+    # treatment with no roof band. The recess pass turns every framed
+    # pane (the monitor, its keys, the right cabinet's inset face) into
+    # relief on its own.
+    "lab_computers": dict(
+        tiles=[
+            [91, 92, 93, 94],
+            [54, 55, 85, 95],
+            [88, 89, 89, 90],
+        ],
+        roof_rows=0, roof_back=0, roof_front=0, roof_cycle=(0, 0),
+        slab=0, front_eave=0, ledge=None, tileset="gym", depth=2,
+    ),
+    # F03: the empty north table beside it -- the starter table's band
+    # table verbatim on a grid two tiles narrower (one placement).
+    "lab_table_small": dict(
+        tiles=[
+            [41, 59, 59, 42],
+            [78, 57, 57, 79],
+            [88, 89, 89, 90],
+        ],
+        roof_rows=19, roof_back=16, roof_front=0, roof_cycle=(2, 13),
+        slab=3, front_eave=0, ledge=None, tileset="gym", depth=2,
+    ),
     # B23: the Victory Road entrance on Route 23: a rock face with two
     # barred doors. The roof band is the pale cliff top seen from above.
     "victory_road_gate": dict(
@@ -642,7 +693,14 @@ def profile(sp, t):
         r = next((y for y in range(H) if inside(x, y)), t["roof_rows"])
         top.append(min(r, t["roof_rows"]))
 
-    wall_h = H - t["roof_rows"]
+    # The drawing's own ground line: the row after the last drawn one. A
+    # building ends on the black threshold row it stands on (ground == H),
+    # but furniture is drawn standing on open floor -- the lab table's
+    # legs stop two rows short of its grid -- and extruding against H
+    # would float it that far above its own plot.
+    ground = max((y for y in range(H) for x in range(W) if inside(x, y)),
+                 default=H - 1) + 1
+    wall_h = ground - t["roof_rows"]
     ytop = wall_h - 1 + t["slab"]
 
     # Recesses: the panes the art seals behind a black frame. Non-black
@@ -674,8 +732,12 @@ def profile(sp, t):
             if x1 - x0 + 1 <= RECESS_MAX and y1 - y0 + 1 <= RECESS_MAX:
                 recess.update(cells)
 
+    # Depth is the PLOT. For a whole-drawing building that is the grid
+    # itself; `depth` (in tile rows) names it when the grid runs past the
+    # plot onto ground the drawing merely stands its legs on.
     return dict(top=top, wall_h=wall_h, ytop=ytop, recess=recess,
-                inside=inside, D=H, W=W, H=H)
+                inside=inside, D=t.get("depth", len(t["tiles"])) * 8,
+                ground=ground, W=W, H=H)
 
 
 # --------------------------------------------------------------- stage 3 --
@@ -720,7 +782,9 @@ def build(sp, pr, t):
 
     # ---- walls: the facade rows extruded straight back, trimmed under the roof
     for sy in range(t["roof_rows"], H):
-        y = H - 1 - sy
+        y = pr["ground"] - 1 - sy       # rows below the ground line are floor
+        if y < 0:
+            continue
         for sx in range(W):
             if not inside(sx, sy) or trimmed(sx, y):
                 continue
@@ -742,7 +806,7 @@ def build(sp, pr, t):
     if t["ledge"]:
         l0, l1 = t["ledge"]
         for sy in range(l0, l1 + 1):
-            y = H - 1 - sy
+            y = pr["ground"] - 1 - sy
             for sx in range(W):
                 if inside(sx, sy) and not trimmed(sx, y):
                     for z in (-2, -1, D, D + 1):
@@ -750,7 +814,7 @@ def build(sp, pr, t):
 
     # ---- recesses: the front voxel of every pane sinks, its frame stays proud
     for sx, sy in pr["recess"]:
-        vox.pop((sx, H - 1 - sy, D - 1), None)
+        vox.pop((sx, pr["ground"] - 1 - sy, D - 1), None)
 
     # ---- roof: flat top over the plateau, stepped diagonal ends
     z0, z1 = 0, D - 1 + t["front_eave"]
@@ -805,6 +869,41 @@ def verify(vox, sp, pr, t):
     for (x, y, z), _ in vox.items():
         assert y <= T(x), f"voxel pokes through the roof at {x},{y},{z}"
 
+    # A facade-only template (roof_rows == 0: the lab computer bank) has
+    # no roof band, so there is no surface to hold level or slope and no
+    # coverage to demand of it; the poke-through assert above and the
+    # facade asserts below are what it answers to. Everything with a roof
+    # band answers the full set.
+    if t["roof_rows"] == 0:
+        # its one geometric intent: a straight extrusion, so each column
+        # of the drawing is solid from its lowest voxel to its top. NOT
+        # from the ground -- the drawing's base band is inset like every
+        # lab table's, and the body's outer columns legitimately
+        # overhang it -- and not on the recess layer, which sinks panes.
+        lo, hi = {}, {}
+        for (x, y, z) in vox:
+            lo[(x, z)] = min(lo.get((x, z), y), y)
+            hi[(x, z)] = max(hi.get((x, z), y), y)
+        for (x, z), h in hi.items():
+            if z == D - 1:
+                continue
+            assert all((x, y, z) in vox for y in range(lo[(x, z)], h + 1)), \
+                f"facade column has a hole at {x},{z}"
+    else:
+        verify_roof(vox, pr, t)
+
+    shell = [k for k in vox if not all(
+        (k[0] + d[0], k[1] + d[1], k[2] + d[2]) in vox
+        for d in ((1, 0, 0), (-1, 0, 0), (0, 1, 0),
+                  (0, -1, 0), (0, 0, 1), (0, 0, -1)))]
+    return shell
+
+
+def verify_roof(vox, pr, t):
+    W, H, D = pr["W"], pr["H"], pr["D"]
+    ytop, top, slab = pr["ytop"], pr["top"], t["slab"]
+    T = lambda x: ytop - top[max(0, min(W - 1, x))]
+
     # The roof surface reads over the columns the drawing actually paints:
     # a sprite inset from its box says nothing about the rest.
     roofed = [x for x in range(W) if top[x] < t["roof_rows"]]
@@ -845,12 +944,6 @@ def verify(vox, sp, pr, t):
             elif any((x, y, z) in vox for y in range(0, T(x) - slab + 1)):
                 assert (x, T(x), z) in vox, f"wall uncovered at {x},{z}"
 
-    shell = [k for k in vox if not all(
-        (k[0] + d[0], k[1] + d[1], k[2] + d[2]) in vox
-        for d in ((1, 0, 0), (-1, 0, 0), (0, 1, 0),
-                  (0, -1, 0), (0, 0, 1), (0, 0, -1)))]
-    return shell
-
 
 def preview(shell, vox, sp, name, out, flip, canvas=(1700, 900), pad=20):
     # Mirroring depth swings the camera round to the other side. Mirror