v0.4.0 M3: GEPD key-layout preset + crouch/reload bindings (D371)

Input.Layout (0=FPS default, 1=GEPD) + Input.CrouchMode (hold/toggle) rows
in F10 INPUT (Turok-standard functional section; the planned MODERN bucket
is dropped per D356). The GEPD preset acts as the per-key effective
default under [Input.Bind] overrides; new IA_RELOAD/IA_CROUCH actions emit
GE_CONT_B (unbound by default, N64-original). Toggle crouch latches B with
a menu-boundary latch drop. optionsoverlay rowSetCommit hook re-derives
the binds via inputLayoutApply(). inputRebuildBinds no longer warns on
intentionally-empty defaults. Boot + ini round-trip + F10 page verified;
in-game feel-check owed (D371).
This commit is contained in:
James Dansereau committed 2026-09-27 16:29:18 -04:00
1 parent c2c6fbdd68
commit 40ef53e395
5 files changed
+160 -3

No files matched your search

+1
View File
@@ -247,3 +247,4 @@ D367,?,F10 full-width control boxes misaligned with their contents (2026-09-27).
D368,?,Simplify F10 click targets:,"INPUT RULE KEPT; tinted strips user-rejected, removed in D369."
D369,FIXED,Remove F10 row boxes/strips entirely (2026-09-27).,FIXED (awaiting live acceptance):
D370,PARTIAL,F10 distance/volume drags hurt frame rate (2026-09-27).,PARTIAL (user live test):
D371,FIXED,"GEPD key-layout preset + reload/crouch bindings, v0.4.0 modern options wave M3 (2026-09-27).",FIXED (headless-verified; awaiting user live accept):
1 # GENERATED by tools_pc/gen_findings_index.py -- do not hand-edit.
247 D368
248 D369
249 D370
250 D371
+48
View File
@@ -655,6 +655,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D368 | **Simplify F10 click targets: setting name selects, the rest changes (2026-09-27).** — full `## D368` entry at file tail | INPUT RULE KEPT; tinted strips user-rejected, removed in D369. |
| D369 | **Remove F10 row boxes/strips entirely (2026-09-27).** — full `## D369` entry at file tail | FIXED (awaiting live acceptance): no row backgrounds, control rectangles or hover paint; selected setting distinguished by brighter text, slider tracks remain. Input rule from D368 unchanged. |
| D370 | **F10 distance/volume drags hurt frame rate (2026-09-27).** — full `## D370` entry at file tail | PARTIAL (user live test): mostly improved; rapid spam can still lower FPS. Unchanged-detent/audio and Draw/LOD reapply fixed; other image sliders still request full video pass (live log shows frequent FOV passes). No further churn without focused repro. |
| D371 | **GEPD key-layout preset + reload/crouch bindings, v0.4.0 modern options wave M3 (2026-09-27).** full `## D371` entry at file tail | FIXED (headless-verified; awaiting user live accept): `Input.Layout` (0=FPS, 1=GEPD) + `Input.CrouchMode` (hold/toggle) in F10 INPUT; GEPD preset acts as per-key effective default under `[Input.Bind]` overrides; new IA_RELOAD/IA_CROUCH emit B (unbound by default). MODERN section dropped per D356 Turok-standard rule (rows land in functional INPUT). Boot/ini-round-trip/F10-page verified; in-game feel-check owed. |
Phase 2 replaced the Phase-1 demo loop with the real `mainproc()` on real OS
threads, compiled GE's real `src/sched.c`, and brought in PD's fast3d software
@@ -14309,3 +14310,50 @@ Residual not isolated: the latest playtest log shows repeated full video
config passes while changing FOV (not Draw/LOD) and several watch EEPROM
persists; sustained high Draw/LOD also increases actual GPU work. Parked as
polish until a focused repro identifies which slider/spam pattern dominates.
## D371 — GEPD key-layout preset + reload/crouch bindings (v0.4.0 modern options wave, M3) (2026-09-27)
**Spec:** `docs/dev/notes/MODERN-OPTIONS-PLAN.md` M3. **Deviation from plan
(user decision, 2026-09-27):** the planned "MODERN" F10 section is DROPPED —
it is a provenance bucket, not a functional category, which conflicts with
the D356 Turok-standard "every section is functional, baseline names" rule
(the same rule that retired the D353 BOND FILE section). The rows land in
the functional section (INPUT); no new category, no `__ResetModern` — the
existing per-section reset rows cover the new keys via `kResetDefaults`.
**Implementation (port-only; `port/src/input.c`, `port/include/input.h`,
`port/src/optionsoverlay.c`):**
- New actions `IA_RELOAD` / `IA_CROUCH` (emits `GE_CONT_B` both; on N64 the
single B button does reload-tap vs crouch-hold, and the game's own
`bond_interact_object` context logic makes that choice — so the port
emits B and leaves the decision to the game, as on the N64). Defaults are
empty strings = unbound (byte-identical N64 layout out of the box).
- `Input.Layout` (0 = FPS defaults, 1 = GEPD) + `Input.CrouchMode`
(0 = hold, 1 = toggle/latched). The GEPD preset
(GEPORT-REFERENCE-DEEPDIVE.md section 7.3: Action=Q, Cancel=E, Reload=R,
Crouch=Left Ctrl, Fire="" — LMB fires/RMB aims, LeanLeft="" — lean is
GEPD-removed) acts as the EFFECTIVE DEFAULT only for actions the user has
not individually overridden in `[Input.Bind]`; per-key ini values layer
on top, so switching layouts never clobbers explicit binds. Revert =
switching back.
- Toggle crouch latches B; the latch is dropped on entering a menu (a
latched B would read as back/cancel) and on `inputLayoutApply()`.
- F10 rows: "Key layout" (kKeyLayout) + "Crouch mode" (reuses kHold) under
INPUT, before its reset row.
- `inputRebuildBinds`: an intentionally-empty string no longer logs the
"action unbound" warning; only a NON-empty string that yields no valid
keys warns (D214's warning was written when no default was ever empty).
- `optionsoverlay.c` `rowSetCommit` hook: `Input.Layout` /
`Input.CrouchMode` -> `inputLayoutApply()` (scheduler thread; a mid-poll
rebuild can at worst drop/add one scancode for one frame — the D214
thread-pairing, just at F10 commit time instead of boot).
**Verification:** NTSC build clean. GEPD boot (`Layout=1`, `CrouchMode=1`,
`Bind.Reload=R`, `Bind.Crouch=Left Ctrl`): no crash, no spurious warnings.
ini round-trip via orderly `GE_QUITFRAME` exit: `Layout`/`CrouchMode`
persist under `[Input]`; `Reload`/`Crouch` under `[Input.Bind]` (config.c
`splitKey` splits on the LAST dot — pre-existing D214 behaviour, binds were
always saved there with bare leaf names). F10 INPUT page
(`GE_OPTIONSOVERLAY=2`) boots clean, both rows resolve. **Owed:** live F10
layout-switch feel-check (GEPD vs FPS), in-game crouch toggle/reload B
semantics + menu-latch-reset, PAL/JP.
+5
View File
@@ -68,6 +68,11 @@ short inputPadAxis(int idx, SDL_GameControllerAxis a);
* ignored -- GE only cycles forward on a bare A edge. */
void inputPostWheel(int notches);
/* v0.4.0 M3: re-derive the keyboard binds after an F10 change to
* Input.Layout / Input.CrouchMode (optionsoverlay rowSetCommit hook).
* Also drops the latched-crouch state. Scheduler thread only. */
void inputLayoutApply(void);
/* D345(b): 1 while the 1:1 menu pointer owns cursor_h/v_pos (in a menu,
* abs pointer available, mouse used within the re-assert window). Port
* screens that teleport the crosshair should skip their snap when set. */
+77 -3
View File
@@ -868,7 +868,12 @@ static int keyDown(const Uint8 *ks, SDL_Scancode sc)
* ---------------------------------------------------------------------- */
enum {
IA_FORWARD, IA_BACK, IA_STRAFE_L, IA_STRAFE_R, IA_TURN_L, IA_TURN_R,
IA_FIRE, IA_AIM, IA_ACTION, IA_CANCEL, IA_LEAN_L, IA_START, IA_COUNT
IA_FIRE, IA_AIM, IA_ACTION, IA_CANCEL, IA_LEAN_L, IA_START,
/* v0.4.0 M3 (modern options wave): GEPD-layout actions. Both map to
* the N64 B button -- the game's own B-context logic
* (bond_interact_object) picks reload (tap, gun in hand, no
* interact target) vs crouch (held) exactly as on the N64. */
IA_RELOAD, IA_CROUCH, IA_COUNT
};
static const struct { const char *key; const char *def; } kBindDefs[IA_COUNT] = {
@@ -884,8 +889,30 @@ static const struct { const char *key; const char *def; } kBindDefs[IA_COUNT] =
[IA_CANCEL] = { "Input.Bind.Cancel", "X,R,F,Escape" },
[IA_LEAN_L] = { "Input.Bind.LeanLeft", "Q" },
[IA_START] = { "Input.Bind.Start", "Return,Tab" },
[IA_RELOAD] = { "Input.Bind.Reload", "" },
[IA_CROUCH] = { "Input.Bind.Crouch", "" },
};
/* v0.4.0 M3: GEPD key-layout preset (docs/dev/notes/GEPORT-REFERENCE-
* DEEPDIVE.md section 7.3). The preset is the EFFECTIVE DEFAULT for any action
* the user has not overridden per-key in [Bind]; a per-key ini value
* layers on top, so switching layouts never clobbers explicit binds.
* Fire loses its Left Ctrl key in this layout (LMB fires, RMB aims;
* Ctrl becomes crouch); LeanLeft is unbound (lean is GEPD-removed).
* NULL entries keep the FPS default. */
static const char *const kGepdPreset[IA_COUNT] = {
[IA_FIRE] = "",
[IA_ACTION] = "Q",
[IA_CANCEL] = "E",
[IA_LEAN_L] = "",
[IA_RELOAD] = "R",
[IA_CROUCH] = "Left Ctrl",
};
static int keyLayout = 0; /* 0 = FPS defaults, 1 = GEPD preset */
static int crouchMode = 0; /* 0 = hold, 1 = toggle (latched B) */
static int s_crouchLatch = 0;
static int s_crouchHeldPrev = 0;
#define BIND_MAX_KEYS 4
static char g_bindStr[IA_COUNT][64];
static SDL_Scancode g_bind[IA_COUNT][BIND_MAX_KEYS];
@@ -896,9 +923,18 @@ static void inputRebuildBinds(void)
for (int k = 0; k < BIND_MAX_KEYS; k++) {
g_bind[a][k] = SDL_SCANCODE_UNKNOWN;
}
/* v0.4.0 M3: the GEPD preset fills the actions the user has not
* individually overridden (empty g_bindStr); FPS layout = the
* kBindDefs defaults, byte-identical to the pre-wave binds. */
const char *def = kBindDefs[a].def;
if (keyLayout == 1) {
const char *preset = kGepdPreset[a];
if (preset) def = preset;
}
char buf[64];
strncpy(buf, g_bindStr[a][0] ? g_bindStr[a] : kBindDefs[a].def, sizeof(buf) - 1);
strncpy(buf, g_bindStr[a][0] ? g_bindStr[a] : def, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = 0;
int hadInput = (buf[0] != 0); /* before strtok clobbers buf */
int n = 0;
for (char *tok = strtok(buf, ","); tok && n < BIND_MAX_KEYS; tok = strtok(NULL, ",")) {
@@ -914,13 +950,32 @@ static void inputRebuildBinds(void)
}
g_bind[a][n++] = sc;
}
if (n == 0) {
/* v0.4.0 M3: an intentionally-empty string (a preset or default
* that leaves an action unbound, e.g. GEPD Fire) is silent --
* only a non-empty string that produced no valid keys warns. */
if (n == 0 && hadInput) {
sysLogPrintf(LOG_WARNING, "input: %s has no valid keys; action unbound",
kBindDefs[a].key);
}
}
}
/* v0.4.0 M3: re-derive the binds after an F10 change to Input.Layout /
* Input.CrouchMode (optionsoverlay rowSetCommit hook, scheduler thread).
* g_bind is read per-frame by the game thread's actHeld(): a mid-poll
* rebuild can at worst drop or add one scancode for one frame (the
* D214 startup-rebuild is the same thread-pairing, just at boot).
* Also drops the crouch latch so a latched crouch never carries B into
* a menu (where B = back/cancel). */
void inputLayoutApply(void)
{
s_crouchLatch = 0;
s_crouchHeldPrev = 0;
inputRebuildBinds();
sysLogPrintf(LOG_INFO, "input: key layout %s, crouch %s applied",
keyLayout ? "GEPD" : "FPS", crouchMode ? "toggle" : "hold");
}
static int actHeld(const Uint8 *ks, int act)
{
for (int k = 0; k < BIND_MAX_KEYS; k++) {
@@ -1126,6 +1181,21 @@ unsigned inputComputePad(int idx, signed char *stick_x, signed char *stick_y)
}
if (actHeld(ks, IA_CANCEL)) /* D145: Escape is in the default Cancel bind */
button |= GE_CONT_B;
/* v0.4.0 M3: reload (B tap, same as any B press) and crouch
* (hold, or a latched toggle). Both are unbound by default (the
* N64-original layout), so this is a no-op out of the box; the
* game's B-context logic decides reload-vs-crouch as on N64. */
if (actHeld(ks, IA_RELOAD))
button |= GE_CONT_B;
int crouchNow = actHeld(ks, IA_CROUCH);
if (crouchMode == 1) {
if (crouchNow && !s_crouchHeldPrev) s_crouchLatch ^= 1;
if (menuMode) s_crouchLatch = 0; /* never carry B into a menu */
if (s_crouchLatch) button |= GE_CONT_B;
s_crouchHeldPrev = crouchNow;
} else if (crouchNow) {
button |= GE_CONT_B;
}
if (actHeld(ks, IA_LEAN_L))
button |= GE_CONT_L;
if (actHeld(ks, IA_START))
@@ -1998,6 +2068,10 @@ PD_CONSTRUCTOR static void inputConfigInit(void)
configRegisterInt("Input.PadDeadzone", &padDeadzone, 0, 30000);
configRegisterInt("Input.PadTriggerPct", &padTriggerPct, 1, 99);
configRegisterInt("Input.PadLookInvertY", &padLookInvertY, 0, 1);
/* v0.4.0 M3 (modern options wave): key-layout preset + crouch bind
* mode; both default to the N64-original FPS layout / hold. */
configRegisterInt("Input.Layout", &keyLayout, 0, 1);
configRegisterInt("Input.CrouchMode", &crouchMode, 0, 1);
/* D214: keyboard rebinding. Seed each buffer with its default so the knob
* is visible/editable in a fresh ge007.ini; configLoad() overwrites any the
+29
View File
@@ -91,6 +91,17 @@ static const char *const kTexFilter[] = { "Nearest", "Bilinear", "3-Point", NULL
static const char *const kAimMode[] = { "N64", "Centred (PC)", NULL }; /* D337 */
static const char *const kAimRange[] = { "PC", "N64", NULL }; /* D338 */
static const int kMsaaSeq[] = { 1, 2, 4, 8 };
/* v0.4.0 modern options wave: value names for the new rows (they land
* in the functional sections -- Turok standard, D356 -- not a bucket). */
static const char *const kOnOffRev[] = { "On", "Off", NULL }; /* 0 = On */
/* M2: named crosshair tints; index 0 = the N64 white (no tint applied). */
static const char *const kCrosshairColor[] = {
"White", "Green", "Red", "Blue", "Yellow", "Cyan", "Magenta",
NULL,
};
/* M3: key-layout preset (0 = the FPS default binds, byte-identical to the
* current defaults; 1 = the GEPD mouse-injector layout) + crouch bind mode */
static const char *const kKeyLayout[] = { "FPS (default)", "GEPD", NULL };
/* D186: the sim's own tick pacemaker is hardcoded to the console's native VI
* rate (60Hz NTSC / 50Hz PAL, port/src/libultra.c) -- Video.FpsCap can only
* throttle down from there, never past it, and throttling it below 30
@@ -190,6 +201,13 @@ static struct Row rows[] = {
{ .key="Input.PadLookInvertY", .label="Invert look (controller)", .kind=ROW_TOGGLE, .step=1, .names=kOnOff },
{ .key="Input.PadDeadzone", .label="Stick deadzone", .kind=ROW_SLIDER, .step=500 },
{ .key="Input.PadTriggerPct", .label="Trigger threshold", .kind=ROW_SLIDER, .step=1 },
/* v0.4.0 M3 (modern options wave, D371): the GEPD mouse-injector
* key-layout preset (docs/dev/notes/GEPORT-REFERENCE-DEEPDIVE.md
* section 7.3) + the crouch bind's fire mode. Turok standard: these are
* control bindings, so they live in INPUT -- no provenance bucket.
* Defaults are the N64-original FPS layout / hold. */
{ .key="Input.Layout", .label="Key layout", .kind=ROW_ENUM, .step=1, .names=kKeyLayout },
{ .key="Input.CrouchMode", .label="Crouch mode", .kind=ROW_ENUM, .step=1, .names=kHold },
{ .key="__ResetInput", .label="Reset to defaults", .kind=ROW_ACTION },
{ .key="__HdrGameplay", .label="GAMEPLAY", .kind=ROW_HEADER },
@@ -726,6 +744,15 @@ static void rowSetCommit(struct Row *r, double v, int commit)
videoRequestLiveConfig();
}
/* v0.4.0 M3: key-layout preset / crouch mode -- re-derive the binds
* (inputRebuildBinds via inputLayoutApply; also drops the crouch latch).
* Safe at scheduler-thread commit time; a mid-poll rebuild can only
* drop/add one scancode for one frame (D214 thread-pairing). */
if (strcmp(r->key, "Input.Layout") == 0 ||
strcmp(r->key, "Input.CrouchMode") == 0) {
inputLayoutApply();
}
/* Linked aim/turn sensitivity (Input.SensLink, default on): moving either
* knob scales the other to hold the stock default ratio -- AimModeSens 38
* : MouseTurnSpeed 50 (the D194/D238 calibrated defaults). */
@@ -871,6 +898,8 @@ static const struct { const char *key; double def; } kResetDefaults[] = {
{ "Input.PadLookInvertY", 0 }, /* = 0 */
{ "Input.PadDeadzone", 7000 },/* = STICK_DEADZONE (7000) */
{ "Input.PadTriggerPct", 23 }, /* = 23 */
{ "Input.Layout", 0 }, /* = 0 (FPS defaults) */
{ "Input.CrouchMode", 0 }, /* = 0 (hold) */
/* GRAPHICS (port/src/video.c initializers) */
{ "Video.MSAA", 2 }, /* = 2 */
{ "Video.TextureFilter", 1 }, /* = 1 (bilinear) */