Files
gen1recomp/tests/game3_object_subpriority_draworder_test.lua
T
Shane McGovern 41935aa2a2 fix(game3): wave 2 - 118 review fixes, seam work, and the gate to 282 suites
Second batch of the Gen 3 review work: the review lanes closed their queues
and the test gate grew with them.

Review census: 139 routed findings -- 118 fixed, 14 invalid (the report had
Gen 4+ semantics in more than one place), 6 struck as stale after
re-verification, 1 re-routed. Every fix carries its own pret citation in
docs/game3/review-v3-triage.md; the E10 opcode closure is itemised in
docs/game3/e10-opcode-spec.md (18 ops wired, 2 reclassified to the seam class).

Representative ROM-grounded changes:
- The POKéMON start-menu entry is now gated on FLAG_SYS_POKEMON_GET the way
  retail does it (pokefirered/src/start_menu.c:217-218, flag 0x828).
- Money ops read their amount raw and gate the change on the disable byte
  (pokefirered/src/scrcmd.c:1798-1830, pokefirered/asm/macros/event.inc:1166-1186);
  random and the warp family VarGet theirs
  (pokefirered/src/scrcmd.c:455-461, :719-731).
- The HM table matches FRLG: there is no Whirlpool HM
  (pokefirered/include/constants/items.h:411-418).
- The day-care party-full guard follows src/daycare.c:525, :1081.
- Knock Off keeps its battle-scoped send-out mask
  (pokefirered/src/battle_script_commands.c:2730-2752, :4489), carried from
  the first batch.

Structural work: the I6/I9/J1 architecture items landed as seams (profile
selector, capability flags, font provider, virtual-object layer), the adopted
footprint register keeps a KEEP verdict, the quantizer target is met, and the
30 drain items plus carves 1-4 are folded in.

Tests: T6 now runs 282 top-level suites (273 + 9 new), the re-sweep ends at
267 pass / 0 fail, the engine tier is 634 suites, modkit 37, and the full
gate ran green twice with T3 active on an imported Red cache. Documented
skips are the lua5.4 oversize-save oracle where lua5.4 is absent and the
config-partials listed in docs/game3/game3-artifact-conversions-v3.md.

Docs shipped: review-v3-triage.md, e10-opcode-spec.md, rse-seams.md,
game3-suite-sweep-v113.md, game3-artifact-conversions-v3.md,
test-baseline-v3.md (plus the first-batch docs already on the branch).
2026-09-22 18:23:06 +01:00

145 lines
6.2 KiB
Lua

