# Changelog

## 1.6.0

### Added

- **STADIUM battles, and a disc stage: three new rungs on the 3D-BTL row.**
  The row that used to read ON / OFF now reads **2D-3D A / 2D-3D B /
  STADIUM A / STADIUM B / OFF**, which is two independent choices laid out
  as one ladder -- WHAT is standing there, and WHERE:

  |            | on the MAP  | on two DISCS |
  | ---------- | ----------- | ------------ |
  | **pics**   | 2D-3D A     | 2D-3D B      |
  | **models** | STADIUM A   | STADIUM B    |

  2D-3D A is what ON always was -- the fight staged on the map with the Game
  Boy's own pics stood up on their tiles as quads. The STADIUM rungs keep
  the whole staging and replace those quads with the **Pokemon Stadium
  battle models**: all 151 species, skinned, lit and animated, playing the
  animation the move being used actually calls for.

  **A** stands the fight on the map, in the world's own light and weather.
  **B** stands it on two discs against the sky and draws no map at all --
  the Game Boy's own framing, staged rather than found.

  All four combinations are reachable rather than only the diagonal. The
  discs do not know what is on them -- they are two platforms at two cells,
  drawn off the arena alone -- so **2D-3D B** costs one value on the ladder
  and gives the disc framing to a player who has no Pokemon Stadium ROM and
  would rather not go and find one. It needs nothing the base game did not
  ship: the stage is generated in Lua and the Pokemon on it are the game's
  own art.

  B exists because the map does not always cooperate. Half of Kanto's
  interiors are furniture, a cave floor can be nothing but two-cell
  corridors, and some maps have nowhere a fight can be SEEN from a low
  camera and are declined outright -- which drops the player back to the
  flat battle screen with no warning. A carried stage has none of those
  problems: it works on every map, at every step, and the framing is the
  same every time. What it gives up is the thing A is for, which is
  fighting somewhere real.

  It is abstracted from the GROUND, not from the world. The sky behind the
  discs is the hour's own -- gold at dusk, navy at midnight -- and a fight
  in a cave or a shop is under that place's void and its own flat light, so
  walking into Mt. Moon at night and starting a battle looks like Mt. Moon
  at night.

  The discs themselves are flat: one quad apiece, wearing a painted circle
  that fades out at its rim, sized to the Pokemon standing on it. The fade
  is an ordered dither baked into the texture's alpha rather than a smooth
  ramp -- the scene shader discards under half alpha outright, so a ramp
  would come out as a hard-edged circle -- which is the same trick the sky's
  own bands use and reads as intended on a mode built out of visible texels.

  Nothing else about the fight moves. The arena is picked the same way, the
  camera is solved the same way, and the HUDs, the frosted text box, the
  move animations and the depth of field are all exactly what 2D-3D draws
  -- because every one of those is hung off the arena's CELLS rather than
  off the pics. Swapping what stands on a cell changes nothing about where
  the cell projects to, which is why this is a rung on the mode rather than
  a second mode.

  The animations are driven from the fight itself: a move plays the animation
  that species' own battle table names for that move (the Stadium ROM's
  per-species move table, packed into the assets, keyed by the same Gen 1
  move id the engine's move defs already carry -- so DIG really does put
  Diglett into the ground), fainting plays the faint and holds on its last
  frame, and a send-out grows the Pokemon out of the ball as it opens and
  plays the entrance with it. Between all of that, the standby loop. The eyes blink and go
  dizzy on their own counter, which is a texture animation glTF has no
  channel for and the pack carries anyway. Charmander's tail flame and
  Weezing's gas are there too, drawn additively over the body.

  The models go through the mod's OWN vertex format and shader rather than
  a path of their own, which is what earns them everything the rest of the
  diorama has: the depth buffer decides what is in front of what, the sun
  pass throws a shadow of the actual pose, the hour's tint lands on them,
  the hit flash flattens them and the tilt-shift and depth of field see
  them as part of the picture. Skinning is done on the CPU -- these are
  674-vertex models and exactly two are ever on screen -- and once per
  frame, so the sun, the camera and both VR eyes draw the same posed mesh.

  It declines per POKEMON rather than per battle: a species whose pack is
  missing, a substitute doll, or the trainer's own pic before the send-out
  all fall back to the flat card, on that side alone, with the other side
  keeping its model.

  Stored as new values on the same key, with 2D-3D A still first -- so a
  save written before this update reads back as exactly the mode it was
  written for.

- **The models are built on your machine, from your own cartridge.** The
  mod ships no Pokemon Stadium data and cannot: it is that game's. What it
  ships is the READER.

  **Press STADIUM ROM on the OPTIONS menu and pick the file.** The row opens
  the host's own file dialog -- osascript on macOS, PowerShell's
  OpenFileDialog on Windows, zenity then kdialog on Linux, which are the same
  four the engine's own Game Boy importer uses -- and the models are built
  from whatever comes back. `.z64`, `.n64` and `.v64` all work; the byte
  order is detected. The row reads IMPORT before and READY after, and
  pressing it again imports a different cartridge.

  The ROM is **not kept**: it is read, built from, and forgotten. A Stadium
  cartridge is 32 MB and the models built out of it are 34, so keeping both
  would double the cost of the feature for a file with no further use -- the
  marker still records its md5, so a swapped cartridge is noticed.

  The wrong file is refused with a reason on the loading screen rather than
  half-built, and refused BEFORE anything is written -- which matters more
  than it sounds, because the marker is the only thing that makes 151 files
  on disk count as installed, so a refusal that wrote one anyway would
  uninstall a working set.

  The original route still works everywhere, and is the answer on the
  platforms with no dialog (Android; a Linux install with neither zenity nor
  kdialog): drop a Pokemon Stadium (US) ROM into a `baseroms/` folder beside
  the game, straight in it rather than in a revision subfolder under it, and
  the first time it runs
  the 151 battle models are built out of it on a loading screen, in about ten
  seconds, one species a frame. The screen says what it is doing in those
  words -- **ONE-TIME EXTRACTION OF STADIUM ASSETS** -- carries a progress bar
  filled by species written rather than by elapsed time, and names the
  Pokemon it is on, which is the difference between a bar that is trusted and
  one that is suspected of having hung. After that they sit in
  the save directory and are read like any other asset. Until then the two
  STADIUM rungs simply are not on the row: they are skipped rather than
  shown and refused, because a setting that can be selected and then does
  nothing is indistinguishable from a broken mod.

  This is the same arrangement the engine already has for the Game Boy ROM
  it is a recompilation of, and it is why nothing ROM-derived is in this
  repository.

  Doing it needed the whole extraction pipeline in Lua: the archive and its
  Yay0 streams (`StadiumRom`), a geo-layout walk and an F3DEX2 interpreter
  with six texture codecs and a bit-packed animation sampler
  (`StadiumFragment`), the generated flame and gas stand-ins (`StadiumFx`),
  and the bind-pose measurement and packer (`StadiumBuild`).

  **Pure Lua, and deliberately so: it runs anywhere LOVE does, phones
  included.** No FFI, no native helper, no second process, nothing outside
  the standard library and stock LOVE calls. On desktop it is about seven
  seconds and peaks at 68 MB of Lua heap, 32 MB of which is the cartridge
  itself; the working set does not grow across the run.
  `tests/stadium_budget_test.lua` measures both and fails if either stops
  being bounded, because a transient peak a desktop shrugs off is what gets
  a process killed on a phone. On Android the save directory is the app's
  external-files folder, so `baseroms/` there is reachable over USB or a
  file manager without root.

