Files
DramaticShape 0091e6d93b the sparkle was playing behind the transition wipe
Armed on the occupant-change edge, which happens while the screen is still
mid-wipe -- so the burst spent its whole three-quarter-second life underneath
it. The instrumentation is what settled it: armed=1, quads=450, which is
45 frames times 10 stars, exactly LIFE. It was never missing. It was drawing
where nobody could see it.

Two gates, because the first one was still wrong. Holding the clock until the
scene DREW the side does not help: the battle renders under the wipe for
about a second before the wipe is gone. The burst now waits until the battle
is the top of the stack -- the wipe popped, somebody watching -- and only
then starts. Armed and released are separate moments now, which is what they
always were.

Also, a shiny no longer discolours a PERSON. Both sides can be holding a
trainer pic rather than a Pokemon (the foe's portrait before the send-out,
the player's own back until "Go!"), and the tint was going straight through
it. Shininess is a fact about a Pokemon, not its owner. sideTexture asks the
same two questions it already used to label the finished texture.

And the capture drivers build the party BEFORE pinning the odds. Pokemon.new
is where shininess is decided, so setting odds to 1 first made the player's
own Pikachu shiny too -- which tinted the player's side, which during the
intro is the trainer sprite. That is what "the player trainer sprite seems
discolored" was.

Lineup is now Charizard, Electrode, Vaporeon, Dratini.

Verified by strip: the burst lands in the frames right after the wipe clears,
and the trainer back sprite is its own colour again.
2026-08-08 13:35:01 -04:00

115 lines
4.3 KiB
Lua

-- Driver: five shiny encounters, five outdoor places, paced for a capture.
--
-- POKEPORT_DRIVER=mods/DramaticShapeVoxelMod/tests/shiny_obs.lua \
-- "/c/Program Files/LOVE/lovec.exe" .
--
-- Run from the PROJECT ROOT. Nothing here is a screenshot test -- it is a
-- performance, timed for somebody recording the window.
--
-- Each beat: teleport, let the ground mesh, open a wild battle, let the
-- Pokemon ARRIVE (the wipe, the send-out, the shiny sparkle), then hold
-- three full seconds on it before closing the battle and moving on. The hold
-- is deliberately silent -- no button taps during it -- so the recording is
-- of the Pokemon standing there and not of a text box being clicked through.
--
-- Every encounter is shiny, because the odds are pinned to 1 for the run and
-- the roll still happens where it always does, inside Pokemon.new.
return function(game)
local U = dofile("tests/drivers/util.lua")
local BattleState = require("src.battle.BattleState")
local Pokemon = require("src.pokemon.Pokemon")
local exports = game.mods and game.mods.exports
local lib = exports and exports.DRAMATIC_SHAPE and exports.DRAMATIC_SHAPE.lib
if not lib then
U.log("DRAMATIC_SHAPE is not loaded -- nothing to show")
return
end
local Shiny = lib.require("Shiny")
local OverworldBattle = lib.require("OverworldBattle")
local StadiumInstall = lib.require("StadiumInstall")
-- ------- timing, in frames at 60fps
local SETTLE = 110 -- after a teleport, for the neighbourhood to mesh
local ARRIVE = 70 -- the wipe, then the Pokemon landing on its tile
local HOLD = 180 -- THE THREE SECONDS
local BETWEEN = 45 -- back on the map before the next one opens
-- ------- the models
if not StadiumInstall.ready() then
U.log("building stadium models first...")
StadiumInstall.begin()
local guard = 0
while not StadiumInstall.ready() and guard < 2000 do
for _ = 1, 6 do StadiumInstall.step() end
U.wait(1)
guard = guard + 1
end
end
U.log("stadium ready: " .. tostring(StadiumInstall.ready()))
OverworldBattle.setting:setValue("stadium", game)
-- party FIRST, at ordinary odds: Pokemon.new is where shininess is
-- decided, so pinning the odds before this made the player's own Pikachu
-- shiny -- and that tints the player's side, which during the intro is the
-- trainer sprite. The foe is what these runs are about.
game.save.player.name = "RED"
game.save.party = { Pokemon.new(game.data, "PIKACHU", 50) }
Shiny.setOdds(1) -- from here on: every encounter
-- Five outdoor places, deliberately unalike -- a coast, an open route, a
-- water city, a rocky pass and a wooded shore -- so the recording is five
-- different-looking fights and not the same meadow five times. Each mon is
-- put somewhere its colour has something to sit against.
local RUNS = {
{ "CHARIZARD", 50, "ROUTE_4", 10, 5 },
{ "ELECTRODE", 40, "VIRIDIAN_CITY", 20, 20 },
{ "VAPOREON", 42, "ROUTE_25", 12, 5 },
{ "DRATINI", 30, "ROUTE_3", 10, 5 },
}
-- a beat before the first one, so a recorder that started with the window
-- is not already mid-encounter by the time it is rolling
U.log("starting in 3...")
U.wait(60)
U.log("2...")
U.wait(60)
U.log("1...")
U.wait(60)
for i, r in ipairs(RUNS) do
local species, level, map, cx, cy = r[1], r[2], r[3], r[4], r[5]
if not game.data.pokemon[species] then
U.log(("skip %s -- not in this dataset"):format(species))
elseif not game.data.maps[map] then
U.log(("skip %s -- no map %s"):format(species, map))
else
U.teleport(game, map, cx, cy, "down")
U.wait(SETTLE)
local battle = BattleState.newWild(game, species, level)
battle.onFinish = function() end
game.overworld:pushBattle(battle)
local mon = battle.enemy and battle.enemy.mon
U.log(("%d/4 %-10s %-14s shiny=%s")
:format(i, species, map, tostring(Shiny.isShiny(mon))))
-- the arrival, then three seconds of nothing but the Pokemon
U.wait(ARRIVE)
U.wait(HOLD)
-- close the battle and go back to the map
while game.stack:top() and game.stack:top() ~= game.overworld do
game.stack:pop()
end
U.wait(BETWEEN)
end
end
Shiny.setOdds(8192)
U.log("done -- five encounters")
end