-- tests/game3_object_subpriority_draworder_test.lua
-- Seam 7 consumer-half contract (docs/game3/e10-opcode-spec.md §5.8):
-- 1. set -> Objects.setSubpriority stores {fixedPriority, subpriority}; the
-- script op applies the bias (prett scrcmd.c:1130 `priority + 83`).
-- 2. draw -> field_view.applyDrawOrder captures the dynamic class once
-- (fixedClass), keeps it across elevation changes, and sorts the frozen
-- actor by subpriority instead of pixel-Y.
-- 3. reset-> the record clears, no stale fixedClass survives, and the
-- elevation-driven dynamic path resumes exactly as before.
-- 4. an unfrozen neighbour still follows ELEVATION_TO_PRIORITY.
--
-- pret anchors (clone /Users/shanemcgovern/dev/pokefirered, HEAD c75f35230):
-- src/event_object_movement.c:8379-8387 UpdateObjectEventElevationAndPriority
-- -> `if (objEvent->fixedPriority) return;`
-- src/event_object_movement.c:8424-8429 ObjectEventUpdateSubpriority
-- -> same early return (gates SetObjectSubpriorityByElevation :8414-8422)
-- src/event_object_movement.c:2089-2101 SetObjectSubpriority
-- -> `fixedPriority = TRUE; sprite->subpriority = subpriority`
-- src/event_object_movement.c:2104-2116 ResetObjectSubpriority
-- -> `fixedPriority = FALSE` (re-enables the dynamic path; does not restore)
-- src/scrcmd.c:1122-1140 set/resetobjectsubpriority (`priority + 83`).
local Objects = require("src.core.game3.objects")
local FieldView = require("src.core.game3.field_view")
local failed = 0
local function check(cond, msg)
if cond then
print("[ok] " .. msg)
else
failed = failed + 1
print("[FAIL] " .. msg)
end
end
local function done()
if failed > 0 then
print("[test] FAILED " .. failed)
os.exit(1)
end
print("[test] all passed")
os.exit(0)
end
-- Current-map live EventObject in the store the producer resolves from.
local eo = {
localId = 3,
def = { x = 4, y = 4, elevation = 0 },
cellX = 4, cellY = 4, px = 4 * 16, py = 4 * 16,
elevation = 0, -- class 2 (ground) per ELEVATION_TO_PRIORITY[0]
}
Objects._byId[3] = eo
local function makeActor(e)
return {
kind = "npc",
i = e.localId,
obj = e.def,
eventObject = e,
elevation = e.elevation,
x = e.px or 0, y = e.py or 0,
sortY = e.py or 0,
}
end
print("[test] 1. baseline: no freeze -> dynamic class, no capture")
do
local under, over = FieldView.applyDrawOrder({ makeActor(eo) })
check(#over == 0 and #under == 1, "elevation 0 lands in the under list")
check(under[1].priority == 2, "dynamic class 2 from ELEVATION_TO_PRIORITY[0]")
check(under[1].subpriority == nil, "no subpriority on an unfrozen actor")
check(eo.fixedClass == nil, "no capture before any freeze is set")
end
print("[test] 2. set stores the record with the +83 script bias")
do
local scriptPriority = 0 -- script byte; pret adds 83 in scrcmd.c:1130
local ok = Objects.setSubpriority(3, nil, nil, scriptPriority + 83)
check(ok == true, "setSubpriority resolves the current-map object")
check(eo.fixedPriority == true, "fixedPriority flag set")
check(eo.subpriority == 83, "subpriority carries the +83 bias (0 + 83)")
end
print("[test] 3. set -> draw freezes the class at first observation")
do
local under = FieldView.applyDrawOrder({ makeActor(eo) })
check(eo.fixedClass == 2, "fixedClass captured on first observation (class 2)")
check(under[1].priority == 2, "frozen priority = fixedClass")
check(under[1].subpriority == 83, "+83 bias survives the round trip as the sort key")
end
print("[test] 4. freeze holds across an elevation change (pret early-return)")
do
eo.elevation = 13 -- would be class 0 if the dynamic writer ran
local under, over = FieldView.applyDrawOrder({ makeActor(eo) })
check(under[1].priority == 2, "priority stays at fixedClass (2), not ELEVATION_TO_PRIORITY[13]=0")
check(eo.fixedClass == 2, "fixedClass captured once, not re-derived")
check(#over == 0, "frozen actor does not move to the over list")
end
print("[test] 5. frozen actor sorts by subpriority; neighbour keeps pixel-Y")
do
local neighbour = {
kind = "npc", i = 9, obj = { elevation = 3 }, elevation = 3,
x = 16, y = 100, sortY = 100, -- pixel-Y 100 > subpriority 83
}
local under = FieldView.applyDrawOrder({ neighbour, makeActor(eo) })
check(#under == 2, "both actors share the under list")
check(under[1].eventObject == eo, "frozen actor (83) sorts before the neighbour (sortY 100)")
check(under[2].priority == 2, "unfrozen neighbour still follows ELEVATION_TO_PRIORITY[3]=2")
check(under[2].subpriority == nil, "unfrozen neighbour keeps the pixel-Y key")
local near = {
kind = "npc", i = 8, obj = { elevation = 3 }, elevation = 3,
x = 32, y = 10, sortY = 10, -- pixel-Y 10 < subpriority 83
}
local under2 = FieldView.applyDrawOrder({ near, makeActor(eo) })
check(under2[1] == near, "pixel-Y 10 still sorts before subpriority 83")
check(under2[2].eventObject == eo, "frozen actor falls behind the nearer neighbour")
end
print("[test] 6. reset resumes the dynamic path with no stale capture")
do
local ok = Objects.resetSubpriority(3, nil, nil)
check(ok == true, "resetSubpriority resolves the current-map object")
check(eo.fixedPriority == nil and eo.subpriority == nil,
"reset clears the record (pret ResetObjectSubpriority does not restore)")
eo.elevation = 13
local under, over = FieldView.applyDrawOrder({ makeActor(eo) })
check(#over == 1 and #under == 0, "dynamic path resumed: class 0 -> over list")
check(over[1].priority == 0, "priority follows ELEVATION_TO_PRIORITY[13] = 0 again")
check(over[1].subpriority == nil, "sort key back to pixel-Y")
check(eo.fixedClass == nil, "no stale fixedClass survives the reset")
-- a later re-freeze must re-capture from the current elevation, not the old one
Objects.setSubpriority(3, nil, nil, 5 + 83)
local under2, over2 = FieldView.applyDrawOrder({ makeActor(eo) })
check(#under2 == 0, "fresh capture at class 0 keeps the actor out of the under list")
check(eo.fixedClass == 0, "re-freeze re-captures at the current class (0 at elevation 13)")
check(over2[1] ~= nil and over2[1].priority == 0, "re-frozen priority uses the fresh capture")
check(over2[1].subpriority == 88, "new bias (5 + 83) replaces the old sort key")
end
done()