- **Acknowledgement for [pret/pokestadium](https://github.com/pret/pokestadium)**,
  in README.md, model_extract/README.md and mod.card. The STADIUM extractor is
  original code, but the decompilation is what it was written against -- the
  bone matrix chain and the fact that scale is kept out of it, the rotation
  basis, the animation and texture-animation samplers, the battle context
  slots and the move-id constants all came from reading that project. None of
  its code or data is vendored here or needed to run this, and the credit says
  so as plainly as it says what was owed.

- `tools/stadium_pack.py` now reads the ROM directly rather than a
  pre-extracted tree, which makes it the ORACLE the Lua port is verified
  against: `tests/stadium_extract_test.lua` runs both over the same
  cartridge and requires all 151 packed files to come out **byte for byte
  identical**. That is the only honest test of a port of that much numeric
  code -- every rounding mode, iteration order and off-by-one shows up as a
  differing byte, and there are thirty-four megabytes of them.

- `ModSetting:setValue`, which sets a setting by its stored value rather
  than by its place on the ladder, and `ModSetting:setGate`, which lets a
  rung exist only when there is something behind it. 3D-BTL grew two rungs
  in the middle of itself, and every caller that had counted to two would
  otherwise have quietly meant something else afterwards.

- `tests/stadium_shots.lua`, a shot driver for both rungs, with a control
  mode and a pinned clock so two runs of it can be compared.

- **`tests/stadium_anim_qa.lua`: every Pokemon, every animation, every
  frame.** A battle asks a species for one of its animations and then poses,
  skins, re-textures and draws it sixty times a second, and nothing exercised
  that chain -- the pack probe reads the format and walks a bind pose, the
  shot drivers show one species in one animation at a time. So the failures
  only some species have had no way of being found except by a player calling
  that Pokemon out, which is exactly how the eviction crash above was found.

  This is that sweep, headless: the real StadiumPack, StadiumRig and
  StadiumMon over stubs for the three things a graphics context provides, and
  the stubs are not lenient -- a released object throws with LOVE's own
  message, because a stub that quietly accepted one would hide the class of
  bug the sweep exists to find. 151 species, 403,716 posed frames, 25
  seconds. It also drives the state machine over all 165 move slots and
  reproduces the cache eviction directly, which no amount of playing one
  species' animations can reach.

  What it found, and what happened to each:

  | finding | count | outcome |
  | --- | --- | --- |
  | pack cache evicted a model still on the field | 2 | **fixed** -- see above |
  | animation walks the Pokemon off its tile | 65 entrances, 36 hits, 35 faints | **fixed** -- see above |
  | part has no texture | 104,728 | **not a defect.** 39 primitives across 37 species, 1.6% of the set's vertices, every one with all-zero UVs: they are flat-shaded geometry in the original. The pack stores texture indices one-based, so the packer's `0xFFFF` "untextured" sentinel arrives as 65,536 and resolves to nothing, which is correct. Counted rather than reported now |
  | pose flies apart | 250 | **understood, left alone.** Two species in one animation each: Farfetch'd's entrance at 6.0x its bind height and Dewgong's at 6.1x, both just over the threshold, and both because the measurement is a bounding box that includes an authored trail -- Farfetch'd's is a dedicated 30-vertex primitive on a five-bone chain. The bodies are intact and, since the anchor, in frame |
  | NaN or infinite vertex, rig would not build, loopStart out of range, missing aux animation, track indexed off its end, move slot with no animation | 0 | none |

  The three species the packer already declines (Exeggutor, Tangela, Magmar)
  are skipped rather than swept: they are never posed in a game, and sweeping
  them anyway produced 356 of the 362 original "flies apart" findings.

### Fixed

- **Idle animations snapped, and then ran at half the frame rate.** Two
  bugs with the same cause, fixed in opposite directions.

  The animation streams are not keyframes: they carry one value per frame at
  30 Hz and the game steps them. Blended naively against a 60 Hz camera
  that produced the reported glitch -- Rattata's idle snapping almost upside
  down for a frame, Charmander's arms turning inside out for a few --
  because rotations here are **Euler triples**, and two triples can describe
  nearly the same orientation while being nowhere near each other component
  by component. `(0, 20976, 32736)` and `(0, -19936, -5904)` are a real
  consecutive pair out of the set. Walking from one to the other passes
  through orientations that are nothing like either end.

  Dropping the blend fixed that and cost the smoothness: every pose then
  held for two frames, and a set of models moving at half the rate of
  everything around them reads as a stutter. So the blend is back, and the
  snaps are **detected** rather than smoothed. A bone whose rotation moves
  more than a quarter turn inside one 30 Hz frame is not being animated, it
  is being re-expressed, and it holds its frame instead of blending -- all
  three components together, because they are one rotation. The same guard
  covers a translation that teleports more than half the Pokemon's own
  height in a frame. Angles blend the short way round the +-pi seam, and
  texture animations still step whole frames, because an eye is open or shut
  and there is no halfway swap to draw.

  Measured, not eyeballed: walking every species' standby loop in quarter
  frames and differencing each bone's world origin against itself, the
  number of species that jump more than a quarter of their own height in a
  quarter frame goes **42 -> 11 -> 3**: naive blend, stepped, guarded blend.
  The three that remain (Pidgeot, Dodrio, Grimer) have erratic rotation data
  at source and are held rather than smoothed, which is exactly the guard
  doing its job.

- **Exeggutor, Tangela and Magmar are drawn as battle sprites instead of
  models.** Those three come out of the ROM with standby loops that throw
  bones hundreds of units off the body -- the game's own index arithmetic
  evidently reads their channel streams differently from the way this does.
  They used to stand still in their **bind pose** instead, on the reasoning
  that a Pokemon looking like itself beats one coming apart.

  It does, but only just: a bind pose is a rigging pose, not a portrait, and
  three species holding a T-pose among a hundred and forty-eight that
  breathe read as broken rather than as still. So they now decline the model
  outright and the Game Boy's own battle pic stands on the tile instead --
  the same per-Pokemon fallback a species with no pack at all already took,
  in the same arena, under the same light.

  Keyed on the packer's own measurement rather than on a list of dex
  numbers, so a re-extraction that fixes those streams -- or breaks a fourth
  species -- moves this with it.

- **The eyes blinked several times a second.** A texture animation was
  running on a clock of its own and wrapping on its own stream's length.
  Rattata's standby loop is forty frames and its blink is five -- `6 8 7 8
  6`, open through shut and back -- so that played it six times a second,
  and every species with a short blink twitched the same way.

  Two things were wrong, and the data says so plainly: 507 of the 691
  animation/texture-animation pairs in the set are exactly the same length
  as each other, which is what one shared frame counter looks like from the
  outside. So the texture animation now rides the SKELETAL animation's own
  frame, and it HOLDS its last entry past the end of its stream rather than
  looping -- which is what the game's own sampler does (`func_80017540`).
  Rattata now blinks once at the top of each idle loop and keeps its eyes
  open for the other thirty-five frames.

  The frame is not recomputed for the textures; `pose` stashes the one it
  resolved and `textures` reads it, so the two cannot drift apart.

- **The faint animation no longer plays while the HP bar is still
  draining.** `onFaint` runs the instant HP reaches zero, but the engine
  queues the visible collapse behind the move animation and the bar drain
  -- and that drain takes real time, some two and a half seconds on a
  full-health Pokemon. The model was therefore lying down while its own
  health went on emptying above it.

  The request is now recorded when HP hits zero and played when the bar
  reaches zero, off the engine's own `shownHP` -- the same number the bar is
  drawn from, so the two cannot drift. A faint that stops being owed in the
  meantime (a revive, a battler replaced under us) is dropped rather than
  fired late at whoever is standing there. Measured rather than eyeballed:
  the shot driver's `DS_FAINT` case prints the frame the bar empties against
  the frame the animation starts, and they are the same frame.

- **Being hit played the ATTACK animation.** The context slot the mod called
  `hit` is not a damage reaction: read against the move table, Bulbasaur's is a
  95-frame animation that 66 of its moves play, and Pidgey's is 138 frames --
  four and a half seconds -- shared by 111 of its moves. That is the species'
  DEFAULT ATTACK, which is why taking damage looked exactly like swinging: it
  was the swing. Slots 173, 178, 179, 180 and 181 all point at the same one.

  Nor is a reaction hiding elsewhere. Exactly one animation per species is
  claimed by no slot and no move, and it is the same length as that species'
  idle for essentially all of them -- 48/48, 56/56, 60/60, 84/84 -- so it is a
  second standby loop, not a recoil. **This set has no damage reaction in it.**

  So damage now plays nothing and the Pokemon carries on with what it was
  doing, which is what the engine already communicates through its own screen
  flash, pic blink and HP drain. The slot is renamed `attack_default`
  throughout (a label on a position -- the file format is the ORDER, so no
  bytes changed), and the generic swing a move with no table entry falls back
  to now uses it rather than resolving to the standby loop.

- **The battle menu no longer changes colour with the scenery.** There was a
  pass that measured each frosted panel's average brightness and flipped the
  glyphs to white over a dark one, with hysteresis so a drifting camera could
  not strobe them. It worked, and it was still wrong: the battle menu is the
  part of the frame the player reads constantly, and having its colour depend
  on where the camera happens to point makes it unreliable furniture. Gen 1's
  battle ink is black, so it is black -- on a cave floor as much as on a
  meadow -- and the panel's tint, which always pushes toward white, is what
  earns it its contrast. Removing it also took out a one-pixel GPU readback
  that ran several times a second to answer a question nothing asks any more.

- **The required ROM is now named everywhere: Pokemon Stadium (US) 1.0.** It
  always was the only one that works -- every offset in the reader was
  measured against that cartridge -- but the docs and the file picker just
  said "Pokemon Stadium (US)", which is three different ROMs. The picker's
  title, the OPTIONS help, the README, mod.card and the manifest all name the
  revision now, the README carries the reference md5
  (`ed1378bc12115f71209a77844965ba50`) so a player can check their own file,
  and a build from anything else says so on the loading screen as well as the
  console rather than quietly producing wrong models.

- **The extraction screen just says STADIUM EXTRACTION now**, with the
  progress bar and the species name under it; the "this runs once" line is
  gone.

- **Pokemon now grow out of the ball, instead of appearing beside it.**
  Measured, the send-out is: the ball is thrown, the POOF animation runs for
  27 frames, and `startGrowIn` fires on the frame AFTER it ends. So the model
  did not begin to exist until the ball had finished opening -- the ball came
  apart, and then a Pokemon was switched on next to it.

  The engine's own ramp is the Game Boy's three steps (0, 3/7, 5/7, full)
  across the twelve frames after that, which on a 56-pixel sprite is a chunky
  pop and on a smooth 3D model is just a pop.

  The model now runs its own ramp, started when the POOF BEGINS and
  continuous: it grows out of nothing while the ball is coming apart and
  reaches full size exactly as the engine's own grow finishes -- 39 frames
  end to end, which is the poof's 27 plus the engine's 12. Smoothstep rather
  than linear or ease-out, because the ball is still opening through the first
  half and a curve that was already near full size by then would have the
  Pokemon standing about waiting for it. The entrance animation starts with
  the grow, so the arrival is one performance rather than a grow followed by
  a flourish.

  Its own ramp OWNS the arrival once it starts: falling back to the engine's
  afterwards shrank the Pokemon from 0.96 back to 0.71 and then snapped it to
  full, because the two finish a few frames apart -- a visible hitch at the
  end of the one animation that exists to not have one.

- **The first Pokemon of a battle arrived, left, and arrived again.** Every
  guard deciding whether the player's Pokemon is on the field is a field the
  engine sets once the battle is RUNNING, and during the opening none of them
  is set yet: `showPlayerBack` is still nil (BattleState assigns it further
  in), `playerBackPic` is nil with it, and `sendingOut` does not go true until
  the ball is thrown. So the whole intro read as "this Pokemon is standing on
  the field" -- two and a half seconds of it, on its tile, playing its standby
  loop, before the trainer sprite it is meant to be hiding behind had even
  appeared. It then vanished when that sprite arrived and came back with its
  entrance when the ball opened. A switch has no intro, which is why a switch
  always looked right and was the thing worth comparing against. The player's
  side is now simply not on the field during the intro phase.

- **The anchor made birds shake, and the shake read as fast flapping.** The
  body estimate it corrects toward was the MEDIAN bone origin -- robust to a
  few bones flung out, which is what it was chosen for, and wrong in a way
  that only shows on a flapping model: a median is a RANK, and on a bird most
  of the skeleton is wing, so which bone sits at the middle of the sorted list
  swaps between the up cluster and the down one every beat. Measured, the
  estimate moved a tenth of a body-height between adjacent half-frames on
  Pidgey and three whole body-heights on Pidgeot, and the anchor turned that
  into a translation of the entire Pokemon -- the body counter-shaking against
  its own wings, which reads as flapping at twice the real speed.

  The centre is now the bone origins averaged and **weighted by how many
  vertices each bone moves**. The weights are a property of the mesh, computed
  once, so there is no rank to flip -- and a bone with little geometry barely
  counts, which is the robustness the median was for in the first place
  (Farfetch'd's trail is 30 vertices on five bones). On top of that the
  correction is low-passed, so what survives is where the Pokemon has drifted
  to and never how it is shaking on the way. Pidgey's shake goes from 0.24 to
  **0.10 pixels a frame** on a fourteen-pixel model, and travel correction
  improved with it.

  Two species -- Pidgeot and Dodrio -- have standby loops with genuinely junk
  rotation frames, which move the estimate three and two body-heights in a
  single frame against a fifth of a body-height for the fastest real motion in
  the set. No filter separates those: rate-limiting the correction bounded the
  shake but put 33 of the 148 entrances back outside the frame, and
  rate-limiting the measurement could not tell a spike from an excursion
  because they are only a factor of fifteen apart. So each species' own
  standby loop is walked once, at rig construction, and one whose estimate is
  that unsteady is **not anchored at all**: it travels as far as its animation
  says and does not vibrate, which is exactly how it behaved before the anchor
  existed. One species trading a framing problem for no problem beats 147
  trading a solved framing problem for a shake.

- **A Pokemon calling out a fifth species killed every model in the fight.**
  Reported as `Cannot use object after it has been released` out of
  `Voxel3D.draw`, after which nothing 3D drew for the rest of the battle.

  The pack cache holds four models and evicts the least recently *loaded* --
  and `load` only runs when a side's species CHANGES, so a Pokemon that has
  been standing there for a few turns is the oldest entry in the cache. Send
  out a fifth species and it was evicted mid-fight and **its textures
  released**, while it was still being drawn sixty times a second.

  Two things were wrong. The eviction released each texture but left the dead
  object in its slot -- and a released Image is still a truthy value, so the
  next `image()` handed the corpse straight back out to `mesh:setTexture`.
  Slots are now cleared, so an evicted model simply decodes its textures
  again. And the mode now says, every frame, which two species are actually
  standing there (`StadiumPack.keep`), so the two in use are always the two
  most recent and cannot reach the front of the queue at all.

- **And a model that does fail now fails gracefully.** Both draws were a bare
  loop inside the caller's single pcall, so a throw on the first side skipped
  the second -- one broken Pokemon took its opponent off the screen with it
  -- and nothing recorded that it had happened, so the same throw came back
  every frame forever. Each side is now drawn, cast and posed inside its own
  guard, and a side that throws is RETIRED: its rig is released and
  OverworldBattle renders its flat battle pic from the next frame on, which
  is the fallback a species with no pack has always had. The fight carries on
  with a flat Pokemon instead of a missing one, and its opponent is
  untouched.

- **Half the set's animations walked the Pokemon out of the shot.** Found by
  the sweep below, not by a bug report, and it is the biggest of them: 65 of
  the 148 send-out entrances carry the body more than its own height off the
  spot it started on -- Dewgong's reaches seven and a half, and its faint
  nearly ten. Every one returns to exactly where it began, because Pokemon
  Stadium framed each Pokemon with a camera of its OWN that followed the
  performance around a stage. This mode has one camera, solved to hold two
  fixed map cells, so a Pokemon that travels seven body-heights is simply
  gone: sending out a Farfetch'd left an empty tile for three and a half
  seconds while its animation played somewhere off to the left of the frame.

  `StadiumRig.anchor` now takes the excess back out -- the pose is measured
  against where the bind pose put the body, and whatever has carried it
  further than `StadiumMon.TRAVEL` (three quarters of a body-height, which is
  what the frame holds) is subtracted from every bone. The EXCESS only: the
  83 species that never reach the limit are bit-for-bit what they were, and a
  lunge, a hop or a collapse still reads as big and still comes back to the
  tile it left. The centre is the median bone origin rather than the mean or
  the root, so Farfetch'd's five-bone trail streaking three thousand units
  out cannot drag the bird with it.

- **FLY and DIG now take the model off the field.** The charging turn of a
  two-turn move puts the Pokemon out of reach, and the engine says so through
  `picFx[battler].hidden` -- FLY runs `SE_SLIDE_MON_OFF` and DIG
  `SE_SLIDE_MON_DOWN`, each a 19-24 frame slide that ends by setting that
  flag, and the release turn puts the pic back. The model was reading
  `fxHidden`, which is the damage BLINK and nothing else, so it stood on its
  tile while the game insisted it was underground -- and insisted in the
  strongest way it has, by making every attack aimed at it miss.

  It now reads the same field the pic does, which also covers every other
  vanishing act on that seam: the user of Explosion, a Pokemon Teleported
  away. Read as the engine's own answer rather than as a list of move ids,
  so a mod that adds a third two-turn move gets it for free.

  It is deliberately NOT held to the end of its own animation the way a
  collapse is. The Stadium animations are authored as the WHOLE move --
  Charizard's DIG is 3.83 seconds of burrow, emerge and strike, because
  Stadium plays it in one turn -- so cutting at the engine's own hide shows
  the burrowing and holds the strike back for the turn it actually lands on.
  Verified as a timeline (`DS_FLY`, and `DS_MOVE=DIG`): the pic hid at frame
  67, the model went with it, and both came back at 264.

- **And the faint animation now gets to finish.** A fainted Pokemon left the
  field when its PIC did, and the engine's pic slide is fourteen frames of a
  60 Hz clock -- `SlideDownFaintedMonPic`, seven rows two frames apart, under
  a quarter of a second. The Stadium faint animations are nothing like that
  short: the briefest in the set is 49 frames of a 30 Hz clock, the median is
  110 and the longest 230. Every model was therefore cut off inside the first
  fifth of its own collapse -- the Pokemon began to fall and then vanished
  mid-fall, which is worse than not animating at all.

  A model that is collapsing now stays until it has finished collapsing, and
  the two timings are simply not tied to each other any more: how long a flat
  pic takes to slide off the bottom of a 160x144 frame has nothing to say
  about how long it takes a Gyarados to fall over. Bounded at both ends --
  it ends when the animation does, so nothing is left lying on the field for
  the rest of the fight, and the side is reset outright the moment a
  different battler stands in that slot, which is what stops the next
  Pokemon out of the ball arriving face down (that one bites when a trainer
  leads with two of the same species, where the dex number never changes and
  nothing downstream would otherwise notice the swap).

  `DS_FAINT` now prints the span as well as the moment: 127 frames against
  the pic's 14.

- Two ordering bugs in the extraction pipeline that made its output
  **non-reproducible**. The generated flame nodes were deduplicated through
  a Python `set` of tuples containing strings, so their order moved with
  `PYTHONHASHSEED` and Ponyta, Rapidash and Moltres got differently-seeded
  flames on different runs of the same build; and texture registration
  order came from iterating a `set` of ints, which is stable across runs
  but is a CPython implementation detail rather than a fact about the data.
  Both now go in order of appearance, which is the game's own order and the
  one a Lua port can reproduce.

### Changed

- The packed format is now **DSM3**: textures are stored as raw RGBA rather
  than PNG. An ImageData over those bytes costs nothing where a PNG costs a
  decode on the frame a battle starts -- but the reason it was done is that
  PNG means zlib, and Python's deflate and LOVE's need not agree byte for
  byte, which would have made the extractor impossible to check against the
  packer. Uncompressed pixels are the same pixels whoever wrote them. The
  set is 34 MB rather than 24 MB, and it is generated locally rather than
  shipped, so that is a trade worth making.

### Known

- Three species -- Exeggutor, Tangela and Magmar -- have standby loops that
  are corrupt in the source extraction, and fall back to their battle
  sprites on the STADIUM rungs (see above). 148 of the 151 have models.

- Pidgeot, Dodrio and Grimer have a handful of erratic rotation frames in
  their standby loops -- a few frames of junk at the top of the loop rather
  than a corrupt stream. The blend guard holds those frames rather than
  smoothing through them, so they step where the source steps, and Pidgeot
  and Dodrio are additionally left unanchored because those frames move the
  body estimate too far to measure against (see above). Not chased
  further: the extraction is byte-identical to a reference pipeline whose
  sampling was validated against the decompilation's own arithmetic, and
  guessing at the format to fix three species risks the other 145.

## 1.5.5

### Added

- **A third-person camera: the 3RD rung.** The VOXEL ladder's eighth rung
  (`3` / SELECT walk onto it after 1ST, and it is on the OPTIONS row)
  stands the camera on a boom behind the player's shoulder. It is the
  first-person rig with one number added, so everything 1ST already did
  it does: the look steers on a mouse, the right stick or a touch drag,
  and the grid walk is replaced by continuous camera-relative movement --
  push in any direction and you go there, at any angle, with collision,
  warps, ledges, encounters and scripts still running through the
  engine's own machinery.

  The boom **collides**: it marches back through the terrain height field
  and the map's own walkability, so backing into a wall walks the camera
  in to your shoulders instead of through it, and rounding a corner eases
  it back out rather than snapping. Squeezed all the way into the head it
  simply draws as 1ST until you step clear. It also carries a small
  over-the-shoulder rail offset, which fades out as the boom shortens.

  Every sprite in the world **turns to face it** -- yours, the NPCs', the
  figures drawn into the furniture -- and shows the frame it would look
  like from where the camera actually stands, so walking behind someone
  shows you their back. Your own character is drawn (with the
  through-the-wall silhouette 1ST had no use for) and **turns to face
  where they are walking** rather than where the camera looks, so a strafe
  reads as one; standing still they come back round to the camera's
  bearing, which is the one A talks along.

  Your card's frame is chosen from your body's **continuous** bearing
  rather than from the compass direction the grid game stores. An NPC's
  facing really is one of four directions, but yours is the angle the
  camera itself is hung off, and quantising it before measuring it against
  the eye leaves no margin for the shoulder rail's few degrees of offset:
  in a band just short of each 45-degree boundary the pair read as 135
  degrees apart and picked the mirrored PROFILE frame. Standing still.
  Spinning the camera swept four of those bands a revolution, which showed
  up as the character flicking sideways for a split second.

  In VR the boom is declined outright and 3RD presents as 1ST does: a
  headset that seats its wearer three cells behind their own body is a
  well-known way to make people ill. The rung still changes the walk and
  the sprites the same way.

- **Every camera zooms, on whatever the machine has.** The mouse wheel,
  `Q`/`E`, a two-finger pinch and the pad's two stick clicks all reach
  whichever camera is actually in front of you -- the third-person boom,
  the staged battle's lens, or the engine's own survey zoom on an orbit
  rung. One module (`lib/CamControl.lua`) answers "which camera is this
  aimed at" so the four cameras never race each other for an event, and
  forwards everything it does not claim. 1ST claims nothing: the eye is in
  the player's head, and a pinch there would only wind the survey zoom for
  whenever they stepped back out.

- **The battle camera is yours to steer.** The right stick, a drag across
  the screen or the mouse walks the staged shot around the arena and raises
  the seat; the wheel, `Q`/`E`, a pinch or a stick click work the lens.

  Both axes stop where the composition does. LEFT stops at the shot the rig
  was solved for, because there is nothing to the left of it. RIGHT ends
  SIDE-ON -- the eye square to the arena's axis, both Pokemon at the same
  distance instead of one behind the other -- computed from each rig's own
  stance rather than written down. DOWN stops at the rig's low stance and
  UP is 45 degrees above it, raised about the focus at a constant radius so
  climbing never doubles as zooming. Input accumulates into a goal the eye
  eases after, so a flick reads as the camera being pushed rather than
  dragged.

  The lens **opens by exactly the amount the pair spreads**: the solved
  shot looks along the arena's axis at a shallow angle, which foreshortens
  the gap between the two mons to less than half its length, and swinging
  round or climbing un-foreshortens it. Left alone that threw both Pokemon
  off the edges of the frame at the far end of either range, which made the
  whole far end unusable.

  Where you leave the camera is where the next battle opens. An angle and a
  lens you chose are how you want to watch battles, not a fact about one
  encounter.

- **Move animations track the camera.** They already slid to follow the
  pair's midpoint; now they follow its SEPARATION too. Both mons are
  geometry standing on the map, so the camera sizes them -- and an effects
  layer that kept the authored 106-pixel spacing through a zoom and a
  60-degree swing fired its beams into the air beside the Pokemon they were
  aimed at.

- **BACK SPRITES locks the battle camera.** That setting pins your own mon
  to the GB's slot on the menu while the foe stands out on the map, and no
  angle holds a composition that is half frame and half world. The steer,
  the climb and the lens all stand down -- in the rig as well as at the
  inputs, so an angle stored from before the row was switched on cannot
  leave it steered anyway. The slow drift stays: it was always there under
  BACK SPRITES and two degrees is not a composition problem.

- **BATTLE BG is pinned to WHITE and its row comes off the menu.** The row
  picks what fills the screen AROUND the battle, and this mode fills the
  window with the map the fight is standing on -- there are no voids left
  for it to be about. WORLD was actively wrong under it: it makes the
  battle non-opaque so the engine draws a second, dimmed copy of the
  overworld beneath the arena pass's own. Pinned rather than merely hidden,
  so a save written before the mod was installed cannot carry a value the
  menu can no longer reach. Uninstall and the row is back.

### Fixed

- **Grass and flowers are closed off at the sides.** Both stand as
  per-pixel slabs built from runs of lit pixels, and only the front, the
  back and a lid were ever emitted -- so from any angle off square you
  looked straight in through the open end of every run and out the far
  side. At the low cameras this release adds, that is most of the time.
  Each run now wears end walls in the colour of the pixel they close off.

  Flowers needed more than that, because a flower SWAYS: the mesh spans the
  union of every animation frame and each frame is cut back out in texture
  space, so a pixel that drops out of a frame takes the union's wall with
  it and leaves an interior boundary that never had one. The first cut of
  this looked solid on the base frame and still had gaps on every other.
  Every pixel of a flower now carries a cap on all four of its remaining
  faces: enclosed and invisible while its neighbour is there, and already
  in place the moment the animation takes that neighbour away.

- **No more machine-gun bonking in 1ST and 3RD.** The grid walk's collision
  sound marks a discrete event -- a direction pressed, a step refused. A
  free walk has no such moment: the body slides along every wall it grazes,
  continuously, so a corridor taken at a slight angle rang the bonk twice a
  second from end to end. The wall stopping you is the feedback.

## 1.5.2

### Added

- **The Pokédex in hand is a quarter larger.** Its voxel pitch went from
  1.1 cm to 1.375 cm (the body from about 10x15 cm to about 12x19 cm),
  because the screen carries every menu in first person and was
  squint-small at the old size. The attachment -- flush along the left
  controller -- is unchanged.

- **Left stick click steps the VOXEL ladder.** In VR the click now makes
  exactly the step the "3" key (and the pad's SELECT) makes -- the same
  function, handed across, so the ladder walk, the FULL step-over and
  the TILT/GBC FX clearing can never drift from the key's. It used to
  toggle first/third person against a remembered return rung.

- **The floating panel shows the same picture at every window size.**
  The GB-frame region used to be copied into the headset's panel
  pixel-for-pixel, and the panel's swapchain image has a fixed size --
  so a window scaled past it (fullscreen above all) ran the frame off
  the copy's edge and cut the START menu out of the panel. The region
  is now blitted OUT of the window and SCALED into the swapchain image
  at the frame's own aspect: identical picture, identical near-square
  ratio, whatever size or shape the window takes.

- **Menus stay inside the GB frame while a headset is live.** The
  engine's new zoom-aware anchoring docks the START menu to the
  WINDOW's edge -- and both VR screens (the floating panel and the
  Pokédex) crop the window to the near-square GB frame, so a docked
  menu was cropped away with the border it hugged. While a headset is
  live the mod answers the engine's own "hold the anchors" predicate
  with yes, and every menu blits where it was drawn: the START menu's
  classic slot, flush with the frame's right edge, which is the right
  edge of everything the headset shows.

- **The sky's dither is glued to the sky.** The gradient's bands were
  already read by true elevation, but the GBC checker between them kept
  SCREEN-cell parity -- so a head's pitch or roll slid the world-fixed
  band edges over a screen-fixed checkerboard and the whole gradient
  shimmered as the pattern recomputed. The ray path is now a computed
  SKYBOX: each pixel's ray lands in a cell of the sky's own angular
  grid (azimuth columns, elevation rows, sized to match the diorama's
  pixel grid on screen), and the band, the checker's parity and the
  twilight glow -- now measured by the angle to the sun's own direction
  -- are all answered from that cell's centre. The screen grid
  quantises nothing, so the picture behaves exactly like a
  nearest-filtered texture on a dome: its cells slide smoothly with the
  world, no motion of the head recomputes the pattern, and only the
  clock moves the sky.

- **VR works from an installed release.** The OpenXR loader used to be
  looked for only against the working directory and the game's source --
  right for the dev tree, wrong for a release install, where importing
  the mod lands it in the game's SAVE DIRECTORY (or keeps it zipped) and
  the VR row silently failed to start. The search now also asks the
  mount that actually holds the mod and the save directory itself; and
  if nothing on disk answers -- the mod imported as an archive -- the
  DLL is copied once out of the mod into the save directory and loaded
  from there, so every install shape reaches the headset.

- **SELECT walks the VOXEL ladder.** In free roam, the pad's SELECT
  button makes exactly the step hotkey 3 makes -- OFF through the angle
  rungs to 1ST and round, stepping over FULL, clearing TILT and GBC FX
  on every press like the key does. For the machines with no number row:
  the phone's touch pad and a controller. SELECT has no overworld job in
  Gen 1 -- its work is all in-menu, and menus keep it untouched.

- **PCVR, in-process, through OpenXR -- with no change to the parent
  app.** A new **VR** row (OFF / ON, off by default; on the OPTIONS menu
  and the mod manager's page). The whole stack rides LuaJIT's FFI from
  inside the mod: the Khronos OpenXR loader ships in `assets/vr/` (with
  its Apache-2.0 license alongside), the session binds to LOVE's own
  OpenGL context, each eye is rendered by the mod's existing scene pass
  under a placed camera built from the tracked pose, and the finished
  canvases are blitted straight into the runtime's swapchain images.
  Works with any Windows OpenXR runtime (SteamVR, Oculus, WMR).

  - **What you see mirrors the VOXEL ladder.** On the orbit rungs the
    world is a TABLETOP DIORAMA hung at the RUNG'S own viewing angle and
    at the scale that reproduces the flat screen's framing -- step onto
    35 and the model presents at 35 degrees, onto 75 and it rises toward
    eye level, easing between rungs; lean in and the town grows, walk
    around the table and honest occlusion shows you the far side of the
    buildings. On **1ST** you stand inside the world at life size (a
    tile is a stride), the headset steers the same yaw and pitch the
    flat screen's mouse does, and FreeMove walks where you look.

  - **VR controllers.** One OpenXR action set, suggested onto Touch,
    Index and WMR (plus the khr/simple fallback), rebindable in the
    runtime's own UI. In both modes: **left stick** moves (through the
    engine's own stick path, so it grid-walks the diorama and free-walks
    1ST), **A/B** are A/B, **either trigger** is START, and **clicking
    the left stick** steps the VOXEL angle ladder exactly as the "3"
    key does. In the diorama: **right stick up/down** zooms the
    model, and **squeezing a grip** while moving that hand up or down
    drags the whole table with it. No controller button leaves VR --
    both directions belong to the VR row alone, so no mid-fight click
    can eject you from the headset.

  - **Battles happen ON the world -- from the flat game's own seat.**
    When a staged fight starts, the headset fades to black and comes
    back seated in the flat battle's over-the-shoulder shot: on the same
    camera line (your mon near-left, the foe far-right), pulled in to
    the wide rig's standing distance at life scale, turned to face the
    arena -- and fades back to wherever you were when the fight ends.
    Both mons stand on their arena cells in the VR eyes' own view, yawed
    per eye, casting real shadows, wearing the hit flash -- and the MOVE
    ANIMATIONS play out there with them: the engine's own effects layer,
    caught on a canvas and stood on a billboard that faces the eye the
    way the mon cards do (effects are 2D drawings, and a drawing must
    face the eye that is looking), with the classic layout's two slot
    marks pinned where each arena cell lands on that plane along the
    eye's own ray -- so a burst authored at a slot sits exactly over the
    mon standing in for it, per eye, and a projectile crossing the frame
    crosses the arena. (The flat screen keeps its
    composed battle shot, untouched.) And the battle camera's parallax
    drift holds still while a headset is watching: the sway is a flat
    screen's depth cue, and inside VR it read as the world lurching.

  - **A voxel POKEDEX along your left controller.** A hand-authored
    voxel model of the series' own field guide -- red slab, lens, LEDs,
    hinge, d-pad, dark screen bezel -- laid flush along the left
    controller's grip pose (a full quarter turn forward, so holding the
    controller is holding the device) through the same XR-to-world
    mapping as the eyes, at its real hand size wherever the camera is.
    It rides in FIRST PERSON and in the BATTLE seat; the diorama does
    without it -- a hand-sized device hovering over a tabletop town is
    clutter, and the floating panel serves there. In first person its
    screen carries EVERYTHING the flat screen shows -- menus, dialogs,
    shops, wipes -- and the floating billboard is retired outright:
    raise your hand to read, lower it to play. In a staged fight the
    screen is the 2D battle -- text, menus, HP bars, and any party or
    bag screen opened over it -- and there too the floating billboard is
    gone entirely: the fight owns the view, the reading is in the hand.
    (No tracked left controller still gets the floating panel -- the UI
    must be readable somewhere.) Drawn by the scene's own pass with real
    depth, per eye; dark when nothing is showing.

  - **The floating panel wears the GB frame.** The quad used to show
    the whole window -- monitor-wide, mostly mirror -- and now crops to
    the 160x144 letterbox where everything the flat screen has to say
    actually lives, so the panel presents near-square (10:9) at 1:1
    pixel aspect. To keep a battle's HUDs inside that frame, the HUD
    blocks stay in their classic GB slots for as long as a headset is
    live instead of snapping out to the window's edges.

  - **The sky is anchored in space.** On the flat screen the band
    gradient hangs off the frame; inside a headset that meant the sky
    (and the sun and moon with it) rode the player's head. The VR eyes
    now hang the gradient over a fixed slice of elevation above the
    world's real horizon and drop the fixed-slice fallback entirely, so
    tilting your head slides the frame across a sky that stays put --
    and the discs, already projected through each eye's true camera,
    stand still over their own azimuth. The gradient is a true SKYBOX:
    each eye hands the sky shader its own ray fan (head rotation plus
    frustum tangents), and every pixel takes its band -- and its GBC
    checker dither -- from the TRUE elevation of its own view ray, so
    no motion of the head, pitch, yaw or roll, in the diorama or in
    first person, moves a band by a pixel; only the clock recolours
    them. The FLAT screen's first person gets the very same treatment:
    the placed rig builds its own ray fan from the basis its view is
    made of, so mouse-look pitch slides the frame over a sky that
    stays put there too, dither and all -- while the orbit rungs keep
    their classic frame-hung painting. And the SUN AND MOON are no
    longer painted on the frame at all (in first person on the flat
    screen included): the cell art is baked to a texture once per
    palette and hung on a quad IN THE WORLD, projected through the
    camera like any geometry -- no per-frame cell snapping (the
    jitter), no pattern squared to the canvas (the face that turned
    with the head).

  - **The VR row exists only where VR can.** Off Windows -- the Android
    build above all -- the row is absent from the OPTIONS menu and the
    mod manager's page both (the loader and the GL interop are Win32),
    and a stored vr=true that migrated over in a save is ignored instead
    of read, so a phone never tries to start a session or force the
    battle rows.

  - **VR owns the battle rows.** While the VR row is ON, staged battles
    are REQUIRED (3D-BTL answers ON whatever it was set to) and BACK
    SPRITES is held OFF -- the battle seat, the pokedex screen and the
    effects plane all assume both mons standing on the world -- and both
    rows leave the OPTIONS menu for the duration, because a switch that
    decides nothing reads as broken. Both come back, at their stored
    values, the moment VR goes off.

  - **First person snap-turns.** Flicking the right stick left or right
    steps the view 45 degrees, once per flick (it re-arms at centre) --
    a snap rather than a smooth spin, because smooth software yaw is
    the classic VR comfort mistake. The turn steps the XR-to-world
    mapping itself, so the eyes, the walk direction and the pokedex in
    your hand all agree about which way the world now faces.

  - **The cast holds one pose for the headset.** Sprite cards lean back
    by the camera pitch on the flat screen; a head that roams the table
    has no one pitch to match, so every VR frame leans the cards at the
    top rung's near-upright 75 degrees instead -- whatever orbit rung
    the ladder is on -- and the flat screen keeps leaning with the rung.

  - **Menus, dialogs, battles and wipes float on a panel** (an OpenXR
    quad layer fed from the window), so everything the flat screen can
    show, the headset can read. The window itself becomes the VR mirror
    -- the left eye, fitted to the window -- and every existing
    keyboard, mouse and pad input keeps working alongside the XR
    controllers.

  - The two eyes share one shadow map, one pose capture and one glint
    step per frame (VoxelScene.render's `eyes` path), so they can never
    disagree about anything but their viewpoint. xrWaitFrame paces the
    app at headset rate while FixedStep keeps game logic at its own 60
    Hz; vsync is handed off while the session runs and put back after.

  - Failure is a status, never a crash: no runtime, no headset, no GL
    interop, or a session lost mid-play all land back on the flat screen
    with the reason printed (`XR_ERROR_FORM_FACTOR_UNAVAILABLE` means
    "plug the headset in, then toggle the row"). Needs the mod running
    from a real folder (the FFI cannot load a DLL out of an archive).

## 1.5.0

### Added

- **1ST: a first-person camera, played like a modern one.** A seventh
  rung on the VOXEL ladder (hotkey 3 walks it; the OPTIONS row carries
  it). Stepping onto it dives the camera from wherever the orbit was
  into the player's own head over half a second, and stepping off flies
  it back out. The rig rides the same placed-camera seam the staged
  battle proved out, so the sky's bands meet the horizon, the sun and
  moon hang where their shadows say, and the water reflects at eye
  level -- all through math that was already there.

  - **Free look.** Relative mouse motion (the cursor is captured while
    the rung is on; left click is A, right click is B), the right
    stick at a rate with a squared response curve, or a touch dragged
    across any open screen -- the overlay's d-pad and buttons still
    work, and a second finger can drag the view while the first
    walks. Pitch clamps short of straight up and straight down.

  - **Free movement.** While 1ST drives, the grid walk is replaced by
    a continuous, camera-relative one: push forward and you go where
    you look, at any angle, sliding along whatever you graze. The left
    stick's raw deflection, the touch d-pad's true vector, or the held
    keys (forward / backpedal / strafe) all steer it. The grid is
    still the game: the walk asks the engine's own collision the same
    per-cell questions a grid step asks, the logical cell tracks the
    body, and every cell crossed runs the engine's own landing
    pipeline -- warps, encounters, spinners, gates, poison, repel, the
    step counters. Walking off the map edge, into a ledge or into a
    boulder hands the push to the engine's own handlers, so
    connections cross, ledges hop and boulders shove exactly as
    themselves. Speed is the grid walker's own (bike included), so
    distance per second and encounters per tile are unchanged.

  - **Billboards seen from inside the world.** Character cards stop
    leaning and start turning: upright, yawed about their feet to face
    the eye, wearing the frame their pose shows *this* viewer -- walk
    behind an NPC and you see their back, circle to a flank and you
    get the profile, exactly the four frames Gen 1 drew. The authored
    figures (the couch sitters) turn the same way, about their own
    middle. The sun pass swaps frames in step, so a card never reads
    its own shadow through a mirror-flipped record of itself. The
    player's own card is left out of the camera draw -- the eye stands
    in it -- but still casts its shadow on the ground ahead.

  - The shadow map's box follows the look (the orbit's fit reaches far
    north and barely south, which is wrong for a head facing south);
    the world curve is declined outright while the head owns the
    camera; and the whole rung falls back to the 75-degree orbit on
    hardware without the 3D pass.

## 1.4.3

### Added

- **The furniture of the whole game goes through the building
  pipeline.** 1.4.1 put four drawings through it; this is the rest of
  the rooms. Every one of them is the same read -- the drawing's own
  bands say what is a top seen from above, what is a face seen head-on,
  and where the thing ends on the floor -- and every one of them
  replaces a pinned box that wore its drawing as a decal. The pins all
  stay as the degradation path, neutralized wherever a template stamps.

  - **The bookcase, the commonest piece of furniture in the game** --
    58 placements across two drawings on the town-house atlas (books
    and a bowl on each shelf at the west end of eighteen homes, books
    on both at the east), plus Red's and the Copycat's pair. Pinned
    `desk` it was a 24px box with the books painted on its flat front.
    Modelled it is 23 voxels of cabinet with its top seen from above,
    and every book, bowl and door panel sunk a voxel behind the frame
    the drawing seals it in.
  - **Celadon's display cabinets** -- the tall one with the trophy
    behind its glass and the short one beside it, band for band the
    same object as the town house's on another atlas, which is what
    makes the pair read as one line of furniture: 23 voxels and 15,
    exactly the 8 rows of drawing between them.
  - **The dining table, everywhere it is drawn** -- the generic town
    house's at 18 placements, Red's and the Copycat's, and the chief's
    long table at four cells wide. All of them the lab table's read at
    a different width, all of them 6 voxels, all of them standing on
    the ground line their legs are drawn stopping at rather than on
    the grid's floor.
  - **The stool at every one of those tables** -- 94 placements on the
    house atlas alone, ten more in Red's and the Copycat's, and the Fan
    Club's four members' chairs, a different drawing that is
    pixel-identical from the seat down. The first template with no base
    piece at all: a stool is drawn mid-cell over its own floor, so it
    is a desk-set of exactly one part, seat lid over legs with the
    floor showing between them.
  - **The Pokemon Center's healing machine** -- two variants, 24
    placements, plus the Indigo Plateau lobby's pair. A wall-height
    cabinet with its monitor perched on the front of its top face,
    drawn across two map rows because it towers over the 16px band
    behind it, which the volume path could only read as more wall. The
    hoses leaving its side are modelled as hoses, at the elevation and
    the depth the two stacked motifs put them; the west machine's
    keyboard is a shelf at counter height wearing its own top-view art.
  - **Bill's desk, and the Silph president's** -- the same drawing in
    both rooms. Its terminal is drawn in 2:1 isometric, turned 45
    degrees to the map, and builds as a cube rather than the slab a 2:1
    reading gives; the kinked dark run between keyboard and computer is
    raised to the keyboard's height and reads as the cable it is. The
    desk stops at its own two cells because the artist drew its apron
    into the walkable cell in front, sharing tiles with the chair
    pushed up to it -- so the chair is modelled as a part of the desk.
  - **The Bike Shop's open toolbox.** The drawing looks down INTO the
    tray, which is why every solid treatment failed it -- as a
    `billboard` the whole cell went up as one 10-voxel slab wearing the
    drawing as a decal. `tray` builds four walls, a floor and air
    between them, with the lid standing open on its hinge.

  What the template language grew to carry them: `tray`; a `desk` band
  that lays its top face flat as a lid; the `box`, `flat` and `iso`
  part kinds; `stretch` for a band mapped over a deeper plot than it
  was drawn on; `inset` for a pane sunk by hand; `panes = false` where
  the global recess pass has the polarity backwards; a `wall` element
  so a template can keep the band behind it solid; `plane` for a height
  the drawing states elsewhere; and `scrub`/`keep`/`support`, which let
  a template model a surface while leaving an object standing on it to
  its own standee -- Red's potted plant on the dining table.

- **Round bins: the `can` class.** Vermilion Gym's switch puzzle stands
  fifteen galvanised trash cans in a row, and the S.S. Anne redraws the
  same object pixel for pixel as its galley barrels. Left to the thin
  standee pool they were flat discs on edge -- fifteen coins standing
  in a row; pinned a plain `cylinder` the drawing's base arc revolves
  too and they came out as barrels balanced on a three-voxel stem.
  `can` is the round hull cut at both ends, hollowed and tapered: the
  drawn mouth ellipse projects across the top and down the well so you
  look into the bin, the drawn base ellipse is ground contact rather
  than body, and the plan narrows toward the floor. The two ellipses
  are measured off the pixels; the height, the well and the taper are
  authored, and the entry says why.

- **The rock gyms' boulders are round.** 87 placements over Pewter's
  walls and maze and Bruno's clusters, and every one of them was a
  square bar wearing a boulder texture in relief -- the repeat-aware
  scenery path extruding the whole drawing as one course. Each cell is
  now a hull whose plan is its own drawn width profile turned in depth:
  a dome full-width from the drawn shoulder down, tapering over the top
  five rows exactly where the art tapers, with the floor's corner
  diamonds opening between them the way the drawing has them. Still
  16px, so nothing standing on or beside a rock moves.

- **The potted plant stands as a plant.** The most repeated interior
  prop in the game -- 78 placements over 13 maps, six per Pokemon
  Center -- and its urn was rendering as a hollow black frame, because
  the drawing's foot lies flush on the block's bottom edge and the
  background vote took the plant's own darks away with the floor. Named
  outright as light and white instead, it stands as one organic
  silhouette 32px tall over its two stacked cells, crown overhanging
  the stem. `planter` carries the same reading for a round drawing
  stacked two cells high on one cell of plot.

- **Bicycles, in both places the Bike Shop draws them.** The six on the
  showroom floor get their own pool at two voxels rather than the thin
  pool's five: a bike is a line drawing, and at five voxels every
  stroke closes the gap to its neighbour with its own side faces, so
  from any angle but dead-on the air inside the frames filled in and
  the six came out as one dark lump. And the two against the north wall
  get `mounted`, a new authored-mask escape for a thing drawn INTO a
  wall band: it holds the wall's plane as a thin per-pixel slab instead
  of standing up as a sprite card, and it keeps its drawn elevation, so
  a bicycle hung clear of the floor stays hung. Its mask is measured
  rather than hand-drawn -- the plain panel tile composited across the
  same grid and the background flooded in through the pixels that still
  match it, which separates bicycle from stripe exactly.

- **The Marts' cash register is a machine, not a decal.** An authored
  figure may now state a `depth`, which makes it an object rather than
  a person: a per-pixel solid standing on the counter instead of the
  flat card that turned edge-on with the camera. And the drawing is not
  a box -- its black linework packs two facings, an L of base and arm
  around a keypad that is the machine's deck seen from above. `flat`
  lays that rect horizontal in the notch of the L, and `thin` gives the
  receipt curl a paper's thickness where the body's would have made it
  a wedge.

- **Shelf fronts have relief.** Everything the `bookcase` collapse is
  used for is a shelf, a rack or a display case, and all of them seal
  their contents behind the drawing's own black frame -- so those
  regions now sink a voxel, the same rule a facade's window panes are
  recessed by, and the books stand in the shelf instead of being
  painted on it. A tileset that borrows the collapse for something that
  is not a shelf says `bookcase_relief = false`: the League's masonry
  and pilasters, whose courses are the wall itself, and Bill's
  transporter drums, whose light regions are a lit barrel.

### Changed

- **Class heights now follow the models under them.** A tileset's
  `heights` gets stools at 5 and tables at 6 in the houses, Bill's desk
  at 8, and cans at 9 -- each of them the drawn elevation the new
  template or hull stands at, so whoever sits on a stool sits on the
  seat, and whatever object sprite stands on a table lands on the
  modelled top rather than three voxels over it or under it.
- The healing machines' two flanks leave the `wall` pin for the thin
  standee pool. They are equipment standing beside the console -- a
  pair of pipes and a keyboard -- and as wall each was boxed into a
  solid 16px half-cell wearing its drawing in relief.

### Fixed

- **Water no longer hides behind water.** The reflective pass writes no
  depth -- the depth canvas is detached for the length of it so the
  shader can read it -- so nothing put a lake in the buffer and no lake
  could occlude another; the sheets were simply painted in mesh order.
  Flat water never showed it, one plane, a farther sheet always landing
  farther down the screen. The world curve ends that: it drops the far
  side of the map into the near field of view, and a sea a hundred and
  fifty tiles away came out rasterised on top of the pond at the
  player's feet, tall grass and all -- water and terrain "from the
  other side of the map", not reflected but there. The water meshes now
  go down flat first, through the ordinary scene shader with depth
  writes on, and the reflective pass draws over what survived. The
  buffer holds the surface, so the pass's own test throws the far sheet
  away; the reflection copy holds it too, so a ray grazing another part
  of the lake reads water rather than the void behind it; and a frame
  that cannot run the pass at all is unchanged, because the flat draw
  is the fallback that was already there.
- **Reflections under the world curve.** The bend tips the world away
  and the things standing on it do not lean with it -- and a lake is
  one of those things. Reflected off the bowl the bend makes, the far
  half of a pond was a mirror tilted twenty degrees: it threw the ray
  past the vertical, where the sky ramp's own measure swings from one
  end to the other across a single column, and hard-edged patches of
  the wrong sky stamped into the water; the same tilt sent the
  screen-space march grazing along the bank rather than over it, which
  is what smeared the dock and the roofs across the harbour. What the
  water reflects is now worked out in the flat world, exactly as it
  would be with the curve off, and every marched sample is bent on its
  way to the screen by the vertex stage's own displacement -- so the
  ray is straight where it should be and lands where the geometry did.
  The wave columns are read on the flat sheet too: the relief walk is
  built on an even slab over a level plane, and in the curved world
  that slab is a bowl, which handed back a column a pixel or three off
  per fragment -- a patch of noise in the middle of a pond.
- **Merged runs tore open under the curve.** A quad's interior is the
  chord of a parabola its neighbours draw the arc of, so a long run
  hangs below the short quads butted against it. Nothing bounded a
  run's length, and the ones that ran away were those wearing a
  constant texel -- a roof's black eave outline, its fascia, its shaded
  underside -- because a flat run has no art to break it. At 102px
  across a gym the eave tore off the roof and the slot showed the
  building's dark interior through it. Runs now stop at the next 8px
  lattice line, which is the lattice buildings are stamped on and the
  one every other quad in the scene already ends on, so every join is
  vertex-for-vertex and the bend carries them together. It costs quads
  whether the curve is on or not -- Cerulean's object stream goes from
  35.7k to 41.6k -- and that is deliberate: the mesh is cached per map
  and built over seconds, so meshing for the curve's sake only when the
  curve is on would mean rebuilding every live map on a keypress.

## 1.4.1

### Added

- **Furniture through the building pipeline.** The band-table voxelizer
  that models whole buildings from their own drawings (lib/Buildings.lua)
  now reads interior furniture too, and the first four drawings are in:

  - **F01, the starter-ball table in Oak's lab** -- the tabletop's 16
    drawn rows lay flat over a 16px plot (1:1, the first template that
    never cycles), the black/#555/black edge band folds into the slab's
    own rim, and the base extrudes with its corner feet. Six voxels
    tall, exactly the drawn elevation.
  - **F03, the empty north table beside it** -- the same band table on a
    grid two tiles narrower.
  - **F02, the lab's computer desk** -- the first DESK-SET template: the
    drawing segments into PARTS, each classified by the surface it
    depicts. The monitor and the computer tower stand upright on the
    desk wearing their own drawn tops as lids; the keyboards and the
    mouse lie flat in front of them; the sheet of paper on the right
    lies flat across the desk. Flat parts keep the drawing's own rule --
    drawn row IS depth row, the same 1:1 the tabletop is drawn with --
    so an object's height on the drawing is its position on the desk.
    The Hall of Fame's recording machine is this drawing tile for tile
    on the GYM atlas, and models identically for free.
  - **F04, the Center PC** -- the desk-set read again: a Mac-style unit
    with its screen and drive slot in relief, standing at the back of a
    low white-topped desk with its keyboard lying at the front edge.
    Eleven Pokemon Centers, plus the Indigo Plateau lobby, whose MART
    tileset shares the atlas.

  Two measurements had to stop being assumptions for furniture to fit
  the pipeline: the GROUND LINE is now read off the drawing (a building
  ends on the black threshold row it stands on; a table's legs stop two
  rows short of theirs, and extruding against the grid floated them in
  the air), and a template may name its PLOT (`depth`) when the matched
  grid runs past it onto the walkable floor the legs merely stand on.
  Both are identities for every existing building.

- **The Center couch has a backrest.** The couch is drawn from above --
  back-and-arm strip down the west side, cushions and seams on the east
  -- and rendered as one seat-high box. The new `backrest` class raises
  the drawn back strip to 12px over the 8px seat, in every Center and
  the Celadon Hotel. The man sitting on it keeps his seat: the figure
  anchor now scans under his card for the tallest authored upright (his
  cushion) instead of reading the corner tile, which is the backrest
  now.

### Changed

- **Sprites ride at the height the art actually stands.** Class heights
  can now be overridden per tileset (a tileset entry's `heights`), and
  DOJO's lab tables use it: they are drawn 6px tall, not the default
  table's 12, so the starter balls sit exactly on the modelled tabletop
  -- and the volume-built north tables drop to the same height, keeping
  every table in the room level.
- The Center PC's old rendering -- a 12px table box with the unit as a
  flat standee on it -- retires wherever the F04 template stamps; the
  pins stay only as the degradation path when the shape profile is
  absent.

## 1.4.0

### Added

- **WATER, a new row on hotkey 9: water reflects the world, the sky, the sun
  and the moon.** Every lake, sea and pond in Kanto was a flat animated
  texture lying in a hole in the ground. It is now a surface, and it is
  reflective.

  What it reflects, in the order the shader resolves them:

  - **The sky.** The reflected direction goes through the very matrix the
    frame is drawn with, as a point at infinity, and the canvas row that
    lands on is looked up on Sky's own band ramp -- the identical texture,
    the identical checkerboard dither, the identical display-mode transform.
    So the sky in the lake is the sky over it, and the two meet at the
    waterline with no seam at any pitch, field of view, window shape or zoom.
    Blue at noon, gold at dusk, navy under the moon; GRAY gets a grey lake
    and CLASSIC a green one, for nothing.

  - **The sun and the moon**, hung by ANGLE rather than by screen position,
    because a reflected body is usually off the top of the frame entirely
    and a projected point stops meaning anything out there. The angular
    radius is the painted disc's own radius run back through the camera's
    field of view, so the two are the same size -- craters, dithered rim,
    the sunset's loom and all, off one shared list. This is also the
    specular: a low sun lays a broken gold path across the water on its own,
    out of the reflection rather than out of a highlight term nailed on
    beside it.

  - **The world, in screen space.** The reflected ray is walked forward in
    world space, each step projected through the same matrix, looking for
    where it passes behind what the depth buffer holds -- then binary-refined
    onto the contact and read out of a copy of the frame as it stood before
    the water went down. Shore trees, buildings, ledges and cliffs land in
    the water because they are on screen; where the ray leaves the frame or
    finds nothing, the sky above answers instead, which is what makes the far
    half of a lake sky and the near half scenery with no seam between them.

  Fresnel decides how much of it shows: almost nothing looked straight down
  at, almost everything looked along -- so the 15-degree rung is a pond and
  the 75-degree rung is a mirror, off the same surface.

  Every rung gets one, though, which took a lean. A reflection off flat water
  points as far above the horizon as the eye is above the water: 15 degrees
  at the top rung -- grazing the sky's pale end, sweeping the sun's own path,
  travelling far enough across the screen for the march to find the shoreline
  -- and 75 degrees, straight up, at the steepest. Up there the bands are at
  their darkest, the sun and moon sit at about 6 degrees of squashed
  elevation and are nowhere near it, and the screen-space ray leaves the top
  of the frame in two steps. All three are correct, and together they are a
  lake with nothing in it.

  So the reflection now LEANS toward the elevation the top rung reflects at,
  by however far the camera is from having a horizon in frame -- **zero** at
  the rung where the horizon IS in frame, so the one place the join can be
  seen, the waterline, is still the exact reflection it was. Toward an
  elevation rather than by a weight, because the ray it starts from differs
  at every rung and a fixed fraction lands them all somewhere different: the
  middle rungs came out further from the sun than the steepest one. And it
  leans the LEVEL reflection with each column's own deflection added back on
  top -- leaning the perturbed ray sets its elevation outright, which at full
  lean gave every column on the lake the same one, flattened the sky to a
  single band and removed the moon entirely.

  Three rungs rather than a toggle. FULL is the whole thing; SKY drops the
  ray march and keeps the sky, sun and moon, which is most of the look for a
  handful of instructions; OFF is the flat water this mode always drew. The
  FULL preset sets it to FULL.

- **The water surface is a field of pixel-tall columns, and they are real.**
  Not a normal map: a heightfield of one-world-pixel bars -- the same unit
  every other voxel in this mode is built from, and exactly one texel of the
  water tile -- each standing a WHOLE number of pixels high and rising and
  falling on its own.

  Three travelling wave trains, and one of them dominates: a wave has a
  DIRECTION, and its crest is a line running across it for as far as the
  water goes. Three trains of equal weight cancel and reinforce in patches
  instead, and the surface comes out as round islands of raised pixels with
  no travel to them -- blobs rather than waves. The dominant train's
  wavelength is about forty world pixels, five tiles, so a crest is a long
  run of columns at one height with a step down either side.

  Drawn with no extra geometry at all: the mesh is still one flat quad per
  tile, and the columns are found by walking the view ray down through the
  slab in the pixel shader. That is what makes them read as solid -- a tall
  bar hides the shorter ones behind it, you see the SIDE of the ones facing
  you (wearing the mesh's own direction shading, so a crest is lit like every
  other voxel in the world), and the whole field parallaxes against the plane
  as the camera moves. The water's art is read at the column the ray landed
  on rather than at the flat quad underneath, so the pixels travel with the
  bars they are made of.

  The columns are what you SEE; the normal they reflect with is read off the
  smooth surface they are a quantisation of. That distinction is the whole
  difference between a moon on the water and confetti: whole-pixel heights
  have whole-pixel differences, so a normal built from them can only point in
  about five directions, and a sun or moon barely two degrees across falls
  between them. Still one normal per column, so the surface stays
  pixel-quantised in space while the value it reflects with is continuous.

  Crests stand up to five world pixels, well past the 2px recess water sits
  in -- deliberately, because the columns are relief drawn inside the water
  quad's own footprint, so a bar that reaches above the bank is clipped at
  the water's edge rather than spilling over it. What it buys is a surface
  with real swell in it instead of a two-rung terrace.

  And it moves in STEPS, at **15 a second** -- the cadence hand-drawn pixel
  art is animated at. A surface built out of whole pixels that crawls
  smoothly between them gives away that the quantisation is only skin deep.
  Each step advances the dominant wave by exactly one world pixel, derived
  from that train's own wavelength rather than tuned beside it, so nothing
  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

- **The water surface is its own mesh, and its own pass.** A mirror cannot be
  drawn until what it reflects exists, so water is lifted out of the terrain
  mesh at build time and drawn between the world and the characters. The
  shoreline faces around it are untouched -- they belong to the GROUND that
  exposes them -- and the sun still sees the surface, so a tree at the water's
  edge still throws its shadow onto the lake.

- **The scene's depth buffer is a readable canvas.** It was an internal buffer
  that could be written and tested and never sampled; it is now the same
  buffer with a texture handle on it, at the same cost. Drivers that will not
  make one fall straight back to the old buffer and lose the reflections and
  nothing else.

- **The cast is reflected too -- by being drawn twice.** Gen 1 draws people
  over the world and water is world, so a surfing player has to composite
  OVER the water they are sitting on, which puts them after it; and a
  reflection can only hold what came before it. So the walkers, the NPCs and
  the authored figures are painted into the reflection COPY alone, where they
  are in the picture the water reflects and not yet in the picture the water
  is drawn into. Both draws go through one function, so they cannot come out
  different. The staged battle does the same with its two Pokemon.

  The ray march finds them the honest way round: a sprite is not in the depth
  buffer at that point, so a ray aimed at one passes through to the terrain
  standing behind it and reads the copy there -- where the sprite is already
  painted. The reflection lands a hair off the sprite's own depth and exactly
  on its colour, which at a lake's worth of wave is the same picture.

### Changed

- **The waves arrive in sets now, and a little slower.** Three fixed trains
  are an exactly periodic field -- every forty-odd pixels of sea wore the
  same crest at the same height, which reads as wallpaper the moment a lake
  is bigger than the repeat. Two long-wavelength fields now ride the
  dominant train, four to five carrier wavelengths apiece so neither reads
  as a wave itself: a SWELL that breathes its amplitude, so a few tall
  crests march through and hand over to a lull that is itself moving, and a
  BEND that bows its phase, so a crest line curves across the surface
  instead of ruling itself over all of it. The two lesser trains stay
  plain: they are texture rather than structure, and a third modulator is
  the soup the train weights exist to avoid. The step beat comes down from
  15 to 12 a second -- the crests were hurrying, and a big wave is slower
  than a walk cycle -- still a clean divisor of the engine's 60, and still
  exactly one world pixel of dominant-crest travel per step.

- **Staged battles draw their water plain, whatever the WATER row says.**
  The reflective pass is tuned for the overworld's ladder of cameras; a
  battle's camera is PLACED -- low, tilted, framed like a picture -- and
  under it the pass read wrong: Fresnel opened all the way up, the leaned
  sky landed on bands the framing never shows, and a lake-sized arena came
  out as murk wearing the tile art. The battle is a stage set, and stage
  water is painted: the flat animated tiles the mode always drew, with the
  mons compositing over them like everything else on the set.

### Fixed

- **On Android the water stayed flat, as if the row were off -- and once it
  did draw, it came up in blocks with the haze showing through the holes.**
  Three separate faults, every one of them invisible on desktop GL, run down
  on a Galaxy Z Fold 7 with the driver's own compiler errors in logcat:

  **The shader would not build.** Fragment floats default to **mediump** on
  GLSL ES while the vertex stage's default is highp, and the water shader is
  the mod's first to declare the same uniform -- the frame's `vp` matrix --
  in BOTH stages, one on each default; GLSL ES refuses to link that, and the
  pass fell back, quietly and by design, to the flat water the mode always
  drew. The pixel stage now lifts its float default to highp (guarded, so a
  GPU without fragment highp still compiles and falls back flat), which
  settles the link and is also simply needed: the march works in world
  coordinates that run to a few thousand, where fp16 has no fraction left.
  The world-position varying is qualified highp for the same reason the
  wireframe's always was, and the depth sampler too -- samplers default to
  **lowp** whatever the floats are set to, and eight bits of depth is a
  march with nothing to land on. One wrinkle inside the fix: LOVE's header
  forward-declares `effect()` under ITS default, and Samsung's Xclipse
  compiler treats a definition whose parameter precisions have drifted from
  the prototype's as an illegal overload -- so effect()'s own float
  parameters stay pinned to mediump, matching the declaration, and the
  maths above them runs highp regardless.

  **The depth test read the wrong texels.** The shader's own depth test
  normalised LOVE's pixel coordinate by the `screen` uniform, which counts
  canvas UNITS -- and on a highdpi phone (Android's density here is 2.625)
  a canvas holds that many PIXELS per unit, so the lookup ran to 2.6,
  clamped, and read edge texels across two thirds of the frame. Water
  discarded itself in blocks wherever the mis-read depth landed in front,
  and the haze backdrop showed through the holes. The coordinate is now
  normalised by `love_ScreenSize.xy` -- the bound canvas's own pixel size,
  measured in the same units on every display.

  **And the readable depth canvas** -- the one hardware requirement the
  rest of the mode does not already have -- now tries four formats before
  giving up: depth24, depth24 riding a stencil (a pairing some mobile
  drivers will texture when they refuse the bare format), depth32f, and
  depth16 as the floor every GLES3 device can read. Refused all four, the
  reflections are lost and nothing else, exactly as before.

- **Under BACK SPRITES some of your own Pokemon were see-through -- Pikachu,
  Seel, Dewgong, Chansey, Jigglypuff -- with the arena showing through the
  middle of them.** Those back pics are drawn as OUTLINES: everything inside
  the ink is the lightest shade, the decoder keys that shade to nothing, and
  on hardware it did not matter because the field behind them was white too.

  BattlePics already put that paper back by flooding the background inward and
  filling whatever it could not reach, and along the bottom of a figure it told
  a narrow opening (a belly the drawing ran out of, sealed) from a wide one (a
  stride, left open for the world to show through). Right for a mon standing
  on the map -- but the pinned back pic is not on the map, it is on the text
  box with its feet on row 96, and there is white box under its lowest row
  rather than arena. Every one of those mons leaks out through an opening far
  too wide to read as a drain, so the flood walked straight up inside them.

  A pic on the box is now told so, and its bottom edge seals: nothing reaches
  it from below at any width, and the rule stops being a heuristic -- paper is
  whatever the background cannot walk to from the left, the right or the top.
  Twelve of the game's 151 back pics turn on this; the other 139 come back
  byte-identical, and no front pic is touched at all.

  **And a hole is filled with the pic's own paper rather than with white.**
  Shade 0 is only white while the pic is still grays, and pics arrive here
  after the bake -- a species SGB colour, a BGP fade mid-animation, PAL_BLACK
  across the whole screen while the blackout text is up. A hardcoded white
  belly would have been the one lit thing on a blacked-out mon. The lightest
  shade still standing in the pic is that colour, and every one of the game's
  battler pics keeps at least one such pixel -- an eye, a highlight down a
  cheek -- so what goes back is the baked shade itself.

### Known

- Screen-space reflections can only reflect what is in the frame. A tree just
  off the top edge is not in the water below it, and a reflection whose ray
  runs off the side of the screen fades into the sky rather than ending on a
  hard line.

## 1.3.1

### Fixed

- **A staged battle on a phone stood some Pokémon three times the size of the
  square they were on.** A Pidgey towered over the arena while the mon beside
  it was the right size, which reads as a bug in one species and is not one.

  Putting the paper back inside a battle pic (BattlePics, 1.3.0) needs the
  pic's pixels, and a LOVE Image does not hand them back -- so the pic is drawn
  into a canvas of its own size and the canvas is read. `newCanvas` takes the
  SURFACE's dpi scale when it is not told otherwise, `conf.lua` turns highdpi
  on for Android and iOS, and Android's display density is routinely 2.75. So
  `newCanvas(56, 56)` allocated a 154x154 texture there, the pic was magnified
  into it, and the readback came back at the magnified size. The rebuilt pic
  was 2.75x the artwork, the engine's pics layer drew it 1:1 because it trusts
  `getWidth()`, and the mon stood on its tile nearly three times too big.

  Only a pic with an enclosed hole in it is rebuilt at all -- the rest are
  handed straight back untouched -- which is why it hit some species and not
  others, and why it never showed on desktop, where the dpi scale is already 1.
  The readback now asks for one texel per pic pixel, the way the engine's own
  `PixelCanvas` does for the same reason. The animated-tile atlas readback took
  the same fix: on a phone it would have come back magnified too, and every
  tile coordinate in it counts in eights from the top-left.

## 1.3.0

### Added

- **BACK SPRITES, a new row under 3D-BTL: your own Pokémon stays on the battle menu.**
  The staged shot stands both mons on the map, which is the mode's whole claim
  -- and it costs the framing Gen 1 is most recognisable by: your own Pokémon,
  seen from behind, sitting on top of the battle menu with its feet on the box.

  With BACK SPRITES on the foe is still geometry standing on its own tile at the far
  end of the arena, and the player's side goes back to being the GB's own flat
  back pic in the GB's own slot: same art, same 2x, same feet on row 96. It is
  the engine's own pics layer that draws it, through the `onlySide` argument
  that layer already takes, so every pic effect -- the grow-out-of-the-ball,
  the faint slide, the damage blink, the send-out trainer pic -- comes along
  unchanged and none of it is reimplemented.

  Nothing else about the shot moves. The arena, the camera and the drift are
  solved exactly as they were, so the foe stands where it always stood and the
  player's cell is simply empty ground in the foreground. Two things follow the
  setting: the `pokemon.sprite` hook stops asking for the front pic on the
  player's side (it is a back view again, and the front art would be that mon
  turned round to face the player it belongs to), and the move-animation offset
  drops that side's contribution, because a pic that has not moved cannot have
  moved the pair's centre.

  OFF by default -- what the mode advertises is the two of them out there --
  and only on the OPTIONS menu while 3D-BTL is on, since with staged battles
  off the engine already draws exactly this.

### Fixed

- **Battle pics were see-through, and it took a back sprite on a tiled floor
  to make it obvious.** Gen 1 pics are two-bit art whose lightest shade is
  white, and the decoded PNGs key that shade to alpha 0 -- which cost nothing
  when the field behind them was white too. Over a route, every belly, every
  eye white and every highlight is a hole with the world showing through, and
  the mon reads as a stencil.

  `BattlePics` exists to put that paper back and, as written, put none of it
  back. It flood-filled the outside from the border and filled what the flood
  could not reach, which is exact and, on this game's art, empty: a Gen 1
  figure is an open drawing, and its belly walks out to the border through the
  gap between its legs. Read across all 305 of the game's battle pics, that
  rule finds an enclosed hole in exactly none of them.

  The fix is to start the flood somewhere else: at the edges of the ARTWORK'S
  OWN BOUNDING BOX, and at three of them -- left, right and top. The bottom is
  closed, because it is not a side the background is behind, it is where the
  drawing was CUT. A pic is bottom-aligned in its slot with all the margin at
  the top, so a mon's lowest row is the last row it was given and everything
  below the belly simply stops. Treat that cut as open and the background
  pours up inside the figure, which is the channel of world that used to show
  through a Clefairy.

  That is exact rather than a heuristic: nothing is filled because of what
  surrounds it, only because the background provably cannot reach it. Which is
  why it needs no idea whether it is holding a front pic or a back one -- the
  sky between a pair of ears reaches the top edge and stays sky, the gap
  between a body and a raised tail reaches the side and stays gap, the belly
  reaches neither and is paper. The silhouette is untouched, so the mon still
  cuts cleanly against the world.

  It replaces the border flood outright rather than sitting beside it, since
  anything the border could not reach the box edges cannot reach either.

  The bottom edge needs one more distinction, because two different things
  meet the underside of a figure. A DRAIN is where the drawing ran out -- a
  belly whose white carries on down until the artist stopped, leaking out
  through the inch between a body and a leg -- and is sealed. A MOUTH is the
  space between two legs, background that happens to be enclosed on three
  sides, and is left open so the world shows through a trainer's stride.

  Width tells them apart, and on this game's art it is not a close call.
  Measured along the bottom of every battle pic, the drains run 3 and 4 pixels
  (Clefairy's back, Wartortle's back, Red's back) and the mouths run 10, 12, 14
  and 17 (a Rattata's underbelly, Blue's stride, Brock's, a Pikachu's back).
  Nothing lands between 4 and 10, so the cut is taken at 6 with room either
  side rather than tuned to one sprite. Apart from that number the rule stays
  exact.

  Front pics come back untouched, and not by being special-cased: they are
  near-solid silhouettes with almost nothing inside them to fill, so their own
  shape is what says so.

  Both mons were affected -- the cards in the arena as much as anything -- so
  this lands wherever a battle pic is drawn over the world, not just under
  BACK SPRITES.

- **The pinned back pic was lit at noon while the world behind it was not.**
  Everything standing in the arena goes through the voxel shader, and that
  shader multiplies by the hour's tint, so at dusk the diorama warms and at
  night it goes blue -- the two mons' cards included, because they are drawn
  in the same pass as the ground they stand on. A back pic pinned to the menu
  is not in that pass; it is a flat blit over the finished shot, and it stayed
  bright over a midnight route.

  The same tint is now applied to that one draw, by multiplying every colour
  the pics layer sets on its way past -- so the alpha, the faint slide's fade
  and the damage blink all compose with it instead of being overwritten. What
  it does not get is the sun: the cards are shadow-mapped and a pic pinned to
  the menu has no position in the scene to be shadowed at, so it carries the
  hour and not the weather.

### Added

- **The hour reaches the FLAT world too, not just the diorama.** DAYTIME drove
  the 3D pass through the voxel shader's own tint uniform -- a uniform the 2D
  tile path never runs -- so with VOXEL off, the same evening that fell on the
  diorama left the flat world at permanent noon. One clock, two worlds, one of
  them ignoring it. Outdoor maps now get the same multiply, painted as one
  rectangle over the composited world.

  The whole difficulty is WHERE, and it is worth writing down. Not on the world
  canvas: in a colorized mode that canvas is grayscale art and the blit that
  puts it on screen runs it through the palette shader, which classifies each
  pixel into a shade BY ITS RED CHANNEL -- multiply a night blue over it first
  and every pixel lands in the wrong bucket, so the world does not darken, it
  changes colour. Not over the finished frame either, or the dialog boxes and
  menus darken along with the world they are held up in front of, which is the
  same reason the tilt-shift blur is a `worldPresent` and not a `present`.

  Which leaves the instant between the world blit and the UI blit, and the
  engine has no seam there -- `worldPresent` only runs when a PIPELINE produced
  the world, which in flat mode is precisely what did not happen. So
  `Renderer:endFrame` is wrapped and the UI canvas's own draw is watched for:
  `blit` passes the canvas it is compositing as the first argument, so the
  first draw of `Renderer.canvas` IS the boundary, by identity rather than by
  counting. The shader and scissor that call arrives under belong to the UI
  blit already in progress, so both are put aside for the rectangle and handed
  straight back.

  Skipped entirely when a pipeline drew the frame (it tinted itself, and twice
  is wrong), indoors (a room has no sky to take its light from), and at midday
  (a multiply by white) -- so a game with the clock at DAY issues not one extra
  call.

### Changed

- **FULL no longer takes the two battle rows off the menu.** It still owns the
  rows that describe the LOOK -- the wireframe, the horizon bend, the blur, the
  hour -- because it is a preset for the diorama and a row that no longer
  decides anything is worse than no row. 3D-BTL and BACK SPRITES are not that:
  one decides what a fight is drawn OVER and the other how it is framed.
  FULL still SETS both on arrival; it does not hold them, and leaving them
  reachable is the difference between a preset and a lock.

  This makes `stagedBattles()` honest as a side effect. It used to answer yes
  under FULL as well, on the grounds that FULL owned the 3D-BTL row and
  switched it on -- safe only while the row was hidden. With the row reachable
  from inside FULL, that clause would have claimed staged battles for a preset
  the player had just switched them off inside, pinning BATTLE LAYOUT to OG for
  a fight that never gets staged. The row is the only thing that decides now,
  which is what `OverworldBattle.begin` and `wantsFront` already believed.

- **TILT and GBC FX are off the OPTIONS menu entirely while this mod is
  installed.** Both fight the diorama and both were already half-taken: the
  mode's own key forces them off on every press, and the registry switches
  TILT off whenever a world pipeline takes the pass. What was left was two
  rows a player could set and watch get reverted -- TILT being the flat fake
  of what this mode does for real, and GBC FX a full-screen present pass over
  the top of the whole thing.

  Dropped AND held at zero, which is the part that matters: hiding a live
  setting is a trap, because a save written before the mod was installed can
  carry TILT 3 and a row that is not there cannot turn it back off. Pinned
  wherever the value could arrive from -- the menu opening, a save being
  loaded or begun -- so there is no route by which either is on and
  unreachable. Uninstalling the mod puts both rows back, at whatever they were
  last set to.

- **The battle's text box and menus are frosted glass, like the HUDs.** The
  HUD blocks got panels because black glyphs on grass are not readable. The box
  at the bottom had the opposite problem and the same cause: it is drawn as an
  opaque white slab with a black border, which was the field's own colour back
  when the field was white and is a sheet of paper laid over the bottom third
  of the diorama now that it is not.

  It gets exactly what the HUDs get -- the world behind it, blurred to frosted
  glass and laid back down translucent, at the same frost and the same tint --
  and it is measured into the same brightness verdict, so the ink over the menu
  flips white with the ink over the HUDs rather than against it. Only the FILL
  is taken away: the border, the text, the cursor and the down arrow are the
  engine's own glyphs in their own places. The move menu's TYPE/PP box and
  Mimic's copy menu get their own panels, trimmed to the rows above the box
  below them so no pixel is frosted twice.

## 1.2.1

### Fixed

- **On Android the sky went black below its first couple of bands.** A hard-edged
  band of black ran from partway down the gradient to the horizon point, with the
  moon still hanging correctly inside it. Desktop was unaffected.

  What gave it away is that the same colour reached the screen by two routes and
  only one of them was wrong. The haze filling the void UNDER the horizon is the
  sky's palest band, and it is delivered by `love.graphics.clear` -- it landed
  correctly. The bottom of the sky above it is that same band delivered by the
  shader, and it was black. So the palette was not reaching the fragment shader,
  and nothing was wrong with the palette, the layout or the camera.

  The bands went in as `uniform vec3 bands[8]`, filled from Lua and read through
  a loop counter, and on Android's GLSL ES the tail of that array arrived as
  zero -- which is black. The likeliest reason is the fragment uniform budget:
  ES 2.0 only guarantees sixteen uniform VECTORS, and eight band slots plus the
  twilight glow plus LOVE's own built-ins is over it. A driver that truncates a
  partly-filled array, or one that reflects `bands[0]` and nothing after it,
  fails identically -- so the fix removes the whole class rather than the one
  cause.

  The bands are a one-texel-per-band TEXTURE now, sampled nearest, with the
  band index clamped against the ramp's width. One texture unit replaces eight
  uniform vectors, there is no array to index and no budget to overrun, and a
  sample past the last band lands on the last band instead of on nothing. It is
  still a palette and not a picture -- one texel per band on a single row -- so
  the sky is still computed per pixel at the size it is displayed at, with
  nothing resampled and nothing baked.

  Also gone with it: `clamp(x, 0.0, 0.999999)`, which rounds its bound to 1.0 at
  mediump -- the fragment default on GLSL ES -- and would have indexed one past
  the last band on the sky's bottom row for the same black result.

## 1.1.1

### Fixed

- A move that shakes the screen no longer whites out the frame. The zone pass
  fills each zone with its blank colour before drawing the shifted copy -- the
  hardware showing empty BG in the strip the shake vacated -- and a shake
  program alternates offset and no-offset frames, so over the map that read as
  the whole battle screen, menu box included, flashing white a few times a
  second. The fill is dropped while a battle is staged on the map; the shake
  itself still moves the HUD.

## 1.2.0

### Added

- **A gradient sky behind the diorama, on every `VOXEL` rung.** The void behind
  the world used to be a black plate at every rung but the top, where it became
  one flat blue -- enough while that void was a sliver, and a wall of paint once
  the horizon came into frame.

  It is the 8-bit skybox recipe now: four blues painted as flat horizontal bands,
  deepest overhead and palest at the bottom, with a CHECKERBOARD of the next band
  dithered into the bottom 40% of each one. Alternating two colours on a pixel
  grid is how a machine with four to a palette got a fifth, sixth and seventh out
  of them, and it is what keeps four bands reading as a gradient rather than as
  four stripes. Every channel of the palette is a multiple of 8 -- where a
  five-bit GBC channel lands -- so no colour in it is one the hardware could not
  have shown. No clouds, nothing moving.

  Where the bands END is the camera's own answer. At `75` the ground plane's
  vanishing line is genuinely in frame -- projected through the same matrix the
  geometry is drawn with -- and the pale end meets it. At the steeper rungs that
  line is above the top edge, and what shows up there is the ground running OUT
  past the map edge instead, so the bands take a fixed slice of the frame and the
  haze fills the rest. One sky across the whole ladder either way.

  **Nothing is resampled**, which is why it is drawn the way it is: no baked
  160x144 image scaled up to the window, no downsized buffer blown back up, no
  texture at all. One rectangle through a shader answers every pixel from its own
  canvas coordinate, so a pixel of sky is computed at the size it is displayed
  at and there is nothing for a filter to soften. The band edges and the dither
  cells are measured in the pass's own pixels-per-world-pixel, handed in fresh
  every frame -- so a `ZOOM` keypress is reflected in the frame that follows it,
  with nothing cached at the old scale, and the sky's grid is the same grid the
  world's own texels sit on.

  The palette goes through the display-mode transform like every other palette in
  this mod, so GRAY gets four greys and CLASSIC four greens. Below the bands the
  void is filled with the palest of them -- which is also what the bottom band
  ends on -- so the join has no seam, and a driver that cannot compile the shader
  gets flat bands and a logged line rather than a wrong sky.

  The overworld only. A battle is a staged shot whose placed camera has the
  horizon above the frame, so the arena keeps exactly the flat sky it had.

- **A day/night cycle**, on a new **DAYTIME** options row: `DAY`, `NIGHT`,
  `DUSK`, `DAWN`, `SYNC`, `CYCLE`. One twenty-minute clock underneath all of
  them -- ten minutes of sun, ten of moon -- where the four named settings
  are PINS on that dial (noon, mid-night, sunset, sunrise), `CYCLE` lets it
  run, picking up from whichever pin or SYNC sky the player was just looking
  at, and `SYNC` -- the DEFAULT -- lays the machine's own clock onto the
  dial: local noon is the DAY pin, midnight is NIGHT, six and eighteen the
  twilights, an hour of the real day is fifty seconds of dial. Everything is
  a pure function of the clock, so the pinned DUSK is exactly the running
  cycle stopped at sunset. While **VOXEL** sits on `FULL` the DAYTIME row is
  HELD at `SYNC` and taken off the menu with the other rows the preset owns
  (DayNight.forceSync, enforced from the preset, the rows hook and the
  manager's options_changed -- the same three places BATTLE LAYOUT's pin
  lives): the full diorama runs on the real sky.

  **The sun and the moon are in the sky**, and their positions are honest:
  the disc is the light's own direction projected through the same matrix the
  geometry is drawn with, so it stands over the point on the horizon its
  shadows point away from, at every pitch, window shape and zoom. The sun's
  noon is this mod's existing sun to the digit -- southeast, 45 degrees up,
  overhead behind the north-facing camera and correctly out of frame -- and
  its arc swings north at both ends, so the disc stands IN frame through dawn
  and dusk, rising half-set on the horizon. The moon arcs the northern sky
  all night, due north (screen centre) at mid-night, with scaled crater
  cells. Both are cell art on the sky's own dither grid, sized by the frame
  (a celestial body's apparent size is an angle, so zooming the ground does
  not swell it), and both are SCISSORED to the sky's region: the horizon
  point is where a setting body disappears -- it never hangs under the map.

  **The sky follows the clock.** Phase palettes -- the daytime blues,
  gold-to-violet dawn, a hotter gold-to-indigo dusk, moonlit navy -- six
  bands each now, blended along the dial and re-quantised onto the 5-bit
  lattice, so every mixed frame is still a colour the hardware could show.
  The blends bend through designed WAYPOINTS rather than straight across --
  a golden hour on the way into dusk, a violet civil twilight either side of
  the night -- because day's blue and dusk's gold are near-complements, and
  a straight lerp between complements bottoms out in dishwater grey. Through the twilights a posterised, checker-dithered GLOW
  warms the bands around the low sun -- painted light, not an airbrush. The
  blends are 75 seconds wide either side of each twilight and the pins land
  on their phase palette unmixed.

  **The shadows follow the sun and the moon.** The shear every shadow is
  thrown by (direction opposite the body's bearing, length its elevation's
  cotangent, clamped at twice the caster's height) comes off the clock, the
  shadow map's signature carries it, and the light's press fades out over the
  last twelve degrees before the horizon -- so sunset hands off to moonrise
  through a soft shadowless gap, and moonlight presses at about two-thirds
  the sun's weight. The scene shader also multiplies every surface by the
  hour's tint: neutral at noon, warm at the twilights, dim blue at night.

  **Outdoors only**, by the same `Map.isOutdoor` test the sky already rests
  on: indoors keeps the noon rig, the neutral tint and no sky -- a cave at
  midnight is exactly as dark as a cave at noon. Viridian Forest is the case
  between, a CANOPY map (DayNight.CANOPY): there is no sky to paint and no
  sun to see, so the shadow rig stays the mod's fixed noon light -- all that
  ever filtered through the leaves -- but night still FALLS in a forest, so
  of everything the clock does, exactly one thing reaches it: the hour's
  tint, in free-roam and staged battles alike. A battle staged on an
  outdoor map fights under the hour: the night sky behind the arena, the
  tint on the mons, the sunset taking the arena's shadows with it; an indoor
  arena is untouched. The engine's own `world.tod` hook is answered
  (`MORNING`/`DAY`/`EVENING`/`NIGHT`), so palette or music packs keyed to the
  period ride this clock for free.

  **The clock rides the save slot.** On the engine's `save.writing` event the
  cycle's time is written into the mod's own save-file bucket
  (`save.modData.DRAMATIC_SHAPE`), and read back when a save is opened. A
  save with no clock in it starts at day.

- **Window glass.** The panes in the overworld art -- the framed squares on
  building fronts, the small lights in doors -- are found by SHAPE in the
  tileset image (a black border row, four or five black-flanked glass rows,
  a closing border), at pixel granularity because the door's pane straddles
  a 2x2 tile block. No tile ids are hardcoded: a conversion that draws its
  own windows in the same idiom gets glass for free. The scan yields a mask
  texture aligned to the tileset atlas, which the scene shader samples with
  the same coordinates the terrain does -- so the effect lands on any wall,
  at any angle, in free-roam and staged battles alike, with no geometry
  work.

  By day a thin glint crosses the panes WHILE THE VIEW MOVES: the sweep's
  phase is fed by the camera's own travel and its strength fades out within
  a beat of standing still -- a reflection is something the viewpoint does,
  so still camera means still glass. The sweep pattern lives in the pane's
  OWN texels, not the screen's: a screen-anchored pattern has the world
  sliding through it at zoom speed whenever the camera pans, which strobed
  (worst walking against the sweep); anchored to the glass, panning moves
  nothing and a step advances the glint a fraction of a texel, the same in
  every direction. It lifts the texels toward sky-white and leaves the
  shine art visible through it. The mask is consulted only by meshes
  textured from the tileset atlas (Voxel3D.glass), never by sprite sheets,
  whose coordinates would land on the panes' atlas positions by accident.
  After dark the panes are LIT: the texel's own pattern carried into a warm
  lamp colour, replacing the shaded answer entirely -- a lit window ignores
  the sun, every shadow and the hour's tint, exactly as a window with a lamp
  behind it does. The lamps follow the clock (DayNight.windowLight): on
  through dusk, full all night, mostly out by dawn, and never lit indoors.

- **A fade out of a battle, where there used to be a hard cut.** The engine
  wipes INTO a fight with one of the original's eight transitions and cuts
  straight out of it: `BattleState:finish` pops itself and the map is simply
  there on the next frame. Between a white field and a tile map the original got
  away with that; between a placed camera looking across an arena and a diorama
  looking down on a walking player it reads as a glitch. The battle now fades to
  black, closes behind it, and the map fades up out of it -- twelve frames each
  way, registered as a `voxel_battle_exit` transitions record so the timing is
  retunable in data like the wipes it answers.

  Only while voxel mode is on, and then for EVERY battle, including one that
  found no arena and drew on the flat battle screen: what is being smoothed over
  is the return to the map, and the map is a diorama either way. With the mode
  off, the vanilla cut is untouched.

  One black rectangle over the FINISHED composite does the fading, so the world,
  the letterbox bars and the battle's own text box all darken by the same amount
  -- the renderer's existing warp-fade overlay is painted between the world and
  the UI, which would have left the text box bright over the black. A blackout's
  own warp fade or an evolution prompt still owns the way out when it takes the
  screen: the fade stops at the cut rather than fading in over the top of it.

- **A `FULL` rung on the VOXEL row**, directly after `OFF`. One choice that
  puts the whole mode in its intended state -- the 35-degree camera, the
  miniature blur at maximum, the horizon flat, the view fitted, and battles
  on the map -- rather than making a player assemble it from four rows.

  While it is selected, every row it owns comes OFF the menu: V-GRID,
  V-CURVE, 3D-BTL and T-SHIFT. A row that no longer decides anything is
  worse than no row. Stepping onto or off `FULL` rebuilds the open menu in
  place, so the rows leave and return under the cursor instead of waiting
  for the menu to be reopened.

  It applies its settings when the row ARRIVES at `FULL`, not every frame:
  holding them would make the zoom keys and the wheel dead while it was on.
  Leaving it deliberately undoes nothing -- reverting would discard whatever
  had been changed since.

### Fixed

- **The hit flash whited out the whole screen.** The engine draws it as a
  full-screen white rectangle, which is a flash on a white battle field and
  a whiteout of the map, the HUD and the text box over a world. It is now
  dropped on the way past and put back where it was ever about: the two
  Pokemon go solid white for those frames, silhouette and all, and nothing
  else in the frame moves.

- **A scripted battle cut straight in with no transition** (an ENGINE seam,
  fixed in `src/script/Commands.lua` rather than in this mod): the rival in
  Oak's lab, and every `start_battle` script, pushed the BattleState bare --
  no flash, no wipe, the theme starting late -- where the original wipes
  into scripted fights like any other. `start_battle` now routes through the
  overworld's own `pushBattle`, which is also the path this mod wraps, so a
  scripted fight gets its arena staged and the cast culled BEFORE the wipe
  instead of catching up behind it. A battle scripted with no overworld
  under it still starts bare, and no music plays twice (BattleState's own
  start is a same-song no-op).

- **A standing figure's shadow detached from its feet under a low sun.** The
  shadow compare forgives `slack` world pixels so lit ground does not acne
  against its own texels, and that same forgiveness lit the first `slack` of
  every cast shadow -- so the shadow started a bias-width away from the feet,
  further the lower the sun reached (the classic peter-panning, invisible at
  the old fixed 45 degrees and plain at a day/night golden hour or under the
  moon). Sprite cards -- characters, authored figures, flowers, battle mons
  -- are now drawn into the shadow map snugged TOWARD the sun along their
  own ray (`ShadowMap.snug`): moving along the ray changes nothing about
  where a shadow falls, but storing the card shallower takes three quarters
  of the forgiveness back for the shadow it throws -- and for nothing else:
  no terrain moved, so the acne margin is untouched where it matters. The
  obligation that comes with it: every snugged caster's LIT draw hands the
  same snugged transform to its own shadow lookup (Voxel3D.draw's
  `sunModel`), so stored and lookup agree exactly and the compare keeps its
  full margin -- read un-snugged, the missing nine tenths showed up as
  diagonal moire bands crawling across every sprite. The shadow root lands
  back under the feet at every hour.

### Changed

- **Under `VOID FILL: TREES` the border wall is modelled trees or nothing.**
  Only the first block past the map body gets carved into round trunks and
  canopies; the two blocks past that were too far out to be worth the quads,
  so they fell through to the mesher's plain box and came out as a flat-topped
  slab of tree ART sitting beside the modelled forest -- a painted-on plateau,
  and the more obvious the lower the camera got. Rather than pay to carve
  hulls nobody walks near, the wall now simply STOPS where the carving does:
  `Structures` does not build the ring past that distance (the same "nothing
  out there" `BLACK` already produces), and the mesher drops any cell inside
  it the 2x2 canopy grouping could not claim, so no strip of boxes survives at
  a corner. `WATER` and every indoor border are untouched -- a flat sheet of
  water is what water looks like from above anyway.

- **The two HP boxes snap to the window's edges during a staged battle.** The
  battle screen is 160x144 in the middle of the window and the world is the
  whole of it, which left both HUD blocks huddled together in the middle of the
  frame with map showing on either side of them -- a Game Boy screenshot pasted
  over a diorama rather than the diorama's own furniture. The foe's block now
  sits against the left edge and the player's against the right, on the same
  frosted glass, with the same tiles at the same size on the same rows. The
  pokeball rows and the safari ball count travel with the block whose rows they
  share. On a window shaped like the GB screen there is nowhere to go and
  nothing moves.

  The engine draws them into the 160x144 canvas, which clips at its own edges,
  so the layer is rendered to a texture and composited into the world image --
  the one surface here that covers the whole window. A driver that cannot do
  that falls back to the HUD in the frame rather than to no HUD.

- **`BATTLE LAYOUT` is pinned to `OG` while battles are staged on the map**, and
  the row comes off the OPTIONS menu with the rows `FULL` owns. The staged shot
  is composed in the GB's own frame -- the arena camera is solved to put a cell
  under each pic's feet, and the HUD rects and the intercepted background fill
  are measured there too -- and `WIDE` re-lays that screen out on a 304x144
  surface, moving every one of them. Set rather than worked around, on every
  route in: the options row, hotkey `8`, the mod manager's page, `FULL`'s
  preset, and a save that arrived with `WIDE` already on. Switching `3D-BTL`
  off hands the row back with `WIDE` selectable again.

- **Hotkey `3` walks the angle rungs only and steps over `FULL`.** The key is
  a display-mode cycler -- it should change the camera and nothing else --
  and `FULL` reaches in and rewrites four other settings. Landing on it
  mid-walk would silently push the blur to maximum and flatten the horizon
  with nothing on screen saying a keypress had done it. `FULL` stays on the
  OPTIONS row, where a preset that changes other rows belongs.

  A press FROM `FULL` goes to `50`. `FULL` is already the 35-degree camera,
  so stepping to the rung of that name would look like the key had done
  nothing. Matched by angle, so it follows `FULL` if that is ever retuned.

- **The mode's four options are one block in the menu.** The engine splices
  a pipeline row in beside TILT and lands a mod's own rows at the end of the
  list, which had these four in two places with unrelated engine rows
  between them. The settings now follow the pipeline rows directly.

## 1.1.0

### Added

- **Battles happen on the map you were standing on.** The battle screen's
  white field is replaced by the world: the mod finds the nearest patch of
  open ground, points a placed over-the-shoulder camera at it, and draws the
  fight over that. New **3D-BTL** row and hotkey `8`, on by default.

  The arena is a 3x6 clearing of cells the player could walk on, with the
  two mons three cells apart down the middle column and a one-cell apron all
  round so the camera looks across floor rather than into a wall. Where no
  map has room for that -- a corridor, a cave, a shop -- the search relaxes
  to a 1x4 corridor with the apron given up, and where even that will not
  fit the battle draws exactly as it always did.

  Everything else in the frame is the engine's own. The mon pics, HUDs, HP
  bars, move animations, faint slides and text box are drawn by BattleState,
  in its order, at its coordinates -- the GB's own layout, with the player's
  mon low and left and the enemy's high and right, which is why the camera
  is placed east of the arena axis rather than the layout being moved to
  suit the camera. What changes is what is behind them.

  Three things carry the shot. The overworld's cast is culled before the
  wipe, so it plays over an empty map and no bystander is standing in the
  arena. The camera drifts on a slow orbit about a point between the two
  mons, which moves the near ground and the far ground by different amounts
  -- parallax, not a sliding backdrop. And a depth-of-field pass holds the
  band of frame the two mons stand in sharp and softens the middle distance
  and the foreground; both mons are in focus by construction, because they
  are drawn as the battle screen's own pics after the pass has run.

  **Nobody moves.** The arena is where the CAMERA goes. Nothing here writes
  a cell, a facing, a flag or a warp, so a trainer's post-battle dialogue is
  still talking to someone standing in front of them, and the blackout path,
  sight lines and every script find the player exactly where they left them.

  The two HUD blocks gain the backing the white field used to be. Gen 1
  draws them as black glyphs straight onto the background with no box round
  them, and black-on-grass is not readable; the backing is painted inside
  `drawHUDs`, so it lands in the same target and takes the same zone colour
  as the HUD it sits under, in both the colorized and flat pipelines.

  Declines cleanly at every step it cannot take: no depth support, no open
  ground, the row switched off, or a terrain mesh still building all end at
  the battle screen the engine has always drawn.

### Changed

- **Characters are flat sprite billboards, and nothing about a sprite is
  voxelized any more.** Every figure -- the player, NPCs, the ghosts
  standing on a neighbour map -- is now its current 2D frame on a single
  flat quad, with the shader's alpha discard cutting the exact silhouette
  out of it. It still faces south and leans back by the camera's pitch,
  so it reads face-on at every tilt exactly as before.

  Two things went away with that. The contoured slab, which gave each row
  a thickness measured from the sheet's own side view; and the carved
  visual-hull models (`lib/VoxelModels.lua`, `tools/build_voxels.py`, and
  ~70 generated files under `assets/voxels/`, 2 MB), which reconstructed
  a figure from its three drawn views.

  A sprite is a DRAWING, not an object seen from one side. Gen 1's
  overworld figures are 16x16 icons with a fixed front-on reading, and
  turning one into a solid invents a body the artist never drew and the
  game never implied. The shipped models were also the one place this mod
  carried a description of the ROM art -- a carve is a faithful record of
  a sprite's silhouette, pixel for pixel -- which sat badly against a mod
  that otherwise ships no game data at all.

  The flat card is cheaper on every axis: no pixel access (only the
  sheet's dimensions), one quad instead of hundreds of faces, and one
  mesh shared by the solid draw, the sun pass and the player's occlusion
  silhouette. That sharing is load-bearing rather than tidy -- the
  silhouette draws with the depth test inverted, and any self-overlap in
  the mesh would read as "behind something" and repaint the figure on
  open ground.

  A mod can still ship `overrides/voxels/<name>.lua`; that path is
  unchanged and still wins where it exists.

## 1.0.6

### Added

- **Conditional pins.** A profile entry may now carry `when_above`:
  tile id -> rules keyed on the tile drawn directly north of it,
  resolved per POSITION in `TileShape.at`. A pin is per tile id and one
  graphic can mean two things -- the route gates' `$32` is both the
  wall's dark base course and every service counter's front, and it is
  the bottom row of its cell either way. Pinned `wall` the counters
  stood a full 16px; pinned `counter` the deep wall banks corrugated
  16/8 for sixteen rows and the room read as crates. What separates the
  two uses is what sits on top, so that is what the rule reads. The
  gates now have half-height counters AND level walls.

- **The Pokemon Tower has an exterior.** It is the one catalogued
  building drawing the map edge cuts off (no roof band is on the map at
  all), and it had no `buildings` entry, so it fell to the volume path
  -- which tops a run by repeating its first two rows, laying window
  courses flat across the plateau. Sealing the silhouette on the north
  alone closes it (88% fill, one piece, against 37% and 126 pieces
  unsealed), and one row of roof band spent on the drawing's top margin
  costs no window course. It stands as a real tower, panes recessed,
  door on the ground.

- **The Indigo Plateau statues stand up**, built exactly like the gym
  statues: plinth a solid 16px block, figure a per-pixel cutout riding
  it. On the avenue the statues stack with no gap, so the flood joined
  six of them into one 24-row region and the volume builder raised
  ridges of boxes with the statue art folded on the front. The same
  bird is drawn at the foot of every badge-check pillar, so those are
  crowned too.

### Fixed

- **Tall grass: one clump per tile.** Each 8x8 tile is a whole clump,
  but the template split every tile AGAIN into its top and bottom four
  art rows and stood those at two different depths -- so any blade
  running down a tile was cut in half, into two 4px stubs 4px apart.
  One tile is now one full-height standing slab at its own depth; a
  cell's 2x2 tiles still stand independently, so the player walks
  between the north and south rows.

- **Ledge lines are continuous.** `$34` is the cliff slope's foot and
  also the pillar between hop-down segments; pinned `wall` with the
  rest of the slope chain (the Diglett's Cave fix) it stood those
  pillars 16px beside a 6px lip. At ledge height the run reads as one
  lip, and the mound is unchanged -- its foot row reads as the talus it
  is drawn as.

- **A prop only stands on furniture when its own cell is blocked.**
  "Is something drawn above me" is not "am I standing on it": a chair
  drawn against the north side of a table is above the table's trim row
  too, and was being lifted onto the tabletop, with its claimed cells
  re-tiled as tabletop so the table marched two rows north. Three
  chairs in Cinnabar's trade room and Fuchsia's meeting room, and the
  Celadon diner's stools. The world already knows the difference: a
  thing that sits ON furniture occupies a blocked cell, a seat you walk
  up to is in a walkable one.

- **Caves: nothing below sea level, and the water is water.** Two tiles
  were identified backwards in the first pass. `$14` -- the tile the
  engine animates, that `Map.WATER_TILES` names and that Surf runs on
  -- was pinned `wall`, so Cerulean Cave's lake and the Seafoam sea
  stood up as rock slabs. And the pale dithered rock fill was pinned
  `water`, cutting 612 tiles of two-cell-wide trench through four maps.
  Both corrected; a sweep of all 19 cave maps now reports zero tiles
  below the datum. The elevation scheme is documented in the entry and
  derived from the game's own `tilePairs`: dark floor, water and drop
  holes at 0, the lit shelf a 6px step above, rock at 16.

- **Cave ladders climb.** Which ladder graphic goes up and which goes
  down is unanimous in the warp table -- 37 cells of one always warp
  down, 40 of the other always up -- so they are real stepped flights
  now, not painted plates.

- **Poke Mart's register stands on the counter.** The pin was on the
  wrong tile: `$08` is the counter's own top band, not the register, so
  the standee flood ate everything but two black lines. The register is
  the keypad-and-receipt drawing one row up.

- **Celadon's televisions**, which were solid 16px boxes wearing the TV
  art on one face, and the **Pokemon Tower reception desk** and the
  **gate counters**, which stood at wall height, are all their drawn
  heights now. The **Fan Club and Silph boardroom statues** were read
  as seated chairmen and painted onto the tabletop; they are cutouts
  standing on their pedestals, and the tables are cut to a true
  octagonal footprint.

## 1.0.5

### Added

- **Every remaining interior is furnished.** The profile covered ten
  tilesets; it now covers twenty-three -- 1,190 pinned tiles across
  `GATE`, `FOREST_GATE`, `LOBBY`, `MUSEUM`, `LAB`, `MANSION`,
  `INTERIOR`, `CLUB`, `SHIP`, `SHIP_PORT`, `FACILITY`, `CEMETERY` and
  `UNDERGROUND`, plus full entries for `CAVERN` and `GYM` which had only
  stubs. Roughly 130 maps, surveyed against the standard the finished
  interiors already set: one 16px wall band carrying whatever is drawn
  built into it, half-cell counters so the drawn front folds up and the
  top stays on top, `bookcase` collapse for free-standing shelves,
  thin-pool standees for plants, and small objects riding the furniture
  they are drawn above.

  What the detector was doing before, by way of what changed:

  - **Rooms with no walls.** Three tile ids (`$14`, `$32`, `$48`) are
    claimed by the engine's water set in EVERY tileset, and collision is
    per CELL, so one of them in a cell's bottom-left corner sank the
    whole cell. `$32` draws both the route gates' wall base course and
    every counter front, so all 25 gate maps were a checkered floor in a
    moat; the same trap put ponds through two thirds of Seafoam B4F,
    under every museum vitrine, along the S.S. Anne's wall corners and
    across Silph Co 1F's lobby island. The set is wider than three ids
    in practice -- `LAB`, `MANSION` and `INTERIOR` each hit six to nine
    -- because the test is per cell, so an innocent tile sharing a cell
    with a trapped one sinks with it and has to be pinned too.
  - **Towers and fused monoliths.** Counters raised to 48px dragging
    their wall band with them, merchandise racks fused sideways into
    32px blocks four tiles deep, cave shelf edges standing as 48px fins
    beside a 16px band, the Vermilion liner folded upright into a lumpy
    48px slab.
  - **Furniture that was not there at all.** Anything whose cell is
    walkable resolved to flat ground: 59 department-store stools, every
    gate lounge table and pair of binoculars, both museum staircases,
    the gym-lounge chairs, and -- via the void rule -- the black
    partition walls every gym is divided by, which left their white rim
    columns standing as hollow 48px fins.
  - **314 gravestones** in Pokemon Tower were 8px stubs, because the
    volume path measured only their bottom row and dropped the arch.

  Notable readings: the S.S. Anne's hull is `roof` (a drawing seen from
  above, so the art belongs on the top face); the Warden's specimens and
  Celadon Gym's shrubs are `cylinder` voxel balls, the first indoor use
  of that class; the Fan Club's octagonal boardroom table is `counter`
  rather than `table`, because only a counter rides its upper rows onto
  the top face in drawn order -- which is what draws the seated chairman
  exactly once, the Pokemon Center couch case verbatim.

  Fuchsia Gym's invisible maze is deliberately left flat. Raising it
  would read better as a room, and would also hand the player the
  solution; a shape is purely presentational, so the drawn answer wins
  and the gym plays as the flat game does.

### Fixed

- **A profile pin now outranks the door fold.** `Structures.forMap`
  folds a door cell into its facade so the doorway does not punch a hole
  in the wall -- but it overwrote the resolved shape unconditionally,
  including for AUTHORED tiles, which contradicts rule 1 of the
  documented resolution order. Any pin on a tile its tileset also lists
  in `doorTiles` was dead on arrival: all four Celadon Mansion
  staircases and Pokemon Mansion 3F's descent are door tiles, so
  `stair_*` pins there silently did nothing and the flights stayed
  painted flat on the floor. The fold now skips authored tiles.

## 1.0.4

### Added

- **Viridian Forest grows real trees.** Nearly everything drawn in the
  forest is ROUND, and the detector was boxing all of it: the big trees
  came out as ragged mixed-height volumes (their sparse canopy-rim
  tiles read 0px against 32px bodies, leaving gap-toothed hedge walls),
  the stump rows merged into 16px crate walls wearing folded stump art,
  and the trail signs were broken piles -- their $32 tile is the
  water-fallback trap and recessed into a pond lip in the middle of the
  woods. All of it is now profile-pinned to the treatments the rest of
  the world already uses: every tile of the tree drawing (ball, rim
  wisps, feet) and the stumps take the per-cell voxel HULL the overworld
  border forest wears -- a tree spans 2x2 cells, so its four
  quarter-hulls tile into one big lumpy canopy, and each stump becomes a
  round bollard; the signs take the standing thin-slab `signpost`
  treatment every town sign gets; and the white sparkle filler inside
  the tree masses is flat ground instead of an invisible zero-height
  box. The whole map now resolves to hulls, signs, ledges and ground --
  a detector sweep finds no stray boxed column anywhere.

  Two refinements over the first cut, both new hull-builder abilities.
  A tree's drawing spans 2x2 CELLS, and per-cell hulls unfolded it onto
  the ground -- the ball's top half sat one cell north of its bottom
  half at the same elevation, reading as a tree cut in half. The new
  `canopy` class pins the drawing's corner tile as a group anchor and
  the whole 2x2-cell drawing carves as ONE 32px hull, so every tree is
  a single tall round canopy (the carver is now parametric over its
  canvas size, and hull stamps carry their footprint radius). And the
  stumps' drawn tops are a CUT FACE -- an ellipse of growth rings seen
  at an angle, not body: the new `stump` class builds the hull from the
  bark rows alone and projects the ellipse across the round flat top,
  near arc to the south (`stump_cap` names the ellipse's drawn height),
  so the rings ride the round part in perspective.

- **Flowers stand up, and keep swaying.** The animated meadow tile
  ($03, the one tile the overworld animates by frame rewrite) now
  renders as a billboard one voxel deep: the drawing's darkest tones
  plus everything they enclose are cut out per pixel, and the ground
  beneath is synthesized from the commonest flat neighbour, exactly
  like the ground under a detected prop.

  The interesting part is that the cutout still animates. A mesh is
  static, so the geometry spans the UNION of the mask over the base art
  and all three animation frames, and the animation lives entirely in
  the texture: TerrainAtlas already rewrites the flower's slot in the
  private animated atlas each step, and for this tile it now writes
  only the current frame's mask opaque with everything else keyed to
  alpha 0 -- which the voxel shader discards, and the shadow pass with
  it. The standing silhouette trims itself frame by frame in texture
  space, off the same engine clock as the flat path, without a vertex
  moving. The class is derived, not authored: any frames-animated tile
  resolves to the new `flower` class with no profile entry, the same
  way tall grass derives from `grassTile` (hand-authoring still wins).

- **The cuttable bush is a standing cutout.** The four tiles Cut
  deletes ($2D/$2E/$3D/$3E -- across the whole tileset they appear only
  in the five cut-tree blocks) are pinned to the thin `prop` pool: a
  per-pixel standee 5 voxels deep, black-outline segmented with its
  enclosed pixels kept, the drawn grass dither flooding away. It
  stands on plain grass ($2C) -- the very tile Cut leaves behind per
  field.cutTreeSwaps -- via the profile's new `prop_ground` key, which
  names the tile painted under a pinned prop instead of whatever flat
  tile its neighbours vote in.

- **Gym statues: a solid plinth, a standing bird.** The statue pair
  flanking every badge gym's aisle (and Bruno's room) is one cell of
  figure over one cell of plinth. The plinth ($22/$23/$32/$33) is
  pinned `wall`: a solid 16px block. The figure ($02/$38/$12/$13) is
  pinned `prop`: a 5-voxel cutout that stands ON the plinth through the
  authored-box support rule, its checkered background flooded away and
  the pixels its outline encloses kept.

  The whole statue keeps ONE cell of footprint. The support rule used
  to extend the box under the claimed cell (the monitor-on-desk path),
  which marched the plinth a second block backwards; a figure whose
  support is a FULL-HEIGHT block now collapses instead -- the drawn
  figure cell becomes synthesized floor, since the block below already
  carries the whole base. Furniture supports keep the extension: their
  drawn cell is the furniture's own upper rows, and floor there would
  amputate the desk. The round boulder drawn beside some statues is
  deliberately NOT pinned -- it also tiles wall-to-wall as Pewter's
  rock rows, which are scenery for the detector.

- **Lt. Surge's trash cans stand up.** The can ($0B/$0C/$1B/$1C, the
  lone graphic of blocks 38/39) takes the same treatment as the
  cuttable bush: a 5-voxel `prop` cutout, black-outline segmented with
  its enclosed pixels kept, standing on the gyms' main floor tile
  ($11) via `prop_ground`.

- **The Poke Marts furnished to the Center's standard.** The MART
  tileset shares the Center's atlas image but is its own id, so none
  of the Center's pins applied, and every mart was raw detector
  output: a 32px double-height display band for a back wall, the two
  shelf racks fused into one four-tile-deep monolith, and the clerk's
  booth towered into a 48px slab wearing the juice poster. Pinned the
  way the finished interiors are -- the back wall's SALE cases and
  drink fridges one 16px face like the Center's healing consoles, the
  racks collapsed to one-cell-deep shelves at drawn height like Red's
  bookcases, the counter half a cell with the poster riding its top
  like the nurse's tray, and the cash register standing ON the counter
  through the authored-box support rule. One 4x4 layout serves every
  city, so this covers all eight marts.

### Fixed

- **Cut trees now vanish in voxel mode -- and grow back.** The
  engine's Cut path swapped the block with a raw `setBlock` + renderer
  rebuild, never emitting `world.block_replaced` -- so this mod's
  listener (which rebuilds the map's mesh exactly for this) never
  heard about it, and the diorama kept showing the tree. The engine
  now routes Cut through `replaceBlock`
  (src/world/OverworldController.lua), whose whole purpose -- per its
  own comment, "Victory Road barriers, Cut trees" -- is that same swap
  plus the event. The regrowth path had the same hole one door away:
  cut trees are restored block by block when the map is re-entered,
  and the card-key doors are stamped closed on floor load, both
  through the same silent `setBlock` -- with the mesh cache staying
  warm across a round trip (that is what prevLive is for), the world
  kept showing the stump you left. Both paths now announce each block.

- **A block edit no longer blinks the world down to 2D.** The
  listener used to drop the edited map's mesh outright, and mesh
  builds are asynchronous -- so cutting a tree (or stepping out of a
  door onto a map whose trees just regrew) flashed the flat 2D world
  for the frames the rebuild took. `ChunkMesher.refresh` rebuilds in
  place instead: the stale mesh keeps drawing, the replacement cooks
  in the background, and each slot swaps as its build lands -- the
  tree pops out (or back in) with the scene never leaving 3D.

- **Flowers cull the player correctly from every angle.** The flower
  billboards were baked into the terrain mesh, which draws without
  the characters' camera-ward pull -- so a walker standing among
  flowers won the depth test against ALL of them, including the
  flower south of their feet that should overdraw them. The flower
  quads now ride their own mesh, drawn after the characters with
  exactly the characters' pull (the tall-grass trick): the flower in
  front of a walker occludes their feet, the one behind them hides,
  at every camera angle. Unlike grass the flower mesh still casts
  shadows -- it is a handful of cutouts per meadow, not thousands of
  tufts.

- The `voxel_anim_probe` driver crashed on engine builds without the
  optional `TileRenderer.animFrame` seam, and again on tilesets whose
  animation list carries a "toggle" entry (spinner rooms), which claims
  a tile LIST rather than one slot. It now reads the clock through the
  mod's own fallback chain and skips toggle entries in the placement
  census.

- **The shoreline no longer opens into the sky beside buildings and
  signs.** Water recesses 2px below the ground, and the ground tile beside
  it closes the step with a small below-ground side band -- but a tile
  CLAIMED by a standing object (a building footprint, a sign standee, the
  bushes ringing Fuchsia's ponds) only painted its synthesized flat ground
  and never emitted sides. Along every stretch where such a tile met
  water, the two-pixel step was an open slit straight through to the sky
  behind the mesh. The skip branch now emits the same below-ground bands
  ordinary ground does, cut from the synthesized ground's own art, so the
  shoreline lip is continuous whatever stands on the bank.

- **Edge-row buildings keep their facades.** The south wall of Saffron's
  row houses -- profiled buildings whose front row is the map's last tile
  row -- lies exactly on the boundary plane shared with Route 6, and the
  prebuilt-quad keep rules dropped it: the strict body test excludes the
  plane, and the closed neighbour mask (which exists to kill ring scraps
  whose rects sit exactly on that line) swallowed what was left, so from
  Route 6 the houses stood hollow. The two cases are geometrically
  identical degenerate rects, but they FACE opposite ways: a face pointing
  away from the body is this map's own facade and nothing in the
  neighbour will ever draw that plane, while a face pointing into the
  body is the scrap the mask is for. The mesher now reads the winding and
  keeps outward faces on the body's boundary planes, on all four edges.

  The roof RIM had the same problem one step further out: an edge-row
  house's eave overhangs `frontEave` voxels PAST the boundary plane into
  the neighbour's airspace, and those quads are neither on the plane
  (the winding rescue) nor over the body -- the neighbour-body mask ate
  them as ring scraps, so from across the seam the roof edge was open
  sky at low camera angles. Building placements only ever scan the map
  BODY, so every building quad is this map's own structure by
  construction: they now carry an `own` flag the edge keep-rules never
  touch.

- **Diglett's Cave mounds (and cliffs everywhere) stop sprouting
  towers.** Two detector misreadings stacked up on the cave-entrance
  mound. The dark east slope of the cliff drawing ($02/$24/$34) is one
  texture repeated over the mound's whole height, but its corner tiles
  break the repeat scan, so those columns rose to 32px -- the rock
  pillar beside the entrance. And a folded doorway column reads its own
  drawn extent (the door plus everything above it), which is a house's
  real height when the door is a house's, but a 32px tower over a 16px
  plateau when the door is a cave mouth -- the entrance jumped a block
  above the mound around it. The slope chain is now profile-pinned to
  one 16px course, and a doorway column answers to its REGION entirely:
  height from the region's dominant column, top flat when those columns
  are flat repeats (the mound) and roofed when they are drawn facades
  (a house). Both cave entrances -- and every cliff built from the same
  slope tiles -- now read as one level mesa with the cave mouth at
  ground level. A new `voxel_mound_probe` driver prints the detector's
  per-column class and height over any rectangle, which is how this was
  diagnosed.

  Routes 3 and 4 had a third variant of the same misreading: the repeat
  scan anchors at a column's FRONT tile, and a plateau column that ends
  in a one-off rounded corner tile ($13/$35) never matched -- it read
  its whole capped extent and shot up as a 48px fin (several together
  made a tent). When the two rows directly above the front are
  identical, the column is now read as that repeat wearing a trim foot:
  its unit is one course plus the trim. Doorway columns still answer to
  their region first, so houses are untouched.

## 1.0.3

### Added

- **The player shows through whatever hides them.** Occlusion in this mode is
  the real thing -- walk north of Red's house and the roof is genuinely in
  front of you -- but a player who cannot see their own character has lost
  track of where they are standing, which the flat game never allowed. The
  figure now draws a second time as a translucent silhouette wherever the
  world is in front of it.

  No code anywhere asks whether the player is occluded: the depth buffer
  already knows, and the test is the question. The silhouette is drawn with
  the depth compare INVERTED -- `greater` where the scene uses `lequal` --
  so it appears exactly where the ordinary draw would have lost, and nothing
  at all is drawn when nothing is in the way. LOVE hands the compare straight
  to `glDepthFunc`, so the two are true complements with no seam between
  them.

  It goes down BEFORE the characters, so the only thing it can meet in the
  depth buffer is the world -- terrain, buildings, trees. Drawn after the
  solid pass it would meet the player's own card instead, and every fragment
  of a figure sits behind the one that just wrote it, so it would paint over
  the player permanently. Characters then draw on top as usual.

  It uses the FLAT card (`SpriteBillboards.shadowQuad`), not the relief slab
  the solid pass draws. The slab carries front and back faces and the mode
  culls neither, so with the test inverted its own back faces -- a few voxels
  deeper than the front ones that just won -- read as "behind something", and
  the figure repaints itself on open ground whether or not anything is in
  front of it. One quad has no self-overlap, which is exactly why the shadow
  pass already uses this mesh, and it cannot double-blend into a mottled
  patch either. A silhouette is an outline, so the outline is the right mesh.

  Depth writes are off: the pass is behind the scenery by definition, and
  writing would file the hidden figure in front of the building hiding it,
  which the grass pass at the end of the frame reads. The card carries the
  same transform and the same camera-ward pull as the solid draw (both now
  come from one shared `billboardMatrix`/`billboardPull`, so they cannot
  drift), which is what keeps the leaning-over-a-near-wall case out of it:
  pull already won that fight for the solid draw, so a character merely
  standing close to a wall does not shimmer a silhouette over it.

  It is drawn as ONE flat translucent grey, not as a dimmed copy of the
  sprite. Tinting through the vertex colour could only MULTIPLY the sprite's
  own pixels, which darkens each one by its own amount and keeps all the
  character's internal detail -- a murky picture of Red rather than a shape.
  So the fragment shader carries a `ghost` / `ghostColor` pair and replaces
  the colour outright, last in the chain so neither the sun nor a voxel seam
  can mottle it. Staying translucent is what keeps it reading as "behind
  that wall" rather than as a hole punched through it.

  `Voxel3D.GHOST_COLOR` and `GHOST_ALPHA` (0.5) are the knobs. Only the
  player gets this -- NPCs and the ghosts standing on a neighbouring map are
  left to honest occlusion, because it is only your own character you cannot
  afford to lose behind a roof.

## 1.0.2

### Fixed

- On Android the diorama drew into the top-left corner at a fraction of the
  screen -- about a third of the width and height on a 420dpi panel -- with
  the field effects (dust, emotes, the cut-tree shudder) correspondingly
  oversized against the world they sat on. Desktop was unaffected.

  The pipeline ctx hands over `width`/`height` measured in LOVE UNITS
  (`love.graphics.getDimensions`), but the engine composites a pipeline's
  returned canvas with `draw(canvas, 0, 0, 0, 1/dpiX, 1/dpiY)` -- a scale
  that only covers the window if the canvas is at PIXEL resolution. Sizing
  the scene canvas from the ctx therefore paid the DPI scale twice: the
  canvas came out that much smaller, and was then drawn that much smaller
  again. On desktop the two units are the same number and nothing shows;
  Android's DPI scale is the display density (2.625 at 420dpi), so that is
  where it surfaced.

  The scene canvas is now sized from `love.graphics.getPixelDimensions`
  directly rather than from the ctx. That is the number a fixed engine would
  hand over, so this does not double-correct if the ctx is ever changed to
  agree with the compositor. It also squares the FX pass for free:
  `ctx.scale` was ALREADY in pixels per world pixel (`Zoom.scale` over
  `Renderer:fitScale`, which measures the drawable), so the closures were
  being scaled for a canvas 2.6x bigger than the one they were drawing into
  -- one wrong number, not two.

## 1.0.1

### Fixed

- The RED++ texture-readback fallback in `TerrainAtlas` never worked on real
  drivers: LOVE refuses `Canvas:newImageData` while that canvas is currently
  active, and `readback()` read the pixels back before restoring the previous
  render target, so the call threw on every driver rather than only on
  stubborn ones. The previous target is now put back BEFORE the read. The
  headless suite could not see this -- its stub canvas does not enforce the
  rule -- so it survived until a live probe ran the chain unguarded.

  Harmless for vanilla tilesets, where the CPU rebuild (`gbcPixels`) answers
  first and the fallback is never consulted; it was the last-resort route for
  a map the palette pack does not know, which until now had no working route
  at all.

## 1.0.0

First release, ported from the engine-internal voxel branch onto the
`render_pipelines` mod API.

Interior furniture gets the shapes it depicts
(mods/DRAMATIC_SHAPE/tools/voxel-survey.md is the procedure that found and
verified these).

Buildings stop being boxes wearing their own elevation. A profiled
building is voxelized from its own sprite, band by band -- the pipeline
written up in `assets/docs/buidling_to_voxel/`.

Terrain meshing goes asynchronous, instanced and bounded. The first voxel
frame used to build every neighbourhood map synchronously (a ~2.4s
freeze), retain every map's analysis forever (gigabytes over a
cross-region trek), and string stray pixels along map seams.

Real shadows. The sun moves to the southeast and drops to 45 degrees, so
shadows fall northwest -- up and to the left on screen -- and run about as
long as the thing throwing them is tall.

### Added

- `voxel` render pipeline: 3D diorama overworld with extruded terrain,
  depth-buffered occlusion, leaning sprite billboards, drop shadows and
  contact AO. VOXEL options row and hotkey `3` (OFF / 15 / 35 / 50 / 75).
- `tiltshift` render pipeline: a `worldPresent` post-process giving the
  miniature-photo look. T-SHIFT options row and hotkey `6` (OFF / 1 / 2 / 3).
- Carved voxel models for 67 overworld sprites, plus the
  `tools/build_voxels.py` that produced them.
- `data/voxel_heights.lua`, the hand-authored tile shape profile.

- Furniture shape classes in the profile: `bed` (a low slab wearing its
  top-down art), `table` and `desk` (boxes at their drawn height whose
  faces fold the artwork up), `relief` (a prop drawn from above -- a
  game console -- lying flat and extruding a few voxels inside its
  outline), the standee pools `billboard` (10px) / `prop` (5px) /
  `stool` (5px, and characters standing on its walkable cell sit at
  seat height) / `cutout` (one voxel: pure profile, for the vase on the
  table), and the stair archetypes `stair_e`/`stair_w` (a rising flight
  of real steps) and `stair_down_e`/`stair_down_w` (a sunken stairwell
  descending below the floor -- stairs that lead down).  Separate pools
  cluster separately, so touching drawings never merge into one cutout.
- Standee clusters split into per-pixel connected components, each
  standing on its own feet in the depth band of the row it is drawn in:
  two stools stacked in adjacent cells become two stools, and no
  fragment of a drawing ever floats at its bounding-box height.  A
  `cutout` keeps only its largest component -- a cast shadow's drawn
  edge is background, not a floating scrap.
- `bookcase` class: free-standing shelf drawings collapse in ranks onto
  one-cell-deep boxes at their full drawn height, back rows becoming
  hidden floor; the trim row above a rank -- undetected structure or a
  row pinned `table`, since the same trim tiles cap other furniture --
  is adopted as its cap.  Pinned for Oak's Lab (`DOJO`).
- Oak's Lab tables pinned `table`: the starter-ball display and the
  north tables stand at real table height, the display frame's black
  corner brackets no longer auto-extract into standing prisms, and the
  Poke Ball / Pokedex sprites ride the authored height onto the
  tabletops.
- Pinned props drawn directly above a pinned box stand ON it: the PC
  monitor on its desk, the flower pot on the dining table.
- Profile pins for `REDS_HOUSE_1` / `REDS_HOUSE_2` (Red's house and the
  Copycat's, both floors): bed, stools, tables, PC desk with its
  standing monitor, bookcases, TV standing on the floor behind the game
  console's relief, potted plant, flower pot, both staircases, and the
  wall/window band.
- `mods/DRAMATIC_SHAPE/tests/voxel_survey.lua`: screenshot-survey driver
  behind the repeatable inspection procedure (SURVEY_MAP / SURVEY_SPOTS /
  SURVEY_LEVELS / SHOT_DIR), documented in
  mods/DRAMATIC_SHAPE/tools/voxel-survey.md.

- `lib/Buildings.lua`: a building archetype. Where the volume path folds a
  whole drawing upright (roof, facade and sloped ends alike) into one box,
  this classifies each BAND of the drawing by the 3D surface it depicts and
  applies the matching operation: top-facing rows lay flat over the
  footprint, the facade extrudes straight back, an awning band juts past
  the walls, and the drawn taper at the ends becomes a stepped slope in
  elevation. Every visible voxel carries a real texel of the drawing, so
  the model recolours with the atlas.
- A `buildings` section in `data/voxel_heights.lua`. A building is matched
  by its exact tile grid (the drawings are catalogued in `assets/docs/buildings/`),
  so one entry covers every map that places the same art -- Red's house,
  Blue's house, Bill's, the Copycat's and the two Fuchsia houses are one
  seven-placement entry, and Oak's lab is a second. Only the band table is
  authored; the silhouette, the taper rate, the eave height and every
  window and doorway are measured off the pixels.
- The flat-roofed civic block and its sixteen relatives -- every Pokemon
  Center and every Poke Mart, Fuchsia Gym, the museum, the Game Corner,
  Celadon Mansion and its department store, the Power Plant, the Route 5
  and Route 22 gates, Silph Co and five anonymous scenery blocks. They are
  one architecture drawn at eleven different footprints, from 4x4 cells up
  to Silph Co's 8x12, and they share one band table: their lattice is drawn
  from straight above, so the measured taper comes out flat and the whole
  band is depth under one level roof. No new roof mode was needed -- the
  drawn profile was always the shape, and a drawing with no taper simply
  yields a level one.
- Pewter's museum hall (`assets/docs/buildings/B24`). It is the one
  sloped-roof building in this pass, and the only one so far whose roof
  texture is not a plain repeat: the drawing states its own period by
  repeating the whole lattice-and-course motif, rows 8..31 again at 32..55,
  so the cycle is 24 rows and the roof carries its drawn courses across the
  depth instead of a bare lattice. The band below them is the roof's
  fascia, wider than the wall it covers, so it belongs to the roof and
  lands on the south rim. Note the museum is TWO drawings: this hall and
  the east entrance beside it, which is B18 and shares its drawing with the
  Route 2 gate.
- The rest of the 2:1 sloped-roof buildings: both gym drawings (the
  standard one at Cinnabar, Pewter, Vermilion and Viridian plus the
  Fighting Dojo, and the wider Celadon / Cerulean / Saffron one), the 4x2
  cottage that houses Mr Fuji, the Cubone house, Bill's grandpa, the Name
  Rater and the Viridian school, Cerulean's three wide houses, the day
  care, and three scenery blocks -- among them B01, which at 19 placements
  is the commonest drawing in the game. Each one's roof band is pixel for
  pixel one of the three already authored, so they take that sibling's band
  table unchanged: 16 rows for Red's house's, 32 for Oak's lab's, 64 for
  the museum's.
- The Safari Zone rest houses and the Victory Road entrance, the first
  buildings outside the `OVERWORLD` tileset -- `buildings` is keyed by
  tileset and until now only had the one section. The rest house's
  corrugated roof repeats every 5 rows rather than the overworld lattice's
  8, which is the point of `roofCycle` being authored per building.
- Route 10's scenery block, via a new `seal` field. Its drawing has no
  black base course -- it ends on a row of light brick -- so the silhouette
  flood climbed in from the south border through the mortar and hollowed
  the wall out, leaving 72% of the sprite in 65 pieces. `seal` names the
  sides a drawing runs off rather than closing, and the flood does not seed
  there: sealed, it is 95% in one piece, and the model is its twin the
  museum's. No other building sets it, and none changes by a voxel.
- Together these take the mod to 31 of the catalogue's 34 drawings and 144
  of its 147 placements. The last three, and why each resists, are written
  up in `assets/docs/buildings/REMAINING.md`.
- A roof no longer runs past the drawing's own silhouette. These sprites
  are inset from their boxes, and the columns outside the inset carry no
  roof: they now get none, and the fascia belongs to the outermost columns
  the drawing actually paints. Before, they raised a four-voxel slab of
  black kerb the full depth of the building, flanking its walls at ground
  level. Nothing shipped hit this -- Red's house and Oak's lab are drawn
  edge to edge -- so no existing model changes by a single voxel.
- Windows and doorways sink a voxel behind their frames, found rather than
  listed: a pane is a non-black region the drawing seals off behind its own
  black outline. A nested frame (the door's own little window) layers for
  free.
- `tools/building_voxels.py`: the reference implementation of the same
  algorithm, with the geometric asserts and isometric previews Stage 5 of
  the methodology calls for. It and the runtime agree exactly on voxel and
  shell counts for all 19 templates -- 96,617 / 12,866 for Red's house up
  to 3,715,963 / 146,762 for Silph Co, which ship as 3,410 and 13,994
  quads. Its slope asserts read the drawn columns only and stand down for a
  roof with no taper, where the check is that the roof is level instead;
  its previews fit the projection to the model rather than to a fixed
  camera, so a building taller or deeper than the first two still lands on
  the canvas.

- A sky at the 75-degree rung. Pitched that far over, the horizon comes
  into frame and a good part of the picture is void, so the void gets
  filled instead of reading as the black plate it does at every rung below.
  No skybox and no geometry -- it is the colour the scene canvas clears to.

  **Outdoor maps only.** A house, a cave or a gym is a room with a ceiling,
  and the void past its walls is the outside of a box rather than open air.
  The test is `Map.isOutdoor`, the same one the engine uses for door SFX
  and the town map, and the same one `Structures` already asks to decide
  whether a map rings with trees.

  The colour is a four-shade ramp shaped like a world palette and run
  through `PaletteFX.effectiveColors`, so it answers to the display mode
  exactly as the baked terrain does: blue in the colour modes, grey under
  GRAY, green under CLASSIC, dark under GBC INV. A hardcoded blue would sit
  wrong in every mode that is not a colour mode. It fades across the
  approach to the top rung rather than switching at the keypress, so it
  arrives with the camera tween.

- The mod's four controls sit on adjacent keys, and the two that never had
  one now have a hotkey at all:

  | key | control |
  | --- | --- |
  | 3 | VOXEL, the camera ladder |
  | 5 | V-GRID, the wireframe |
  | 6 | T-SHIFT, the blur ladder |
  | 7 | V-CURVE, the horizon bend |

  Only 6 arrives by the documented route. `Game:keypressed` answers the
  engine's own display keys first and returns -- 2 COLORS, 3 TILT, 4 ZOOM,
  5 GBC FX -- and only then offers the key to `Pipelines.hotkey`, expressly
  so that "a pipeline can never shadow one". Two of the four wanted keys are
  in that set, and V-GRID and V-CURVE own no render pass, so they have no
  registry to claim a key from in the first place. The mod wraps
  `Game:keypressed` to take the four. Polling the keyboard in `update`
  would not do: it fires alongside the engine's handler rather than instead
  of it, so 3 would cycle this mode AND the engine's TILT on one press.

  **TILT (3) and GBC FX (5) are no longer reachable by key while this mod
  is enabled.** Both are still on the OPTIONS menu. Key 9 is now free.

  So the VOXEL key turns both off itself, on every press. Both fight the
  diorama -- TILT is the flat fake of what this mode does for real, GBC FX
  a full-screen present pass laid over the top -- and with 3 the only key
  that now reaches either, it also has to be the way back from having left
  one on. Every press, not just the one that switches the mode on: the
  registry's own tilt exclusion covers switching ON, but the press that
  cycles the ladder round to OFF would otherwise leave both running with
  no key left to clear them.

  The wrapper delegates rather than reimplements: `Pipelines.hotkey` still
  applies its own gate and ladder, the settings borrow the same free-roam
  gate the voxel pipeline uses, and a screen with its own key handler keeps
  the keyboard -- so typing a nickname cannot cycle a render mode behind
  the text box.

- `lib/ShadowMap.lua`: the scene rendered once from the sun into an
  orthographic depth map, which the main pass then samples per fragment.
  What the sun cannot see is in shadow, whatever surface it is, so a
  shadow climbs a wall, drapes over a roof and slides across a passing
  NPC with no case in the code -- and every caster is simply whatever the
  pass draws. The terrain mesh goes in, which means buildings, trees,
  ledges, signs and every prop cast, where before only characters did.
- Depth is packed into two 8-bit channels of an ordinary color canvas
  (~16 bits over a ~700px frustum, a hundredth of a world pixel).
  Readable depth textures are the least portable corner of the graphics
  API, and the mod's contract is that an unsupported driver falls back
  rather than errors: `available()` reports, and VoxelScene keeps the old
  flat decals when it says no.
- The map resolution is picked per frame from a 1024/1536/2048 ladder
  against a 0.45 world-pixels-per-texel target, because the light frustum
  is fitted to the world view and that swings 3x between the closest zoom
  and a maximised window at the widest. The frustum is snapped to whole
  texels, without which every shadow edge in the world crawls as you walk.
- Ambient occlusion, the genuine article: each vertex counts the
  neighbours crowding it and steps down once per neighbour, on top faces
  (four corners, three neighbours each) and now on upright faces too --
  the crease a wall rises out of, and the inside corners where flanking
  columns box it in. It is the complement of the shadow pass rather than
  a duplicate: the map draws the long directional shadow, this draws the
  dark seam in every corner the sky cannot see into, at scales finer than
  a shadow map texel.
- Plus a ground-contact term for the prebuilt prop quads -- per-pixel
  plants, signs and lone trees, and the round-tree stamps. Those arrive
  from Structures already finished, so the neighbour counting has no
  columns to count; what it can still say is that the floor blocks half
  the sky, so a voxel's first 6px of rise ramps back to full light. It is
  what stops a prop reading as pasted over the ground rather than
  standing on it, and outdoors it is most of what AO does at all, since
  trees and posts are nearly all prop geometry.
- One knob for the lot: `AO_STRENGTH` in `ChunkMesher` scales every term
  (they are written as darkening amounts, not multipliers), against a
  floor that keeps a crank from punching holes of pure black.
- The **V-CURVE** row in OPTIONS: the curved world, the Animal Crossing
  horizon. Every vertex is pushed down by the square of its horizontal
  distance from the camera's focus, and that is the whole effect -- a
  quadratic is nearly zero near its vertex, so the ground being played on
  stays flat, and the falloff accelerates, so the far edge rolls away over
  a near horizon and the town reads as sitting on a small sphere.
- It is deliberately NOT a fisheye. A fisheye is a LENS -- a screen-space
  warp -- which bends straight lines everywhere including right in front of
  the player, resamples every pixel to do it, and would leave this mode's
  art blurred and crawling. Bending the WORLD is one line in the vertex
  shader: lines near the camera stay straight and not a pixel is resampled.
- Displacing along Y only is what keeps it readable rather than
  nauseating: the drop depends on where a column stands, not how tall it
  is, so the world tips away and the buildings on it stay upright.
- Shadows and the wireframe ride along for free. Both are already worked
  out before the bend -- the shadow map in flat world space, the grid in
  model space -- so the bend carries them exactly as if they had been
  painted on, and neither the light frustum nor the grid needs to know the
  curve exists. `Voxel3D.project` applies the same drop on the CPU, which
  is what keeps the overworld's 2D field FX on their ground points.
- The strength scales with the view height, so a rung reads the same at
  every zoom, and the ladder is calibrated against the far edge of the
  visible ground rather than against nothing.
- `lib/ModSetting.lua`: the ladder/store/rows a setting of this mod's own
  needs, now that there are two of them. V-GRID's copy of it moved here.
- A **75 degree** rung on the VOXEL ladder, below the 15/35/50 it shared
  with the engine's TILT: low enough to read as a diorama shot from table
  height. Tilt could not have it -- its flat plane degenerates into a
  horizon line down there -- but geometry only gets more of itself to show.
- The **V-GRID** row in OPTIONS: a one-display-pixel wireframe along every
  voxel edge, 3D Dot Game Heroes style. Every mesh here is built one unit
  per voxel in its OWN model space, so the seams are that space's integer
  planes, and reading them in model space rather than world space is what
  keeps them glued to a thing however it is posed -- a character's slab
  leans back by the camera's pitch and its seams lean with it.
- The seams fade out where a voxel shrinks under about 3 display pixels.
  Survey zoom draws a world pixel at roughly a display pixel, and a wall
  seen nearly edge-on squashes one to nothing at any zoom; drawn anyway,
  the lines land closer together than they are wide and the wireframe
  stops being a wireframe and becomes a flat 45% dimming of the scene.
- `lib/VoxelGrid.lua` owns the toggle. It is NOT a pipeline: it owns no
  pass of the frame, it parameterises the voxel one, so it has nothing to
  put in `drawWorld` or `present` and the registry rightly rejects it. A
  plain mod setting instead -- `options:define` for the store and the mod
  manager's page, `ui.options.rows` for the row in OPTIONS next to VOXEL
  and T-SHIFT. Both rows read and write the one stored value.
- The wireframe is a SECOND COMPILATION of the scene shader rather than a
  branch inside it, because it needs shader derivatives (`fwidth`) -- the
  one part of the mode a driver can refuse. A refusal costs the grid and
  nothing else.
- `mods/DRAMATIC_SHAPE/tests/voxel_shadow_probe.lua`: reports the fitted
  frustum and the resolution rung, dumps the map itself, and shoots a
  stand point at every pitch. `SHADOW_SUN="kx,kz"` retunes the bearing for
  one run, `SHADOW_GRID=1` forces the wireframe on, and `SHADOW_ZOOM` pins
  the zoom, without which two runs are not comparable -- a driver inherits
  whatever the player left in `options.lua`, and the world view size (which
  the light frustum is fitted to) swings 3x across that range.

- A `counter` class (8px, upright): half-cell furniture. One 8px band,
  so exactly the drawing's bottom row stands up as the front and every
  row above it rides the top face in drawn order. `table`'s 12px could
  not be retuned for it; the houses share that class.

- `tilesets.POKECENTER` in `data/voxel_heights.lua`. Before it, the
  detector merged the wall-touching counters and healing machines into
  the wall band and towered them 3-6 blocks, flattened the machines'
  near-black screens to void, read the pillar bases, plant pots and
  machine bodies as ponds (the $14/$32/$48 stale-cache water fallback),
  boxed each plant pair into one hedge cube, and extruded the lounge
  seat -- a PERSON is drawn into its tile art -- into a monolith wearing
  his face.
- The pins, by shape: the wall band, windows, poster, pillars and the
  16px machine bodies are `wall`; the counters (with the nurse's tray)
  are `counter` and the PC's desk is `table`; the machine screens and
  the PC are `billboard`, standing on the pinned boxes below them; the
  potted plants are `prop` standees like every other interior plant.
- The lounge couch with the man sitting on it is a `counter` box: its
  bottom row stands up as the couch's front and the cushion and the man
  ride the top face, each drawn exactly once.
  He cannot be stood upright, and the reason is structural rather than a
  tuning question. His skin pixels span two tile rows and stop dead at
  the row 9/10 seam; folding two rows upright requires both to share a
  class, which makes the box two tiles deep, and a fully folded box
  repeats its north row across its whole top face. So every upright
  arrangement puts his head on screen two or three times -- as a 16px
  seat-back, on the front and twice more on the top; as a 32px bookcase,
  a cabinet taller than the room's own walls. Dropping the seat in front
  of him to floor level only changes which copy you see. Nor can he be a
  standee: the drawing has no floor margin, so all three non-black
  shades touch the cluster rim and the mask drains 307 of its 420
  interior pixels -- 46% of him even segmented alone, because his skin
  is the same light shade as the couch behind him.
- Survey evidence: full before/after passes of VIRIDIAN_POKECENTER at
  15/35/50 degrees, plus spot-checks of CELADON_POKECENTER and
  CELADON_HOTEL (shared tileset, both inherit correctly) and of
  REDS_HOUSE_1F, OAKS_LAB and VIRIDIAN_CITY (unchanged -- the new class
  is additive and no other tileset lists it).

### Changed

- Pinned props are segmented the way the art is authored: objects wear a
  black outline, so background is the shades touching the cluster's edge
  (white floor around a TV, grey tabletop around a vase) flooded in from
  the aprons; the outline, its interior, paint whites and anything they
  enclose survive -- pixel-perfect cutouts on any surface.
- Indoor structure analysis floods background from all four aprons and
  accepts ground contact on any side (outdoors keeps the south-only rule
  that protects roofs), so face-on furniture drawings voxelize per pixel
  instead of rising as wall-height volumes.
- Profile-pinned standees are 10px deep (detected props stay 6px), so a
  deliberate object like a TV keeps a body at shallow camera angles.
- Authored upright boxes fold their artwork up every face (flanks and
  back wear the front stack darkened) and top faces keep the drawn
  tabletop: face-on rows wear the row above the fold instead of
  repeating their front art lying flat, and a run that folded entirely
  tops with the furniture row drawn above it (a bookcase's shelf trim).
- Characters no longer ride a pinned stair tile's class height: stairs
  are walked through at floor level, fixing the step-up onto thin air in
  front of stairwells.

- A voxelized building is as tall as its facade plus its roof slab rather
  than as tall as its drawing: the roof rows are DEPTH now, not height, so
  Red's house is 36px over a 4x3-cell plot instead of a 48px cube.
- Round trees (the `cylinder` pin: lone canopies and the border tree
  wall) stop being lathes -- the sprite wrapped around a 12-segment
  column read as exactly that, art smeared on a barrel. Each cell is now
  a real voxel hull: the canopy is segmented out of its cell as the
  darkest-pixel outline plus everything it encloses (which also drops
  the background grass that used to inflate every row to full width, and
  the cast shadow under the ball), and each mask row runs its own span's
  circular chord in depth -- the front view is the sprite pixel for
  pixel, the plan view is the sprite's width profile turned in depth.
  Dithered art with no closed outline (the tree wall) falls back to
  light-shades-only flooding, per the methodology doc's boundary rule.
  Sides de-outline like building extrusions so flanks read as canopy
  rather than solid black, and dome caps keep their outline on the rim
  while the interior samples the canopy a couple of rows deeper.
- A `post` standee pool, pinned for the overworld's vertical fence-post
  cell (tiles 14/85 -- across every map the pair appears only as this
  cell). The detector already turns HORIZONTAL fence runs (tile 57) into
  per-post standees, but a vertical run of repeated cells trips its
  scenery-repetition guard and fell to the volume path as a
  fence-textured tower (Viridian's west line, Route 25).
  `post` extracts every CELL as its own cluster -- pooled clustering
  would stand the whole line up as one drawing-tall slab at one depth --
  and classifies pixels the way the detector does (non-white is body)
  rather than by the pinned-prop outline rule, which would strip the
  posts to black skeletons; at the detector's own 6px depth, pinned and
  detected fences look alike.
- Town signs move from the `billboard` pool to a new `signpost` pool: the
  same per-pixel standing slab, but 2 voxels thin instead of 10. A sign
  is a plate on a stick, and the standee body that keeps a TV from
  vanishing at shallow angles read as a solid block of furniture here.
- The ground under a round tree matches the tree's own drawn background
  instead of the map's commonest ground tile. The hull's segmentation
  already knows which pixels are NOT the tree; those pixels are scored
  against every flat ground tile the map places and the closest art
  wins, per template -- so border trees drawn over checker grass stand
  on checker grass even on a map that is mostly pale path (the old
  fallback painted path under every mid-forest tree, which has no flat
  neighbour to vote with). The drawn cast shadow stays out of the score:
  no ground tile carries a shadow, and its darks would drag every match.

- Mesh builds stream in the background. `ChunkMesher` queues per-map
  build jobs and `pump()` -- driven from the pipeline's update -- runs
  them inside a few-millisecond frame budget (`lib/BuildBudget.lua`
  suspends the build coroutine mid-loop when the slice is spent). The
  camera tween holds at flat until the current map's terrain exists, so
  toggling voxel mode shows a handful of flat frames instead of a frozen
  one; neighbours pop in as they finish. Warp fades prefetch the
  destination (the pipeline update ticks while the Transition covers the
  screen, with a wider pump slice), so a door exit lands on terrain that
  is already built.
- Vertex packing goes through FFI into one native buffer
  (`Mesh:setVertices(ByteData)`) instead of a Lua table per vertex --
  the headless table path remains for the pure `geometry()` API and its
  suite.
- Round-tree hulls are carved once per (tileset, art, ground set) and
  kept as stamps -- template plus cell offset, expanded during vertex
  packing -- instead of materialized per-cell quad tables. A route's
  border forest was ~500 quads x hundreds of cells of retained heap.
- Mesh and analysis caches evict down to the live neighbourhood (current
  map + rendered neighbours, plus one set of history so a house
  round-trip keeps the town warm). Evicted meshes are released
  explicitly. Memory over Pallet -> Mt Moon: was ~2.9GB and monotonic,
  now oscillates between ~90 and 200MB.

- `Voxel3D.SHADOW_KX/KZ` are -0.85 / -0.55, from +0.30 / +0.45: the sun
  crosses to the southeast and drops from 62 degrees to 45. The bearing
  leans WEST of northwest on purpose -- a character is drawn as a slab
  leaning away from the camera, which covers the ground due north of its
  feet, so a shadow thrown straight up-screen lands entirely underneath
  the figure casting it and is never seen.
- `Voxel3D.SHADOW_ALPHA` 0.32 -> 0.40, a quarter darker.
- `FACE_SHADE` east 0.78 -> 0.84 and west 0.78 -> 0.72. The two were equal
  because the old sun sat due northwest and they were symmetric about it;
  under a southeastern sun east is a lit flank and west a shaded one.
- A character's shadow lookup runs off the UPRIGHT card the sun saw, not
  the leaning slab the camera sees (`Voxel3D.draw`'s `sunModel`). Casting
  the leaning slab instead would shrink every shadow to nothing as the
  camera flattened toward top-down; looking up with the leaned position
  put each sprite's own card across its front.
- The contact-shadow term in `ChunkMesher` was a one-directional stripe
  keyed to a northwestern sun -- two neighbours, one corner, top faces
  only. It is now the ambient occlusion above.
- The light frustum is fitted to the ground the CAMERA CAN SEE rather than
  to a view-sized box around the focus, and both of its margins are now
  asymmetric -- for opposite reasons. The camera sits south of its focus
  and looks north, so the ground it sees runs far north and barely south;
  the sun sits southeast, so the casters for that ground stand south and
  east of it. Paying for a view-sized box plus caster margin on all four
  sides covered about a third of what was on screen at 75 degrees, and
  overpaid at 15.
- Shadows ease off at the frustum's rim instead of ending on it. Past the
  low rungs the horizon is further out than any box worth paying for, and
  a covered region that simply stops draws a hard line across the middle
  distance where every shadow ends at once.

The Pokemon Center interiors. One `POKECENTER` group in
`data/voxel_heights.lua` plus one new class, and because
VIRIDIAN_POKECENTER places every tile the tileset's other maps use, the
one pin set covers all eleven Centers and the Celadon Hotel.

### Fixed

- The generic town-house tileset (`HOUSE` -- Blue's house, Daisy at her
  table, and eighteen more homes, the schoolhouse and the trashed house
  among them) is now pinned in `data/voxel_heights.lua` the way Red's
  rooms already were: the dining table stops towering as a wall-height
  volume and sits at table height with its front folded upright, stools
  become seat-high boxes that characters sit on, the corner potted
  plants become per-pixel standees instead of texture-smeared box
  stacks, the bookcases get clean capped tops, and the wall band (with
  its window, picture and the schoolhouse blackboard) stays one 16px
  face.  The schoolhouse's open book stands on the pinned tabletop as a
  cutout, and the trashed house's ransacked table corner keeps table
  height.
- Interior door mats lie flat again in Red's and the generic houses.
  Their collision tile is $14, which the engine's stale-cache fallback
  counts as water in every tileset, so the rug recessed into a pond lip;
  a `ground` pin now overrides the water read.

- Stray pixels along map seams: ring props (border-tree hulls) whose
  quad CENTER sat exactly on a neighbour body's edge line escaped the
  strict point-in-rect mask and survived as fragments of otherwise
  dropped trees. Object quads now keep/drop by their full extent,
  boundary inclusive; props straddling the body edge also stay whole
  instead of shedding their outer half.
- The one-step "ledge hop" when crossing a connection into a tree-ringed
  map: the seam step stands the player one cell off the new map, where
  `Map:cellTile` border-extends into the borderBlock -- a raised tile on
  maps ringed with trees. Off-map ground now reads as height 0 (the
  departed neighbour's flat walkway, which is what is actually rendered
  there).

- Cycling palette modes with voxel mode on eventually killed the pipeline
  outright: `attempt to call field 'atlasImageData' (a nil value) --
  disabled for this session`. Nothing brought it back short of a restart.

  `TerrainAtlas` reads three engine seams to animate water and flowers in
  the terrain texture, and this build ships only one of them
  (`defaultAnimatedTiles`). The tile clock, `animFrame`, was already read
  guarded and simply degrades. `atlasImageData` was called straight -- but
  only down the branch where the mod had NOT baked the atlas itself, which
  is why it looked stable until a palette changed. Every mode with no world
  palette for the map (`PaletteFX.pal` answering nil), plus RED++ and any
  trueColor tileset, takes that branch, so the first map with animated
  tiles entered under one of them threw out of `drawWorld` and the engine
  disabled the pass for the session, exactly as it should.

  The seam is now read guarded like its sibling, and when it is absent the
  pixels are recovered rather than given up on. An atlas neither we nor
  RED++ replaced is the tileset art itself, so animation carries on from
  the art on disk. RED++'s per-map bake exists only as a texture --
  `getGbcAtlas` throws its `ImageData` away -- so that one comes back off
  the GPU: the atlas is drawn 1:1 into a canvas and read back, once per map,
  with the pass's own render target captured and restored around it (the
  usual `setCanvas()` would drop the rest of the frame). A driver that
  refuses the readback declines to animate and keeps the static atlas.
  Worst case now costs one animation, never the pipeline.

- Water and flowers did not animate in voxel mode at all, and had not since
  the mode shipped -- a silent one, since the terrain was otherwise correct.

  The tile clock is the third seam, and this build does not export it
  either. Being read guarded, it answered 0 forever instead of throwing,
  which pinned every animated tile at step 0. `animFrame` is a plain local
  in `TileRenderer`, but an upvalue of the exported `tick()`, so the mod now
  reads the real counter through it. That it is the ENGINE's counter is the
  point: the flat tile layer draws from the same number, so toggling voxel
  mode mid-cycle continues the animation rather than restarting it. A build
  that exports `animFrame()` outright is preferred; a build that hides the
  local falls back to wall time in 60Hz steps, which free-runs against the
  2D path but still moves the water.

- Toggling palettes in voxel mode flashed the flat 2D world for a moment on
  every switch.

  `PaletteFX.setMode` reloads the live map to rebuild its atlas, and this
  mod dropped that map's terrain mesh on any `map.reloaded` at all. Mesh
  builds are asynchronous, so the frames between the drop and the first
  rebuilt mesh had no terrain to draw -- and a voxel `drawWorld` with no
  terrain returns nil, which is exactly how the pipeline asks for the 2D
  fallback. The flash was the mod correctly reporting that it had nothing
  to show.

  The geometry was never stale: the mesher reads block layout and tile ids
  and never reads colour, and the palette lives entirely in the texture
  `TerrainAtlas` hands back per frame, keyed by palette and so already
  rebuilt by the next frame. A reload whose reason is `colors` now keeps
  the mesh, and the new palette lands on the diorama already on screen in
  one frame. Every other reload -- warps re-entering a map, hot reload, a
  replaced block -- still drops it.

- Every non-colour palette mode rendered as SGB in voxel mode: GRAY and
  both INVERTED modes came through as the map's blue.

  `paletteFor` hands a pipeline the map's RAW SGB zone palette. The flat
  path runs that through `PaletteFX.effectiveColors` on its way to the
  shade-remap shader, and that call is where the non-colour modes actually
  happen -- OG and OG INV swap in the DMG greys (reversed for the latter),
  CLASSIC swaps in the green set, GBC INV permutes the zone's own shades,
  and only GBC and RED++ pass through. This pass bakes colour into the
  atlas and the sprite sheets ahead of the draw rather than shading at blit
  time, so it never reached that call and painted the raw zone palette in
  every mode.

  Both bakes now run the same transform the shader would have. Terrain and
  characters go through one resolve, so they cannot disagree about what
  mode is on.

- VOID FILL did nothing in voxel mode, in two separate ways.

  **BLACK crashed the build.** The mode is not a block at all --
  `TileRenderer.borderBlockFor` answers `false` for it -- and `Structures`
  added 1 to that `false`. The arithmetic threw, which failed the mesh
  build for every map in the neighbourhood, which left the mode with no
  terrain and dropped it to the flat 2D path entirely. It now builds no
  ring: `tileLookup` answers nil past the body and those keys are never
  written, which the rest of the file already copes with -- every
  neighbour query in it reaches one step outside the analysed range and
  reads nil for its trouble, so an absent cell is the shape "nothing" has
  always had here.

  **WATER changed nothing on screen.** The ring is BAKED INTO THE MESH in
  this mode rather than drawn each frame, and nothing dropped the cache
  when the option moved, so the old ring simply stayed until the meshes
  were invalidated for some other reason. The pipeline's update hook now
  polls `TileRenderer.voidFill` and invalidates on a change -- polled
  rather than hooked because the engine changes it from three places (the
  options row, `applyOptions` on load, `setVoidFill`) and none of them
  announces it, and checked ahead of the active() gate so switching it
  while voxel mode is off still drops what is cached.

  `mods/DRAMATIC_SHAPE/tests/voxel_void_probe.lua` walks the three modes
  and reports the border block, whether the mesh built and whether the
  scene took the 3D path. It deliberately does NOT invalidate the cache
  itself, since doing so would hide the second half of this.

- Water and flowers did not animate. The 2D path animates them by
  OVERDRAWING the animated cells on top of the static tile layer each
  frame, which a single static mesh has no equivalent of -- the geometry
  samples one texture and that is that. So `TerrainAtlas` animates the
  texture instead: a private copy of the atlas whose animated tile slots
  are rewritten when the step advances, which moves every instance of that
  tile across the whole mesh at once. Which is what the Game Boy does in
  the first place (`home/vcopy.asm` rewrites the tile's VRAM bytes); the
  overdraw is the port's workaround for a tile layer, not the original.
  ~130 pixels of work three times a second, on the same
  `TileRenderer.animFrame` clock the 2D path uses, so the two can never
  disagree about which frame they are on.
- The frame files (`flower1..3.png`) are raw grayscale and have to land on
  the colours of the tile they replace, but the two recolour paths do not
  share a rule -- SGB bakes one world palette over everything, RED++ picks
  a palette group per tile graphic. So the shade mapping is LEARNED from
  the atlas: read the static tile's slot in the raw art and in the finished
  atlas side by side and ask what each shade became. Right under both
  without this file knowing which one ran.
- Terrain art was off the pixel grid by up to half a pixel, with one art
  pixel per tile sampled twice and another never at all. A tile is 8
  texels across 8 world pixels -- one texel per pixel exactly -- and
  `ChunkMesher`'s uv inset squeezed that art into a 7-texel sample range
  while the quad still covered 8 world pixels, so it advanced 7/8 of a
  texel per pixel and drifted. The inset exists to stop the rasteriser
  reaching a neighbouring tile along a shared edge, but half a texel was
  fifty times more than that needs: 0.02 is as safe (interpolation error
  is nowhere near it) and drifts 0.25% of a pixel across a whole tile.
  Nothing showed the fault until the voxel wireframe drew the grid those
  pixels were supposed to be sitting on.

### Changed from the pre-mod version

- The level is no longer the mod's to keep. The engine owns the ladder, the
  options rows, the hotkeys, persistence and the TILT exclusion; the mod
  keeps only the camera-angle tween.
- Persistence moved from `save.options.voxel` / `save.options.tiltshift` to
  `save.options.pipelines.voxel` / `.tiltshift`.
- Hotkeys moved from `4`/`9` to `3`/`6`: the fork already uses `4` for
  survey zoom.
- The tilt-shift pass is a declared `worldPresent` stage rather than a call
  spliced into the world draw, so it composes with any world pipeline
  instead of only this one.
- The cut-tree animation now draws in voxel mode; the pre-mod version
  omitted it from the 3D field-effect list.
