Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added rectests/SHOULDFAIL.REC
Binary file not shown.
111 changes: 25 additions & 86 deletions run_rectests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,77 +6,13 @@ if [ -z "$1" ]; then
fi

export BUILD_DIR="$1"
OPENOMF_BIN=$(find "$BUILD_DIR" -name openomf -type f -executable -print -quit)
OPENOMF_BIN=$(find "$BUILD_DIR" "(" -name openomf -or -name openomf.exe ")" -type f -executable -print -quit)
if [ -z "$OPENOMF_BIN" ]; then
echo "Could not find openomf executable from $BUILD_DIR" >&2
exit 1
fi
export OPENOMF_BIN="./${OPENOMF_BIN#$BUILD_DIR}"

# Define your tests here (description:filename)
tests=(
"There is a 3 tick delay between jumps:JUMP_DELAY.REC"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dislike losing the rectest description as a side effect here, the description was very handy in quickly understanding what you broke when you started to see rectests fail.

Copy link
Member Author

@Nopey Nopey Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind if we embedded them in the .REC files as some sort of comment?
Then the rec controller can find them when an assert fails

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe? or just have a lookup table we can use to print a better description on a failure?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we have room for an artitrary string in the REC

Copy link
Member Author

@Nopey Nopey Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i was thinking add another "move" that's just a comment about what we're about to test.
when the rec fails, the rec controller would rewind looking for the last rec test comment & print it (again).

or we'll do a lookup table as you suggest, have that loaded from a .txt file in the rectests folder

"Overhead throw should grab opponent on the first tick of anim 22, throw opponent to the right, and not be interrupted by the ground:JAG-THROW-LAND.REC"
"Electra should be able to kick while doing inputs for rolling thunder:65K6P.REC"
"Jaguar uses same animation for K and 6P but Shadow does not:6P.REC"
"Novas 1P can hit more than once with the Q tag and use AI for air launch:NOVA-1P.REC"
"Jaguar standing throw should not result in overlap with opponent in corner:JAG-CORNER-THROW.REC"
"Jaguar doing a standing throw as the winning move should not cause a slide:JAG-NO-SLIDE-WIN-THROW.REC"
"Flail charging punch should cancel when blocked:FLAIL_454P.REC"
"Jaguar should keep walking right after doing a standing throw if the input is held:KEEP-WALKING-AFTER-JAG-STANDING-THROW.REC"
"Jaguar should be able to walk underneath Gargoyle:WALK_UNDERNEATH.REC"
"Test Jaguar destruction:JAG-DESTRUCT.REC"
"Kreissack has a custom defeat animation:KREISSACK.REC"
"Shadow grab should work in the middle and the corners of arena:SHADOW-GRAB.REC"
"Projectiles should do knockback:PROJECTILE-KNOCKBACK.REC"
"Enemy should recover from air hits unless it is a KO:AIR-HIT.REC"
"Only some moves should wallslam:STADIUM-WALL-SPLAT.REC"
"Power Plant with hazards enabled has a lower wallslam tolerance:POWERPLANT-WALL-SPLAT.REC"
"HARs in stasis cannot hit opponent:STASISED-HARS-CANNOT-HIT.REC"
"HARs play ANIM_DEFEAT instead of idle:NO-IDLE-DEFEAT.REC"
"Electra P656 cannot be interrupted during anim 36:ELECTRA-MOVE36-EMFLAG.REC"
"Chronos matter phasing (14K) noclips through both walls:CHRONOS-WALLNOCLIP.REC"
"Chronos can control teleport by holding left/right:CHRONOS_TELEPORT_CONTROL.REC"
"Electra Extended Rolling Thunder plays anim 40 upon hitting the wall:ELECTRA-EXROLLTHNDR-WALLBOUNCE.REC"
"Katana Wall Spin (14K) cannot be interrupted:KATANA-WALLSPIN-NOINTERRUPT.REC"
"Being kicked in the back of the head by chronos matter phasing knocks you forwards, flipping you:KNOCKBACK-DIR.REC"
"While walking, face your jumping enemy:FACE-JUMPING-ENEMY.REC"
"Nova's grenade should not fly over Electra's head:NOVA-GRENADE.REC"
"Buffering 2 (DOWN) 6 ticks before jumping grants a SUPER JUMP. Only the first two jumps should be super.:SUPERJUMP.REC"
"Buffered inputs are only used to match moves if the direction has changed in the last 9 ticks:INPUT-FRESHNESS.REC"
"Stasis prevents walking, turning, and endurance regen:STASIS-FREEZES.REC"
"Electra's shards can be blocked:SHARDS-ARE-BLOCKABLE.REC"
"Electra can throw Katana on wakeup without getting hit:WAKEUP_THROW.REC"
"Katana can chain stomps and end with a jump kick:STOMP.REC"
"Gargoyle's diving claw doesn't flip the opponent around:DIVING-CLAW.REC"
"Flail cannot hit air 66K throw out of range:THROW-RANGE.REC"
"Flail cannot hit 66K if the followup animation is out of range:THROW_RANGE_BUG.REC"
"Flail cannot be interrupted by hazards after throwing:THROW_INTERRUPT.REC"
"Flail's 66K attempt KO's the opponent and the game is fine with it:THROW_KO.REC"
"Throwing a stunned opponent resets their stun bar:STUN_THROW.REC"
"Gargoyle reaches the top of the stage:FLY.REC"
"Shadow grab can be interrupted by hitting the originating HAR:SHADOW-GRAB-INTERRUPT-HIT.REC"
"Jaguar throw deals half stun due to rehit rules:DAMPENED_STUN.REC"
"Pyros's flames cannot be hit:PYROS_PRIORITY.REC"
"Flail's destruct sequence works completely:FLAIL_DESTRUCT.REC"
"Electra is stuck blocking until Thorn's ul tag wears off:UL_TAG_HOLD_UP.REC"
"Pyros's destruct sequence works completely:PYROS_DESTRUCT.REC"
"Katana's corkscrew blade has landing recovery:LANDING_RECOVERY.REC"
"Flail 66K is unblockable:UNBLOCKABLE_THROW.REC"
"Electra 66P dodges Earthquake Smash:DODGE_EARTHQUAKE.REC"
"Katana's extended rising blade deals the correct amount of damage:KATANA_DAMAGE.REC"
"Enemies have invuln after recovering from an air hit:AIR_IMMUNITY_NO_REHIT.REC"
"Enemies cannot be juggled by the same move:AIR_IMMUNITY_REHIT.REC"
"Jaguar's Bread And Butter throw combo:REHIT_COMBO.REC"
"Jaguar cannot pick up the opponent off the ground:WAKEUP_THROW_PROTECTION.REC"
"Players cannot blocking while attacking:BLOCKING_INTERRUPT.REC"
"Katana cannot infinitely heel stomp to the moon with rehit mode:SPACE_JUMP.REC"
"Katana can turn around after heel stomp connects:KATANA_STOMP_TURN.REC"
"Nova can combo a stunned HAR from belly flop and enemy continues to be stunned:NOVA_FLOP_STUN_COMBO.REC"
"Rehit juggles are not allowed against stunned opponents:NO_STUN_JUGGLE.REC"
"Katana enters winpose after landing from heel stomp:KATANA_WINPOSE.REC"
)

