mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-12 09:10:49 +02:00
shiny Pokemon, on by default
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>
This commit is contained in:
+74
-20
@@ -62,13 +62,22 @@ local floor = math.floor
|
||||
StadiumPack.CACHE_DIR = "dramatic_shape/stadium"
|
||||
StadiumPack.DIR = "assets/stadium"
|
||||
|
||||
local function readPack(species)
|
||||
-- The shiny variant sits beside its species as NNNs.dsm -- the same DSM3,
|
||||
-- written by the same writer, differing only in its texture bytes. See the
|
||||
-- note over StadiumInstall's writePack for why it is a separate file and not
|
||||
-- a second block in the pack.
|
||||
local function packName(dir, species, shiny)
|
||||
return shiny and ("%s/%03ds.dsm"):format(dir, species)
|
||||
or ("%s/%03d.dsm"):format(dir, species)
|
||||
end
|
||||
|
||||
local function readPack(species, shiny)
|
||||
-- The cache only counts when StadiumInstall's marker says it is a
|
||||
-- complete, CURRENT build -- an old cache (a rev the extractor has since
|
||||
-- fixed, a format that moved) must not shadow a fresh shipped set, and a
|
||||
-- half-written folder must not be read at all. Required lazily: Install
|
||||
-- requires this module at load, so the reverse edge cannot be taken then.
|
||||
local rel = ("%s/%03d.dsm"):format(StadiumPack.CACHE_DIR, species)
|
||||
local rel = packName(StadiumPack.CACHE_DIR, species, shiny)
|
||||
if love and love.filesystem and love.filesystem.getInfo
|
||||
and V.require("StadiumInstall").ready() then
|
||||
local okInfo, info = pcall(love.filesystem.getInfo, rel, "file")
|
||||
@@ -80,7 +89,7 @@ local function readPack(species)
|
||||
local mod = V.mod
|
||||
if not (mod and mod.read) then return nil end
|
||||
local ok, bytes = pcall(mod.read, mod,
|
||||
("%s/%03d.dsm"):format(StadiumPack.DIR, species))
|
||||
packName(StadiumPack.DIR, species, shiny))
|
||||
if ok and type(bytes) == "string" and #bytes > 4 then return bytes end
|
||||
return nil
|
||||
end
|
||||
@@ -474,8 +483,27 @@ end
|
||||
|
||||
-- ------- the cache
|
||||
|
||||
local cache = {} -- species -> model
|
||||
local order = {} -- species, least recently used first
|
||||
local cache = {} -- cache key -> model
|
||||
local order = {} -- cache key, least recently used first
|
||||
|
||||
-- The key is the species for a normal model and species+SHINY for a shiny
|
||||
-- one, so the two are separate entries that cannot overwrite each other.
|
||||
--
|
||||
-- They MUST be separate. The model table carries the decoded textures and
|
||||
-- the lazily-built love Images hanging off them, and it is deliberately
|
||||
-- shared by both sides and both VR eyes -- so a single entry per species
|
||||
-- would mean a shiny Rattata and an ordinary one in the same fight fighting
|
||||
-- over one texture set, and whichever loaded last would colour both.
|
||||
local SHINY = 1000 -- clear of the 1..151 dex range
|
||||
|
||||
local function cacheKey(species, shiny)
|
||||
if not species then return nil end
|
||||
return shiny and (species + SHINY) or species
|
||||
end
|
||||
|
||||
-- Four, because a mirror match between a shiny and a normal of the SAME
|
||||
-- species is now two distinct models rather than one shared table, and both
|
||||
-- sides must survive a fifth species being called out mid-fight. See keep().
|
||||
StadiumPack.KEEP = 4
|
||||
|
||||
local function touch(species)
|
||||
@@ -520,30 +548,48 @@ end
|
||||
-- So the mode says, every frame, which two species are actually standing
|
||||
-- there (see Stadium.update). With KEEP at 4 and two sides, the two in use
|
||||
-- are always the two most recent and cannot reach the front of the queue.
|
||||
function StadiumPack.keep(species)
|
||||
if species and cache[species] then touch(species) end
|
||||
function StadiumPack.keep(species, shiny)
|
||||
local key = cacheKey(species, shiny)
|
||||
if key and cache[key] then touch(key) end
|
||||
end
|
||||
|
||||
-- Whether a pack for this species is on disk at all. Cheap enough to ask
|
||||
-- before a battle commits to the mode, and the honest test: a mod
|
||||
-- installed without its assets folder must decline rather than error.
|
||||
function StadiumPack.available(species)
|
||||
if cache[species] then return true end
|
||||
return readPack(species) ~= nil
|
||||
--
|
||||
-- Asked WITHOUT the shiny flag on purpose by the callers that gate the mode:
|
||||
-- whether a species can be modelled at all is a question about its normal
|
||||
-- pack. A missing shiny variant does not disqualify the species, it just
|
||||
-- means that one mon is drawn in its ordinary colours.
|
||||
function StadiumPack.available(species, shiny)
|
||||
local key = cacheKey(species, shiny)
|
||||
if key and cache[key] then return true end
|
||||
return readPack(species, shiny) ~= nil
|
||||
end
|
||||
|
||||
-- The model for a National Dex number (1..151), or nil.
|
||||
function StadiumPack.load(species)
|
||||
--
|
||||
-- `shiny` selects the recoloured variant. When a species has no shiny pack
|
||||
-- -- an install from before rev 3, a recolour that failed at extraction, a
|
||||
-- species we have no colours for -- this FALLS BACK to the normal model
|
||||
-- rather than returning nil. The alternative is a shiny Pokemon that drops
|
||||
-- to a flat 2D pic while its ordinary twin stands in 3D, which reads as a
|
||||
-- bug; wrong colours read as a mod that has not finished installing.
|
||||
function StadiumPack.load(species, shiny)
|
||||
if not (species and species >= 1 and species <= 151) then return nil end
|
||||
local hit = cache[species]
|
||||
local key = cacheKey(species, shiny)
|
||||
local hit = cache[key]
|
||||
if hit ~= nil then
|
||||
touch(species)
|
||||
touch(key)
|
||||
return hit or nil
|
||||
end
|
||||
|
||||
local bytes = readPack(species)
|
||||
local bytes = readPack(species, shiny)
|
||||
if not bytes and shiny then
|
||||
return StadiumPack.load(species, false)
|
||||
end
|
||||
if not bytes then
|
||||
cache[species] = false
|
||||
cache[key] = false
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -562,14 +608,22 @@ function StadiumPack.load(species)
|
||||
return m
|
||||
end)
|
||||
if not ok then
|
||||
V.mod.log:warn("stadium: %03d.dsm did not read: %s -- that Pokemon "
|
||||
.. "falls back to its flat pic", species, tostring(model))
|
||||
cache[species] = false
|
||||
V.mod.log:warn("stadium: %s did not read: %s -- that Pokemon "
|
||||
.. "falls back to its flat pic",
|
||||
packName("", species, shiny):sub(2), tostring(model))
|
||||
-- A corrupt SHINY pack must not cost the species its model: fall back to
|
||||
-- the normal one, exactly as a missing file does above.
|
||||
if shiny then
|
||||
cache[key] = false
|
||||
return StadiumPack.load(species, false)
|
||||
end
|
||||
cache[key] = false
|
||||
return nil
|
||||
end
|
||||
|
||||
cache[species] = model
|
||||
touch(species)
|
||||
model.shiny = shiny and true or nil
|
||||
cache[key] = model
|
||||
touch(key)
|
||||
return model
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user