Skip to content

Commit 07cdb71

Browse files
JoeMattclaude
andcommitted
Add save state round-trip and rewind regression tests
Adds two new test suites to the regression script: - Save state round-trip: save at frame 200, load in fresh run, verify frame 400 matches a straight 400-frame reference run - Rewind simulation: save states every 100 frames during a full run, load the frame-200 state, resume, verify output matches reference Both tests pass for jagniccc (polygon demo) and yarc (timer-driven music demo), validating that retro_serialize/retro_unserialize correctly captures and restores all emulation state. Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent 5943390 commit 07cdb71

1 file changed

Lines changed: 132 additions & 0 deletions

File tree

test/regression_test.sh

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,138 @@ for rom in "${ROM_DIR}"/*.j64 "${ROM_DIR}"/*.rom; do
248248
fi
249249
done
250250

251+
# --- Save state round-trip test ---
252+
# Validates retro_serialize/retro_unserialize: save state at frame N,
253+
# load it in a fresh run, continue for M frames. Compare the final
254+
# screenshot with frame N+M of a straight reference run.
255+
#
256+
# miniretro timing: --dump-savestates-every N captures state AFTER
257+
# retro_run for frame N. Loading that state and running M more
258+
# retro_runs produces the same output as a straight run's frame N+M.
259+
echo ""
260+
echo "==> Running save state round-trip test..."
261+
SS_SAVE_AT=200
262+
SS_RESUME_LEN=200
263+
SS_REF_FRAME=$((SS_SAVE_AT + SS_RESUME_LEN))
264+
for rom in "${ROM_DIR}"/*.j64 "${ROM_DIR}"/*.rom; do
265+
[ -f "${rom}" ] || continue
266+
rom_name="$(basename "${rom}" | sed 's/\.[^.]*$//')"
267+
268+
# Run 1: straight reference run, capture screenshot at target frame
269+
ss_ref="${WORK_DIR}/ss_ref/${rom_name}"
270+
mkdir -p "${ss_ref}"
271+
"${MINIRETRO_BIN}" \
272+
--core "${CORE}" --rom "${rom}" \
273+
--output "${ss_ref}" --system "${ss_ref}" \
274+
--frames $((SS_REF_FRAME + 1)) \
275+
--dump-frames-every "${SS_REF_FRAME}" \
276+
--no-alarm >/dev/null 2>&1 || true
277+
ref_frame=$(find "${ss_ref}" -name "screenshot*.png" 2>/dev/null | sort | tail -1)
278+
279+
# Run 2: save state at frame SS_SAVE_AT
280+
ss_save="${WORK_DIR}/ss_save/${rom_name}"
281+
mkdir -p "${ss_save}"
282+
"${MINIRETRO_BIN}" \
283+
--core "${CORE}" --rom "${rom}" \
284+
--output "${ss_save}" --system "${ss_save}" \
285+
--frames $((SS_SAVE_AT + 1)) \
286+
--dump-savestates-every "${SS_SAVE_AT}" \
287+
--no-alarm >/dev/null 2>&1 || true
288+
state_file=$(find "${ss_save}" -name "state*.bin" 2>/dev/null | sort | tail -1)
289+
290+
# Run 3: load state, run SS_RESUME_LEN frames, capture final screenshot
291+
# After loading state@N and running M-1 retro_runs, the last
292+
# screenshot dumped at interval M-1 corresponds to ref frame N+M.
293+
ss_load="${WORK_DIR}/ss_load/${rom_name}"
294+
mkdir -p "${ss_load}"
295+
if [ -n "${state_file}" ]; then
296+
"${MINIRETRO_BIN}" \
297+
--core "${CORE}" --rom "${rom}" \
298+
--output "${ss_load}" --system "${ss_load}" \
299+
--frames "${SS_RESUME_LEN}" \
300+
--dump-frames-every $((SS_RESUME_LEN - 1)) \
301+
--load-savestate "${state_file}" \
302+
--no-alarm >/dev/null 2>&1 || true
303+
fi
304+
load_frame=$(find "${ss_load}" -name "screenshot*.png" 2>/dev/null | sort | tail -1)
305+
306+
if [ -z "${ref_frame}" ] || [ -z "${state_file}" ] || [ -z "${load_frame}" ]; then
307+
echo " FAIL: ${rom_name} save state (missing frames or state file)"
308+
FAIL=$((FAIL + 1))
309+
SUMMARY="${SUMMARY}| ${rom_name} (save state) | :x: FAIL | missing output | - |\n"
310+
elif cmp -s "${ref_frame}" "${load_frame}"; then
311+
echo " PASS: ${rom_name} save state round-trip (frame ${SS_REF_FRAME} matches)"
312+
PASS=$((PASS + 1))
313+
SUMMARY="${SUMMARY}| ${rom_name} (save state) | :white_check_mark: PASS | round-trip matches | - |\n"
314+
else
315+
echo " FAIL: ${rom_name} save state round-trip (frame ${SS_REF_FRAME} differs)"
316+
cp "${ref_frame}" "${DIFF_DIR}/${rom_name}_ss_ref.png"
317+
cp "${load_frame}" "${DIFF_DIR}/${rom_name}_ss_load.png"
318+
FAIL=$((FAIL + 1))
319+
SUMMARY="${SUMMARY}| ${rom_name} (save state) | :x: FAIL | round-trip mismatch | See artifacts |\n"
320+
fi
321+
done
322+
323+
# --- Rewind simulation test ---
324+
# Simulates rewind: run past frame N while saving states periodically,
325+
# then load the frame-N state and run forward again. The result must
326+
# match the reference from the save state test above.
327+
echo ""
328+
echo "==> Running rewind simulation test..."
329+
RW_REWIND_TO=${SS_SAVE_AT}
330+
RW_REMAIN=${SS_RESUME_LEN}
331+
RW_REF_FRAME=${SS_REF_FRAME}
332+
for rom in "${ROM_DIR}"/*.j64 "${ROM_DIR}"/*.rom; do
333+
[ -f "${rom}" ] || continue
334+
rom_name="$(basename "${rom}" | sed 's/\.[^.]*$//')"
335+
336+
# Reuse reference frame from save state test
337+
ref_frame=$(find "${WORK_DIR}/ss_ref/${rom_name}" -name "screenshot*.png" 2>/dev/null | sort | tail -1)
338+
339+
# Run past the rewind point, dumping states every 100 frames
340+
rw_full="${WORK_DIR}/rw_full/${rom_name}"
341+
mkdir -p "${rw_full}"
342+
"${MINIRETRO_BIN}" \
343+
--core "${CORE}" --rom "${rom}" \
344+
--output "${rw_full}" --system "${rw_full}" \
345+
--frames $((RW_REF_FRAME + 1)) \
346+
--dump-savestates-every 100 \
347+
--no-alarm >/dev/null 2>&1 || true
348+
349+
# Find state file for the rewind point
350+
rw_state="${rw_full}/state$(printf '%06d' ${RW_REWIND_TO}).bin"
351+
352+
# Load rewind state and continue
353+
rw_resume="${WORK_DIR}/rw_resume/${rom_name}"
354+
mkdir -p "${rw_resume}"
355+
if [ -f "${rw_state}" ]; then
356+
"${MINIRETRO_BIN}" \
357+
--core "${CORE}" --rom "${rom}" \
358+
--output "${rw_resume}" --system "${rw_resume}" \
359+
--frames "${RW_REMAIN}" \
360+
--dump-frames-every $((RW_REMAIN - 1)) \
361+
--load-savestate "${rw_state}" \
362+
--no-alarm >/dev/null 2>&1 || true
363+
fi
364+
rw_frame=$(find "${rw_resume}" -name "screenshot*.png" 2>/dev/null | sort | tail -1)
365+
366+
if [ -z "${ref_frame}" ] || [ ! -f "${rw_state}" ] || [ -z "${rw_frame}" ]; then
367+
echo " FAIL: ${rom_name} rewind (missing frames or state file)"
368+
FAIL=$((FAIL + 1))
369+
SUMMARY="${SUMMARY}| ${rom_name} (rewind) | :x: FAIL | missing output | - |\n"
370+
elif cmp -s "${ref_frame}" "${rw_frame}"; then
371+
echo " PASS: ${rom_name} rewind (resume from frame ${RW_REWIND_TO} matches)"
372+
PASS=$((PASS + 1))
373+
SUMMARY="${SUMMARY}| ${rom_name} (rewind) | :white_check_mark: PASS | rewind matches | - |\n"
374+
else
375+
echo " FAIL: ${rom_name} rewind (resume from frame ${RW_REWIND_TO} differs)"
376+
cp "${ref_frame}" "${DIFF_DIR}/${rom_name}_rw_ref.png"
377+
cp "${rw_frame}" "${DIFF_DIR}/${rom_name}_rw_resume.png"
378+
FAIL=$((FAIL + 1))
379+
SUMMARY="${SUMMARY}| ${rom_name} (rewind) | :x: FAIL | rewind mismatch | See artifacts |\n"
380+
fi
381+
done
382+
251383
echo ""
252384
echo "==> Results: ${PASS} passed, ${FAIL} failed, ${NEW} new (no baseline)"
253385

0 commit comments

Comments
 (0)