Shiny Pokemon: derived from DVs via the engine's own Stats.isShiny, models
recoloured as part of the Stadium extraction, flat art tinted, a sparkle on
arrival and a star on the status page.
Verified on the merged tree rather than only on the branch, since
shadow-toggle moved underneath it (the new menus work): the mod loads clean,
tests/shiny_test passes 49, and the extraction still reproduces all 151
packs byte-for-byte against the Python oracle with 151 shiny variants
alongside.
Gen 1 has no shininess of its own, but it has the four DVs Gen 2 reads to
decide it -- and the engine already ships that reading (Stats.isShiny, its
own comment calling it "the RBY virtual shiny", allowlisted for mods
precisely so an indicator mod can call it). Nothing new is stored on a
Pokemon and nothing migrates: every save already contains the answer, and
this starts drawing it. Random DVs land on the pattern 1 in 8192, which is
the classic rate and the default the odds dial ships at.
Deriving rather than storing is what makes it survive a save, a box, a
trade and an evolution with no second copy of the truth to drift. mon.shiny
is a cache written from the DVs, never read as the source.
The roll goes in Pokemon.new -- every wild, gift, starter and traded mon is
built there, and it is before the battle bakes its sprite, which
battle.started is already too late for. It draws from the mod's own random
stream so installing this does not shift the sequence damage rolls and
encounter slots come out of. Trainers stay ordinary by themselves: the
engine pins their DVs, as the real games do.
The models are genuinely recoloured, as part of the extraction. Each
species is decoded once, packed as usual, then recoloured and packed again
as NNNs.dsm. The colours are Stadium's own HSL slide (hue in degrees,
saturation and lightness on a -8..+8 scale at 12.5% a step); five species
carry an explicit colour table instead, because Stadium gives them a real
alternate texture that no single slide reproduces -- Jigglypuff's body must
stay pink while its irises rotate to green.
Extraction is the right moment because StadiumFx's generated frames are
still marked there and the packer drops the marker: it is the last point a
flame is distinguishable from a hide. A shiny Charizard has a shiny hide
and an ordinary fire. The normal packs are written BEFORE the recolour, so
they come out byte-identical and stadium_extract_test still diffs all 151
against the Python oracle unchanged -- no format change, no DSM4, no second
implementation to keep in step. REV goes to 3 so an existing cache rebuilds.
Flat art is tinted instead, because the engine bakes a species palette into
a cache with no notion of which individual is drawn. The tint comes from
that species' own slide rather than a generic gold. A multiply can only
darken, so species whose shiny is lighter read quieter there than on the
model; the status page's star is the mode-proof mark.
Tests: 58 assertions in tests/shiny_test.lua, including the colour
transform against 640 real colour pairs lifted from the verified texture
set, the DV model, the read side, and the end-to-end through the engine's
own constructor. stadium_extract_test gains --mod (worktrees have neither
the ROM nor the packs, both gitignored) and now also checks that every
shiny pack is the same length as its twin and actually differs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cut, the tree regrowth on re-entry and the card-key door stamps all write
the block layer directly without emitting world.block_replaced, so
meshes derived from that layer went stale unannounced. An earlier cut of
this work made the engine announce each one -- the wrong place: it edits
the game for one mod's benefit, and every future path that writes a
block has to remember to do the same.
They all funnel through one choke point, so wrap Map:setBlock from the
mod instead. Map is a plain metatable shared by every instance, so this
covers paths written after this mod too. The write is read back rather
than trusted: setBlock ignores an out-of-bounds write, and a stamp that
rewrites a block with the value it already held is not an edit and must
not throw the mesh away.
src/world/OverworldController.lua is reverted to stock.
Adds `when_above` conditional pins, resolved per position in
TileShape.at: one graphic can mean two things (the gates' $32 is both
wall base course and counter front), and a flat pin has to pick one.
Gates get half-height counters and level walls.
Pokemon Tower gains a buildings entry -- it is the drawing the map edge
cuts off, sealed on the north -- and the Indigo Plateau statues are
built like the gym statues.
Fixes: grass stands one full-height clump per tile instead of two
half-cut stubs at different depths; ledge pillars drop to ledge height;
a prop only rides furniture when its own cell is blocked (chairs were
being lifted onto tables); and the caves had water and rock pinned
backwards, which stood the Seafoam sea up as rock and cut trenches
through Mt Moon. No cave tile is below the datum now.
Extends the shape profile from ten tilesets to twenty-three -- 1,190
pinned tiles over ~130 maps -- surveyed against the standard the
finished interiors set: one 16px wall band carrying its built-ins,
half-cell counters, bookcase-collapsed shelving, thin-pool plant
standees, objects riding the furniture they are drawn above.
The recurring failures were rooms sunk into ponds by the three
stale-cache water ids (collision is per cell, so one trapped tile sank
its neighbours too), counters and cave edges towering to 32-48px,
racks fusing into deep monoliths, and anything in a walkable cell --
stools, tables, staircases, gravestone arches -- vanishing into the
floor.
Also fixes the door fold overwriting profile pins: it replaced the
resolved shape unconditionally, so stair pins on tiles their tileset
lists in doorTiles (Celadon Mansion, Pokemon Mansion 3F) silently did
nothing.
Flowers stand as 1px animated cutouts on their own pulled mesh (own-cell
flowers hide behind the walker, the cell south still overdraws feet).
Cuttable bushes, gym statues (single-block plinth) and Lt. Surge's trash
cans stand as 5-voxel prop cutouts; prop_ground names the tile painted
beneath. Block edits refresh meshes in place instead of blinking to 2D.