Fix diagnostics back navigation and keep mod selection rows stable
This commit is contained in:
1 parent
ab23255d2c
commit
4b1edefeec
3 files changed
+136
-41
No files matched your search
@@ -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/`.
|
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.
|
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/`:
|
Run the local audit from `RustRaptor/`:
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ worker from its existing outputs before submitting another generation request.
|
|||||||
|
|
||||||
## Next user fixes
|
## Next user fixes
|
||||||
|
|
||||||
1. Mod/save diagnostics: Escape can get trapped after details are reopened from
|
1. Mod/save diagnostics: parent and selection are now preserved when details
|
||||||
diagnostics, replacing its parent with itself. Preserve parent and selection.
|
are reopened. The Escape regression passes; full screenshot audit is running.
|
||||||
2. Keep installed mod rows stable when enabled/disabled. Load priority must be
|
2. Installed mod rows now stay in ID order through toggles and priority changes,
|
||||||
separate from the displayed order; dependencies and confirmation stay intact.
|
with dependency and confirmation behavior preserved. Navigation tests pass.
|
||||||
3. Default Forward Guns still appear to miss miscellaneous buildings/cars.
|
3. Default Forward Guns still appear to miss miscellaneous buildings/cars.
|
||||||
Expand actual original-content coverage beyond the earlier ten-unit test.
|
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 |
|
| Agent | Recover and continue |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| hd_coordinator | Sole global manifest/ledger owner; register fresh approved29boss frames, keepMole9hold; audit all recoveredrequests before resuming |
|
| hd_coordinator | Sole manifest/ledger owner; 29 boss and 48 terrain frames await final registration checks; retain Mole9 hold |
|
||||||
| hd_full_review | Terrain corpus49/66pagesreviewed; pages50–66remain;48terrainframeholdstaysuntilfullreview |
|
| 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…;29bossframesapproved,Mole9held;8newportraitscreenssaved,7/fullboardinspectionremain |
|
| hd_terrain_render | Frozen compositor SHA b30b4a65…; 29 boss frames approved, Mole9 held; all eight new portrait screenshots approved |
|
||||||
| campaign_integration | 34effect/projectilefamilies303IDsraw; detailed RESTART.md preserved; severalgeometry/brightness/layout corrections and independentreviewsremain |
|
| campaign_integration | 34 effect/projectile families, 303 IDs have raw outputs; geometry, brightness and layout corrections continue |
|
||||||
| campaign_release_review | Terrain4queue84jobs1344IDs; recover currentraw/contextcandidates andholds |
|
| campaign_release_review | Terrain 4: 84 jobs, 1,344 IDs; 64 candidates saved, half await further visual/Host QA |
|
||||||
| hd_dark_player | Equipment/HUDdigits, acceptedandheldfamilies; reconcilecurrentmanifest before newgeneration |
|
| hd_dark_player | Equipment and HUD digits; reconcile accepted frames, preserve pending Death Ray/Energy review |
|
||||||
| hd_enemies_01 | Enemy01queue139IDs; sourcegeometryholds,SHIP25connectionfailure,APE4service-refusalpending |
|
| hd_enemies_01 | Enemy 1: 139 IDs; source geometry holds, SHIP25 connection failures and APE service refusal preserved |
|
||||||
| hd_terrain_set_1 | Terrain1queue42jobs672IDs; first16registered; recover alreadycompleted103e0output andsubsequentcandidateholds |
|
| 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 | Terrain2queue90jobs1437IDs; continue savedcanonicalchunks andall-contextreview |
|
| hd_terrain_set_2 | Terrain 2: 90 jobs, 1,437 IDs; continue canonical chunks and full neighboring-context review |
|
||||||
| iron_coast_2 | Frozenenemy03firstdrafts59families138IDs; recovered45familyenemy02queuecontinues, old11familiesfrozen |
|
| iron_coast_2 | Enemy 3: 59 families, 138 raw IDs; recovered 45-family enemy 2 queue continues, old 11 families frozen |
|
||||||
| iron_coast_3 | Boss01queue26families83IDs; recovered11completedatlasfamiliesneedbindings/QA; previous15candidatespreserved |
|
| iron_coast_3 | Boss 1: 26 families, 83 IDs; 11 recovered atlas families need binding/QA; previous 15 candidates preserved |
|
||||||
| iron_coast_4 | Boss02queue27families83IDs; approvedsubset/corrections,connectionfailurecooldowns |
|
| iron_coast_4 | Boss 2: 27 families, 83 IDs; continue corrections, complete reviews and respect service cooldowns |
|
||||||
| iron_coast_5 | Boss03complete25families82IDs/328scalechecks; equipment8reviewpending; temporarygroundgunbugassignment |
|
| 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 | Terrain3queue68jobs1082IDs, withmiddleandlateshards; preserve strictdestroyedgeometryholds |
|
| iron_coast_6 | Terrain 3: 68 jobs, 1,082 IDs with middle/late shards; retain destroyed-state geometry holds |
|
||||||
|
|
||||||
## Durable worker evidence
|
## Durable worker evidence
|
||||||
|
|
||||||
|
|||||||
+110
-23
@@ -189,6 +189,8 @@ struct Host {
|
|||||||
awaiting_binding: Option<String>,
|
awaiting_binding: Option<String>,
|
||||||
diagnostic_mod: Option<String>,
|
diagnostic_mod: Option<String>,
|
||||||
diagnostic_return: Screen,
|
diagnostic_return: Screen,
|
||||||
|
diagnostic_selection: usize,
|
||||||
|
mods_selection: usize,
|
||||||
pending_disable: Option<crate::mod_selection::DisablePlan>,
|
pending_disable: Option<crate::mod_selection::DisablePlan>,
|
||||||
world: Option<World>,
|
world: Option<World>,
|
||||||
cinematic: Option<Cinematic>,
|
cinematic: Option<Cinematic>,
|
||||||
@@ -333,6 +335,8 @@ impl Host {
|
|||||||
awaiting_binding: None,
|
awaiting_binding: None,
|
||||||
diagnostic_mod: None,
|
diagnostic_mod: None,
|
||||||
diagnostic_return: Screen::Main,
|
diagnostic_return: Screen::Main,
|
||||||
|
diagnostic_selection: 0,
|
||||||
|
mods_selection: 0,
|
||||||
pending_disable: None,
|
pending_disable: None,
|
||||||
world: None,
|
world: None,
|
||||||
cinematic: None,
|
cinematic: None,
|
||||||
@@ -373,8 +377,15 @@ impl Host {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn go(&mut self, screen: Screen) {
|
fn go(&mut self, screen: Screen) {
|
||||||
|
if self.screen == Screen::Mods {
|
||||||
|
self.mods_selection = self.selected;
|
||||||
|
}
|
||||||
self.screen = screen;
|
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 {
|
if screen == Screen::Shop {
|
||||||
self.shop_selling = false;
|
self.shop_selling = false;
|
||||||
}
|
}
|
||||||
@@ -905,13 +916,7 @@ impl Host {
|
|||||||
.collect(),
|
.collect(),
|
||||||
Screen::Mods => {
|
Screen::Mods => {
|
||||||
let mut r = vec!["Apply selections".into(), "Rescan installed mods".into()];
|
let mut r = vec!["Apply selections".into(), "Rescan installed mods".into()];
|
||||||
let mut ids = self.pending.clone();
|
for id in self.mod_ids() {
|
||||||
for id in self.discovery.mods.keys() {
|
|
||||||
if !ids.contains(id) {
|
|
||||||
ids.push(id.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for id in ids {
|
|
||||||
if let Some(m) = self.discovery.mods.get(&id) {
|
if let Some(m) = self.discovery.mods.get(&id) {
|
||||||
r.push(format!(
|
r.push(format!(
|
||||||
"[{}] {} {}",
|
"[{}] {} {}",
|
||||||
@@ -966,18 +971,9 @@ impl Host {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn mod_ids(&self) -> Vec<String> {
|
fn mod_ids(&self) -> Vec<String> {
|
||||||
let mut ids = self
|
// Installed rows stay in ID order. Activation priority is a separate
|
||||||
.pending
|
// pending list, so toggles and dependency selection cannot move rows.
|
||||||
.iter()
|
self.discovery.mods.keys().cloned().collect()
|
||||||
.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
|
|
||||||
}
|
}
|
||||||
fn require_pilot(&self) -> Result<()> {
|
fn require_pilot(&self) -> Result<()> {
|
||||||
if let Some(error) = &self.pilot_error {
|
if let Some(error) = &self.pilot_error {
|
||||||
@@ -1310,6 +1306,13 @@ impl Host {
|
|||||||
manifest.conflicts.join(", ")
|
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 {
|
if let Some(e) = &self.pilot_error {
|
||||||
@@ -1335,6 +1338,7 @@ impl Host {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(", ")
|
.join(", ")
|
||||||
));
|
));
|
||||||
|
lines.push(format!("Pending order: {}", self.pending.join(", ")));
|
||||||
lines.extend(self.active.registry.overrides.clone());
|
lines.extend(self.active.registry.overrides.clone());
|
||||||
for m in self.discovery.mods.values() {
|
for m in self.discovery.mods.values() {
|
||||||
lines.push(format!(
|
lines.push(format!(
|
||||||
@@ -1358,12 +1362,16 @@ impl Host {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
fn open_diagnostics(&mut self) {
|
fn open_diagnostics(&mut self) {
|
||||||
|
if self.screen == Screen::Diagnostics {
|
||||||
|
return;
|
||||||
|
}
|
||||||
self.diagnostic_mod = if self.screen == Screen::Mods && self.selected >= 2 {
|
self.diagnostic_mod = if self.screen == Screen::Mods && self.selected >= 2 {
|
||||||
self.mod_ids().get(self.selected - 2).cloned()
|
self.mod_ids().get(self.selected - 2).cloned()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
self.diagnostic_return = self.screen;
|
self.diagnostic_return = self.screen;
|
||||||
|
self.diagnostic_selection = self.selected;
|
||||||
self.go(Screen::Diagnostics);
|
self.go(Screen::Diagnostics);
|
||||||
self.play_ui_sound("FX_SWEP");
|
self.play_ui_sound("FX_SWEP");
|
||||||
}
|
}
|
||||||
@@ -1727,8 +1735,11 @@ impl Host {
|
|||||||
};
|
};
|
||||||
if next != n {
|
if next != n {
|
||||||
self.pending.swap(n, next);
|
self.pending.swap(n, next);
|
||||||
// Keep the selected mod under the cursor as the order changes.
|
self.message = format!(
|
||||||
self.selected = next + 2;
|
"Load priority {} / {}; later overrides win",
|
||||||
|
next + 1,
|
||||||
|
self.pending.len()
|
||||||
|
);
|
||||||
self.play_ui_sound("FX_SWEP");
|
self.play_ui_sound("FX_SWEP");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1818,7 +1829,12 @@ impl Host {
|
|||||||
self.go(self.previous)
|
self.go(self.previous)
|
||||||
}
|
}
|
||||||
Screen::Mods if self.world.is_some() => self.go(Screen::Pause),
|
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 => {
|
Screen::Bindings => {
|
||||||
self.awaiting_binding = None;
|
self.awaiting_binding = None;
|
||||||
self.save_config()?;
|
self.save_config()?;
|
||||||
@@ -6767,6 +6783,70 @@ mod tests {
|
|||||||
assert_eq!(host.slots(), vec![0, 1]);
|
assert_eq!(host.slots(), vec![0, 1]);
|
||||||
}
|
}
|
||||||
#[test]
|
#[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() {
|
fn selecting_a_mod_selects_its_installed_dependency_without_applying() {
|
||||||
let (_dir, mut host) = ui_fixture();
|
let (_dir, mut host) = ui_fixture();
|
||||||
install_dependency_fixture(&mut host);
|
install_dependency_fixture(&mut host);
|
||||||
@@ -7289,9 +7369,15 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
+ 2;
|
+ 2;
|
||||||
capture(&mut host, "17-mod-manager-dependencies");
|
capture(&mut host, "17-mod-manager-dependencies");
|
||||||
|
let mod_rows = host.mod_ids();
|
||||||
|
let mod_selection = host.selected;
|
||||||
host.open_diagnostics();
|
host.open_diagnostics();
|
||||||
capture(&mut host, "18-mod-details");
|
capture(&mut host, "18-mod-details");
|
||||||
|
host.open_diagnostics();
|
||||||
host.back().unwrap();
|
host.back().unwrap();
|
||||||
|
assert!(host.screen == Screen::Mods);
|
||||||
|
assert_eq!(host.mod_ids(), mod_rows);
|
||||||
|
assert_eq!(host.selected, mod_selection);
|
||||||
host.selected = host
|
host.selected = host
|
||||||
.mod_ids()
|
.mod_ids()
|
||||||
.iter()
|
.iter()
|
||||||
@@ -7302,6 +7388,7 @@ mod tests {
|
|||||||
assert!(host.screen == Screen::ConfirmDisable);
|
assert!(host.screen == Screen::ConfirmDisable);
|
||||||
capture(&mut host, "19-mod-disable-confirmation");
|
capture(&mut host, "19-mod-disable-confirmation");
|
||||||
host.back().unwrap();
|
host.back().unwrap();
|
||||||
|
assert_eq!(host.selected, mod_selection);
|
||||||
host.go(Screen::Missions);
|
host.go(Screen::Missions);
|
||||||
host.selected = host
|
host.selected = host
|
||||||
.missions()
|
.missions()
|
||||||
|
|||||||
Reference in new issue
Block a user