city
The district: the tile grid every other slice stands on, and the painted city built from it. One tile is one world unit; tile `(tx, ty)` covers world x in `[tx, tx+1)` and y in `[ty, ty+1)`, so the surface on top of tile row `ty` is at world **y = ty + 1**.
game.city — Ashcross
The district: the tile grid every other slice stands on, and the painted city
built from it. One tile is one world unit; tile (tx, ty) covers world x in
[tx, tx+1) and y in [ty, ty+1), so the surface on top of tile row ty is at
world y = ty + 1.
The district is 26 columns wide and runs from y = -8 to y = 103. Columns 0-1 and 24-25 are its own walls, and everything outside that box reads solid, so nothing can walk off the edge of Ashcross and the camera can never look past it.
The climb, bottom to top
| y | |
|---|---|
| -8 … 9 | The flooded well. Black water, standing miasma on it, six ledges alternating up the shaft, a chain, a fire on a shelf. You start on the bottom ledge at (9.5, -3). |
| 9 … 25 | The street. Two shuttered houses facing each other, the mouth of the well as a hole in the road, a brazier, a stone stair up the west side and boards running back east between the roofs. |
| 25 … 42 | The shuttered houses. Higher storeys, tighter boards, and the first rotten plank — placed off the path, so the lesson costs a cache and not the run. |
| 42 … 58 | The scaffold. Lashed poles, planks between them, miasma standing in the base, and a rotten plank that is on the way up. |
| 58 … 74 | The gantries. Wide spans between houses, a brazier, and a pool of miasma sitting on one of the gantries. |
| 74 … 92 | The cathedral buttress. A stepped mass of pale masonry climbing east, then a pier you work up the face of on mason's planks. |
| 92 … 104 | The lazaretto gate. A terrace broken open at x 9-11 — that hole is the only way up onto it — and the arch itself at x 12-17. |
Every step of the critical path is within a rise of 2 and a gap of 3 world units, and every one has two tiles of headroom, so the whole climb is walkable with a plain jump. The instruments open shortcuts and side branches; they are never the only way up. That invariant is checked, not assumed — see "Checking it" below.
What it provides
Installed the moment the module is required, before start() runs:
NB.provide.solidAt(x, y) -- boolean: that world point is inside solid tile
NB.provide.oneWayTopAt(x, y) -- number?: surface y of a drop-through plank
NB.provide.inMiasmaAt(x, y) -- boolean: standing miasma
oneWayTopAt answers for the tile the point is in, and also for a point sitting
up to 0.05 above a plank's surface, so a doctor standing exactly on a plank
still reads as being on it.
Everything else is under NB.ext.city:
spawn | { x = 9.5, y = -3 } — the ledge in the well the round starts on |
gate | { x, y, w, h } — the lazaretto arch; entering it emits "gate.reached" |
bounds | what Camera2D:setBounds was given |
braziers | { { x, y }, … } — censer refills; standing at one emits "brazier" every frame |
caches | { { x, y, id }, … } — id is a NB.TINCTURES key; taking one emits "pickup" |
patientSpots | { { x, y, face, note }, … } — ledges with a wall behind them, for the rounds slice to put a patient on. face is -1 or 1: which side the shuttered wall is |
route | every standable surface of the climb, bottom to top, as { x0, x1, x, y, note, side }. x0..x1 is the span you can stand on, measured off the finished grid. side = true marks a branch off the critical path |
ladderAt(x, y) | a ladder or a chain you may climb |
isRotten(x, y) | that plank is rotten |
probeBoard(x, y) | "sound" / "rotten" / nil — what the cane's probe finds under a point |
breakBoard(x, y) | Theriac's weight goes through a rotten plank: the whole plank goes, collision and picture together |
surfaceUnder(x, y) | the y of the first surface below a point |
pulse(dt) | the district's per-frame work; CityRuntime calls it, nothing else should |
Emits "brazier", "pickup", "gate.reached". Calls
Camera2D:setBounds(6.5, 19.5, 0.5, 95.5) and NB.claimVisual("city").
How it is drawn
The grid is data; the picture is a separate pass over it. Runs of solid tile are
merged greedily into rectangles and each rectangle is one quad wearing
art.tiles.tile — a shader written for this slice that repeats a painted sheet
across the quad and picks a different cell per tile from a hash of where that
tile sits. So a twenty-tile wall is one draw and is never the same cell twenty
times. The whole district is a little over two hundred pieces.
The painted sheets live in /zero/source/art/tiles/: masonry, timber,
facade (2x2 grids of cells) plus props and gate (cut out of flat magenta
by the shader's chroma key). Each surface names a flat palette colour as well,
which is what it wears if a sheet is missing — so a gap in the art reads as dim
stonework rather than a wall of white cards.
There are no lights in Ashcross. Braziers, tincture caches, lit windows and
the lantern over the gate are soft discs of amber laid over the scene by the
same shader's radial mode, and the lit lip along the top of every ledge is
top_light. That is the entire lighting model of the city.
Everything this slice spawns is temporary and parented under CityFabric,
itself under CityRoot. The district is rebuilt from this file on every load and
is never written into the scene.
Checking it
local City = require("game.city")
City.build() -- rebuild the picture; safe to call repeatedly
print(City.dump(8, 26)) -- an ASCII elevation of a slice of the grid
dump renders # solid stone, H house facade, = beam, - plank,
x rotten plank, ~ water, , miasma, . air.
Notes for the other slices
Camera2D:driveTodoes not clamp to the bounds this slice sets — the follow path clamps, the override path does not, so a scripted move can take the shot outside the district.- The
routespans are measured off the finished grid, including the headroom test, so a patrol that stays betweenx0andx1never walks into masonry.
Scoped to this part · feeds back into the world's score.