# Setup temp directory for outputs
temp_dir=$(mktemp -d)
trap 'rm -rf "$temp_dir"' EXIT
Expand All @@ -96,31 +32,34 @@ RUNDIR=$(pwd)

cd $BUILD_DIR

export OPENOMF_RESOURCE_PATH="."
export LSAN_OPTIONS="suppressions=../lsan.supp"
i=0
for test in "${tests[@]}"; do
IFS=':' read -r desc filename <<< "$test"
# Trim whitespace from description and filename
desc=$(echo "$desc" | sed -re 's/^[[:blank:]]+|[[:blank:]]+$//g')
filename=$(echo "$filename" | sed -re 's/^[[:blank:]]+|[[:blank:]]+$//g')
output_file="$temp_dir/output_$i.log"

echo -n "${desc} :"
if $OPENOMF_BIN --force-audio-backend=NULL --force-renderer=NULL --speed=10 -P "$RUNDIR/rectests/${filename}" >"$output_file" 2>&1; then
echo " PASS"
else
echo " FAILED (${filename})"
fail_summary="${fail_summary} ${filename}"
cat $output_file
((fail_count++))
output_file="$temp_dir/output_shouldfail.log"
if $OPENOMF_BIN --force-audio-backend=NULL --force-renderer=NULL --warp -P "$RUNDIR/rectests/SHOULDFAIL.REC" >"$output_file" 2>&1; then
cat "$output_file"
echo "CRITICAL ERROR: SHOULDFAIL.REC succeeded."
exit 1
fi

