mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-25 23:11:15 +02:00
ca4d3d283c
Crystal boots from a user-supplied ROM, imports a full cache and is playable: copyright, the Crystal intro movie, the animated title, gender select, Oak, and out into Johto. 122 of the cart's 169 script specials are implemented. Import and data - tools/make_crystal_manifest.py derives the manifest by importing make_gold_manifest as a library, with three additive keyword seams. Gold and Silver still regenerate byte-identical, which is the standing requirement for touching that generator. - crystal_symbol_deltas.py and crystal_movie_symbols.py carry the symbol delta: Crystal renames the credits mons, splits the trainer card, Pokegear and pack-pal blocks by gender, and replaces the intro and title outright. - Crystal-only manifest keys: engineFlagOrder (162 flags to Gold's 93, so the badge block sits one higher) and unownCharmap (the main charmap parser stops at the first newcharmap so the two cannot contaminate each other). Extractor - RomExtractorGen2 becomes three-edition. Crystal corrections: PAL_MAP_BANK 0x13, a flat PICS_FIX pic bank, audio bank 0x5e, the mapSongs id-100 hole, seven NPC trades, a TradeTexts stride of 8, the five Crystal tileset anim steps with per-row degrade, and the column-major trainer card portraits. - New: animated front sprites (frames, bitmasks, play and idle scripts), the Battle Tower roster, Kris assets, Mobile System GB art, and the Crystal intro and title via src/import/CrystalMovie.lua. Engine - GameVersion gains engine(id) and fixes(id). Gold and Silver keep their original bugs where the bug is not hardware dependent; Crystal gets the fixes Crystal shipped: Lucky Number boxes 10-14, surfing onto an NPC, and the Reflect and Light Screen defence overflow. - Crystal story: Suicune and Eusine, Celebi behind the GS Ball flag, the Ruins of Alph chambers, Buena, the Move Tutor, the Poke Seer, and the Battle Tower including the wInBattleTowerBattle badge-boost guard. - Kris and the gender flag, animated fronts in battle and the summary screen, and mon caught data. Verification - Every extracted asset is pixel-compared against pret's own source PNGs. - Gold caches are byte-identical before and after, file for file. - New Crystal suites plus a T2 Gen 2 tier; the full suite passes.
291 lines
12 KiB
Bash
Executable File
291 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Unified test entry point (21-testing-and-ci §CI).
|
|
#
|
|
# Runs every tier that this checkout can run and exits non-zero if any of
|
|
# them fails. The tier split is what makes that possible: T1/T2/T4 need
|
|
# nothing but the committed fixture dataset, so they run anywhere --
|
|
# including CI, which has no ROM. T3 asserts Pokemon Red facts and needs
|
|
# data/generated/ or an imported Red cache, so it is skipped automatically
|
|
# when neither exists rather than failing the run.
|
|
#
|
|
# scripts/test.sh every tier this checkout can run
|
|
# scripts/test.sh --quick skip the slow content tier
|
|
# scripts/test.sh --bless re-pin the fingerprint goldens
|
|
# WITH_SHOTS=1 scripts/test.sh also capture and diff golden shots
|
|
# (fails today -- see the T5 block below)
|
|
#
|
|
# LUA overrides the interpreter (luajit here; CI installs lua5.4 too, but
|
|
# the engine targets LuaJIT/5.1 semantics so luajit is the default).
|
|
#
|
|
# POKEPORT_TEST_CACHES points at one LOVE identity holding an imported cache
|
|
# per version (red/ blue/ yellow/ gold/ silver/ crystal/); each is exported as
|
|
# RED_CACHE .. CRYSTAL_CACHE for the suites that read one. Build it with:
|
|
# POKEPORT_IDENTITY=pokeport-test-caches POKEPORT_VERSION=<version> \
|
|
# POKEPORT_IMPORT_ONLY=1 POKEPORT_IMPORT_ROM="<rom>" love .
|
|
# An explicit RED_CACHE/GOLD_CACHE/... in the environment always wins.
|
|
|
|
set -uo pipefail
|
|
|
|
cd "$(dirname "$0")/.."
|
|
|
|
LUA=${LUA:-luajit}
|
|
LUA54=${LUA54:-lua5.4}
|
|
BLESS=0
|
|
QUICK=0
|
|
SHOTS=${WITH_SHOTS:-0}
|
|
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--bless) BLESS=1 ;;
|
|
--bless-shots) SHOTS=1; BLESS=1 ;;
|
|
--quick) QUICK=1 ;;
|
|
--help|-h) sed -n '2,26p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
*) echo "unknown option: $arg" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
if ! command -v "$LUA" >/dev/null 2>&1; then
|
|
echo "no lua interpreter '$LUA' on PATH (set LUA=...)" >&2
|
|
exit 2
|
|
fi
|
|
|
|
# The save-directory sandbox (conf.lua reads POKEPORT_IDENTITY) is scoped
|
|
# to the shot tier, which is the only one that starts a real LOVE process
|
|
# and could write into a developer's save folder. Exporting it for the
|
|
# whole run instead would change what SaveIO.defaultPath() returns, and the
|
|
# save-editor suite pins that to the default identity.
|
|
SANDBOX_IDENTITY="ci-$$"
|
|
|
|
# Per-version caches. POKEPORT_TEST_CACHES names one LOVE identity holding an
|
|
# imported cache per version (red/ blue/ yellow/ gold/ silver/ crystal/).
|
|
CACHE_IDENTITY=${POKEPORT_TEST_CACHE_IDENTITY:-pokeport-test-caches}
|
|
if [ -n "${POKEPORT_TEST_CACHES:-}" ]; then
|
|
CACHE_ROOT="$POKEPORT_TEST_CACHES"
|
|
elif [ -d "$HOME/Library/Application Support/LOVE/$CACHE_IDENTITY" ]; then
|
|
CACHE_ROOT="$HOME/Library/Application Support/LOVE/$CACHE_IDENTITY"
|
|
else
|
|
CACHE_ROOT="$HOME/.local/share/love/$CACHE_IDENTITY"
|
|
fi
|
|
|
|
# Only a cache with the importer's completion marker is offered: a half-written
|
|
# one would fail suites that are meant to self-skip.
|
|
adopt_cache() {
|
|
local var="$1" dir="$CACHE_ROOT/$2"
|
|
[ -z "${!var:-}" ] || return 0
|
|
[ -f "$dir/rom-cache.complete" ] || return 0
|
|
export "$var=$dir"
|
|
echo " $var=$dir"
|
|
}
|
|
|
|
echo ""
|
|
echo "-- per-version caches under $CACHE_ROOT"
|
|
adopt_cache RED_CACHE red
|
|
adopt_cache BLUE_CACHE blue
|
|
adopt_cache YELLOW_CACHE yellow
|
|
adopt_cache GOLD_CACHE gold
|
|
adopt_cache SILVER_CACHE silver
|
|
adopt_cache CRYSTAL_CACHE crystal
|
|
|
|
FAILED=()
|
|
run_tier() {
|
|
local label="$1"; shift
|
|
echo ""
|
|
echo "=============================================================="
|
|
echo " $label"
|
|
echo "=============================================================="
|
|
if "$@"; then
|
|
echo "-- $label: PASS"
|
|
else
|
|
echo "-- $label: FAIL"
|
|
FAILED+=("$label")
|
|
fi
|
|
}
|
|
|
|
# ------- ROM-free tiers: these are what CI runs
|
|
|
|
if command -v luacheck >/dev/null 2>&1; then
|
|
run_tier "T0 luacheck gate (undefined globals, unreachable code)" \
|
|
./scripts/lint.sh --gate
|
|
else
|
|
echo ""
|
|
echo "-- T0 luacheck gate: skipped (no luacheck on PATH --"
|
|
echo " luarocks install luacheck; CI installs and gates on it regardless)"
|
|
fi
|
|
|
|
run_tier "T0 ROM builder version routing" python3 tests/build_rom_data_cli_test.py
|
|
run_tier "T0 ROM manifest generator pin/overrides" python3 tests/rom_manifest_generator_test.py
|
|
run_tier "T0 Crystal manifest + specials coverage" "$LUA" tests/crystal_import_test.lua
|
|
run_tier "T0 switch CI workflow content gate" "$LUA" tests/switch_ci_workflows_test.lua
|
|
run_tier "T0 switch transfer docs gate" "$LUA" tests/switch_transfer_docs_test.lua
|
|
# NX Blue/Yellow asset overlay: ROM-free, must run on every checkout so a
|
|
# Sound.lua / overlay regression is not gated only on switch-changes paths.
|
|
run_tier "T0 NX asset overlay fallback" "$LUA" tests/engine/assets_version_fallback_test.lua
|
|
run_tier "T0 NX generated-path static guard" "$LUA" tests/engine/nx_generated_guard_test.lua
|
|
run_tier "T0 NX Yellow/Blue boot (dynamic paths)" "$LUA" tests/engine/nx_yellow_boot_test.lua
|
|
run_tier "T0 NX Gold cache load (maps.lua prefix)" "$LUA" tests/engine/cache_fs_gold_nx_load_test.lua
|
|
run_tier "T0 touch-controls pad cursor" "$LUA" tests/engine/touch_controls_pad_cursor_test.lua
|
|
run_tier "T1/T2 engine invariants + parity gates" "$LUA" tests/run_engine.lua
|
|
# Gen 2 / Crystal: ROM-free (own fixtures, or a self-skip on a missing cache),
|
|
# so it runs here rather than behind the Red content gate below.
|
|
run_tier "T2 Gen 2 / Crystal suites" "$LUA" tests/run_gen2.lua
|
|
run_tier "T4 mod-SDK" "$LUA" tests/run_modkit.lua
|
|
run_tier "T4 title checkpoint cold restart" \
|
|
bash tests/integration/title_checkpoint_cold_start.sh
|
|
|
|
# The modded-link desync suite (symmetric mod, handshake fail-closed,
|
|
# extra-bag round trip) is ROM-free and runs inside the T4 tier above, as
|
|
# tests/modkit/cases/link_desync.lua.
|
|
#
|
|
# tests/run_link_tests.lua is a different matter: it calls Data:load() at
|
|
# :27 and so needs data/generated/. It is grouped with the content tier
|
|
# until that bootstrap can take an injected dataset.
|
|
# ------- content tier: only meaningful with an imported ROM
|
|
|
|
|
|
# tests/run_tests.lua is expected to be clean. It used to carry two stale
|
|
# chip-audio assertions on the allowlist below (Pikachu cry WAV exists /
|
|
# low-health alarm sfx extracted); both have since been fixed, so the
|
|
# baseline is zero and any failure fails the tier. Keep the allowlist
|
|
# mechanism rather than ignoring the exit code -- that would hide every
|
|
# future content regression.
|
|
KNOWN_CONTENT_FAILURES=0
|
|
KNOWN_CONTENT_LINES=""
|
|
|
|
run_content_behavior() {
|
|
local out
|
|
out=$("$LUA" tests/run_tests.lua 2>&1)
|
|
local count
|
|
count=$(printf '%s\n' "$out" | grep -c '^FAIL ' || true)
|
|
local lines
|
|
lines=$(printf '%s\n' "$out" | grep '^FAIL ' | sort)
|
|
|
|
if [ "$count" -eq "$KNOWN_CONTENT_FAILURES" ] \
|
|
&& [ "$lines" = "$(printf '%s\n' "$KNOWN_CONTENT_LINES" | sort)" ]; then
|
|
printf '%s\n' "$out" | tail -3
|
|
if [ "$KNOWN_CONTENT_FAILURES" -gt 0 ]; then
|
|
echo "(the $KNOWN_CONTENT_FAILURES known stale assertions, unchanged)"
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
printf '%s\n' "$out" | grep '^FAIL ' || true
|
|
printf '%s\n' "$out" | tail -2
|
|
echo "expected exactly $KNOWN_CONTENT_FAILURES known failures; got $count"
|
|
return 1
|
|
}
|
|
|
|
# The Red dataset is the source tree's when tools/build_data.py wrote one, and
|
|
# otherwise the imported Red cache, through Data:load's POKEPORT_DATA_DIR hook.
|
|
HAVE_RED_DATA=0
|
|
if [ -f data/generated/maps.lua ]; then
|
|
HAVE_RED_DATA=1
|
|
elif [ -n "${RED_CACHE:-}" ] && [ -f "$RED_CACHE/data/generated/maps.lua" ]; then
|
|
HAVE_RED_DATA=1
|
|
export POKEPORT_DATA_DIR="$RED_CACHE/data/generated"
|
|
echo ""
|
|
echo "-- T3 content: reading Red from $POKEPORT_DATA_DIR"
|
|
fi
|
|
|
|
if [ "$HAVE_RED_DATA" = "1" ]; then
|
|
if [ "$QUICK" = "1" ]; then
|
|
echo ""
|
|
echo "-- T3 content: skipped (--quick)"
|
|
else
|
|
run_tier "T3 content behavior (Red)" run_content_behavior
|
|
# The save editor ships inside every build (the launcher's Edit button on
|
|
# a save row opens it), so its panel suites run in CI rather than by hand.
|
|
run_tier "T3 save editor" "$LUA" tests/run_save_editor_tests.lua
|
|
run_tier "T3 save editor: boxes + items" "$LUA" tests/save_editor_task6_tests.lua
|
|
run_tier "T3 save editor: events + dex" "$LUA" tests/save_editor_task7_tests.lua
|
|
run_tier "T3 save editor: map browser" "$LUA" tests/save_editor_task8_tests.lua
|
|
run_tier "T3 save editor: mod awareness" "$LUA" tests/save_editor_mod_tests.lua
|
|
run_tier "T3 save editor: gold / gen2" "$LUA" tests/save_editor_gen2_tests.lua
|
|
run_tier "T3 save editor: wheel scrolling" "$LUA" tests/save_editor_wheel_bug595_test.lua
|
|
run_tier "T3 save editor: pad / NX input" "$LUA" tests/save_editor_pad_input_test.lua
|
|
run_tier "T5 link (loopback lockstep)" "$LUA" tests/run_link_tests.lua
|
|
# The oversize-save vendor oracle (tests/save_oversize_vendor_test.lua)
|
|
# cross-checks the launcher's footer-truncation import against the
|
|
# INDEPENDENT PKHeX-derived gen1lib codec, which cannot run under luajit
|
|
# (native 5.3+ operators). Needs a stock Lua 5.3/5.4; skip when absent.
|
|
if command -v "$LUA54" >/dev/null 2>&1; then
|
|
run_tier "T3 save oversize vendor oracle" "$LUA54" tests/save_oversize_vendor_test.lua
|
|
else
|
|
echo ""
|
|
echo "-- T3 save oversize vendor oracle: skipped (no '$LUA54' on PATH; set LUA54=...)"
|
|
fi
|
|
fi
|
|
else
|
|
echo ""
|
|
echo "-- T3 content + run_link_tests: skipped (no data/generated/ and no"
|
|
echo " RED_CACHE -- import a ROM to run them; the modded-link cases ran"
|
|
echo " in T4 and the Gen 2 suites in T2)"
|
|
fi
|
|
|
|
# ------- golden screenshots: needs love + a display
|
|
|
|
if [ "$SHOTS" = "1" ]; then
|
|
SHOT_DIR=${SHOT_DIR:-/tmp/pokeport-shots}
|
|
export SHOT_DIR
|
|
mkdir -p "$SHOT_DIR"
|
|
SHOT_DRIVER=tests/drivers/shots_fixture.lua
|
|
|
|
# The fixture goldens are not capturable yet. A driver only ever runs
|
|
# after main.lua's bootGame(), so it cannot redirect Data:load(), and
|
|
# src/core/Data.lua has no POKEPORT_DATA_DIR branch -- 21-testing-and-ci
|
|
# §"Engine changes" specifies one, but it is not implemented, so a LOVE
|
|
# process has no way to boot tests/fixture_data. On a ROM-less checkout
|
|
# main.lua does not even reach the game: RomImporter.isReady() is false
|
|
# and it opens the importer instead.
|
|
#
|
|
# WITH_SHOTS is opt-in, so asking for a tier that cannot run is an error,
|
|
# not a skip. Reporting "pass" here is what made the whole pipeline look
|
|
# delivered while never diffing a single pixel.
|
|
if [ ! -f "$SHOT_DRIVER" ]; then
|
|
echo ""
|
|
echo "-- T5 shots: NOT WIRED ($SHOT_DRIVER does not exist)."
|
|
echo " Fixture capture needs the POKEPORT_DATA_DIR override in"
|
|
echo " src/core/Data.lua so LOVE can boot tests/fixture_data."
|
|
FAILED+=("T5 shots (requested but not wired)")
|
|
elif ! command -v love >/dev/null 2>&1; then
|
|
echo ""
|
|
echo "-- T5 shots: love is not on PATH but WITH_SHOTS was requested"
|
|
FAILED+=("T5 shots (love missing)")
|
|
else
|
|
RUNNER="love ."
|
|
command -v xvfb-run >/dev/null 2>&1 && RUNNER="xvfb-run -a love ."
|
|
run_tier "T5 shot capture" \
|
|
env POKEPORT_IDENTITY="$SANDBOX_IDENTITY" POKEPORT_DRIVER="$SHOT_DRIVER" $RUNNER
|
|
if [ "$BLESS" = "1" ]; then
|
|
run_tier "T5 shot bless" \
|
|
python3 tools/compare_shots.py tests/goldens/shots "$SHOT_DIR" --bless
|
|
else
|
|
run_tier "T5 shot diff" \
|
|
python3 tools/compare_shots.py tests/goldens/shots "$SHOT_DIR"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# ------- fingerprint blessing
|
|
|
|
if [ "$BLESS" = "1" ] && [ "$SHOTS" != "1" ]; then
|
|
echo ""
|
|
echo "re-pinning fingerprint goldens (deliberate parity change -- record it"
|
|
echo "in docs/known-differences.md or docs/new-features.md)"
|
|
"$LUA" tests/bless_fingerprints.lua || FAILED+=("fingerprint bless")
|
|
fi
|
|
|
|
# ------- verdict
|
|
|
|
echo ""
|
|
echo "=============================================================="
|
|
if [ ${#FAILED[@]} -eq 0 ]; then
|
|
echo " ALL TIERS PASSED"
|
|
echo "=============================================================="
|
|
exit 0
|
|
fi
|
|
|
|
echo " ${#FAILED[@]} TIER(S) FAILED"
|
|
for tier in "${FAILED[@]}"; do echo " - $tier"; done
|
|
echo "=============================================================="
|
|
exit 1
|