diff --git a/.github/release-notes.md b/.github/release-notes.md index 80543d33..2e1d2c50 100644 --- a/.github/release-notes.md +++ b/.github/release-notes.md @@ -12,6 +12,25 @@ > missions load and run crash-free, but the known issues below are real; > feedback is very welcome. +### What's new since v0.2.0 + +Small hotfix batch from user playtest feedback on v0.2.0: + +- **Fixed: `All unlocked` + a fresh save produced silent music/SFX** (D259). + A brand-new save now seeds max volume the same way the N64 does, whether + or not `All unlocked` is on. +- **Mouse wheel weapon cycling now matches the on-screen wheel-menu order**: + scroll up = previous weapon, scroll down = next (D260). +- **Watch menu: holding a direction now auto-repeats** instead of crawling + one item per key press/notch (D261). +- **Fixed: the watch menu's item-preview 3D model wasn't rendering** — + weapons now show their model when selected on the watch inventory page + (D264). +- Investigated the Facility report of Ourumov never executing Trevelyan: + confirmed it's proximity/line-of-sight-triggered exactly as on the N64 + (walk toward the room), not a port bug — the full scripted beat, including + the execution shot, fires correctly (D263). + ### What's new since v0.1.0 - **Drop-in ROM, no tooling**: unpack the bundle, drop your NTSC-U (US) ROM @@ -77,6 +96,9 @@ selected** (e.g. the MSAA value appears both on its own row and again at the panel bottom); earlier builds showed it as an intermittent 4K- fullscreen ghost of the top row. +- **Surface 1: the 2D billboard trees near the start render as a solid wall + of tree texture** instead of discrete sprites (D236). Under active + investigation; no fix in this release. - **Steam Deck: one intermittent crash remains**: the v0.1.0-era Facility crash is confirmed fixed on Deck hardware, but a SIGSEGV can occasionally occur during heavy firefights (D255; seen on Bunker and Frigate). The diff --git a/port/src/input.c b/port/src/input.c index 3d8b361b..a97b179f 100644 --- a/port/src/input.c +++ b/port/src/input.c @@ -1177,6 +1177,14 @@ unsigned inputComputePad(int idx, signed char *stick_x, signed char *stick_y) } } + if (idx == 0 && scriptIsActive()) { + /* D263: apply BEFORE the stick is written out -- scripted stick + * tokens (SUP/SDOWN/SLEFT/SRIGHT) used to be discarded. */ + button = scriptApply(button); + sx = scriptCurSX; + sy = scriptCurSY; + } + if (sx > STICK_MAX) sx = STICK_MAX; if (sx < -STICK_MAX) sx = -STICK_MAX; if (sy > STICK_MAX) sy = STICK_MAX; @@ -1185,12 +1193,6 @@ unsigned inputComputePad(int idx, signed char *stick_x, signed char *stick_y) if (stick_x) *stick_x = (signed char)sx; if (stick_y) *stick_y = (signed char)sy; - if (idx == 0 && scriptIsActive()) { - button = scriptApply(button); - sx = scriptCurSX; - sy = scriptCurSY; - } - if (configGetInputLog() && (button || sx || sy)) { sysLogPrintf(LOG_NOTE, "GE_INPUTLOG cont%d: btn=%04x stick=(%d,%d)", idx, button, sx, sy); @@ -1297,11 +1299,13 @@ void inputSuspendForOverlay(void) void inputPostWheel(int notches) { - /* D223: keep the direction -- up cycles to the next weapon, down to the - * previous one. A burst of same-direction notches just re-arms the same - * pulse; a direction change mid-sequence restarts it (last wins). */ - if (notches > 0) { wheelFwd = WHEEL_FWD_POLLS; wheelBack = 0; } - else if (notches < 0) { wheelBack = 2; wheelFwd = 0; } + /* D223: keep the direction. v0.2.1: swapped per user request -- up now + * cycles to the PREVIOUS weapon, down to the NEXT one (PC convention + * "scroll down = advance list"). A burst of same-direction notches just + * re-arms the same pulse; a direction change mid-sequence restarts it + * (last wins). */ + if (notches > 0) { wheelBack = 2; wheelFwd = 0; } + else if (notches < 0) { wheelFwd = WHEEL_FWD_POLLS; wheelBack = 0; } } /* Called from the host event pump on SDL_CONTROLLERDEVICEADDED/REMOVED. diff --git a/port/src/libultra.c b/port/src/libultra.c index e87714da..60991a04 100644 --- a/port/src/libultra.c +++ b/port/src/libultra.c @@ -1278,6 +1278,25 @@ static void geEepromPatchAllCheats(u8 *buf) int changed = 0; for (int i = 0; i < 5; i++) { + /* D259: a fresh ge007.eep is zero-filled, so every slot reads as + * all-zero. Normally such a slot fails fileValidateSaves' CRC and is + * reset to BLANKSAVEDATA (music_vol/sfx_vol = 0xFF); but this patch + * gives the slot a valid CRC below, so it survives with volume 0 + * -> silence. Emulate fileResetSave: seed max volume on all-zero + * slots only -- real saves (incl. a deliberately muted one) keep + * their bytes. */ + { + const u8 *raw = (const u8 *)&slots[i]; + int allzero = 1; + for (int b = 0; b < (int)sizeof(ge_save_slot); b++) + if (raw[b]) { allzero = 0; break; } + if (allzero && (slots[i].music_vol != 0xFF || + slots[i].sfx_vol != 0xFF)) { + slots[i].music_vol = 0xFF; + slots[i].sfx_vol = 0xFF; + changed = 1; + } + } /* Cheat ids are level ids 0..19 (CHEAT_INPUT_BUFFER_SIZE == 20): * bits 0-7 in _1, 8-15 in _2, 16-19 in the low nibble of _3. */ if (slots[i].unlocked_cheats_1 != 0xFF || diff --git a/src/game/gunfire.c b/src/game/gunfire.c index 0f59d0ed..8b0eb4d9 100644 --- a/src/game/gunfire.c +++ b/src/game/gunfire.c @@ -1721,7 +1721,21 @@ void gunRenderFirstPersonGunModels(Gfx **gdlptr) Gfx *set_enviro_fog_for_items_in_solo_watch_menu(Gfx *gdl, ITEM_IDS itemid, Mtxf *mtx, s32 arg3, s32 arg4) { +#ifdef PORT + /* D264 (ABI/layout class, cf. D140): on N64 this 64-byte copy spans two + * adjacent globals — D_80035D00 (zero) and D_80035D04 {1, 3, 0..} — i.e. + * the ModelRenderData template documented above D_80035D00 in gun.c + * (zbufferenabled=TRUE, flags=3, rest zero; CULLMODE_BOTH is 0). The PC + * link puts them in separate .bss/.data sections, so the read was all + * zeros: flags==0 gates every geometry node in subdraw() and the watch + * item preview emitted nothing. Use the explicit template (identical + * values to the N64 ROM bytes). */ + ModelRenderData renderdata = {0}; + renderdata.zbufferenabled = TRUE; + renderdata.flags = 3; +#else ModelRenderData renderdata = *((ModelRenderData *) (&D_80035D00)); +#endif ModelHeader model; u8 spb8[0x80]; s32 padb4; diff --git a/src/game/options.c b/src/game/options.c index dd0cce0a..28bc3b86 100644 --- a/src/game/options.c +++ b/src/game/options.c @@ -67,6 +67,44 @@ #define GE_WATCH_STICK_FASTDOWN(pad) (joyGetStickY(pad) < -0x46) #endif +#ifdef PORT +/* v0.2.1 (user report: "watch menu sensitivity for wasd/controller"): D118d + * removed the N64 slam-the-stick fast scroll because digital sources peg + * past its threshold -- but that left |stickY| >= 0x46 with NO repeat at all + * (the latched path steps once per press), so holding W/S or a full stick + * press crawled one item per tap through the list. Restore fast navigation + * as a bounded auto-repeat: while the stick is held past the smooth-scroll + * band (0x1f..0x45, untouched), step one extra item every GE_WATCH_NAV_RATE + * frames after an initial GE_WATCH_NAV_DELAY hold. The first press still goes + * through the latched single-step path, so a tap behaves exactly as before. + * Returns -1 (up) / +1 (down) on repeat frames, 0 otherwise. */ +#define GE_WATCH_NAV_DELAY 15 +#define GE_WATCH_NAV_RATE 6 +static int s_watchNavHoldDir = 0; /* -1 up, +1 down, 0 none */ +static int s_watchNavHoldFrames = 0; + +static s32 geWatchStickFastStep(void) +{ + int dir = 0; + if (joyGetStickY(PLAYER_1) >= 0x46) dir = -1; + else if (joyGetStickY(PLAYER_1) <= -0x45) dir = 1; + + if (dir != s_watchNavHoldDir) { + s_watchNavHoldDir = dir; + s_watchNavHoldFrames = 0; + return 0; /* new direction: the latched press path already stepped */ + } + if (dir == 0) + return 0; + if (++s_watchNavHoldFrames > GE_WATCH_NAV_DELAY && + s_watchNavHoldFrames % GE_WATCH_NAV_RATE == 0) + return dir; + return 0; +} +#else +static s32 geWatchStickFastStep(void) { return 0; } +#endif + // bss Mtx gfx_background_8007B0A0; Mtx gfx_background_8007B0E0; @@ -1107,6 +1145,19 @@ after_updown: watch_inventory_cursor_pos += 1.0f; } +#ifdef PORT + /* v0.2.1: bounded auto-repeat for a stick held past the smooth band + * (see geWatchStickFastStep). */ + { + s32 fast = geWatchStickFastStep(); + if (fast < 0 && g_curWatchItemIndex > 0 && !watch_item_is_actively_selected) + watch_inventory_cursor_pos += (f32) fast; + else if (fast > 0 && (s32) watch_inventory_cursor_pos < count - 1 + && !watch_item_is_actively_selected) + watch_inventory_cursor_pos += (f32) fast; + } +#endif + if (is_holding_less_than_10_up_on_stick() || is_holding_less_than_10_down_on_stick()) { watch_stick_y_prev_active = 1;