D252/D314/D291: explosion-debris investigation, overlay wheel race fix, Dam truck notes

- D252 (M-200): the 'rainbow confetti' is the explosion debris
  (explosionInitFlyingParticles). Statically clean for colour; 4 headless
  repros including the user's grenade-launcher trigger are all clean. Still OPEN.
- d320repro (TEMP): new explode= and cheat= actions for headless explosion
  captures; documented in GE-ENV-PROBES.
- D314: optionsOverlayScroll (host thread) rebuilt s_visIdx/s_visN and wrote
  s_sel while the scheduler thread draws the overlay. Wheel notches are now
  queued atomically and applied in HandleInput (D287 pattern). Emit and
  HandleInput share the scheduler thread. Headless file/mission select is
  pixel-stable; likely fixed together with D287, needs a user check.
- D291: truck AI mapped (ai_9 / 0x040a, path 7); headless walk did not reach it.
This commit is contained in:
James Dansereau committed 2026-09-25 23:20:38 -04:00
1 parent d07ed0f2d0
commit aedc98e247
4 files changed
+93 -4

No files matched your search

+1
View File
@@ -71,6 +71,7 @@ Two classes:
| `GE_D235_OFF` | `src/boss.c` (same block) | **TEMP** A/B switch: skips the per-stage `videoResetTextureCache()`, i.e. reverts the D235 fix for one run. For comparing a restart with and without the fix. | **TEMP** (D235; remove when D235 closes) |
| `GE_D235_RELOAD=<frames>` | `src/boss.c` (same block + `bossMainloop` retrace branch) | **TEMP** reload harness: once the title stage loads after the first level (e.g. after a scripted watch Abort), wait `<frames>` retraces, then re-enter that level via `bossSetLoadedStage()`, the front end's own mission-start call. A headless death/restart without the pointer-menu walk. Pair with `GE_INPUTSCRIPT="800:START;1200:A;1300:RIGHT;1400:A"` (abort) on `-level_XX`. | **TEMP** (D235; remove when D235 closes) |
| `GE_D235_NEXT=<stage id>` | `src/boss.c` (with `GE_D235_RELOAD`) | **TEMP**: re-enter a *different* stage after the abort (level A → title → level B), used for D336 (Runway → Frigate). `GE_D85TEX=1` now also logs each stage's texture-pool size and address range (`src/game/initmttex.c`). | **TEMP** |
| `GE_D320R="tick:action;..."` | `port/src/d320repro.c` (hooked in `src/game/lv.c` per-tick PORT block) | **TEMP** forced-repro harness keyed on `g_GlobalTimer`. Actions: `roster`, `jump=<chr>,list=<hexid>,off=<n>`, `bit=<hex>`, `trace=<chr>`, `rename=<chr>,<num>`, `seed=<hi>,<lo>` (D320); `explode=<type>,<dist>` (explosionCreate ahead of Bond) and `cheat=gl|rl|inv|<id>` (game cheat activation) for D252 headless explosion/debris captures. | **TEMP** (D320/D252) |
| `GE_AIMLOG=1` (+ `GE_INPUTSCRIPT` tokens `MDX<n>` `MDY<n>` `RHOLD` `LHOLD` `RREL`) | `port/src/input.c` | D337 aim-model harness: scripted sustained mouse deltas (treated as a grabbed mouse) and aim holds, plus a per-poll line with aim state, active device, the game's drawn crosshair x, `crosshair_x/y_pos` and camera angles. Measures range, edge-scroll and jitter headlessly. | **live** |
| `GE_PERFSTAT=1` | `port/fast3d/gfx_pc.cpp` (`gfx_run`/`gfx_end_frame`), `port/fast3d/gfx_opengl.cpp` (start/end_frame), `src/boss.c` (main loop) | #92/D339 perf probe: per-300-frame averages of render phases (pre / display-list / post / swap+pace), tris and draw batches per frame, game-thread busy time per scheduler message, and a GL_TIME_ELAPSED GPU timer (meaningful only when GPU-bound). | **live** |
| `GE_D75V` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_vertex`, end of the copy/transform loop; cached env check) | D75 (Nintendo logo) per-batch vertex probe: for each `G_VTX` batch in frames `lo..hi`, logs frame#, source pointer, n_vertices, original dest_index, raw ob[0..2] of the first vertex, min/max of the transformed x/y/z/w across the batch, and the MP translation row. Used to prove the transform is correct (M-195) and that every batch encodes v0=0 (slot-overwrite hypothesis). Value form `"lo-hi"` (e.g. `300-900` = the logo screen); unset = off. | **live** (D75) |
+27
View File
@@ -11787,6 +11787,11 @@ Superseded history below (the original OPEN investigation, root cause not found
**v0.4.0 check (2026-09-23): not reachable headlessly.** Bare `-level_33` capture (300–3100 every 70 frames): the truck is parked in the intro flyby (frames 580–720, textures correct) and never drives through the spawn view; it is set off by the player moving toward the checkpoint. Needs the playtest brief item (Dam, watch the truck pass, F12 if glitched).
**2026-09-25 (M-200 session): headless walk attempted, truck not reached; AI mapped.** The truck is the Dam `Vehicle` record (setup index 321, `UsetupdamZ.c:1152`). Its AI list id `0x040a` = `ai_9` (`:2028`, debug-logs "brrm brrm"): `vehicle_start_path(0x07)` + `vehicle_speed(0x0002, 0x7800)`, then sleeps. So it drives patrol path 7 as soon as its AI ticks, and nothing else gates it in script. The "set off by walking" behaviour from the 2026-09-23 note is therefore prop activation (its room coming into play), not an AI trigger. Headless: `GE_INPUTSCRIPT` stick-up is *look* in the default control style; forward is `CUP` pulses (6 polls each, max 64 entries ⇒ ~6 s of walking). A straight walk from spawn drifts onto the ramp by the wall and draws guard fire (`GE_D320R="850:cheat=inv"` keeps Bond alive); no truck in view by frame 3300. Reaching it headless needs a steered route (turn + walk) or a teleport action in the harness; not worth it before a user sighting says which surface glitches. The intro wheel depth-fight (D306/D308, real per `GE_ZF`) remains the only captured truck-area rendering defect. D291 may be that same effect seen in motion.
**Status:** OPEN; still needs the playtest item (Dam: watch the truck pass, F12 screenshot if glitched, note whether it's the wheels, i.e. D306).
## D292 — Death animations are glitched — Bond is often sunk halfway into the floor (M-141, Steam Deck user playtest).
**Death animations are glitched — Bond is often sunk halfway into the floor (M-141, Steam Deck user playtest).** **User confirms this is the same broader "Bond's world model is positioned wrong" problem as D173** — not a new/separate mechanism, another surface of the same known-but-under-investigated bug. The umbrella is already tracked: `GRAPHICS-BACKLOG.md` D148 is explicitly "the tracking entry for the whole cutscene-reliability problem," already lists D173 (floating at level start / Cuba credits) and a Cradle instance ("Bond spins in circles" during the Trevelyan-falls cutscene) under it — findings.md's D173 entry has the fullest investigation detail (M-106's two ruled-out D209-style struct-aliasing candidates, LOW-MEDIUM confidence that class is even the right one). Nothing has actually been lost — the write-up just isn't concentrated in one place (split across `GRAPHICS-BACKLOG.md` D148/D173 and `findings.md` D173), which reads as "known but not detailed" unless you go looking. Sunk-into-floor is a third concrete instance of the family (level-start floating, cutscene spin, now death-pose sinking) — worth keeping as one thread when it's next investigated live, since a single root cause (most likely the render-side puppet/joint transform, not collision) could explain all three.
@@ -11918,6 +11923,18 @@ This is a **perfect, smooth white-hot → yellow → orange → deep-red → fad
**M-188 (2026-09-18, same session as the D219 fix): the user's actual live-observed symptom for this label — "little lingering particle sprites that show up after an explosion ran" (explicitly NOT the main fireball, which the user separately confirmed was already fixed a while back, presumably the D228/M-115 byte-order fix) — is CONFIRMED RESOLVED by the D219 fix.** Traced the mechanism before asking for a retest, not just guessed: `bullet_spark_create(pos, 1, ..., room)` (`explosion_smokeimages`-typed spark, `glass2.c`) is called from `objHit` (`propobj.c:9633`) on any bullet/explosion-damage impact against a world object, plus `chr.c:3408/3414`, `chraction.c:6865`, `chrprop.c:1200` — i.e. the "lingering embers/residue" particles left behind after something is shot or blown up **are** `s_bullet_spark` instances, rendered through the exact same `bullet_spark_render` function D219's fix corrected. User retested live on Facility (destroying an object/explosion) on the post-D219-fix build: "seems fine now." **This row (the lingering-particle-residue manifestation of D252) closes as a duplicate of D219, not a separate bug.** The *original* M-124 "particle effects cycle through a rainbow palette" report bundled at least two distinct code paths under one label (see the M-157/M-158 history above, which already started separating them) — the surviving open question, if any, is only the **main explosion fireball's own palette** (the `explosion.c`/`import_texture_rgba16`/`G_CC_INTERFERENCE` combiner investigation above), which the user says was already fixed separately and has not been re-reported since. Re-open only if a *fireball* (not residue/embers) rainbow report comes in again.
**M-200 (2026-09-25, v0.4.0): the M-191 "rainbow confetti" is identified as the explosion *debris* particles; four headless explosion repros are all clean; that code path is statically clean for colour; still OPEN, with a working repro harness.** (M-191's own text was filed under `## D303` by mistake. Read it there.)
- **What the "confetti" is.** The only confetti-like explosion element is the shrapnel/debris quads: `explosionInitFlyingParticles` spawns them (`explosion.c:1541`, called from `explosionCreate` with `shrapnel_*` sizes), they tumble with random rotation, fall under gravity, and are drawn by `explosionRenderFlyingParticles` (`explosion.c:1722`) via `globalDL_0xa50`. Captured frames show exactly that: dozens of small tumbling flecks over the fireball.
- **Statically clean for colour.** Vertex colour is always grey (`cn[0]=cn[1]=cn[2]`, alpha 0xDC). The texture is `IMAGE_STATIC_NOISE` (IA8 32x32, loaded as IA16 LoadBlock; the texture-cache key includes the render tile's fmt/siz, so it always decodes as greyscale). The combiner is `G_CC_MODULATEIA` 1-cycle. The only way to get hue on this path is a coloured SHADE, i.e. `G_LIGHTING` on at `gSPVertex`, which would make `cn[]` read as a normal and give a colour that changes as the quad rotates. That would fit "rainbow confetti", but the preceding `glassRenderShards` always clears `G_LIGHTING|G_TEXTURE_GEN` and nothing else in the `lv.c` sequence sets it. `struct FlyingParticles` has no pointer fields; the buffer uses `sizeof`; the `(struct FlyingParticles *)(u32)g_FlyingParticlesBuffer` cast (`:1737`) is safe because DRAM is mapped below 4 GiB (`dram.c`). No A1 raw-offset reads exist in `explosion.c`, `glass.c`, `cleanexplosions.c` or `initexplosioncasing.c` (the D219 site in `glass2.c` is fixed in full). Smoke (`g_SmokeTypes[]` rgb), scorch (grey `cn`), and explosion parts (white default vertex, 15 fire/smoke records) are the same: no per-vertex colour source.
- **Measured.** `GE_D157=1` on the Bunker 1 forced-explosion repro: every debris vertex (`cn` alpha 220) has `shaded == cn` and grey, with `LIGHTING=off`. The only `LIGHTING=ON` draws are `geom=0x60205` (texgen: viewmodel/glass), not particles.
- **Repro matrix, all clean** (hue scan: saturated cyan/blue/magenta pixels outside the HUD strip; plus contact sheets checked by eye): (1) Bunker 1, forced `EXPLOSION_DEF_STANDARD` at 500 u; (2) Jungle, 4 forced explosions; (3) Jungle, **the user's original trigger**: 2x grenade launcher cheat + invincibility, 12 grenades fired (`scratch/d252gl_sheet.png`); (4) the same at the user's video settings (1280x720 windowed, MSAA 8, aniso 16, filter 2, FOV 150, native widescreen). Debris is grey/white in every frame, and the fireball and smoke palettes are correct. This is the sixth-plus clean dev-box attempt for this family (cf. M-158's 0/6), so the trigger is environmental or state-dependent and not a fixed mechanism in this path.
- **Ruled-out side lead (worth keeping):** the GL texture cache (`gfx_texture_cache_lookup`) has no content hash for non-CI keys and is cleared only at stage load (D235 M-193). Particle textures load through the main texpool, which is bump-only inside a level (`texLoad`, `image.c:2510`: no eviction; on pool-full it returns `pool->start`). So they can't hit a stale address. Per-weapon pools that reset on weapon switch remain a theoretical stale-hit site (unrelated to explosions).
- **Harness (TEMP, in the existing `GE_D320R`, `port/src/d320repro.c`):** new actions `explode=<type>,<dist>` (calls `explosionCreate` `<dist>` units ahead of Bond) and `cheat=gl|rl|inv|<id>` (the game's own `cheatButtonHandleCheatsTurnedOn`). Real-trigger repro: `GE_D320R="850:cheat=gl;850:cheat=inv" GE_INPUTSCRIPT="950:A;1060:Z;1100:Z;…" GE_PCDUMP="1000-2500:2" ./build-pc/ge007.x86_64.exe -level_37` (A once equips the dual GLs; a second A goes to mines). Scan script: `scratch/d252hue.py`. Do **not** combine with `GE_D157` on Jungle: the per-vertex log drops rendering to ~1 frame/1.5 s.
- **Next (needs the user):** when it is seen live, capture it. Take a screenshot, note the level and what exploded, and ideally run with `GE_D157=1` on an indoor level (logging is too heavy outdoors). The probe logs `LIGHTING`/`shaded` for the debris vertices; a `LIGHTING=ON` line or non-grey `shaded` for an alpha-220 vertex confirms the lighting-leak branch and names the preceding state. If shaded is grey while the screen shows colour, the fault is GL-side (texture binding/sampling), not the vertex stage.
**Status: OPEN (PARTIAL) — unchanged; not reproducible headless across 4 configurations including the user's own trigger; debris-particle path statically clean for colour.**
## D255 — Facility firefight SIGSEGV on Steam Deck (v0.2.0-pre CI bundle; D253 fix confirmed working).
**Facility firefight SIGSEGV on Steam Deck (v0.2.0-pre CI bundle, D253 fix confirmed working — user got past the opening crouch tile).** Crash during gunplay; abort-unwind backtrace points at `sub_GAME_7F09BAC4` (`vtxstore.c:158`) ← `sub_GAME_7F09BBBC` (`:220`) ← `lvlManageMpGame` (`lv.c:1302`) ← `bossMainloop` (`boss.c:567`). Disassembling the exact CI binary at the fault PC (`0x200db135`) gives `mov 0x14(%rax),%edi; mov 0x10(%rdi),%rax`: the function walks the active PropRecord list keeping `type == 1` — which is **PROP_TYPE_OBJ, not CHR** (the decompiler cast to the union's first member and misnamed the field `ChrRecord.chrflags`; it is actually `ObjectRecord.model` at `0x14`) — then dereferences `model->obj`. Two port-relevant wrinkles: (a) the load is **32-bit zero-extended** because the decomp types the field through the `CHRFLAG` enum, so a model pointer ≥ 4 GB would truncate to garbage; (b) the loop skips records whose `model->obj` is NULL but *dereferences* `model` unconditionally, so an active object with a NULL/freed `model` faults — plausible in a firefight where destructibles (crates etc.) die. Not reproduced on WSL after equivalent play. **Register capture (D254 format, 2026-09-13):** two more Deck crashes — Bunker mid-firefight, then Frigate with ~3 NPCs alive; log: `SIGNAL: 11 / FAULT ADDR: 0x10 / Rax=0x701e5f54 Rdi=(nil) / PC 0x200db135`. `Rdi` = the record's model value = **exactly NULL** → case (b) confirmed: an active PROP_TYPE_OBJ record with a NULL `model`, dereferenced by the unconditional `mov 0x10(%rdi),%rax`; truncation (a) and stale/garbage pointers are ruled out. Faulting PC matches the recorded disassembly (`sub_GAME_7F09BAC4` = `vtxstore_fix_refs`). Grep-verified: nothing in game code ever writes NULL into `ObjectRecord.model` — `clear_model_obj()` nulls `model->obj` only, and `objInit`/spawn paths only activate a record once model ≠ NULL — so the NULL comes from either a fresh/uninitialized record slot being activated or a destroy-path ordering race (model freed/nulled while the record is still active this tick). **Fix constraint:** any guard lands in game code (`vtxstore.c`) → needs rule-#2 sign-off; `port/` cannot intercept this dereference. Next: (1) decision on whether a NULL-model skip in `vtxstore_fix_refs` qualifies as an ABI-class exception (semantics-preserving defensive guard, PD analogue check); (2) if yes, add it plus a `#ifdef PORT` trace at the deactivation/model-free sites to name the path that leaves the record model-less. **Corrected, deterministic repro (v0.2.1 user testing, 2026-09-15, superseding the initial report): the trigger is Facility's computer terminals — the ones you ACTIVATE (interact with, not shoot/destroy) to open a door for level progression.** User confirms this is consistent/reliably reproducible on those specific terminals, on Steam Deck **and** Linux generally (not Deck-hardware-specific). This is a much stronger lead than the original "sometimes during firefights" — a terminal activation deactivating/freeing its associated object model while the record briefly stays active this tick is fully consistent with the destroy-path-ordering theory above, just via a scripted trigger instead of combat destruction, and gives a deterministic repro instead of a probabilistic one. **Next session should reproduce via terminal activation specifically**, not generic combat — much cheaper to instrument. **M-140: added a diagnostic probe (`GE_D255`, off by default, `#ifdef PORT`) — `vtxstore.c`'s `sub_GAME_7F09BAC4` now skips-and-logs instead of dereferencing when it finds a NULL model on an active OBJ record (identity + state fields), and `propobj.c`'s `objFree` logs every free call for correlation. Attempted a live Windows repro this session (5 terminal activations across 3 Facility launches, one direct `-level_34` boot each time): zero probe hits, zero crashes — Windows does not reproduce this, consistent with the existing WSL-negative result. Two negatives (WSL, native Windows) against one positive (Linux/Steam Deck) points at something genuinely platform-specific (allocator/timing), not a Windows-reachable path — don't spend further Windows session time chasing a live repro.** The probes are harmless (env-gated, zero behavior change when unset) and committed so a Linux/Deck session can set `GE_D255=1` and get the trace directly. **User report, 2026-09-15 (same day, later, real Steam Deck hardware on the published v0.2.1 release — unverified by this session, logging as reported, not re-asserting the activation theory as wrong):** Facility crashes **consistently on terminals being destroyed** (shot), not (only) on activation. Frigate crashed once during this same testing pass, specifics not recorded. Shooting terminals elsewhere/other attempts did not reliably reproduce. This is closer to the *original* pre-M-139 framing (combat/destructibles triggering the NULL-model race) than the M-139 "corrected, activation-Line truncated
@@ -12466,6 +12483,16 @@ This confirms the M-160/M-164/M-165 hypothesis (`## D173`'s own consolidated-nex
**Status:** OPEN — deprioritized by user request (2026-09-18); probe landed, repro run not currently planned.
**2026-09-25 (v0.4.0, M-200 session): threading audit, one race fixed, headless repro clean; plausibly fixed via D287; owed a live check.**
- **Thread map (verified in source):** `optionsOverlayEmit` runs inside `gfx_run`, called from `osSpTaskStartGo` in `__scMain` (scheduler thread). `optionsOverlayHandleInput` runs from `joyPoll` → `osContGetReadData` → `inputComputePad`, *also* synchronously inside `__scMain` (`sched.c:308/321`; see the `osRecvMesg` note in `libultra.c`). So Emit and HandleInput never race. The host thread (SDL event pump in `videoPumpEvents`) calls `optionsOverlayToggle` (F10) and `optionsOverlayScroll` (wheel).
- **Race fixed:** `optionsOverlayScroll` ran `overlayUpdateVisible()` (`s_visN = 0`, then refill in place) and wrote `s_sel` on the host thread. That can race Emit's draw loop on the scheduler thread, which could draw a truncated row list for a frame or clamp the selection against a half-built `s_visN`. It now only does `SDL_AtomicAdd` of the notches; `overlayApplyWheel()` in HandleInput applies them on the scheduler thread (the D287 pattern). Behaviour is unchanged (one row per notch, clamped, headers skipped). This only mattered while the wheel was moving.
- **Pre-D287 cross-thread SDL calls on this exact path:** HandleInput calls `inputSuspendForOverlay()` on every poll while open. Before `d07ed0f2` (D287, 2026-09-25) that called `SDL_SetRelativeMouseMode`/`SDL_ShowCursor` directly from the scheduler thread while the host thread made its own cursor/focus calls. That's the D287 class, and it fits a "flicker" that was seen only with the overlay open. The D314 report (2026-09-18) predates D287, so D287 may already have fixed it.
- **Headless repro, clean:** `GE_STARTMENU=7` (mission select) and `GE_STARTMENU=5` (file select) with `GE_OPTIONSOVERLAY=1`, `GE_PCDUMP="200-800:1"` at 640x480: 600 consecutive frames each, **0 frames** with a text-level (>120 summed RGB delta, >30 px) change against the previous frame (`scratch/d314diff.py`). The post-fix file-select frame 500 is pixel-identical to pre-fix. So without input the overlay draws deterministically; any real flicker needs input (mouse/cursor) or the in-game→file-select transition.
- **User check (next playtest):** open F10 on file-select and mission-select and move the mouse, click rows, wheel-scroll; also open it in-game, then abort to file-select with it still open. If there's still flicker, note whether it's the overlay rows or the game's menu behind it, and whether it happens with the mouse still.
**Status:** OPEN, likely fixed (D287 + wheel race), pending the user check above.
## D315 — Menu scaling differs between main menu and in-game — main menu scales OK, in-game menu "seems kinda big sometimes" (user report, v0.3.0 playtest build, 2026-09-18).
**Reported:** "Menu scaling can change between main menu / game — main menu scales OK, game seems kinda big sometimes." The "sometimes" is the key word: it tracks some state (video mode? window size? level?) rather than being constant.
+44
View File
@@ -29,6 +29,10 @@
* seed is deterministic, so vary this
* between runs to sample different
* aim-variant branches)
* explode=<type>,<dist> -- D252: explosionCreate(<type>) <dist>
* units in front of Bond (headless
* explosion-particle capture)
* cheat=gl|rl|inv|<id> -- D252: cheatButtonHandleCheatsTurnedOn
* trace=<chr> -- from this tick on, log the chr's
* off/act/atk/ent/frame/endframe on
* any change + a 120-tick heartbeat
@@ -47,6 +51,10 @@
#include "chraction.h"
#include "lv.h"
#include "model.h"
#include "explosion.h"
#include "player.h"
#include "cheat.h"
extern void cheatButtonHandleCheatsTurnedOn(CHEAT_ID cheat_id);
#ifdef PORT
@@ -256,6 +264,25 @@ void d320ReproTick(void)
a->kind = 4;
}
}
else if (strncmp(colon + 1, "cheat=", 6) == 0)
{
const char *q = colon + 7;
a->listid = (strcmp(q, "gl") == 0) ? CHEAT_2X_GRENADE_LAUNCHER
: (strcmp(q, "rl") == 0) ? CHEAT_2X_ROCKET_LAUNCHER
: (strcmp(q, "inv") == 0) ? CHEAT_INVINCIBILITY
: (s32)strtol(q, NULL, 0);
a->kind = 7;
}
else if (strncmp(colon + 1, "explode=", 8) == 0)
{
const char *q = colon + 9;
char *cm = strchr(q, ',');
a->listid = (s32)strtol(q, NULL, 0); /* type */
a->bit = cm ? (u32)strtol(cm + 1, NULL, 0) : 400; /* dist */
a->kind = 6;
}
else if (strncmp(colon + 1, "seed=", 5) == 0)
{
const char *q = colon + 6;
@@ -329,6 +356,23 @@ void d320ReproTick(void)
}
break;
}
case 7: /* cheat (D252) */
cheatButtonHandleCheatsTurnedOn((CHEAT_ID)a->listid);
osSyncPrintf("D320R: t=%d CHEAT %d\n",(int)g_GlobalTimer, (int)a->listid);
break;
case 6: /* explode (D252) */
if (g_CurrentPlayer && g_CurrentPlayer->prop)
{
struct coord3d pos = g_CurrentPlayer->prop->pos;
pos.f[0] += -g_CurrentPlayer->vv_sintheta * (f32)a->bit;
pos.f[2] += g_CurrentPlayer->vv_costheta * (f32)a->bit;
explosionCreate(0, &pos, g_CurrentPlayer->prop->stan, (s16)a->listid,
0, 0, g_CurrentPlayer->prop->rooms, 0);
osSyncPrintf("D320R: t=%d EXPLODE type=%d dist=%u\n",
(int)g_GlobalTimer, (int)a->listid, (unsigned)a->bit);
}
break;
case 2: /* bit */
objectiveregisters1 |= a->bit;
osSyncPrintf("D320R: t=%d BIT |= 0x%08x (now 0x%08x)\n",
+21 -4
View File
@@ -235,6 +235,7 @@ static int s_sel = 0; /* selection, index into s_visIdx (visible list) *
static int s_visIdx[NUM_ROWS];
static int s_visN = 0;
static int s_scroll = 0;
static SDL_atomic_t s_wheelPending; /* D314: host-thread wheel notches */
/* D213: optional on-screen FPS readout (PD parity: Video.DisplayFPS). Drawn
* top-right whenever enabled, independent of the F10 panel. Config-only knob
@@ -696,10 +697,24 @@ void optionsOverlayScroll(int dir)
if (!s_open || dir == 0) {
return;
}
overlayUpdateVisible();
/* wheel-up -> move up the list; clamps at both ends like a normal PC
* settings list (wrapping read as a duplicate) and skips D237 headers. */
s_sel = overlayStepSel(s_sel, (dir > 0) ? -1 : 1);
/* D314: this runs on the host thread (SDL wheel event), while HandleInput
* and Emit rebuild s_visIdx/s_visN/s_scroll on the scheduler thread; the
* old direct overlayUpdateVisible() + s_sel write here could leave Emit
* drawing a half-rebuilt list. Queue the notches instead (D287 pattern);
* optionsOverlayHandleInput() applies them on the scheduler thread. */
SDL_AtomicAdd(&s_wheelPending, dir);
}
/* Scheduler thread: apply wheel notches queued by optionsOverlayScroll().
* wheel-up -> move up the list; clamps at both ends like a normal PC settings
* list (wrapping read as a duplicate) and skips D237 headers. */
static void overlayApplyWheel(void)
{
int n = SDL_AtomicSet(&s_wheelPending, 0);
while (n != 0) {
s_sel = overlayStepSel(s_sel, (n > 0) ? -1 : 1);
n += (n > 0) ? -1 : 1;
}
}
/* Set a slider row from an overlay-space x inside its value bar, snapped to
@@ -738,6 +753,7 @@ void optionsOverlayHandleInput(void)
if (!s_open) {
prevUp = prevDn = prevLf = prevRt = prevLmb = prevRmb = 0;
dragRow = -1;
SDL_AtomicSet(&s_wheelPending, 0);
return;
}
@@ -754,6 +770,7 @@ void optionsOverlayHandleInput(void)
inputSuspendForOverlay();
overlayUpdateVisible(); /* % rows may have appeared/vanished (auto toggles) */
overlayApplyWheel(); /* D314: wheel notches queued by the host thread */
overlayUpdateScroll();
const Uint8 *ks = SDL_GetKeyboardState(NULL);