options UI: settle file terminology on the end-user term 'Profile' (D359)

'Game file' (the manual's term) reads, in modern PC usage, as the
program's own files, not the user's save. 'Profile' is the clearest
end-user term for what these files actually are -- a per-player
bundle of progress + per-file settings -- and '(per profile)'
expresses the tag's scope where '(save)' read as a verb.

Strings (both UIs: front options + F10 overlay):
- front top row: 'Save file' / 'File N'  ->  'Profile' / 'N'
- section annotation: '(File N)' / '(no file)'  ->  '(Profile N)' / '(none)'
- per-file row tag: '(game file)'  ->  '(per profile)'
- F10 unavailable value: 'Select game file'  ->  'Select a profile'

Display layer only: identifiers and ini keys (Bond.* rows,
watchSettings*, ini) are untouched. D357 plan section 4 is the
terminology record; new D359 finding + index row; manual
'game file' -> 'profile' sync noted as a TODO (port-owned doc,
not in this repo). Build + headless smoke clean.
This commit is contained in:
James Dansereau committed 2026-09-27 15:03:24 -04:00
1 parent 44d2b4353a
commit d8971fded0
5 files changed
+85 -44

No files matched your search

+26 -17
View File
@@ -8,10 +8,11 @@ user notes, not checked independently*), and the N64 watch (ground truth for
the `Bond.*` rows only). Triggered by maintainer report: the current values the `Bond.*` rows only). Triggered by maintainer report: the current values
are "a bit confusing". are "a bit confusing".
Companion change landed with this doc's review: the dim per-file row tag is Companion change landed with this doc's review: the terminology settled on
now **"(game file)"** (was "(save)"); the front top row reads **"Game **"Profile"** as the end-user term for a per-player game file -- the
file"** and the F10 unavailable value reads **"Select game file"** — the front top row reads **Profile**, the per-file row tag reads
instruction manual's term (§4). **"(per profile)"** and the F10 unavailable value reads **"Select a
profile"** (§4).
## 1. Problem ## 1. Problem
@@ -113,23 +114,31 @@ ranges (2026-09-27). "Display" = what the value column shows today.
| Frame rate cap | 60 · 0–1000 · 1 · `Uncapped/N FPS` | Turok: "up to 120 FPS" / uncapped | **Preset grid**: 0/30/60/120/144 (step = next preset, wraps to Uncapped); value text already `N FPS`/`Uncapped`. Decision: preset grid vs free 1-step (recommend presets; 117 FPS has no purpose). | | Frame rate cap | 60 · 0–1000 · 1 · `Uncapped/N FPS` | Turok: "up to 120 FPS" / uncapped | **Preset grid**: 0/30/60/120/144 (step = next preset, wraps to Uncapped); value text already `N FPS`/`Uncapped`. Decision: preset grid vs free 1-step (recommend presets; 117 FPS has no purpose). |
| Show FPS | Off/On | both | Keep. | | Show FPS | Off/On | both | Keep. |
## 4. Terminology (instruction manual = "game file") ## 4. Terminology (end-user term = "Profile")
The instruction manual calls the per-player save a **"game file"**; the Decision (2026-09-27, UX review): the end-user term for a per-player
settings UI anchors on that term (2026-09-27, maintainer request). Final game file is **"Profile"** — the per-player bundle of progress +
string set: per-file settings. Rejected: **"game file"** (the instruction manual's
current term) reads, in modern PC usage, as the *program's* own files
("Verify integrity of game files"), not the user's save; **"Save"** was
the fallback (universally understood) but reads as a verb in the row tag
and as progress-only, which these files are not. "Profile" covers both
and fits the dossier styling of the screen.
| String | Where | State | Final string set (both UIs: front options + F10 overlay):
|---|---|---|
| **"Game file"** | front options top-row label (was "Save file") | changed | | String | Where |
| **"(game file)"** | per-file row tag, both UIs (was "(save)", briefly "(profile)") | changed | |---|---|
| **"Select game file"** | F10 unavailable value (was "Select file") | changed | | **"Profile"** + value `N` | front options top row (was "Save file: File N") |
| `File N` | row values + the `(File N)` section annotation | kept (value-column short form, PD "Player N" pattern) | | **"(Profile N)"** / **"(none)"** | section-title annotation on save-scoped sections (was "(File N)"/"(no file)") |
| `(no file)` | unavailable state, dim annotation | kept | | **"(per profile)"** | per-file row tag (was "(save)", then "(profile)", then "(game file)") |
| **"Select a profile"** | F10 unavailable value (was "Select file") |
**Manual sync TODO:** the instruction manual's "game file" should be
updated to "profile" (the manual is a port document we own).
"Bond file" remains in code identifiers/config keys (`Bond.*` rows, "Bond file" remains in code identifiers/config keys (`Bond.*` rows,
`watchSettingsChooseFile`, ini keys) only -- never user-visible; the manual `watchSettingsChooseFile`, ini keys) only -- never user-visible.
and UI say "game file".
## 5. Gates ## 5. Gates
+1
View File
@@ -235,3 +235,4 @@ D354,FIXED,Front-end F10 Bond rows silently disabled:,"FIXED (port-only, port/sr
D355,FIXED,F10/options Music slider inaudible in-stage:,"FIXED (port-only, port/src/watchsettings.c):" D355,FIXED,F10/options Music slider inaudible in-stage:,"FIXED (port-only, port/src/watchsettings.c):"
D356,FIXED,"Settings regroup: Turok-style pages renamed to functional sections (INPUT/GAMEPLAY/GRAPHICS/AUDIO/VIDEO) with PD-style ""(File N)"" scoping annotations, the redu…","FIXED (port-only, port/src/optionsoverlay.{h,c} + port/src/watchsettings.{h,c} + port/src/frontoptions.c; plan docs/dev/D356-SETTINGS-REGROUP-PLAN.md):" D356,FIXED,"Settings regroup: Turok-style pages renamed to functional sections (INPUT/GAMEPLAY/GRAPHICS/AUDIO/VIDEO) with PD-style ""(File N)"" scoping annotations, the redu…","FIXED (port-only, port/src/optionsoverlay.{h,c} + port/src/watchsettings.{h,c} + port/src/frontoptions.c; plan docs/dev/D356-SETTINGS-REGROUP-PLAN.md):"
D358,FIXED,D356 level-1 overlap:,"FIXED (port-only, port/src/frontoptions.c):" D358,FIXED,D356 level-1 overlap:,"FIXED (port-only, port/src/frontoptions.c):"
D359,PARTIAL,"Settings UI file terminology settled on the end-user term ""Profile"":","LANDED (port-only, frontoptions.c + optionsoverlay.c + comments):"
1 # GENERATED by tools_pc/gen_findings_index.py -- do not hand-edit.
235 D355
236 D356
237 D358
238 D359
+32 -4
View File
@@ -643,6 +643,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D355 | **F10/options Music slider inaudible in-stage: the slider drives only the X-track (track 2) via `set_mTrack2Vol`, but in-stage BGM is track 1, which the game pins to `VOLUME_MAX` at level start (lv.c:364) and never rescales from `mTrack2Vol` in solo -- so neither the N64 watch nor the F10 could change the music the player was hearing; maintainer-reported (2026-09-27).** — full `## D355` entry at file tail | FIXED (port-only, `port/src/watchsettings.c`): the Music commit now also calls `musicTrack1ApplySeqpVol(v)` (the exact resync the MP path already does, mpmenu.c:354) and a once-per-activation resync runs on stage entry; by-ear confirmed by the maintainer. N64 watch path untouched. | | D355 | **F10/options Music slider inaudible in-stage: the slider drives only the X-track (track 2) via `set_mTrack2Vol`, but in-stage BGM is track 1, which the game pins to `VOLUME_MAX` at level start (lv.c:364) and never rescales from `mTrack2Vol` in solo -- so neither the N64 watch nor the F10 could change the music the player was hearing; maintainer-reported (2026-09-27).** — full `## D355` entry at file tail | FIXED (port-only, `port/src/watchsettings.c`): the Music commit now also calls `musicTrack1ApplySeqpVol(v)` (the exact resync the MP path already does, mpmenu.c:354) and a once-per-activation resync runs on stage entry; by-ear confirmed by the maintainer. N64 watch path untouched. |
| D356 | **Settings regroup: Turok-style pages renamed to functional sections (INPUT/GAMEPLAY/GRAPHICS/AUDIO/VIDEO) with PD-style "(File N)" scoping annotations, the redundant N64-watch rows (`Bond.Look`, `Bond.AimControl`) off the menu surface, and a real per-section Reset-to-defaults (two-step arm->confirm, watch rows to BLANKSAVEDATA via the D352 commit path, ini rows to the port's C initializers; `__Resolution` a documented exclusion) replacing the Turok reset rows the PC build never honored (2026-09-27).** — full `## D356` entry at file tail | FIXED (port-only, `port/src/optionsoverlay.{h,c}` + `port/src/watchsettings.{h,c}` + `port/src/frontoptions.c`; plan `docs/dev/D356-SETTINGS-REGROUP-PLAN.md`): 5 sections all below MAX_PROWS, hidden rows D181/D216-style (keys + watch paths stay live), edge-triggered reset with 3 s arm window + maintain/clear disarm, GE_WSPROBE_RESET front + in-stage probes headless-verified (all sections failures=0, scope isolation OK, saved bytes 0xFF), GE_WSPROBE/GE_WSPROBE_FRONT regressions clean. | | D356 | **Settings regroup: Turok-style pages renamed to functional sections (INPUT/GAMEPLAY/GRAPHICS/AUDIO/VIDEO) with PD-style "(File N)" scoping annotations, the redundant N64-watch rows (`Bond.Look`, `Bond.AimControl`) off the menu surface, and a real per-section Reset-to-defaults (two-step arm->confirm, watch rows to BLANKSAVEDATA via the D352 commit path, ini rows to the port's C initializers; `__Resolution` a documented exclusion) replacing the Turok reset rows the PC build never honored (2026-09-27).** — full `## D356` entry at file tail | FIXED (port-only, `port/src/optionsoverlay.{h,c}` + `port/src/watchsettings.{h,c}` + `port/src/frontoptions.c`; plan `docs/dev/D356-SETTINGS-REGROUP-PLAN.md`): 5 sections all below MAX_PROWS, hidden rows D181/D216-style (keys + watch paths stay live), edge-triggered reset with 3 s arm window + maintain/clear disarm, GE_WSPROBE_RESET front + in-stage probes headless-verified (all sections failures=0, scope isolation OK, saved bytes 0xFF), GE_WSPROBE/GE_WSPROBE_FRONT regressions clean. |
| D358 | **D356 level-1 overlap: the front options save-file row (item 0) painted under the first content row, and `s_hl` (item-numbered) was indexed straight into `s_rowIdx` (content-numbered) -- wrong-row adjusts, a dead last row, a one-past read, wrong highlight (2026-09-27).** — full `## D358` entry at file tail | FIXED (port-only, `port/src/frontoptions.c`): content rows draw at `rowY(k+1)`; all `s_hl` -> `s_rowIdx` uses take `s_hl-1`; A-press guards `s_hl >= 1 && s_hl <= s_rowN`; hit-test/cursor untouched (already item-numbered). Companion: "(save)" tag -> "(profile)". | | D358 | **D356 level-1 overlap: the front options save-file row (item 0) painted under the first content row, and `s_hl` (item-numbered) was indexed straight into `s_rowIdx` (content-numbered) -- wrong-row adjusts, a dead last row, a one-past read, wrong highlight (2026-09-27).** — full `## D358` entry at file tail | FIXED (port-only, `port/src/frontoptions.c`): content rows draw at `rowY(k+1)`; all `s_hl` -> `s_rowIdx` uses take `s_hl-1`; A-press guards `s_hl >= 1 && s_hl <= s_rowN`; hit-test/cursor untouched (already item-numbered). Companion: "(save)" tag -> "(profile)". |
| D359 | **Settings UI file terminology settled on the end-user term "Profile": the screen mixed five terms ("Save file"/"File N"/"(profile)"/"Select file"/"(no file)"), and the manual's "game file" reads as the program's own files in modern PC usage (2026-09-27).** — full `## D359` entry at file tail | LANDED (port-only, `frontoptions.c` + `optionsoverlay.c` + comments): top row `Profile`/`N`, annotation `(Profile N)`/`(none)`, per-file row tag `(per profile)`, F10 value `Select a profile`; identifiers/ini keys unchanged (display-layer only). Manual "game file" -> "profile" sync TODO (port-owned doc, not in repo). |
Phase 2 replaced the Phase-1 demo loop with the real `mainproc()` on real OS 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 threads, compiled GE's real `src/sched.c`, and brought in PD's fast3d software
@@ -13965,7 +13966,34 @@ and are untouched. Verified by build + headless boot smoke; visual check
of the screen owed (D357 plan gate 4). of the screen owed (D357 plan gate 4).
**Companion change (same commit):** the per-file row tag now reads **Companion change (same commit):** the per-file row tag now reads
"(profile)" (was "(save)", maintainer request), both UIs. (Later the same "(profile)" (was "(save)", maintainer request), both UIs. (Full term
day the tag and the rest of the file terminology were anchored on the history: "(save)" -> "(profile)" -> "(game file)" -> the final
instruction manual's "game file" term: "(game file)" tag, "Game file" top "(per profile)" set anchored on the end-user "Profile" term -- D357
row, "Select game file" -- D357 plan §4.) plan section 4.)
## D359 — Settings UI file terminology settled on the end-user term "Profile" (2026-09-27)
**Reported:** maintainer English/terminology review of the settings UI:
the screen mixed five terms for the same thing ("Save file" label,
"File N" values, "(profile)" tag, "Select file" fallback, "(no file)"
state), and the instruction manual's "game file" was questioned for
clarity.
**Decision:** the end-user term is **"Profile"** — a per-player bundle
of progress + per-file settings. "Game file" rejected: in modern PC
usage it reads as the program's own files (Steam's "Verify integrity of
game files"), not the user's save. "Save" was the fallback but reads as
a verb in the row tag and implies progress-only. Rationale + the full
string table: `docs/dev/D357-SETTINGS-VALUES-PLAN.md` §4.
**Landed (port-only, `frontoptions.c` + `optionsoverlay.c`, both draw
paths + comments):** top row `Profile` / value `N` (was `Save file` /
`File N`); section-title annotation `(Profile N)` / `(none)` (was
`(File N)` / `(no file)`); per-file row tag `(per profile)` (was
`(save)` -> `(profile)` -> `(game file)`); F10 unavailable value
`Select a profile` (was `Select file`). Code identifiers and ini keys
(`Bond.*` rows, `watchSettings*`, ini) are unchanged — terminology is a
display-layer concern only (principle 3).
**TODO:** update the instruction manual's "game file" to "profile"
(port-owned doc, not in this repo).
+14 -12
View File
@@ -495,16 +495,17 @@ Gfx *frontOptionsMenuDraw(Gfx *DL)
if (optionsRowIsSaveScoped(s_pageHdr[s_page])) { if (optionsRowIsSaveScoped(s_pageHdr[s_page])) {
int f = watchSettingsActiveFolder(); int f = watchSettingsActiveFolder();
char note[16]; char note[16];
if (f < 0) snprintf(note, sizeof(note), "(no file)"); if (f < 0) snprintf(note, sizeof(note), "(none)");
else snprintf(note, sizeof(note), "(File %d)", f + 1); else snprintf(note, sizeof(note), "(Profile %d)", f + 1);
DL = ink(DL, ROW_X + titleW + 8, TITLE_Y, note, INK_DIM); DL = ink(DL, ROW_X + titleW + 8, TITLE_Y, note, INK_DIM);
} }
/* D356: the top game-file row (item 0, not in the row table; the /* D356: the top profile row (item 0, not in the row table; the label
* label reads "Game file", the instruction manual's term): the * reads "Profile" -- the end-user term for a per-player game file,
* single file control of the front screen (F10 always targets the * see D357 plan section 4): the single file control of the front
* active file, so it has no file row). L/R (and A) cycle folders; * screen (F10 always targets the active file, so it has no file
* (no file) is the unavailable state until one is created. */ * row). L/R (and A) cycle folders; (none) is the unavailable state
* until one is created. */
{ {
int y = rowY(0); int y = rowY(0);
char sv[32]; char sv[32];
@@ -512,10 +513,10 @@ Gfx *frontOptionsMenuDraw(Gfx *DL)
DL = microcode_constructor_related_to_menus(DL, ROW_X - 2, y - 1, DL = microcode_constructor_related_to_menus(DL, ROW_X - 2, y - 1,
ROW_X + 140, y + 0xE, HILITE); ROW_X + 140, y + 0xE, HILITE);
} }
DL = ink(DL, ROW_X, y, "Game file\n", INK); DL = ink(DL, ROW_X, y, "Profile\n", INK);
int f = watchSettingsActiveFolder(); int f = watchSettingsActiveFolder();
if (f < 0) snprintf(sv, sizeof(sv), "(no file)"); if (f < 0) snprintf(sv, sizeof(sv), "(none)");
else snprintf(sv, sizeof(sv), "File %d\n", f + 1); else snprintf(sv, sizeof(sv), "%d\n", f + 1);
DL = inkR(DL, VAL_R, y, sv, INK); DL = inkR(DL, VAL_R, y, sv, INK);
} }
@@ -534,9 +535,10 @@ Gfx *frontOptionsMenuDraw(Gfx *DL)
DL = ink(DL, ROW_X, y, label, INK); DL = ink(DL, ROW_X, y, label, INK);
/* D357: per-file rows in the mixed-scope section carry a dim /* D357: per-file rows in the mixed-scope section carry a dim
* "(game file)" tag (the section title already says which file). */ * "(per profile)" tag -- "stored separately in each profile";
* the section title says which one is active. */
if (optionsRowIsSaveScoped(i)) { if (optionsRowIsSaveScoped(i)) {
DL = ink(DL, ROW_X + measureW(label) + 4, y, "(game file)\n", INK_DIM); DL = ink(DL, ROW_X + measureW(label) + 4, y, "(per profile)\n", INK_DIM);
} }
if (optionsRowIsSlider(i)) { if (optionsRowIsSlider(i)) {
+12 -11
View File
@@ -146,7 +146,7 @@ struct Row {
int dispDiv; int dispDiv;
/* D356: 1 = this row's value lives in the selected save file (per-file /* D356: 1 = this row's value lives in the selected save file (per-file
* watch rows). Carries the dim "(game file)" tag on the only section that * watch rows). Carries the dim "(per profile)" tag on the only section that
* mixes scopes (GAMEPLAY); on headers the flag is recomputed at init to * mixes scopes (GAMEPLAY); on headers the flag is recomputed at init to
* mean "this section contains per-file rows" (drives the "(File N)" * mean "this section contains per-file rows" (drives the "(File N)"
* title annotation in both UIs). */ * title annotation in both UIs). */
@@ -158,7 +158,7 @@ static struct Row rows[] = {
* GAMEPLAY, GRAPHICS, AUDIO, VIDEO; docs/dev/D356-SETTINGS-REGROUP-PLAN.md). * GAMEPLAY, GRAPHICS, AUDIO, VIDEO; docs/dev/D356-SETTINGS-REGROUP-PLAN.md).
* The D353 "BOND FILE" section -- the one named after WHERE a value is * The D353 "BOND FILE" section -- the one named after WHERE a value is
* saved -- is gone: its surviving rows split into GAMEPLAY (the per-file * saved -- is gone: its surviving rows split into GAMEPLAY (the per-file
* watch toggles, dim "(game file)" tagged since the section mixes scopes) and * watch toggles, dim "(per profile)" tagged since the section mixes scopes) and
* AUDIO (the per-file volume sliders, homogeneous so title annotation * AUDIO (the per-file volume sliders, homogeneous so title annotation
* only), and the "Edit file" chooser row is retired in favour of the * only), and the "Edit file" chooser row is retired in favour of the
* single top save-file row on the front options screen (frontoptions.c). * single top save-file row on the front options screen (frontoptions.c).
@@ -198,7 +198,7 @@ static struct Row rows[] = {
* Bond.AimControl collides with Input.AimMode (two rows fighting over * Bond.AimControl collides with Input.AimMode (two rows fighting over
* the same N64-vs-PC aim model). * the same N64-vs-PC aim model).
* The four surviving per-file toggles move here from the D353 BOND FILE * The four surviving per-file toggles move here from the D353 BOND FILE
* section and carry .saveScoped (dim "(game file)" tag, the only mixed-scope * section and carry .saveScoped (dim "(per profile)" tag, the only mixed-scope
* section). */ * section). */
/* { .key="Bond.Look", .label="Look up/down (watch; stacks)", .kind=ROW_TOGGLE, /* { .key="Bond.Look", .label="Look up/down (watch; stacks)", .kind=ROW_TOGGLE,
.names=kReverse, .found=1, .uiMax=1, .cfgMax=1 }, */ .names=kReverse, .found=1, .uiMax=1, .cfgMax=1 }, */
@@ -1231,12 +1231,12 @@ static void valueText(int i, char *out, int n)
double v = rowGet(r); double v = rowGet(r);
if (r->kind == ROW_BOND_FILE) { if (r->kind == ROW_BOND_FILE) {
int f = watchSettingsFolder(); int f = watchSettingsFolder();
if (f < 0) snprintf(out, n, "Select game file"); if (f < 0) snprintf(out, n, "Select a profile");
else snprintf(out, n, "File %d", f + 1); else snprintf(out, n, "%d", f + 1);
return; return;
} }
if (watchSettingsFieldForKey(r->key) >= 0 && !watchSettingsAvailable()) { if (watchSettingsFieldForKey(r->key) >= 0 && !watchSettingsAvailable()) {
snprintf(out, n, "Select game file"); snprintf(out, n, "Select a profile");
return; return;
} }
if (r->kind == ROW_RES) { if (r->kind == ROW_RES) {
@@ -1428,8 +1428,8 @@ Gfx *optionsOverlayEmit(void)
if (r->saveScoped) { if (r->saveScoped) {
int f = watchSettingsActiveFolder(); int f = watchSettingsActiveFolder();
char note[16]; char note[16];
if (f < 0) snprintf(note, sizeof(note), "(no file)"); if (f < 0) snprintf(note, sizeof(note), "(none)");
else snprintf(note, sizeof(note), "(File %d)", f + 1); else snprintf(note, sizeof(note), "(Profile %d)", f + 1);
gdl = drawText(gdl, OV_X0 + measureText(r->label) + 6, rowY, note, 0x909090ff); gdl = drawText(gdl, OV_X0 + measureText(r->label) + 6, rowY, note, 0x909090ff);
} }
continue; continue;
@@ -1437,10 +1437,11 @@ Gfx *optionsOverlayEmit(void)
gdl = drawText(gdl, OV_LABEL_X, rowY, (char *)r->label, gdl = drawText(gdl, OV_LABEL_X, rowY, (char *)r->label,
r->found ? col : 0x808080ff); r->found ? col : 0x808080ff);
/* D357: per-file rows in the mixed-scope section carry a dim /* D357: per-file rows in the mixed-scope section carry a dim
* "(game file)" tag (the section title already says which file). */ * "(per profile)" tag -- "stored separately in each profile";
* the section title says which one is active. */
if (r->saveScoped && r->kind != ROW_HEADER) { if (r->saveScoped && r->kind != ROW_HEADER) {
gdl = drawText(gdl, OV_LABEL_X + measureText(r->label) + 5, rowY, gdl = drawText(gdl, OV_LABEL_X + measureText(r->label) + 5, rowY,
"(game file)", 0x909090ff); "(per profile)", 0x909090ff);
} }
if (!r->found) { if (!r->found) {
gdl = drawTextR(gdl, right, rowY, "(n/a)", 0x808080ff); gdl = drawTextR(gdl, right, rowY, "(n/a)", 0x808080ff);
@@ -1520,7 +1521,7 @@ int optionsRowIsBondChooser(int i)
/* D356: content rows carry the literal per-file flag from the table; header /* D356: content rows carry the literal per-file flag from the table; header
* rows were recomputed at init to "this section contains per-file rows". * rows were recomputed at init to "this section contains per-file rows".
* Both drive UI decoration (the "(game file)" tag, the "(File N)" annotation). * Both drive UI decoration (the "(per profile)" tag, the "(Profile N)" annotation).
* The F10 overlay and the front options screen are the only consumers. */ * The F10 overlay and the front options screen are the only consumers. */
int optionsRowIsSaveScoped(int i) int optionsRowIsSaveScoped(int i)
{ {