Say what NX2NAVI_AUDIO_STRICT, the audio_bad log lines and the docs really do, as the review of the audio change found them overstated.

The review found six texts that claimed more than the code does. NX2NAVI_AUDIO_STRICT=1 does not bring 0.10.1 back
exactly: it also ends the process on a limiter attack or release that is not a number and on a mix in use with a
negative buffer count, which 0.10.1 let through, so the cfg.c comment, nx2navi.ini.example, the audren.h audio_bad
comment and the test's comment now say it does so for most of these values. Its FATAL line described the non-strict
outcome ("its sound passes through unchanged", "the renderer goes on") and then ended the process; it now ends with
"NX2NAVI_AUDIO_STRICT=1 ends the process here instead". The audren.c header and docs/architecture.md said nothing the
game sends ends the process, while OpenAudioRenderer's parameters and the request's buffer descriptors still do; both
now say a value in the game's RequestUpdate does not, and name the two that still do. The first log line of a kind
gave "N such values so far", but bad_count counts every update and every render pass that meets any kind, so it now
reads "N times so far for all kinds together"; the renderer_close line and the bad_count comment say the same.
docs/building.md and the test's comment called 0x5299 the one result the SDK hands back; the SDK hands back 0x5299,
0x5499 and 0x5699 and aborts on any other. Texts only: no behaviour changes.

The first commit's message says any other result than 0x5299 makes the SDK abort; it should read any result but
0x5299, 0x5499 and 0x5699. The branch's history is kept, so this message carries the correction.

audio_params_check (the strict runs still end with "FATAL: audio:" and the switch's name), cfg_rows_check (0
problems) and check_content passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-13 19:35:48 -05:00
parent 7e7fed639e
commit 9770d409d5
7 changed files with 27 additions and 22 deletions

View File

