Add deterministic bench harness (KOBO_BENCH=1)
When KOBO_BENCH=1 is set in the environment, the game becomes a
hands-off benchmark:
* Skips the intro/menu sequence and pushes st_game directly with
scene=0, skill=CLASSIC, invulnerable ship, always_fire = constant
auto-fire, countdown = 0 so "GET READY" auto-pops in 700ms.
* Seeds pubrand and gamerand from KOBO_BENCH_SEED (default 12345)
so the map, stress spawn pattern, and enemy AI are identical
run-to-run.
* Defaults KOBO_STRESS=1500 unless overridden, so the move pass has
a steady workload, and KOBO_PROF=1 so the profiler is on.
* Counts frames in run_game; after KOBO_BENCH_FRAMES (default 1800)
it dumps prof to stderr and exits cleanly.
Usage from repo root (the cwd matters — relative GFX paths only
resolve from here):
KOBO_BENCH=1 ./build/kobodl 2>bench-logs/phaseN.log
A single command produces a comparable profiler dump at every phase
of the DOD refactor — no menus to navigate, no keyboard input,
identical workload.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
869056c5d2
commit
0263be65d4
@ -160,7 +160,7 @@ set(KOBO_SOURCES
|
|||||||
myship.cpp radar.cpp random.cpp scenes.cpp score.cpp screen.cpp
|
myship.cpp radar.cpp random.cpp scenes.cpp score.cpp screen.cpp
|
||||||
filemap.cpp prefs.cpp cfgform.cpp options.cpp gamestate.cpp
|
filemap.cpp prefs.cpp cfgform.cpp options.cpp gamestate.cpp
|
||||||
states.cpp form.cpp cfgparse.cpp game.cpp kobo.cpp logger.c
|
states.cpp form.cpp cfgparse.cpp game.cpp kobo.cpp logger.c
|
||||||
dashboard.cpp sound.cpp prof.cpp
|
dashboard.cpp sound.cpp prof.cpp bench.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(kobodl ${KOBO_SOURCES})
|
add_executable(kobodl ${KOBO_SOURCES})
|
||||||
|
|||||||
35
bench.cpp
Normal file
35
bench.cpp
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#include "bench.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
int kobo_bench_enabled = 0;
|
||||||
|
int kobo_bench_frames = 1800;
|
||||||
|
Uint32 kobo_bench_seed = 12345;
|
||||||
|
|
||||||
|
extern "C" void kobo_bench_init_env(void)
|
||||||
|
{
|
||||||
|
const char *e = getenv("KOBO_BENCH");
|
||||||
|
kobo_bench_enabled = (e && *e && *e != '0') ? 1 : 0;
|
||||||
|
|
||||||
|
const char *f = getenv("KOBO_BENCH_FRAMES");
|
||||||
|
if (f && *f) kobo_bench_frames = atoi(f);
|
||||||
|
|
||||||
|
const char *s = getenv("KOBO_BENCH_SEED");
|
||||||
|
if (s && *s) kobo_bench_seed = (Uint32)strtoul(s, NULL, 10);
|
||||||
|
|
||||||
|
if (!kobo_bench_enabled) return;
|
||||||
|
|
||||||
|
// Default a steady stress workload so the move pass has predictable cost.
|
||||||
|
if (!getenv("KOBO_STRESS"))
|
||||||
|
setenv("KOBO_STRESS", "1500", 1);
|
||||||
|
// Profiler is the whole point of running in bench mode.
|
||||||
|
if (!getenv("KOBO_PROF"))
|
||||||
|
setenv("KOBO_PROF", "1", 1);
|
||||||
|
|
||||||
|
fprintf(stderr,
|
||||||
|
"[bench] enabled: frames=%d seed=%u stress=%s prof=%s\n",
|
||||||
|
kobo_bench_frames,
|
||||||
|
(unsigned)kobo_bench_seed,
|
||||||
|
getenv("KOBO_STRESS"),
|
||||||
|
getenv("KOBO_PROF"));
|
||||||
|
}
|
||||||
36
bench.h
Normal file
36
bench.h
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Deterministic, hands-off benchmark mode.
|
||||||
|
//
|
||||||
|
// Activated by KOBO_BENCH=1 in the environment. When on, the game:
|
||||||
|
// * skips the intro/menu sequence and drops straight into stage 0
|
||||||
|
// * seeds gamerand and pubrand from KOBO_BENCH_SEED (default 12345)
|
||||||
|
// * enables cmd_indicator (collisions register but neither side takes
|
||||||
|
// damage — true invulnerability for deterministic workload),
|
||||||
|
// cmd_cheat (skip score recording), and always_fire (auto-fire)
|
||||||
|
// * defaults KOBO_STRESS=1500 unless overridden, for a steady workload
|
||||||
|
// * dumps the profiler table and exits after KOBO_BENCH_FRAMES
|
||||||
|
// run_game frames (default 1800 = ~60s at the game's tick rate)
|
||||||
|
//
|
||||||
|
// Together this makes a single command (`KOBO_BENCH=1 ./kobodl`) produce a
|
||||||
|
// reproducible profiler dump across phases — no menu navigation, no keyboard
|
||||||
|
// input, identical RNG and spawn pattern run-to-run.
|
||||||
|
|
||||||
|
#ifndef KOBO_BENCH_H
|
||||||
|
#define KOBO_BENCH_H
|
||||||
|
|
||||||
|
#include "sdl_compat.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern int kobo_bench_enabled;
|
||||||
|
extern int kobo_bench_frames;
|
||||||
|
extern Uint32 kobo_bench_seed;
|
||||||
|
|
||||||
|
void kobo_bench_init_env(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // KOBO_BENCH_H
|
||||||
34
kobo.cpp
34
kobo.cpp
@ -62,6 +62,7 @@ extern "C" {
|
|||||||
#include "myship.h"
|
#include "myship.h"
|
||||||
#include "enemies.h"
|
#include "enemies.h"
|
||||||
#include "prof.h"
|
#include "prof.h"
|
||||||
|
#include "bench.h"
|
||||||
|
|
||||||
#define MAX_FPS_RESULTS 64
|
#define MAX_FPS_RESULTS 64
|
||||||
|
|
||||||
@ -1388,12 +1389,40 @@ int KOBO_main::open()
|
|||||||
ct_engine.render_highlight = kobo_render_highlight;
|
ct_engine.render_highlight = kobo_render_highlight;
|
||||||
wdash->mode(DASHBOARD_GAME);
|
wdash->mode(DASHBOARD_GAME);
|
||||||
wradar->mode(RM_NOISE);
|
wradar->mode(RM_NOISE);
|
||||||
pubrand.init();
|
|
||||||
|
// Bench mode overrides flags that downstream init reads (always_fire is
|
||||||
|
// captured by gamecontrol.init at call-time, so it has to be set before).
|
||||||
|
if(kobo_bench_enabled)
|
||||||
|
{
|
||||||
|
// cmd_indicator is "collision testing mode" — collisions still
|
||||||
|
// register sounds but neither side takes damage, so the ship
|
||||||
|
// can't die and the bench workload doesn't drift across runs.
|
||||||
|
// (cmd_cheat is *unlimited lives*, not invulnerability.)
|
||||||
|
prefs->cmd_indicator = 1;
|
||||||
|
prefs->cmd_cheat = 1; // skip score recording on exit
|
||||||
|
prefs->always_fire = 1; // constant auto-fire
|
||||||
|
prefs->countdown = 0; // auto-pop the "GET READY" wait
|
||||||
|
}
|
||||||
|
|
||||||
|
if(kobo_bench_enabled)
|
||||||
|
pubrand.init(kobo_bench_seed);
|
||||||
|
else
|
||||||
|
pubrand.init();
|
||||||
init_js(prefs);
|
init_js(prefs);
|
||||||
gamecontrol.init(prefs->always_fire);
|
gamecontrol.init(prefs->always_fire);
|
||||||
manage.init();
|
manage.init();
|
||||||
|
|
||||||
gsm.push(&st_intro_title);
|
if(kobo_bench_enabled)
|
||||||
|
{
|
||||||
|
// Hands-off bench: jump straight into stage 0, no menu navigation.
|
||||||
|
scorefile.profile()->skill = SKILL_CLASSIC;
|
||||||
|
manage.set_scene_num(0);
|
||||||
|
gsm.push(&st_game);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gsm.push(&st_intro_title);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -2055,6 +2084,7 @@ int main(int argc, char *argv[])
|
|||||||
signal(SIGTERM, breakhandler);
|
signal(SIGTERM, breakhandler);
|
||||||
signal(SIGINT, breakhandler);
|
signal(SIGINT, breakhandler);
|
||||||
|
|
||||||
|
kobo_bench_init_env();
|
||||||
prof_init();
|
prof_init();
|
||||||
|
|
||||||
SDL_Init(0);
|
SDL_Init(0);
|
||||||
|
|||||||
29
manage.cpp
29
manage.cpp
@ -51,6 +51,7 @@
|
|||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
#include "random.h"
|
#include "random.h"
|
||||||
#include "prof.h"
|
#include "prof.h"
|
||||||
|
#include "bench.h"
|
||||||
|
|
||||||
#define GIGA 1000000000
|
#define GIGA 1000000000
|
||||||
|
|
||||||
@ -350,7 +351,10 @@ void _manage::init_resources_to_play(int newship)
|
|||||||
next_state_out = 0;
|
next_state_out = 0;
|
||||||
next_state_next = 0;
|
next_state_next = 0;
|
||||||
|
|
||||||
gamerand.init();
|
if(kobo_bench_enabled)
|
||||||
|
gamerand.init(kobo_bench_seed); // deterministic map + spawn pattern
|
||||||
|
else
|
||||||
|
gamerand.init();
|
||||||
game_seed = gamerand.get_seed();
|
game_seed = gamerand.get_seed();
|
||||||
enemies.init();
|
enemies.init();
|
||||||
myship.init();
|
myship.init();
|
||||||
@ -608,6 +612,29 @@ void _manage::run_game()
|
|||||||
update();
|
update();
|
||||||
++hi.playtime;
|
++hi.playtime;
|
||||||
prof_frame_tick();
|
prof_frame_tick();
|
||||||
|
|
||||||
|
// Bench harness: stop the program after a fixed number of frames so a
|
||||||
|
// single command produces a comparable profiler dump across phases.
|
||||||
|
// Push SDL_EVENT_QUIT instead of calling exit() — exit() runs the
|
||||||
|
// atexit handler which tries to shut down SDL audio synchronously and
|
||||||
|
// hangs on macOS. Going through the engine's own quit path tears down
|
||||||
|
// in the right order.
|
||||||
|
if(kobo_bench_enabled && (int)hi.playtime >= kobo_bench_frames)
|
||||||
|
{
|
||||||
|
static int triggered = 0;
|
||||||
|
if(!triggered)
|
||||||
|
{
|
||||||
|
triggered = 1;
|
||||||
|
fprintf(stderr,
|
||||||
|
"[bench] reached %d frames; dumping profile and quitting\n",
|
||||||
|
kobo_bench_frames);
|
||||||
|
prof_dump(stderr);
|
||||||
|
SDL_Event ev;
|
||||||
|
SDL_zero(ev);
|
||||||
|
ev.type = SDL_EVENT_QUIT;
|
||||||
|
SDL_PushEvent(&ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Top up the enemy pool with rocks until we hit the target population.
|
// Top up the enemy pool with rocks until we hit the target population.
|
||||||
|
|||||||
1
manage.h
1
manage.h
@ -95,6 +95,7 @@ class _manage
|
|||||||
static void regenerate();
|
static void regenerate();
|
||||||
static void select_scene(int scene, int redraw_map = 1);
|
static void select_scene(int scene, int redraw_map = 1);
|
||||||
static int scene() { return scene_num; }
|
static int scene() { return scene_num; }
|
||||||
|
static void set_scene_num(int n) { scene_num = n; } // for bench harness
|
||||||
static void abort();
|
static void abort();
|
||||||
static void freeze_abort();
|
static void freeze_abort();
|
||||||
static int aborted() { return exit_manage; }
|
static int aborted() { return exit_manage; }
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user