Fix diagnostics back navigation and keep mod selection rows stable

This commit is contained in:
raptorreplica committed 2026-10-11 16:25:59 +11:00
1 parent ab23255d2c
commit 4b1edefeec
3 files changed
+136 -41

No files matched your search

+8
View File
@@ -8,6 +8,14 @@ Gameplay uses the original 320×200 simulation canvas, presented at 4:3 with asp
The widescreen mouse check covers all eight main actions at 1280×720, 1280×800 and 1920×1080, secondary-screen offsets and clicks outside the viewport. The full 42-frame menu/intro audit was rerun after the layout change. A relocated packaged SDL executable also rendered menu and gameplay captures at 1280×800 and 1920×1080, with nonzero stereo audio captures. Those screenshots are under ignored `.reference/standalone-package/`.
Installed mod rows stay in stable ID order through activation, dependency
selection, disabling and priority changes. Requested load priority remains
separate and is visible in diagnostics; arrow-key changes report the new
priority without moving the cursor. Returning from details or a disable
confirmation restores the selected mod and its list position. Opening details
again while already in diagnostics preserves its parent, so Escape always
returns one level. Portable regressions exercise these navigation sequences.
The audit also verifies the complete startup reaches its return screen at exactly 1,891 70 Hz service ticks and restores menu music. Cancelling a mod-mismatch prompt preserves the original save; accepting it displays unavailable equipment without silently discarding it.
Run the local audit from `RustRaptor/`:
+18 -18
View File
@@ -24,10 +24,10 @@ worker from its existing outputs before submitting another generation request.
## Next user fixes
1. Mod/save diagnostics: Escape can get trapped after details are reopened from
diagnostics, replacing its parent with itself. Preserve parent and selection.
2. Keep installed mod rows stable when enabled/disabled. Load priority must be
separate from the displayed order; dependencies and confirmation stay intact.
1. Mod/save diagnostics: parent and selection are now preserved when details
are reopened. The Escape regression passes; full screenshot audit is running.
2. Installed mod rows now stay in ID order through toggles and priority changes,
with dependency and confirmation behavior preserved. Navigation tests pass.
3. Default Forward Guns still appear to miss miscellaneous buildings/cars.
Expand actual original-content coverage beyond the earlier ten-unit test.
@@ -43,20 +43,20 @@ mod artwork and sound belong in Git.
| Agent | Recover and continue |
| --- | --- |
| hd_coordinator | Sole global manifest/ledger owner; register fresh approved29boss frames, keepMole9hold; audit all recoveredrequests before resuming |
| hd_full_review | Terrain corpus49/66pagesreviewed; pages50–66remain;48terrainframeholdstaysuntilfullreview |
| hd_terrain_render | Frozen compositor SHA b30b4a65…;29bossframesapproved,Mole9held;8newportraitscreenssaved,7/fullboardinspectionremain |
| campaign_integration | 34effect/projectilefamilies303IDsraw; detailed RESTART.md preserved; severalgeometry/brightness/layout corrections and independentreviewsremain |
| campaign_release_review | Terrain4queue84jobs1344IDs; recover currentraw/contextcandidates andholds |
| hd_dark_player | Equipment/HUDdigits, acceptedandheldfamilies; reconcilecurrentmanifest before newgeneration |
| hd_enemies_01 | Enemy01queue139IDs; sourcegeometryholds,SHIP25connectionfailure,APE4service-refusalpending |
| hd_terrain_set_1 | Terrain1queue42jobs672IDs; first16registered; recover alreadycompleted103e0output andsubsequentcandidateholds |
| hd_terrain_set_2 | Terrain2queue90jobs1437IDs; continue savedcanonicalchunks andall-contextreview |
| iron_coast_2 | Frozenenemy03firstdrafts59families138IDs; recovered45familyenemy02queuecontinues, old11familiesfrozen |
| iron_coast_3 | Boss01queue26families83IDs; recovered11completedatlasfamiliesneedbindings/QA; previous15candidatespreserved |
| iron_coast_4 | Boss02queue27families83IDs; approvedsubset/corrections,connectionfailurecooldowns |
| iron_coast_5 | Boss03complete25families82IDs/328scalechecks; equipment8reviewpending; temporarygroundgunbugassignment |
| iron_coast_6 | Terrain3queue68jobs1082IDs, withmiddleandlateshards; preserve strictdestroyedgeometryholds |
| hd_coordinator | Sole manifest/ledger owner; 29 boss and 48 terrain frames await final registration checks; retain Mole9 hold |
| hd_full_review | All 66 terrain pages and 1,706 cases now reviewed; exact-hash terrain approval saved |
| hd_terrain_render | Frozen compositor SHA b30b4a65…; 29 boss frames approved, Mole9 held; all eight new portrait screenshots approved |
| campaign_integration | 34 effect/projectile families, 303 IDs have raw outputs; geometry, brightness and layout corrections continue |
| campaign_release_review | Terrain 4: 84 jobs, 1,344 IDs; 64 candidates saved, half await further visual/Host QA |
| hd_dark_player | Equipment and HUD digits; reconcile accepted frames, preserve pending Death Ray/Energy review |
| hd_enemies_01 | Enemy 1: 139 IDs; source geometry holds, SHIP25 connection failures and APE service refusal preserved |
| hd_terrain_set_1 | Terrain 1: 42 jobs, 672 IDs; first 16 registered; 80 other candidates and explicit rejected scenery remain |
| hd_terrain_set_2 | Terrain 2: 90 jobs, 1,437 IDs; continue canonical chunks and full neighboring-context review |
| iron_coast_2 | Enemy 3: 59 families, 138 raw IDs; recovered 45-family enemy 2 queue continues, old 11 families frozen |
| iron_coast_3 | Boss 1: 26 families, 83 IDs; 11 recovered atlas families need binding/QA; previous 15 candidates preserved |
| iron_coast_4 | Boss 2: 27 families, 83 IDs; continue corrections, complete reviews and respect service cooldowns |
| iron_coast_5 | Boss 3: 25 families, 82 candidates and 328 scale checks; temporarily auditing every destructible classic tile for ground-gun misses |
| iron_coast_6 | Terrain 3: 68 jobs, 1,082 IDs with middle/late shards; retain destroyed-state geometry holds |
## Durable worker evidence
+110 -23
View File
@@ -189,6 +189,8 @@ struct Host {
awaiting_binding: Option<String>,
diagnostic_mod: Option<String>,
diagnostic_return: Screen,
diagnostic_selection: usize,
mods_selection: usize,
pending_disable: Option<crate::mod_selection::DisablePlan>,
world: Option<World>,
cinematic: Option<Cinematic>,
@@ -333,6 +335,8 @@ impl Host {
awaiting_binding: None,
diagnostic_mod: None,
diagnostic_return: Screen::Main,
diagnostic_selection: 0,
mods_selection: 0,
pending_disable: None,
world: None,
cinematic: None,
@@ -373,8 +377,15 @@ impl Host {
})
}
fn go(&mut self, screen: Screen) {
if self.screen == Screen::Mods {
self.mods_selection = self.selected;
}
self.screen = screen;
self.selected = 0;
self.selected = if screen == Screen::Mods {
self.mods_selection.min(self.rows().len().saturating_sub(1))
} else {
0
};
if screen == Screen::Shop {
self.shop_selling = false;
}
@@ -905,13 +916,7 @@ impl Host {
.collect(),
Screen::Mods => {
let mut r = vec!["Apply selections".into(), "Rescan installed mods".into()];
let mut ids = self.pending.clone();
for id in self.discovery.mods.keys() {
if !ids.contains(id) {
ids.push(id.clone());
}
}
for id in ids {
for id in self.mod_ids() {
if let Some(m) = self.discovery.mods.get(&id) {
r.push(format!(
"[{}] {} {}",
@@ -966,18 +971,9 @@ impl Host {
}
}
fn mod_ids(&self) -> Vec<String> {
let mut ids = self
.pending
.iter()
.filter(|id| self.discovery.mods.contains_key(*id))
.cloned()
.collect::<Vec<_>>();
for id in self.discovery.mods.keys() {
if !ids.contains(id) {
ids.push(id.clone());
}
}
ids
// Installed rows stay in ID order. Activation priority is a separate
// pending list, so toggles and dependency selection cannot move rows.
self.discovery.mods.keys().cloned().collect()
}
fn require_pilot(&self) -> Result<()> {
if let Some(error) = &self.pilot_error {
@@ -1310,6 +1306,13 @@ impl Host {
manifest.conflicts.join(", ")
}
));
if let Some(priority) = self.pending.iter().position(|id| id == mod_id) {
lines.push(format!(
"Requested load priority: {} / {} (later overrides win)",
priority + 1,
self.pending.len()
));
}
}
}
if let Some(e) = &self.pilot_error {
@@ -1335,6 +1338,7 @@ impl Host {
.collect::<Vec<_>>()
.join(", ")
));
lines.push(format!("Pending order: {}", self.pending.join(", ")));
lines.extend(self.active.registry.overrides.clone());
for m in self.discovery.mods.values() {
lines.push(format!(
@@ -1358,12 +1362,16 @@ impl Host {
.collect()
}
fn open_diagnostics(&mut self) {
if self.screen == Screen::Diagnostics {
return;
}
self.diagnostic_mod = if self.screen == Screen::Mods && self.selected >= 2 {
self.mod_ids().get(self.selected - 2).cloned()
} else {
None
};
self.diagnostic_return = self.screen;
self.diagnostic_selection = self.selected;
self.go(Screen::Diagnostics);
self.play_ui_sound("FX_SWEP");
}
@@ -1727,8 +1735,11 @@ impl Host {
};
if next != n {
self.pending.swap(n, next);
// Keep the selected mod under the cursor as the order changes.
self.selected = next + 2;
self.message = format!(
"Load priority {} / {}; later overrides win",
next + 1,
self.pending.len()
);
self.play_ui_sound("FX_SWEP");
}
}
@@ -1818,7 +1829,12 @@ impl Host {
self.go(self.previous)
}
Screen::Mods if self.world.is_some() => self.go(Screen::Pause),
Screen::Diagnostics => self.go(self.diagnostic_return),
Screen::Diagnostics => {
self.go(self.diagnostic_return);
self.selected = self
.diagnostic_selection
.min(self.rows().len().saturating_sub(1));
}
Screen::Bindings => {
self.awaiting_binding = None;
self.save_config()?;
@@ -6767,6 +6783,70 @@ mod tests {
assert_eq!(host.slots(), vec![0, 1]);
}
#[test]
fn mod_diagnostics_reopening_preserves_parent_and_selected_row() {
let (_dir, mut host) = ui_fixture();
install_dependency_fixture(&mut host);
host.go(Screen::Mods);
host.selected = 3;
host.open_diagnostics();
assert!(host.screen == Screen::Diagnostics);
assert_eq!(host.diagnostic_mod.as_deref(), Some("dependent"));
host.move_selection(3);
host.open_diagnostics(); // A second D press must not replace its parent.
assert!(host.diagnostic_return == Screen::Mods);
assert_eq!(host.diagnostic_mod.as_deref(), Some("dependent"));
host.back().unwrap();
assert!(host.screen == Screen::Mods);
assert_eq!(host.selected, 3);
host.back().unwrap();
assert!(host.screen == Screen::Main);
host.go(Screen::Options);
host.selected = 8;
host.open_diagnostics();
host.open_diagnostics();
host.back().unwrap();
assert!(host.screen == Screen::Options);
assert_eq!(host.selected, 8);
}
#[test]
fn mod_rows_and_cursor_stay_fixed_through_dependency_enable_disable_and_priority() {
let (_dir, mut host) = ui_fixture();
install_dependency_fixture(&mut host);
host.go(Screen::Mods);
let installed = host.mod_ids();
let row = installed.iter().position(|id| id == "dependent").unwrap() + 2;
host.selected = row;
host.activate().unwrap();
assert_eq!(host.mod_ids(), installed);
assert_eq!(host.selected, row);
assert_eq!(host.pending, vec!["dependency", "dependent"]);
host.adjust(-1);
assert_eq!(host.pending, vec!["dependent", "dependency"]);
assert_eq!(host.mod_ids(), installed);
assert_eq!(host.selected, row);
host.activate().unwrap();
assert!(host.screen == Screen::ConfirmDisable);
host.back().unwrap();
assert!(host.screen == Screen::Mods);
assert_eq!(host.selected, row);
host.activate().unwrap();
host.selected = 0;
host.activate().unwrap();
assert!(host.screen == Screen::Mods);
assert!(host.pending.is_empty());
assert_eq!(host.mod_ids(), installed);
assert_eq!(host.selected, row);
host.selected = 2;
host.activate().unwrap();
host.activate().unwrap(); // Standalone dependency disables immediately.
assert!(host.screen == Screen::Mods);
assert!(host.pending.is_empty());
assert_eq!(host.selected, 2);
assert_eq!(host.mod_ids(), installed);
}
#[test]
fn selecting_a_mod_selects_its_installed_dependency_without_applying() {
let (_dir, mut host) = ui_fixture();
install_dependency_fixture(&mut host);
@@ -7289,9 +7369,15 @@ mod tests {
.unwrap()
+ 2;
capture(&mut host, "17-mod-manager-dependencies");
let mod_rows = host.mod_ids();
let mod_selection = host.selected;
host.open_diagnostics();
capture(&mut host, "18-mod-details");
host.open_diagnostics();
host.back().unwrap();
assert!(host.screen == Screen::Mods);
assert_eq!(host.mod_ids(), mod_rows);
assert_eq!(host.selected, mod_selection);
host.selected = host
.mod_ids()
.iter()
@@ -7302,6 +7388,7 @@ mod tests {
assert!(host.screen == Screen::ConfirmDisable);
capture(&mut host, "19-mod-disable-confirmation");
host.back().unwrap();
assert_eq!(host.selected, mod_selection);
host.go(Screen::Missions);
host.selected = host
.missions()