M-26: [B/C] level_sweep STALLED verdict, raw mouse input opt-in

- level_sweep.sh: frames rendered then frozen with process alive ->
  STALLED (froze at N/M frames) instead of a false PASS (the Silo
  -level_20 fly-down / D117-D134 load case). Retried once like NO-FRAMES.
- input.c: Input.MouseRawInput=1 (default 0) sets SDL relative
  system-scale + warp hints off in inputInit -> no OS pointer accel on aim.
- docs: M-26-QOL-REVIEW.md + HANDOFF section.
This commit is contained in:
James Dansereau committed 2026-08-30 12:55:12 -04:00
1 parent db1834ca9b
commit 710d02f802
4 files changed
+111 -12

No files matched your search

+21
View File
@@ -311,6 +311,27 @@ behavior exactly.
idle machine the user drove Silo live past ~1800 frames, VI pacemaker
healthy, no stall. See `docs/M-25-QOL-REVIEW.md`.
## Done this session (M-26) — port-layer QoL batch (A/B/C/E)
Low-risk QoL, `port/` only, zero `src/` change. All defaults reproduce
prior behaviour. Full review: **`docs/M-26-QOL-REVIEW.md`**.
- **A — save-on-exit + `[Window]` persistence.** `atexit(portAtExit)` in
`main.c` (`videoSaveWindowState()` + `configSave()`), fires on every
clean quit (`exit(0)` in `videoPumpEvents`); `abort()` paths skip it.
New `[Window]` ini section (`Width/Height/X/Y/Maximized`, sentinels =
old behaviour) in `video.c`; `Video.Fullscreen` now round-trips.
- **B — `level_sweep.sh` STALLED verdict.** Frames rendered then froze
with the process still alive → `STALLED (froze at N/M frames)` instead
of a bogus PASS (the Silo case). Retried once like NO-FRAMES.
- **C — raw mouse input.** `Input.MouseRawInput=1` (default 0) sets the
SDL relative-scale / warp hints off in `inputInit` — no OS pointer accel.
- **E — `--help` / `--version`** in `main.c` before `crashInit()`: build
id, usage, the 21-level `-level_XX` table.
- **Verified:** build green (`ntsc-final`); `-level_09` + `-level_20`
boot crash-free 6/6 GE_PCDUMP frames (unregressed); `ge007.ini` gains a
populated `[Window]` block on exit.
## Next task (M-24 — Opus 5)
**Scope call (user, M-23):** D139 (stage unload) + D140 (pause menu / watch)
+56
View File
@@ -0,0 +1,56 @@
# M-26 QoL review — save-on-exit, window persistence, raw mouse, --help, STALLED verdict
Port-layer only. Zero `src/` / game-logic change. Every default reproduces
prior behaviour exactly (all new knobs default-off / default-current).
## A — `atexit()` save-on-exit + `[Window]` persistence
- `port/src/main.c`: `atexit(portAtExit)` registered right after
`configLoad()`. `portAtExit` = `videoSaveWindowState()` then
`configSave()`. The `exit(0)` in `videoPumpEvents` (SDL_QUIT / ESC /
window-close) is reachable, so the handler fires on every clean quit.
Crash / `sysFatalError` paths call `abort()` → handler does **not** run
(intended — don't persist a broken state).
- `port/src/video.c`: new `[Window]` config section — `Width`, `Height`,
`X`, `Y`, `Maximized`. Sentinels reproduce the old hardcoded behaviour:
`Width/Height = 0` → native (640×480 / 640×400), `X/Y = -1` → SDL
centres the window. `videoInit` uses them for the `GfxWindowInitSettings`.
- `videoSaveWindowState()` reads `wmAPI->get_fullscreen_state` /
`get_maximized_state` / `get_dimensions` (all already in the SDL2 wapi
backend) and writes geometry back into the cfg vars. A maximized or
fullscreen window keeps its last *restored* size/pos on disk and only
updates the flag. `Video.Fullscreen` now round-trips too.
- **Verified:** `-level_09` / `-level_20` boot crash-free (6/6 GE_PCDUMP
frames each, unregressed); `ge007.ini` gains a populated `[Window]`
block on exit; relaunch reads it back with no unknown-key warnings.
## B — `level_sweep.sh` STALLED verdict
Pure shell (not built). A level that renders a few frames then freezes
while the process stays alive (the Silo `-level_20` fly-down case,
D117/D134 machine-load sensitivity) previously reported **PASS** because
`last=$(ls ppm/*.ppm | tail -1)` was non-empty. Now the watchdog records
`stalled=1` when the frame count is frozen for `STALL_SECS`, and `alive=1`
if the process was still running when the watchdog gave up →
`STALLED (froze at N/M frames)`. A complete dump set is still a real PASS.
STALLED is retried once (like NO-FRAMES) before it's believed.
## C — raw mouse input (`Input.MouseRawInput`, default 0)
`port/src/input.c inputInit`: when `MouseRawInput=1`, sets
`SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE=0` and
`SDL_HINT_MOUSE_RELATIVE_MODE_WARP=0` before `SDL_SetRelativeMouseMode` —
removes OS pointer acceleration / warp emulation from aim. Off by default
so nothing changes unless the user opts in.
## E — `--help` / `--version`
`port/src/main.c`, handled before `crashInit()`:
- `--version` → rom id / target platform / build hash, exit 0.
- `--help` / `-h` → the above + usage + the 21 solo-level `-level_XX`
table (matches `tools_pc/level_sweep.sh` / `playtest.sh --list`), exit 0.
## Not done (Tier 2/3 from the board)
F (minidump), G (PNG screenshots), H (rumble), I/J (in-render overlay /
toasts) — left for a dedicated session.
+10
View File
@@ -129,6 +129,7 @@ static int mouseTurnSpeed = 100; /* hipfire yaw sensitivity, percent */
static int mouseInvertY = 0; /* 1 = mouse-down looks up */
static int mouseYScale = 100; /* extra vertical (pitch) sensitivity, % */
static int mouseSmoothing = 0; /* 0 = raw; 1..90 = low-pass strength (%) */
static int mouseRawInput = 0; /* 1 = bypass OS pointer accel for aim */
/* Gamepad tuning -- defaults reproduce the old hardcoded constants exactly. */
static int padDeadzone = STICK_DEADZONE; /* left-stick deadzone, raw 0..32767 */
@@ -202,6 +203,14 @@ int inputInit(void)
/* Relative mouse mode for mouse-look. ESC still quits (video.c). */
mouseGrabbed = mouseEnabled;
if (mouseEnabled) {
if (mouseRawInput) {
/* Feed the raw device delta straight through: no OS pointer
* acceleration, no warp-based emulation. Must be set before
* relative mode is enabled. */
SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE, "0");
SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "0");
sysLogPrintf(LOG_INFO, "input: raw mouse input (no OS pointer accel)");
}
SDL_SetRelativeMouseMode(SDL_TRUE);
/* Drain the initial jump. */
SDL_GetRelativeMouseState(NULL, NULL);
@@ -477,6 +486,7 @@ PD_CONSTRUCTOR static void inputConfigInit(void)
configRegisterInt("Input.MouseInvertY", &mouseInvertY, 0, 1);
configRegisterInt("Input.MouseYScale", &mouseYScale, 1, 500);
configRegisterInt("Input.MouseSmoothing", &mouseSmoothing, 0, 90);
configRegisterInt("Input.MouseRawInput", &mouseRawInput, 0, 1);
configRegisterInt("Input.PadDeadzone", &padDeadzone, 0, 30000);
configRegisterInt("Input.PadTriggerPct", &padTriggerPct, 1, 99);
configRegisterInt("Input.PadLookInvertY", &padLookInvertY, 0, 1);
+24 -12
View File
@@ -66,18 +66,22 @@ run_level() {
# known -- exit as soon as the whole dump set is on disk (PASS), or the frame
# count has been frozen for STALL_SECS (level rendered a bit then wedged), or
# the process exits on its own (clean exit / crash).
local waited=0 fcnt=0 prev=0 stall=0
local waited=0 fcnt=0 prev=0 stall=0 stalled=0
while [ $waited -lt $SWEEP_SECS ] && kill -0 $pid 2>/dev/null; do
sleep 1; waited=$((waited + 1))
fcnt=$(ls ppm/*.ppm 2>/dev/null | wc -l)
[ "$fcnt" -ge "$EXPECT_FRAMES" ] && break
if [ "$fcnt" -gt 0 ] && [ "$fcnt" -eq "$prev" ]; then
stall=$((stall + 1)); [ $stall -ge $STALL_SECS ] && break
stall=$((stall + 1))
if [ $stall -ge $STALL_SECS ]; then stalled=1; break; fi
else
stall=0
fi
prev=$fcnt
done
# was the process still alive when we gave up (vs. a clean exit / crash)?
local alive=0
kill -0 $pid 2>/dev/null && alive=1
taskkill //F //IM ge007.x86_64.exe > /dev/null 2>&1
wait $pid 2>/dev/null
sleep 2
@@ -95,7 +99,14 @@ run_level() {
last=$(ls ppm/*.ppm 2>/dev/null | tail -1)
if [ -n "$last" ]; then
pcnt=$(python tools_pc/pixcount.py "$last" 2>/dev/null | grep -oE 'non-clear [0-9]+ \([0-9.]+%\)')
# frames rendered then froze while the process stayed alive -> STALLED,
# not a real PASS (the Silo -level_20 fly-down case, D117/D134 load
# sensitivity). A complete dump set is still a genuine PASS.
if [ "$stalled" = 1 ] && [ "$alive" = 1 ] && [ "$fcnt" -lt "$EXPECT_FRAMES" ]; then
echo "STALLED (froze at $fcnt/$EXPECT_FRAMES frames) $pcnt"
else
echo "PASS $pcnt"
fi
else
echo "NO-FRAMES"
fi
@@ -105,18 +116,19 @@ for entry in $LV; do
name=${entry%%:*}; num=${entry##*:}
status=$(run_level "$name" "$num")
# NO-FRAMES is the flaky outcome (D117 + host load) -- retry once before
# believing it. A CRASH is deterministic enough to report as-is.
if [ "$status" = "NO-FRAMES" ]; then
# NO-FRAMES and STALLED are the flaky outcomes (D117 + host load) -- retry
# once before believing them. A CRASH is deterministic enough to report as-is.
case "$status" in
NO-FRAMES|STALLED*)
sleep 3
retry=$(run_level "$name" "$num")
if [ "$retry" = "NO-FRAMES" ]; then
status="NO-FRAMES (2 attempts)"
else
status="$retry [passed on retry]"
[ -f $LOGS/$name.crash.log ] || true
fi
fi
case "$retry" in
NO-FRAMES) status="NO-FRAMES (2 attempts)" ;;
STALLED*) status="$retry (2 attempts)" ;;
*) status="$retry [passed on retry]" ;;
esac
;;
esac
echo "$name ($num): $status" | tee -a $OUT
done