@@ -34,10 +34,11 @@ from other projects, with its license, is listed in [THIRD_PARTY_NOTICES.md](../
- The SDK boundary is at the `nn::*` function level: threads and synchronisation, IPC, memory, fs/RomFS, time,
account, applet, hid, audio renderer and effects run as host code. Only `audren:u` and `sm:` are real IPC services.
What the game sends the audio renderer never ends the process (`audren.c` `audio_bad`): an update whose sections do
not match the renderer is refused whole with 0x5299, which the SDK hands back to the game, and a record, effect or
sink whose values the renderer cannot use is left out, passed through or skipped, with one log line per kind.
`NX2NAVI_AUDIO_STRICT=1` ends the process there instead, for bug reports.
A value in the game's RequestUpdate no longer ends the process (`audren.c` `audio_bad`); OpenAudioRenderer's
parameters and the request's buffer descriptors still do. An update whose sections do not match the renderer is
refused whole with 0x5299, which the SDK hands back to the game, and a record, effect or sink whose values the
renderer cannot use is left out, passed through or skipped, with one log line per kind. `NX2NAVI_AUDIO_STRICT=1`
ends the process there instead, for bug reports.
- Window, keyboard and mouse: Win32 (`gfx_window_win.c`, `input_win.c`). Gamepads and audio output: SDL3 (every
connected gamepad is open; the game reads the one pressed last, `input.c`). Voice: libopus. Movies: a host VP9
decoder over libvpx replaces the SDK's `movie::Decoder` (`runtime/ov_movie.c`).

View File

@@ -524,8 +524,8 @@ A change to what the audio renderer does with values the game sends (`runtime/au
It builds `audren.c` and `audio_mixer.c` with the rest of the runtime stubbed and the real effect DSPs linked, and
sends hand-built RequestUpdates through `renderer_handler`. An update whose sections do not match the renderer (sizes,
magic words, overruns, the output buffer, a mix layout with a negative count or more buffers than the renderer has, a
splitter id run whose length wraps in 32 bits) answers 0x5299, the one result the SDK hands back to the game instead
of aborting, and writes no output, changes no renderer state and places no C buffers. A channel, voice or splitter
splitter id run whose length wraps in 32 bits) answers 0x5299, a result the SDK hands back to the game (it aborts on
any result but 0x5299, 0x5499 and 0x5699), and writes no output, changes no renderer state and places no C buffers. A channel, voice or splitter
record with an id the renderer does not have, a wave buffer index of 4, a filter that is not a finite number and a
changed mix with a foreign id are left out of an update that is otherwise applied; a splitter destination id the
renderer does not have is never read (a guard entry before the destination table would make it heard). Each effect and

View File

@@ -871,7 +871,8 @@
# audio_catchup = 1
# When the game sends an audio value this version cannot use (an effect setting out of range, an id it does not have),
# that effect or sound is left out and the log gets one line per kind (0, the default). 1 ends the game there instead,
# with the details in the log, as versions before 0.10.2 did: for bug reports. Takes effect at the next start.
# with the details in the log, as versions before 0.10.2 did for most of them: for bug reports. Takes effect at the
# next start.
# audio_strict = 0
# Volume in percent (0 to 100), on top of the game's own. Changes while the game runs take effect at once.
# volume = 100

View File

@@ -4,9 +4,10 @@
// service validates every section against the header, keeps server-side voice state, advances it in real time (one
// frame of sample_count / sample_rate seconds per render tick, signalling the renderer's event) and writes the output
// status the client expects. The mixer behind it is audio_mixer.c.
// What the game sends is never a reason to end the process (audio_bad): an update whose sections do not match the
// renderer is refused whole with AUDREN_R_INVALID_UPDATE, and a record, effect or sink whose values the renderer
// cannot use is left out of an update that is otherwise applied. NX2NAVI_AUDIO_STRICT=1 ends the process there instead.
// A value in the game's RequestUpdate is not a reason to end the process (audio_bad): an update whose sections do not
// match the renderer is refused whole with AUDREN_R_INVALID_UPDATE, and a record, effect or sink whose values the
// renderer cannot use is left out of an update that is otherwise applied. NX2NAVI_AUDIO_STRICT=1 ends the process there
// instead. OpenAudioRenderer's parameters (need) and the request's buffer descriptors (ipc.c ipc_guest) still end it.
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
@@ -83,9 +84,9 @@ void audio_bad(AudioRenderer *r, int kind, const char *fmt, ...) {
va_end(ap);
if (r->strict) {
ipc_backtrace(); // the game's frame chain when the game's update thread is here; nothing on the render thread
rt_fatal("audio: %s (NX2NAVI_AUDIO_STRICT=1)", what);
rt_fatal("audio: %s; NX2NAVI_AUDIO_STRICT=1 ends the process here instead", what);
}
rt_log("audio: %s; game frame %llu, %llu such values so far; logged once per kind", what,
rt_log("audio: %s; game frame %llu, %llu times so far for all kinds together; logged once per kind", what,
(unsigned long long)game_frame(), (unsigned long long)n);
}
@@ -846,8 +847,8 @@ static void renderer_close(void *p) {
kern_handle_close(r->event);
uint64_t bad = __atomic_load_n(&r->bad_count, __ATOMIC_RELAXED);
if (bad || r->refused)
rt_log("audren: renderer closed after %llu frames, %llu game audio values left out or refused (%llu updates "
"refused)", (unsigned long long)r->elapsed_frames, (unsigned long long)bad,
rt_log("audren: renderer closed after %llu frames, game audio values left out or refused %llu times (%llu "
"updates refused)", (unsigned long long)r->elapsed_frames, (unsigned long long)bad,
(unsigned long long)r->refused);
else
rt_log("audren: renderer closed after %llu frames", (unsigned long long)r->elapsed_frames);

View File

@@ -205,14 +205,15 @@ typedef struct AudioRenderer {
void *mixer;
uint8_t strict; // NX2NAVI_AUDIO_STRICT (OpenAudioRenderer): audio_bad ends the process
uint8_t bad_seen[AB_KINDS]; // audio_bad: the kinds logged so far (atomic exchange; any thread)
uint64_t bad_count; // audio_bad: values left out and updates refused (atomic)
uint64_t bad_count; // audio_bad: times a value was left out (per update or per render pass) or an
// update refused (atomic)
uint64_t refused, refused_run; // updates refused, and refused in a row (the thread that sends RequestUpdate)
} AudioRenderer;
// A value the game sent that this host cannot use: the caller refuses the update or leaves the one record, effect or
// sink out. Logs the first of each kind per renderer, with the game frame, and counts every one; with
// NX2NAVI_AUDIO_STRICT=1 it ends the process instead, as these values did before 0.10.2. Any thread; takes no lock but
// the log's.
// NX2NAVI_AUDIO_STRICT=1 it ends the process instead, as most of these values did before 0.10.2. Any thread; takes no
// lock but the log's.
void audio_bad(AudioRenderer *r, int kind, const char *fmt, ...) __attribute__((format(printf, 3, 4)));
// audio_bad for a refused RequestUpdate, as an expression that gives AUDREN_R_INVALID_UPDATE.
#define AUDIO_REFUSE(r, kind, fmt, ...) \

View File

@@ -295,7 +295,8 @@ static const CfgMeta metas[] = {
{"MICROPHONE", "1", "0|1", RESTART}, // ov_audio_input.c mic_on: 0 refuses the game's input streams
{"AUDIO_RAMP", "1", "0|1", RESTART}, // audio_mixer.c accumulate_ramp: a changed volume is reached across the frame
{"AUDIO_CATCHUP", "1", "0|1", RESTART}, // audren.c render_thread: frames owed after a stall are rendered and dropped
// audren.c audio_bad: 1 ends the process at the first game audio value out of range, as before 0.10.2
// audren.c audio_bad: 1 ends the process at the first game audio value the renderer cannot use, as 0.10.1 did for
// most of them (a limiter time that is not a number and a negative mix buffer count end it too)
{"AUDIO_STRICT", "0", "0|1", RESTART},
// sprint.c: the sprint button (b the game's B, l3 the left stick's click) and toggle or hold; the Controls tab's
// Sprint rows (1.1.1)

View File

@@ -5,8 +5,8 @@
device: NX2NAVI_AUDIO=none mixes without one.
Every RequestUpdate goes through renderer_handler with a hand-built input. A refused update must answer 0x5299 (the
SDK hands that result to the game, which ignores it; any other result makes the SDK abort), write no output and
change no renderer state. A record, effect or sink the renderer cannot use is left out of an update that is
SDK hands that result to the game, which ignores it; the SDK aborts on any result but 0x5299, 0x5499 and 0x5699),
write no output and change no renderer state. A record, effect or sink the renderer cannot use is left out of an update that is
otherwise applied, or passed through or skipped in the render pass. Each kind is logged once per renderer. rt_fatal
prints "FATAL:" and exits with 2, so a value that still ends the process fails the run.
@@ -890,8 +890,8 @@ static void in_range_quiet(void) {
}
}
/* 6. NX2NAVI_AUDIO_STRICT=1: such a value ends the process, as before 0.10.2 (tests/audio_params_check.py runs these
in their own processes and expects exit 2). */
/* 6. NX2NAVI_AUDIO_STRICT=1: such a value ends the process, as most did before 0.10.2 (tests/audio_params_check.py
runs these in their own processes and expects exit 2). */
static int strict(const char *what) {
strict_setting = 1;
AudioRenderer *r = open_renderer();