Make Mon.syncIdentity's shiny recompute monotonic

syncIdentity unconditionally recomputed mon.shiny from the mon's DVs,
overwriting whatever was there. It is wired into refreshStats, which
SummaryMenu.new calls on every menu open, so a forced shiny -- Mon.new's
opts.shiny path, DVs that do not themselves read as shiny -- got
un-shinied the moment the summary screen opened, even though opts.shiny
already wins over shiny.roll at construction for exactly this case (a
scripted shiny is the cart overriding the roll, not a roll to be
hooked).

mon.shiny now only ever gets PROMOTED by the DV check, never demoted:
`mon.shiny or Mon.isShiny(...)`. A naturally shiny mon and a plain one
are unaffected -- the DV check still runs and still decides the first
time -- and a mon whose DVs are edited to justify shininess later still
promotes normally; only an already-true shiny stops being able to flip
back to false on a later refresh.
This commit is contained in:
sanjinpepic
2026-08-16 20:31:46 +02:00
parent d03d2af5f8
commit 70b9def0b0
2 changed files with 33 additions and 1 deletions
+7 -1
View File
@@ -112,7 +112,13 @@ function Mon.syncIdentity(mon, data)
if mon.dvs then if mon.dvs then
mon.gender = Mon.gender(def, mon.dvs, mon.gender = Mon.gender(def, mon.dvs,
{ species = mon.species, level = mon.level }) { species = mon.species, level = mon.level })
mon.shiny = Mon.isShiny(mon.dvs, -- shiny is monotonic once true, the same as opts.shiny winning over
-- shiny.roll at Mon.new: a forced shiny (the scripted-shiny path, DVs
-- that do not themselves read as shiny) must not un-shiny the moment
-- this runs again, and it runs on every SummaryMenu open via
-- refreshStats. A mon not already shiny still promotes normally if
-- its DVs justify it, e.g. after an edit.
mon.shiny = mon.shiny or Mon.isShiny(mon.dvs,
{ species = mon.species, def = def, level = mon.level }) { species = mon.species, def = def, level = mon.level })
if mon.species == Unown.SPECIES then if mon.species == Unown.SPECIES then
mon.unownLetter = Unown.letterFromDVs(mon.dvs) mon.unownLetter = Unown.letterFromDVs(mon.dvs)
+26
View File
@@ -468,6 +468,32 @@ do
T.eq(forced.shiny, true, "opts.shiny still wins over shiny.roll") T.eq(forced.shiny, true, "opts.shiny still wins over shiny.roll")
end) end)
-- Mon.syncIdentity (wired into refreshStats, which SummaryMenu.new calls
-- on every menu open) used to recompute mon.shiny from DVs unconditionally,
-- so opening the summary screen on a forced shiny -- one whose DVs do not
-- happen to match the natural pattern -- un-shinied it the moment the menu
-- opened. shiny is monotonic once true: a natural roll or a forced one
-- both stay shiny through any later refresh, the way opts.shiny already
-- wins at construction.
do
local forced = Mon.new(DATA, "SEEDMON", 5, { dvs = plainDvs, shiny = true })
T.eq(forced.shiny, true, "still shiny straight out of Mon.new")
Mon.syncIdentity(forced, DATA)
T.eq(forced.shiny, true, "syncIdentity does not clobber a forced shiny")
Mon.refreshStats(forced, DATA)
T.eq(forced.shiny, true,
"refreshStats (SummaryMenu.new's call) does not either")
-- the natural cases are unaffected: DVs that read shiny stay shiny,
-- DVs that do not stay plain
local natural = Mon.new(DATA, "SEEDMON", 5, { dvs = shinyDvs })
Mon.syncIdentity(natural, DATA)
T.eq(natural.shiny, true, "a naturally shiny mon still reads shiny")
local plain = Mon.new(DATA, "SEEDMON", 5, { dvs = plainDvs })
Mon.syncIdentity(plain, DATA)
T.eq(plain.shiny, false, "a plain mon is not promoted to shiny")
end
local genderCtx local genderCtx
withHook("gender.roll", function(nextFn, ctx) withHook("gender.roll", function(nextFn, ctx)
genderCtx = ctx genderCtx = ctx