cmd="\$OPENOMF_BIN --force-audio-backend=NULL --force-renderer=NULL --warp"

for filename in "$RUNDIR"/rectests/*.REC; do
if [[ "$filename" == *SHOULDFAIL.REC ]]; then
continue
fi
((i++))
cmd="$cmd -P '$filename'"
found_something=1
done

if [ $fail_count -ne 0 ]; then
fail_summary="Failed ${fail_count} tests:${fail_summary}"
echo "${fail_summary}"
if [ -z "${found_something}" ]; then
echo "Failed to find any rectests!"
exit 1
fi

# Exit with non-zero status if any test failed
exit $fail_count
if RUNDIR="${RUNDIR}" OPENOMF_BIN="${OPENOMF_BIN}" sh -c "$cmd"; then
echo "ALL PASSED."
else
echo "RECTESTS FAILED, GOODBYE."
exit 1
fi
15 changes: 13 additions & 2 deletions src/controller/rec_controller.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,19 @@ int rec_controller_poll(controller *ctrl, ctrl_event **ev) {
if(parse_assertion(buf, &ass)) {
log_assertion(&ass);
if(!game_state_check_assertion_is_met(&ass, ctrl->gs)) {
crash("REC file assert failed!");
if(ctrl->gs->init_flags->playback <= 1) {
// no need to continue, we're only playing a single rec.
crash("RECTEST Failed.");
}
// batch rectest
str ass_str;
rec_assertion_to_str(&ass_str, &ass);
char const *rec_filename = path_c(&ctrl->gs->init_flags->rec_files[ctrl->gs->rec_playback_id]);
str_append_format(&ctrl->gs->rectest_failures,
"REC Assertion Failed!\n File: %s\n %s (tick %d)\n", rec_filename,
str_c(&ass_str), ticks);
controller_close(ctrl, ev);
return 0;
}
}
} else if(move->lookup_id == 96) {
Expand Down Expand Up @@ -169,7 +181,6 @@ void rec_controller_step_back(controller *ctrl) {

game_state *gs_new = omf_calloc(1, sizeof(game_state));
game_state_clone(gs_bak, gs_new);
gs_new->clone = false;
ctrl->gs->new_state = gs_new;

data->last_tick = ctrl->gs->tick;
Expand Down
22 changes: 5 additions & 17 deletions src/engine.c
Original file line number Diff line number Diff line change
Expand Up @@ -210,14 +210,8 @@ void engine_run(engine_init_flags *init_flags) {
if(game_state_get_player(gs, 0)->ctrl->type == CTRL_TYPE_REC) {
controller_rewind(game_state_get_player(gs, 0)->ctrl);
controller_rewind(game_state_get_player(gs, 1)->ctrl);
if(gs->new_state) {
// one of the controllers wants to replace the game state
game_state *old_gs = gs;
game_state *new_gs = gs->new_state;
gs = new_gs;
game_state_clone_free(old_gs);
omf_free(old_gs);
}
// check if we need to replace the game state
game_state_check_for_new(&gs);
visual_debugger = 1;
}
}
Expand Down Expand Up @@ -328,15 +322,7 @@ void engine_run(engine_init_flags *init_flags) {
if(has_static) {
game_state_static_tick(gs, false);
// check if we need to replace the game state
if(gs->new_state) {
// one of the controllers wants to replace the game state
game_state *old_gs = gs;
game_state *new_gs = gs->new_state;
gs = new_gs;
// gs->new_state = NULL;
game_state_clone_free(old_gs);
omf_free(old_gs);
}
game_state_check_for_new(&gs);
console_tick(gs);
static_wait -= STATIC_TICKS;
}
Expand All @@ -347,6 +333,8 @@ void engine_run(engine_init_flags *init_flags) {
has_dynamic = dynamic_wait > dyntick_ms;
if(has_dynamic) {
game_state_dynamic_tick(gs, false);
// check if we need to replace the game state
game_state_check_for_new(&gs);
dynamic_wait -= dyntick_ms;
if(gs->delay > 0) {
log_debug("applying delay %d", gs->delay);
Expand Down
2 changes: 1 addition & 1 deletion src/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ typedef struct engine_init_flags {
unsigned int playback;
char force_renderer[16];
char force_audio_backend[16];
path rec_file;
path *rec_files;
int warpspeed;
int speed;
} engine_init_flags;
Expand Down
Loading
Loading