mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-20 12:40:21 +02:00
ok
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
-- Case 1 (level path, cancelable): open EvolutionState directly, wait a
|
||||
-- few frames into the flash (t well under FLASH_FRAMES=220), hold B, and
|
||||
-- assert the mon stays CATERPIE with "stopped evolving" text on screen.
|
||||
-- Case 1b: after cancel, checkParty with no level-ups must not re-offer;
|
||||
-- a subsequent level-up set must offer again (EvolveAfterBattle parity).
|
||||
-- Case 2 (control): let the flash run to completion with no input and
|
||||
-- assert the mon becomes METAPOD with the "Congratulations!" text.
|
||||
--
|
||||
@@ -103,6 +105,27 @@ return function(game)
|
||||
assert(done1, "cancel onDone never fired")
|
||||
assert(mon.species == "CATERPIE", "species changed after cancel tail")
|
||||
|
||||
-- === Case 1b: after B-cancel, checkParty without a level-up must not
|
||||
-- re-offer (regression: cancelled mons stuck at threshold tried again
|
||||
-- after every later battle, even non-participants). ===
|
||||
local nQuiet = Evolution.checkParty(game, nil, {})
|
||||
assert(nQuiet == 0, "checkParty with no level-ups re-offered after cancel")
|
||||
assert(not evoTop(), "EvolutionState opened after quiet afterBattle")
|
||||
assert(mon.species == "CATERPIE", "species changed on quiet checkParty")
|
||||
|
||||
local nLevel = Evolution.checkParty(game, nil, { [mon] = true })
|
||||
assert(nLevel == 1, "checkParty after a real level-up should offer once")
|
||||
if not waitFor(evoTop, 300) then
|
||||
error("EvolutionState never opened after level-up re-offer")
|
||||
end
|
||||
U.wait(20)
|
||||
U.hold(game, "b", 20) -- cancel so case 2 stays independent
|
||||
if not waitFor(function() return not evoTop() end, 240) then
|
||||
error("level-up re-offer did not abort on B")
|
||||
end
|
||||
mashUntil(function() return not findText("stopped evolving") end, 80)
|
||||
assert(mon.species == "CATERPIE", "species changed after re-offer cancel")
|
||||
|
||||
-- === Case 2 (control): no input -> evolution completes ===
|
||||
local mon2 = Pokemon.new(game.data, "CATERPIE", 7)
|
||||
table.insert(game.save.party, 1, mon2)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
-- Driver: party list must not sit under the bottom message box (#262).
|
||||
-- Gen1 (pokered engine/menus/party_menu.asm RedrawPartyMenu_) places the
|
||||
-- first name at hlcoord 3, 0 and advances each entry by 2*SCREEN_WIDTH.
|
||||
-- The recomp used y = (i-1)*16 + 12, which shoved a full party down so
|
||||
-- slot 6 was clipped by the "Choose a POKéMON." text box (tile row 12).
|
||||
-- This driver opens a 6-mon party menu, screenshots it, and asserts
|
||||
-- PartyMenu.entryY keeps slot 6's HP row above y=96.
|
||||
-- POKEPORT_DRIVER=tests/drivers/party_bug262_offset_test.lua \
|
||||
-- POKEPORT_IDENTITY=bug262 POKEPORT_TOUCH=0 POKEPORT_VERSION=red love .
|
||||
return function(game)
|
||||
local U = dofile("tests/drivers/util.lua")
|
||||
local DIR = os.getenv("SHOT_DIR") or "/tmp/shots"
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
local Screens = require("src.ui.Screens")
|
||||
local PartyMenu = require("src.ui.PartyMenu")
|
||||
|
||||
local pass, fail = 0, 0
|
||||
local function check(label, ok)
|
||||
if ok then pass = pass + 1; U.log("PASS", label)
|
||||
else fail = fail + 1; U.log("FAIL", label) end
|
||||
end
|
||||
|
||||
game.save.party = {
|
||||
Pokemon.new(game.data, "CHARMANDER", 12),
|
||||
Pokemon.new(game.data, "PARAS", 10),
|
||||
Pokemon.new(game.data, "DIGLETT", 15),
|
||||
Pokemon.new(game.data, "FARFETCHD", 5),
|
||||
Pokemon.new(game.data, "GASTLY", 20),
|
||||
Pokemon.new(game.data, "SANDSHREW", 22),
|
||||
}
|
||||
|
||||
U.teleport(game, "ROUTE_1", 5, 5, "down")
|
||||
Screens.push(game, "PartyMenu")
|
||||
U.wait(8)
|
||||
U.shot(game, DIR .. "/party_bug262_offset.png")
|
||||
|
||||
local pm = game.stack:top()
|
||||
check("top is PartyMenu", getmetatable(pm) == PartyMenu)
|
||||
check("entryY(1) == 0", PartyMenu.entryY(1) == 0)
|
||||
check("entryY(6) == 80", PartyMenu.entryY(6) == 80)
|
||||
check("slot 6 HP row < message box y=96", PartyMenu.entryY(6) + 8 < 96)
|
||||
check("field message == 'Choose a POKéMON.'",
|
||||
pm and pm.bottomMessage and pm:bottomMessage() == "Choose a POKéMON.")
|
||||
|
||||
U.log(("RESULT pass=%d fail=%d"):format(pass, fail))
|
||||
end
|
||||
@@ -512,6 +512,46 @@ do
|
||||
"the registered method's own gate holds")
|
||||
end
|
||||
|
||||
-- ------- checkParty only offers evolutions for mons that leveled (#213)
|
||||
|
||||
do
|
||||
local caterpie = Pokemon.new(Data, "CATERPIE", 7) -- at LEVEL evo threshold
|
||||
local bench = Pokemon.new(Data, "PIDGEY", 5)
|
||||
local save = SaveData.newGame()
|
||||
save.party = { caterpie, bench }
|
||||
local stack = { states = {} }
|
||||
function stack:push(state) self.states[#self.states + 1] = state end
|
||||
function stack:pop() return table.remove(self.states) end
|
||||
function stack:top() return self.states[#self.states] end
|
||||
local game = { data = Data, save = save, stack = stack }
|
||||
|
||||
local offered = {}
|
||||
local origEvolve = Evolution.evolve
|
||||
Evolution.evolve = function(_, mon, to, onDone)
|
||||
offered[#offered + 1] = { mon = mon, to = to }
|
||||
if onDone then onDone() end
|
||||
end
|
||||
|
||||
check(Evolution.checkParty(game) == 0,
|
||||
"checkParty with no leveledUp set offers nothing")
|
||||
check(#offered == 0, "no evolve calls without leveledUp")
|
||||
|
||||
check(Evolution.checkParty(game, nil, {}) == 0,
|
||||
"checkParty with empty leveledUp offers nothing")
|
||||
check(#offered == 0, "cancel-at-threshold does not re-offer next battle")
|
||||
|
||||
check(Evolution.checkParty(game, nil, { [bench] = true }) == 0,
|
||||
"a leveled mon with no pending evo is skipped")
|
||||
check(#offered == 0, "bench level-up alone does not evolve CATERPIE")
|
||||
|
||||
check(Evolution.checkParty(game, nil, { [caterpie] = true }) == 1,
|
||||
"checkParty offers evo for the mon that leveled")
|
||||
check(#offered == 1 and offered[1].mon == caterpie and offered[1].to == "METAPOD",
|
||||
"the leveled CATERPIE is queued for METAPOD")
|
||||
|
||||
Evolution.evolve = origEvolve
|
||||
end
|
||||
|
||||
-- ------- the rare-candy flow runs the hook-wrapped dispatch
|
||||
|
||||
do
|
||||
|
||||
+47
-18
@@ -1845,9 +1845,12 @@ do
|
||||
eq(cam.y, 160 - (288 / 2 - 8), "wide view keeps player centered y")
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------- dpi fit scale (#87)
|
||||
-- ---------------------------------------------------------------- dpi fit scale (#87 / #208)
|
||||
-- Android density is often non-integer; fitScale must use framebuffer
|
||||
-- pixels so each GB pixel maps to a whole number of physical pixels.
|
||||
-- When axis unit→pixel ratios diverge (truncated highdpi sizes, dual-screen
|
||||
-- / forced-rotation mismatch), draw scales must be anisotropic so X and Y
|
||||
-- both land on the same integer physical count (square pixels).
|
||||
do
|
||||
local Renderer = require("src.render.Renderer")
|
||||
local Zoom = require("src.render.Zoom")
|
||||
@@ -1873,27 +1876,46 @@ do
|
||||
local vw, vh = Renderer:worldViewSize()
|
||||
check(vw % 2 == 0 and vh % 2 == 0, "world view sizes are even (integer camera)")
|
||||
-- ceil(pw/Sp)=ceil(1920/7)=275 → even 276; ceil(1080/7)=155 → even 156
|
||||
eq(vw, 276, "world fill width covers the unit window at pixel scale 7")
|
||||
eq(vh, 156, "world fill height covers the unit window at pixel scale 7")
|
||||
eq(vw, 276, "world fill width covers the drawable at pixel scale 7")
|
||||
eq(vh, 156, "world fill height covers the drawable at pixel scale 7")
|
||||
|
||||
-- #208: on a dual-screen surface (AYN Thor, forced landscape) LOVE can
|
||||
-- report drawable == unit size (pw/ww == 1) while its real coordinate->pixel
|
||||
-- transform is 1.5. fitScale stays keyed off the drawable pixel size, but
|
||||
-- the unit<->pixel conversion must use the REAL getDPIScale, not pw/ww, so
|
||||
-- each GB pixel still lands on a WHOLE number of physical pixels (square) --
|
||||
-- not the stretched 6-vs-9-device-px fractional pixels the reporter saw.
|
||||
-- Before the fix drawScale()==Sp/(pw/ww)==7 and 7*1.5==10.5 px/GB px.
|
||||
-- #208: axis DPI mismatch. LOVE's projection uses pw/ww on X and ph/wh
|
||||
-- on Y independently; getDPIScale() is only ph/wh. A single-dpi draw
|
||||
-- scale makes one axis fractional → stretched / non-square GB pixels
|
||||
-- (fonts especially). Truncated density-2.75 unit sizes on 1080p:
|
||||
local dpiX = 1920 / 698
|
||||
local dpiY = 1080 / 392
|
||||
Zoom.reset()
|
||||
g.getDimensions = function() return 1920, 1080 end
|
||||
g.getDimensions = function() return 698, 392 end
|
||||
g.getPixelDimensions = function() return 1920, 1080 end
|
||||
g.getDPIScale = function() return 1.5 end
|
||||
g.getDPIScale = function() return dpiY end -- real love.graphics.getDPIScale
|
||||
eq(Renderer:fitScale(), 7,
|
||||
"#208 divergent DPI: fitScale still 7 from drawable pixels")
|
||||
local physical = Renderer:drawScale() * g.getDPIScale()
|
||||
local rounded = math.floor(physical + 0.5)
|
||||
check(math.abs(physical - rounded) < 1e-9,
|
||||
"#208 GB pixel lands on a whole number of physical pixels (square)")
|
||||
eq(rounded, 7, "#208 each GB pixel covers exactly fitScale (7) physical px")
|
||||
"#208 anisotropic DPI: fitScale still 7 from drawable pixels")
|
||||
local physX = Renderer:drawScaleX() * dpiX
|
||||
local physY = Renderer:drawScaleY() * dpiY
|
||||
check(math.abs(physX - 7) < 1e-9,
|
||||
"#208 GB pixel covers exactly fitScale (7) physical px on X")
|
||||
check(math.abs(physY - 7) < 1e-9,
|
||||
"#208 GB pixel covers exactly fitScale (7) physical px on Y")
|
||||
check(math.abs(physX - physY) < 1e-9,
|
||||
"#208 physical X/Y match (square pixels)")
|
||||
-- single-dpi (getDPIScale-only) path would stretch X:
|
||||
-- 7 * dpiX / dpiY ≈ 6.989 ≠ 7
|
||||
check(math.abs(7 * dpiX / dpiY - 7) > 1e-6,
|
||||
"#208 fixture really has dpiX ≠ dpiY (guards the regression)")
|
||||
|
||||
-- #208 severe case: unit aspect swapped vs drawable (forced-rotation /
|
||||
-- dual-screen mis-report). Uniform dpi would heavily stretch one axis;
|
||||
-- anisotropic scales keep 7x7 physical GB pixels.
|
||||
Zoom.reset()
|
||||
g.getDimensions = function() return 1080, 1920 end
|
||||
g.getPixelDimensions = function() return 1920, 1080 end
|
||||
g.getDPIScale = function() return 1080 / 1920 end
|
||||
eq(Renderer:fitScale(), 7, "#208 swapped-aspect: fitScale from drawable")
|
||||
physX = Renderer:drawScaleX() * (1920 / 1080)
|
||||
physY = Renderer:drawScaleY() * (1080 / 1920)
|
||||
check(math.abs(physX - 7) < 1e-9 and math.abs(physY - 7) < 1e-9,
|
||||
"#208 swapped-aspect still yields square 7x7 physical GB pixels")
|
||||
|
||||
-- missing pixel API falls back to getDimensions (headless / old stub)
|
||||
g.getPixelDimensions = nil
|
||||
@@ -3003,6 +3025,13 @@ eq(frameFor("WATER", true), 3, "WATER animates to the walk frame")
|
||||
-- icons outside the table keep the old uniform fallback
|
||||
eq(frameFor("BALL", true, 96), 3, "fallback: 16x96 sheet animates to 3")
|
||||
eq(frameFor("HELIX", true, 32), 1, "fallback: 16x32 sheet animates to 1")
|
||||
|
||||
-- party list Y (pokered party_menu.asm hlcoord 3, 0 + 2-row stride). #262
|
||||
-- A +12 offset pushed slot 6 into the bottom message box at tile row 12.
|
||||
local entryY = require("src.ui.PartyMenu").entryY
|
||||
eq(entryY(1), 0, "party slot 1 name row is y=0")
|
||||
eq(entryY(6), 80, "party slot 6 name row is y=80")
|
||||
check(entryY(6) + 8 < 96, "slot 6 HP row stays above the message box (y=96)")
|
||||
end
|
||||
|
||||
-- ---------------------------------------------- suite discovery
|
||||
|
||||
Reference in New Issue
Block a user