/** * @file interop_test.c * @brief Round-trip test for the galaga boundary, hoststruct.c-style. * * Links the real script.c and the real galaga.bas -- not copies -- so this * fails the moment the boundary and the script disagree. The four claims it * pins: * * 1. The script writes the engine's actor memory: a formation enemy's sway * lands in akgl_Actor.x with no marshalling step. * 2. The outbox works: a diving enemy above the player raises FIRE# and the * C side reads it. * 3. The boss flips actor state bit 13 at one hit point -- the boundary * crossed engine-ward. * 4. Sustained calling holds: 24000 calls through the per-call * akbasic_environment_zero() regime, the load a 40-enemy wave puts on * the runtime in ten seconds. * * Exit status equals the number of failed claims. */ #include #include #include #include #include #include #include #include "galaga.h" #ifndef GALAGA_SCRIPT_PATH #define GALAGA_SCRIPT_PATH "galaga.bas" #endif /* script.c reads these; main.c usually defines them. This test is the host. */ galaga_Game galaga_game; galaga_Shared galaga_shared; static int FAILURES = 0; #define CLAIM(__cond, __text) \ if ( !(__cond) ) { \ fprintf(stderr, "FAILED: %s\n", __text); \ FAILURES += 1; \ } else { \ printf("ok: %s\n", __text); \ } static akerr_ErrorContext *run_claims(void) { galaga_Enemy enemy; akgl_Actor actor; int i = 0; PREPARE_ERROR(errctx); PASS(errctx, galaga_script_boot((char *)GALAGA_SCRIPT_PATH)); /* --- 1: formation sway lands in the actor ---------------------------- */ memset(&enemy, 0, sizeof(enemy)); memset(&actor, 0, sizeof(actor)); enemy.kind = GALAGA_ENEMY_BEE; enemy.state = GALAGA_ES_FORMATION; enemy.homex = 400.0f; enemy.homey = 300.0f; enemy.hp = 1; actor.x = 0.0f; actor.y = 0.0f; PASS(errctx, galaga_script_update_enemy(&enemy, &actor, 0.016f)); CLAIM((fabsf(actor.x - enemy.homex) <= 16.5f) && (actor.y == enemy.homey), "a formation bee's sway is written into akgl_Actor.x/y by the script"); /* --- 2: the fire outbox ---------------------------------------------- */ memset(&enemy, 0, sizeof(enemy)); memset(&actor, 0, sizeof(actor)); enemy.kind = GALAGA_ENEMY_BEE; enemy.state = GALAGA_ES_DIVING; enemy.rnd = 0.0f; /* 0.0 < DT% * 1.5: always willing */ actor.x = 600.0f; actor.y = 200.0f; galaga_shared.playerx = 610.0f; /* just off the shot's column */ galaga_shared.playery = 860.0f; /* well below */ PASS(errctx, galaga_script_update_enemy(&enemy, &actor, 0.016f)); CLAIM(enemy.fire == 1, "a diving bee above the player raises FIRE# for the engine to consume"); /* --- 3: the boss's hurt bit ------------------------------------------ */ memset(&enemy, 0, sizeof(enemy)); memset(&actor, 0, sizeof(actor)); enemy.kind = GALAGA_ENEMY_BOSS; enemy.state = GALAGA_ES_FORMATION; enemy.homex = 500.0f; enemy.homey = 120.0f; enemy.hp = 1; actor.state = AKGL_ACTOR_STATE_ALIVE; PASS(errctx, galaga_script_update_enemy(&enemy, &actor, 0.016f)); CLAIM((actor.state & AKGL_ACTOR_STATE_UNDEFINED_13) != 0, "a boss at one hit point raises actor state bit 13 from BASIC"); /* --- 4: sustained calling --------------------------------------------- */ memset(&enemy, 0, sizeof(enemy)); memset(&actor, 0, sizeof(actor)); enemy.kind = GALAGA_ENEMY_BEE; enemy.state = GALAGA_ES_FORMATION; enemy.homex = 400.0f; enemy.homey = 300.0f; for ( i = 0; i < 24000; i++ ) { enemy.rnd = 0.9f; /* never dive: keep the state put */ PASS(errctx, galaga_script_update_enemy(&enemy, &actor, 0.016f)); } CLAIM(galaga_game.script_errors == 0, "24000 calls survive the per-call akbasic_environment_zero() regime"); SUCCEED_RETURN(errctx); } int main(void) { PREPARE_ERROR(errctx); ATTEMPT { CATCH(errctx, run_claims()); } CLEANUP { } PROCESS(errctx) { } HANDLE_DEFAULT(errctx) { LOG_ERROR_WITH_MESSAGE(errctx, "the interop test could not run"); FAILURES += 1; } FINISH_NORETURN(errctx); return FAILURES; }