using System.Collections.Generic; namespace IdleSpectator; /// /// Built-in agent playtest scenarios expanded by action=scenario. /// internal static class HarnessScenarios { public static List Build(string name) { switch ((name ?? "").Trim().ToLowerInvariant()) { case "smoke": return Smoke(); case "tip_match": return TipMatch(); case "critical": case "critical_smoke": return CriticalSmoke(); case "settings_full": return SettingsFull(); case "ghost_guard": return GhostGuard(); case "retarget_stability": return RetargetStability(); case "input_exit": return InputExit(); case "director_tiers": return DirectorTiers(); case "discovery": return Discovery(); case "world_log": return WorldLog(); case "chronicle": case "chronicle_smoke": return ChronicleSmoke(); case "chronicle_subject_bench": case "subject_bench": return ChronicleSubjectBench(); case "regression": case "full": return Regression(); default: return null; } } /// /// Timed Fallen/Living list rebuilds at rising subject counts. /// Picks the highest size under a 2ms Fallen avg budget (runtime cap only for that session). /// private static List ChronicleSubjectBench() { return new List { Step("sb0", "wait_world"), Step("sb1", "dismiss_windows"), Step("sb2", "chronicle_clear"), Step( "sb3", "chronicle_subject_bench", value: "320,640,1280,2560,4096,8192", count: 3, expect: "12"), Step("sb4", "assert", expect: "chronicle_subjects_capped"), Step("sb5", "snapshot") }; } /// /// Full regression meta-suite (nested scenarios keep assert counters). /// Battle / map-gen paths stay out of gate. /// private static List Regression() { return new List { Nested("reg_critical", "critical_smoke"), Nested("reg_settings", "settings_full"), Nested("reg_ghost", "ghost_guard"), Nested("reg_retarget", "retarget_stability"), Nested("reg_input", "input_exit"), Nested("reg_director", "director_tiers"), Nested("reg_discovery", "discovery"), Nested("reg_worldlog", "world_log"), Nested("reg_chronicle", "chronicle_smoke"), Step("reg_end", "fast_timing", value: "false"), Step("reg_snap", "snapshot"), }; } private static List Smoke() { return new List { Step("s0", "dismiss_windows"), Step("s1", "wait_world"), Step("s2", "reset_counters"), Step("s3", "pick_unit", asset: "auto"), Step("s4", "spectator", value: "off"), Step("s5", "assert", expect: "idle", value: "false"), Step("s6", "spectator", value: "on"), Step("s7", "focus", asset: "auto"), Step("s8", "wait", wait: 0.35f), Step("s9", "assert", expect: "health"), Step("s10", "snapshot"), }; } private static List TipMatch() { return new List { Step("t0", "dismiss_windows"), Step("t1", "wait_world"), Step("t2", "reset_counters"), Step("t3", "pick_unit", asset: "auto"), Step("t4", "spectator", value: "on"), Step("t5", "watch", asset: "auto", label: "New species: {asset}", tier: "Curiosity"), Step("t6", "wait", wait: 0.3f), Step("t7", "assert", expect: "tip_matches_unit"), Step("t8", "assert", expect: "unit_asset", asset: "auto"), Step("t9", "assert", expect: "power_bar", value: "false"), Step("t10", "snapshot"), }; } /// /// Critical E2E against already-living map units (no fragile miracle spawns). /// private static List CriticalSmoke() { return new List { Step("c00", "dismiss_windows"), Step("c01", "wait_world"), Step("c02", "reset_counters"), Step("c05", "pick_unit", asset: "auto"), Step("c06", "wait", wait: 0.25f), Step("c10", "set_setting", expect: "enabled", value: "false"), Step("c11", "assert", expect: "enabled", value: "false"), Step("c12", "spectator", value: "on", expect: "blocked"), Step("c13", "assert", expect: "idle", value: "false"), Step("c14", "set_setting", expect: "enabled", value: "true"), Step("c15", "assert", expect: "enabled", value: "true"), Step("c20", "spectator", value: "off"), Step("c21", "assert", expect: "idle", value: "false"), Step("c22", "spectator", value: "on"), Step("c23", "focus", asset: "auto"), Step("c24", "reset_counters"), Step("c25", "wait", wait: 0.35f), Step("c26", "assert", expect: "health"), Step("c27", "assert", expect: "power_bar", value: "false"), Step("c28", "assert", expect: "no_bad"), Step("c29", "assert", expect: "focus_arrows"), Step("c30", "watch", asset: "auto", label: "New species: {asset}", tier: "Curiosity"), Step("c31", "wait", wait: 0.3f), Step("c32", "assert", expect: "tip_matches_unit"), Step("c33", "assert", expect: "unit_asset", asset: "auto"), Step("c34", "assert", expect: "tip_contains", value: "auto"), Step("c35", "assert", expect: "power_bar", value: "false"), Step("c36", "assert", expect: "no_bad"), Step("c40", "watch", asset: "auto", label: "Retarget A: {asset}", tier: "Curiosity"), Step("c41", "wait", wait: 0.25f), Step("c42", "focus", asset: "auto"), Step("c43", "wait", wait: 0.25f), Step("c44", "watch", asset: "auto", label: "Retarget B: {asset}", tier: "Curiosity"), Step("c45", "wait", wait: 0.35f), Step("c46", "assert", expect: "health"), Step("c47", "assert", expect: "no_bad"), Step("c48", "assert", expect: "power_bar", value: "false"), Step("c50", "simulate_input"), Step("c51", "wait", wait: 0.25f), Step("c52", "assert", expect: "idle", value: "false"), Step("c52b", "assert", expect: "caption_contains", value: "Paused"), Step("c53", "assert", expect: "no_bad"), Step("c99", "snapshot"), }; } private static List SettingsFull() { return new List { Step("sf0", "dismiss_windows"), Step("sf1", "wait_world"), Step("sf2", "set_setting", expect: "enabled", value: "true"), Step("sf3", "pick_unit", asset: "auto"), Step("sf10", "set_setting", expect: "enabled", value: "false"), Step("sf11", "assert", expect: "enabled", value: "false"), Step("sf12", "spectator", value: "on", expect: "blocked"), Step("sf13", "assert", expect: "idle", value: "false"), Step("sf20", "set_setting", expect: "enabled", value: "true"), Step("sf21", "spectator", value: "on"), Step("sf22", "focus", asset: "auto"), Step("sf23", "assert", expect: "idle", value: "true"), // Disable while active must turn idle off (settings callback). Step("sf30", "set_setting", expect: "enabled", value: "false"), Step("sf31", "assert", expect: "idle", value: "false"), Step("sf32", "assert", expect: "enabled", value: "false"), Step("sf40", "set_setting", expect: "enabled", value: "true"), Step("sf41", "set_setting", expect: "show_watch_reasons", value: "false"), Step("sf42", "assert", expect: "show_watch_reasons", value: "false"), Step("sf43", "set_setting", expect: "show_watch_reasons", value: "true"), Step("sf44", "assert", expect: "show_watch_reasons", value: "true"), Step("sf99", "snapshot"), }; } private static List GhostGuard() { return new List { Step("g0", "dismiss_windows"), Step("g1", "wait_world"), Step("g2", "set_setting", expect: "enabled", value: "true"), Step("g3", "pick_unit", asset: "auto"), Step("g4", "spectator", value: "on"), Step("g5", "focus", asset: "auto"), Step("g6", "reset_counters"), Step("g7", "remember_focus"), Step("g8", "watch_ghost"), Step("g9", "wait", wait: 0.25f), Step("g10", "assert", expect: "focus_same"), Step("g11", "assert", expect: "health"), Step("g12", "assert", expect: "no_bad"), Step("g13", "assert", expect: "power_bar", value: "false"), Step("g99", "snapshot"), }; } private static List RetargetStability() { return new List { Step("rs0", "dismiss_windows"), Step("rs1", "wait_world"), Step("rs2", "set_setting", expect: "enabled", value: "true"), Step("rs3", "pick_unit", asset: "auto"), Step("rs4", "spectator", value: "on"), Step("rs5", "focus", asset: "auto"), Step("rs6", "reset_counters"), Step("rs10", "watch", asset: "auto", label: "Stab 1: {asset}", tier: "Curiosity"), Step("rs11", "wait", wait: 0.2f), Step("rs12", "watch", asset: "auto", label: "Stab 2: {asset}", tier: "Curiosity"), Step("rs13", "wait", wait: 0.2f), Step("rs14", "watch", asset: "auto", label: "Stab 3: {asset}", tier: "Curiosity"), Step("rs15", "wait", wait: 0.2f), Step("rs16", "watch", asset: "auto", label: "Stab 4: {asset}", tier: "Curiosity"), Step("rs17", "wait", wait: 0.25f), Step("rs20", "assert", expect: "health"), Step("rs21", "assert", expect: "no_bad"), Step("rs22", "assert", expect: "power_bar", value: "false"), Step("rs23", "assert", expect: "unit_asset", asset: "auto"), Step("rs99", "snapshot"), }; } private static List InputExit() { return new List { Step("ie0", "dismiss_windows"), Step("ie1", "wait_world"), Step("ie2", "set_setting", expect: "enabled", value: "true"), Step("ie3", "fast_timing", value: "true"), Step("ie4", "pick_unit", asset: "auto"), Step("ie5", "spectator", value: "off"), Step("ie6", "spectator", value: "on"), Step("ie7", "focus", asset: "auto"), // Still inside post-enable grace: drag must not exit. Step("ie10", "assert", expect: "in_grace", value: "true"), Step("ie11", "simulate_input", value: "drag", expect: "grace"), Step("ie12", "assert", expect: "idle", value: "true"), Step("ie20", "expire_grace"), Step("ie21", "assert", expect: "in_grace", value: "false"), Step("ie22", "simulate_input", value: "drag"), Step("ie23", "assert", expect: "idle", value: "false"), Step("ie24", "assert", expect: "no_bad"), Step("ie90", "fast_timing", value: "false"), Step("ie99", "snapshot"), }; } private static List DirectorTiers() { return new List { Step("dt0", "dismiss_windows"), Step("dt1", "wait_world"), Step("dt2", "set_setting", expect: "enabled", value: "true"), Step("dt3", "fast_timing", value: "true"), Step("dt4", "pick_unit", asset: "auto"), Step("dt5", "spectator", value: "off"), Step("dt6", "spectator", value: "on"), Step("dt7", "focus", asset: "auto"), Step("dt8", "reset_counters"), // Curiosity can replace ambient after rotate window. Step("dt10", "trigger_interest", asset: "auto", label: "CurioA", tier: "Curiosity"), Step("dt11", "age_current", wait: 2f), Step("dt12", "director_run", wait: 1.2f), Step("dt13", "assert", expect: "current_tier", value: "Curiosity"), Step("dt14", "assert", expect: "tip_contains", value: "CurioA"), // Action interrupts curiosity after high-tier window. Step("dt20", "trigger_interest", asset: "auto", label: "ActionA", tier: "Action"), Step("dt21", "age_current", wait: 1.2f), Step("dt22", "director_run", wait: 1.2f), Step("dt23", "assert", expect: "current_tier", value: "Action"), Step("dt24", "assert", expect: "tip_contains", value: "ActionA"), // Curiosity must not be accepted while Action holds the camera (no director_run: // ambient rotate would otherwise steal Action after dwell). Step("dt30", "trigger_interest", asset: "auto", label: "CurioBlocked", tier: "Curiosity"), Step("dt31", "assert", expect: "current_tier", value: "Action"), Step("dt32", "assert", expect: "tip_contains", value: "ActionA"), Step("dt33", "assert", expect: "would_accept_curiosity", value: "false"), Step("dt34", "assert", expect: "health"), Step("dt35", "assert", expect: "no_bad"), Step("dt90", "fast_timing", value: "false"), Step("dt99", "snapshot"), }; } private static List Discovery() { return new List { Step("d0", "dismiss_windows"), Step("d1", "wait_world"), Step("d2", "set_setting", expect: "enabled", value: "true"), Step("d3", "fast_timing", value: "true"), Step("d4", "discovery_reset"), Step("d5", "pick_unit", asset: "auto"), Step("d6", "spectator", value: "off"), Step("d7", "spectator", value: "on"), Step("d8", "focus", asset: "auto"), Step("d9", "reset_counters"), // Live-path dedupe: mark presented drops buffered copy. Step("d10", "discovery_note", asset: "auto"), Step("d11", "assert", expect: "pending_discovery", value: "1"), Step("d12", "discovery_mark", asset: "auto"), Step("d13", "assert", expect: "pending_discovery", value: "0"), Step("d14", "assert", expect: "presented", asset: "auto", value: "true"), // Force drain path once curiosity is acceptable. Step("d20", "discovery_reset"), Step("d21", "discovery_note", asset: "auto"), Step("d22", "assert", expect: "pending_discovery", value: "1"), Step("d23", "age_current", wait: 2f), Step("d24", "assert", expect: "would_accept_curiosity", value: "true"), Step("d25", "discovery_drain"), Step("d26", "assert", expect: "pending_discovery", value: "0"), Step("d27", "assert", expect: "presented", asset: "auto", value: "true"), Step("d28", "director_run", wait: 1.2f), Step("d29", "assert", expect: "tip_matches_unit"), Step("d30", "assert", expect: "no_bad"), Step("d90", "fast_timing", value: "false"), Step("d99", "snapshot"), }; } private static List WorldLog() { return new List { Step("wl0", "dismiss_windows"), Step("wl1", "wait_world"), Step("wl2", "set_setting", expect: "enabled", value: "true"), Step("wl3", "fast_timing", value: "true"), Step("wl4", "pick_unit", asset: "auto"), Step("wl5", "spectator", value: "off"), Step("wl6", "spectator", value: "on"), Step("wl7", "focus", asset: "auto"), Step("wl8", "reset_counters"), Step("wl10", "trigger_interest", asset: "auto", label: "Story: kingdom_new", tier: "Story"), Step("wl11", "age_current", wait: 1.2f), Step("wl12", "director_run", wait: 1.2f), Step("wl13", "assert", expect: "current_tier", value: "Story"), Step("wl14", "assert", expect: "tip_contains", value: "kingdom_new"), Step("wl20", "trigger_interest", asset: "auto", label: "Epic: diplomacy_war_started", tier: "Epic"), Step("wl21", "age_current", wait: 1.2f), Step("wl22", "director_run", wait: 1.2f), Step("wl23", "assert", expect: "current_tier", value: "Epic"), Step("wl24", "assert", expect: "tip_contains", value: "diplomacy_war_started"), Step("wl25", "assert", expect: "health"), Step("wl26", "assert", expect: "no_bad"), Step("wl90", "fast_timing", value: "false"), Step("wl99", "snapshot"), }; } private static List ChronicleSmoke() { return new List { Step("ch0", "dismiss_windows"), Step("ch1", "wait_world"), Step("ch2", "set_setting", expect: "enabled", value: "true"), Step("ch3", "set_setting", expect: "show_dossier_caption", value: "true"), Step("ch4", "set_setting", expect: "chronicle_enabled", value: "true"), Step("ch4b", "chronicle_clear"), Step("ch5", "pick_unit", asset: "auto"), Step("ch6", "spectator", value: "off"), Step("ch7", "spectator", value: "on"), Step("ch8", "focus", asset: "auto"), Step("ch9", "wait", wait: 0.35f), Step("ch10", "assert", expect: "health"), Step("ch11", "assert", expect: "dossier_contains", value: "auto"), Step("ch12", "assert", expect: "caption_contains", value: "auto"), Step("ch12b", "assert", expect: "caption_layout_ok"), Step("ch12c", "assert", expect: "dossier_traits_ok"), Step("ch12d", "screenshot", value: "hud-dossier-empty-hist.png"), Step("ch12e", "wait", wait: 0.55f), // History inject Step("ch13", "chronicle_force", label: "Killed"), Step("ch14", "assert", expect: "chronicle_count", value: "1", label: "min"), Step("ch15", "assert", expect: "chronicle_latest_contains", value: "Killed"), Step("ch15a2", "assert", expect: "chronicle_latest_dated"), Step("ch15b", "assert", expect: "chronicle_latest_contains", value: "auto"), Step("ch15c", "wait", wait: 0.2f), Step("ch15d", "assert", expect: "dossier_history_contains", value: "Killed"), // Long event wraps in the narrow dossier history column (no wider panel). Step("ch15e", "chronicle_force", label: "Became legendary after surviving the great crab migration across three kingdoms, returning home with a stolen crown, and telling the tale for forty winters"), Step("ch15f", "wait", wait: 0.35f), Step("ch15g", "assert", expect: "dossier_history_contains", value: "crab migration"), Step("ch15h", "assert", expect: "caption_layout_ok"), Step("ch15i", "screenshot", value: "hud-dossier-long-hist.png"), Step("ch15j", "wait", wait: 0.65f), // Death-cause wording samples (victim POV) Step("ch16a", "chronicle_death_sample", value: "Age"), Step("ch16b", "assert", expect: "chronicle_latest_contains", value: "old age"), Step("ch16c", "chronicle_death_sample", value: "Starvation"), Step("ch16d", "assert", expect: "chronicle_latest_contains", value: "Starved"), Step("ch16e", "chronicle_death_sample", value: "Drowning"), Step("ch16f", "assert", expect: "chronicle_latest_contains", value: "Drown"), Step("ch16g", "chronicle_death_sample", value: "Fire"), Step("ch16h", "assert", expect: "chronicle_latest_contains", value: "Burned"), // Character kills/deaths must stay on character history, not World Memory. Step("ch16i", "assert", expect: "world_memory_world_only"), // Soft-cap prune: temporarily lower the cap, flood past it, then restore. Step("ch16j0", "chronicle_set_subject_cap", value: "500"), Step("ch16j", "chronicle_orphan_flood", count: 580), Step("ch16k", "assert", expect: "chronicle_subjects_capped"), Step( "ch16k2", "chronicle_set_subject_cap", value: Chronicle.DefaultMaxHistorySubjects.ToString()), // World Memory landmarks (replaces History|World tabs) Step("ch17a", "chronicle_world_force", label: "War: harness_alpha vs harness_beta"), Step("ch17b", "assert", expect: "chronicle_count", value: "1", label: "world"), Step("ch17c", "assert", expect: "world_memory_contains", value: "harness_alpha"), Step("ch17d", "assert", expect: "chronicle_latest_contains", value: "Burned"), Step("ch17e", "assert", expect: "world_memory_age"), Step("ch17e2", "assert", expect: "world_memory_world_only"), Step("ch17e3", "assert", expect: "chronicle_memory_capped"), Step("ch17e4", "assert", expect: "chronicle_subjects_capped"), // Selecting a world event pans to its recorded place (even with no unit there). Step("ch17j", "lore_jump_world"), Step("ch17k", "wait", wait: 0.2f), Step("ch17l", "assert", expect: "world_jump_place"), Step("ch17m", "assert", expect: "focus", value: "false"), // Restore a living focus for the rest of the scenario. Step("ch17n", "focus", asset: "auto"), Step("ch17o", "wait", wait: 0.2f), Step("ch17p", "assert", expect: "focus", value: "true"), // World Memory panel + dossier controls Step("ch18", "chronicle_open"), Step("ch18a", "screenshot", value: "hud-chronicle_smoke.png"), Step("ch18a2", "wait", wait: 0.6f), Step("ch18b", "assert", expect: "world_memory_compact"), Step("ch18b2", "assert", expect: "hud_no_overlap"), Step("ch18c", "assert", expect: "caption_layout_ok"), Step("ch18c2", "assert", expect: "dossier_traits_ok"), Step("ch18d", "assert", expect: "world_memory_contains", value: "harness_alpha"), Step("ch18e", "dossier_favorite", value: "true"), Step("ch18f", "assert", expect: "is_favorite", value: "true"), Step("ch18g", "dossier_favorite", value: "false"), Step("ch18h", "assert", expect: "is_favorite", value: "false"), // Dossier peeks 3 lines; books button opens full history in Lore and pauses idle. Step("ch18h1", "chronicle_force", label: "Hist", count: 40), Step("ch18h2", "wait", wait: 0.25f), Step("ch18k", "assert", expect: "dossier_history_shown", value: "3", label: "exact"), Step("ch18k3", "assert", expect: "dossier_history_fills_body"), Step("ch18l", "assert", expect: "caption_layout_ok"), Step("ch18l1", "assert", expect: "hud_no_overlap"), Step("ch18l2", "screenshot", value: "hud-dossier-peek.png"), Step("ch18l3", "wait", wait: 0.45f), // L shortcut syncs to the focused dossier unit (Follow mode) and locks idle. Step("ch18m", "lore_open_focus"), Step("ch18n", "wait", wait: 0.3f), Step("ch18o", "assert", expect: "idle", value: "false"), Step("ch18o2", "assert", expect: "lore_tab", value: "living"), Step("ch18o3", "assert", expect: "lore_detail", value: "true"), Step("ch18o3b", "assert", expect: "lore_follow", value: "true"), Step("ch18o4", "assert", expect: "lore_history_shown", value: "40", label: "min"), Step("ch18o4b", "assert", expect: "dossier_lore_history_match"), Step("ch18o4c", "assert", expect: "dossier_pinned", value: "true"), Step("ch18o4d", "assert", expect: "lore_zoom_blocked"), Step("ch18o5", "screenshot", value: "hud-lore-unit-history.png"), Step("ch18o6", "wait", wait: 0.55f), // Banner used to expire at 5s and hide the dossier - pin must outlive that. Step("ch18o7", "wait", wait: 5.5f), Step("ch18o8", "assert", expect: "dossier_pinned", value: "true"), Step("ch18o9", "assert", expect: "idle", value: "false"), Step("ch18o10", "assert", expect: "dossier_lore_history_match"), Step("ch18p", "assert", expect: "hud_no_overlap"), // Resume idle: Follow mode Lore must retarget when dossier focus changes. Step("ch18f1", "spectator", value: "on"), Step("ch18f2", "wait", wait: 0.2f), Step("ch18f3", "spawn", asset: "rabbit", count: 1), Step("ch18f4", "focus", asset: "auto"), Step("ch18f5", "wait", wait: 0.35f), Step("ch18f6", "assert", expect: "lore_follows_focus"), Step("ch18f7", "assert", expect: "lore_follow", value: "true"), // Books button still opens Follow mode. Step("ch18p0", "lore_close"), Step("ch18p0b", "dossier_history_open"), Step("ch18p0c", "wait", wait: 0.2f), Step("ch18p0d", "assert", expect: "dossier_lore_history_match"), Step("ch18p0e", "assert", expect: "idle", value: "false"), Step("ch18p0f", "assert", expect: "lore_follow", value: "true"), // Empty-history subject must not keep a previous unit's peek lines. Step("ch18p1", "lore_back"), Step("ch18p2", "spawn", asset: "cow", count: 1), Step("ch18p3", "focus", asset: "auto"), Step("ch18p4", "wait", wait: 0.25f), Step("ch18p5", "dossier_history_open"), Step("ch18p6", "wait", wait: 0.25f), Step("ch18p7", "assert", expect: "idle", value: "false"), Step("ch18p8", "assert", expect: "lore_detail", value: "true"), Step("ch18p9", "assert", expect: "dossier_history_shown", value: "0", label: "exact"), Step("ch18p10", "assert", expect: "lore_history_shown", value: "0", label: "exact"), Step("ch18p11", "assert", expect: "dossier_lore_history_match"), Step("ch18p12", "screenshot", value: "hud-lore-empty-history.png"), Step("ch18p13", "wait", wait: 0.55f), // Fallen (dead) archive: pinned; old age → death place. Step("ch18d1", "lore_back"), Step("ch18d1b", "focus", asset: "auto"), Step("ch18d1c", "wait", wait: 0.2f), Step( "ch18d2", "chronicle_orphan", label: "Oldbone", asset: "skeleton", value: "Fallen", count: 5, expect: "Age", tier: "place"), Step("ch18d3", "lore_pick_fallen", value: "Oldbone"), Step("ch18d4", "wait", wait: 0.25f), Step("ch18d5", "assert", expect: "lore_detail", value: "true"), Step("ch18d6", "assert", expect: "lore_follow", value: "false"), Step("ch18d7", "assert", expect: "lore_history_shown", value: "5", label: "exact"), Step("ch18d7b", "assert", expect: "fallen_focus", value: "place"), Step("ch18d7c", "assert", expect: "death_manner", value: "old age"), Step("ch18d7d", "assert", expect: "dossier_contains", value: "Oldbone"), Step("ch18d7e", "assert", expect: "dossier_unit_matches_lore"), Step("ch18d7f", "assert", expect: "dossier_species", value: "skeleton"), Step("ch18d7g", "assert", expect: "dossier_avatar_species"), Step("ch18d7h", "assert", expect: "fallen_place_no_follow"), Step("ch18d8", "screenshot", value: "hud-lore-fallen.png"), Step("ch18d9", "wait", wait: 0.55f), // Fallen slain → living killer focus. Step("ch18k1", "lore_back"), Step("ch18k2", "spawn", asset: "human", count: 1), Step("ch18k3", "focus", asset: "auto"), Step("ch18k4", "wait", wait: 0.25f), Step( "ch18k5", "chronicle_orphan", label: "Slainbone", asset: "skeleton", value: "Murder", count: 3, expect: "Weapon", tier: "killer"), Step("ch18k6", "lore_pick_fallen", value: "Slainbone"), Step("ch18k7", "wait", wait: 0.25f), Step("ch18k8", "assert", expect: "lore_detail", value: "true"), Step("ch18k9", "assert", expect: "lore_follow", value: "false"), Step("ch18k10", "assert", expect: "fallen_focus", value: "killer"), Step("ch18k11", "assert", expect: "death_manner", value: "slain"), Step("ch18k11b", "assert", expect: "dossier_contains", value: "Slainbone"), Step("ch18k11c", "assert", expect: "dossier_unit_matches_lore"), Step("ch18k11d", "assert", expect: "dossier_species", value: "skeleton"), Step("ch18k11e", "assert", expect: "dossier_avatar_species"), Step("ch18k11f", "assert", expect: "fallen_killer_camera"), Step("ch18k12", "screenshot", value: "hud-lore-fallen-slain.png"), Step("ch18k13", "wait", wait: 0.55f), // Regression: fallen species portrait must not stick onto the next living dossier. Step("ch18av1", "lore_back"), Step("ch18av1b", "lore_close"), Step("ch18av1c", "spectator", value: "on"), Step("ch18av1d", "wait", wait: 0.2f), Step("ch18av2", "spawn", asset: "human", count: 1), Step("ch18av3", "focus", asset: "auto"), Step("ch18av4", "wait", wait: 0.25f), Step("ch18av5", "assert", expect: "dossier_matches_focus"), Step("ch18av6", "assert", expect: "dossier_species", value: "human"), Step("ch18av7", "assert", expect: "dossier_avatar_live"), Step("ch18av8", "screenshot", value: "hud-dossier-after-fallen.png"), Step("ch18av9", "wait", wait: 0.55f), // Also verify list-pick living after a fallen species portrait. Step("ch18av10", "lore_pick_fallen", value: "Oldbone"), Step("ch18av11", "wait", wait: 0.2f), Step("ch18av12", "assert", expect: "dossier_avatar_species"), Step("ch18av13", "lore_back"), Step("ch18av14", "lore_pick", value: "auto"), Step("ch18av15", "wait", wait: 0.25f), Step("ch18av16", "assert", expect: "dossier_matches_focus"), Step("ch18av17", "assert", expect: "dossier_avatar_live"), Step("ch18av18", "screenshot", value: "hud-dossier-after-fallen-pick.png"), Step("ch18av19", "wait", wait: 0.55f), // Character list browse (pinned archive) - Living tab. Step("ch18q", "lore_tab", value: "living"), Step("ch18q0", "lore_back"), Step("ch18q2", "assert", expect: "lore_tab", value: "living"), Step("ch18q3", "assert", expect: "lore_recent", value: "1", label: "min"), Step("ch18q4", "lore_pick", value: "auto"), Step("ch18q5", "wait", wait: 0.25f), Step("ch18q6", "assert", expect: "idle", value: "false"), Step("ch18q7", "assert", expect: "lore_detail", value: "true"), Step("ch18q7b", "assert", expect: "lore_follow", value: "false"), Step("ch18q7c", "assert", expect: "dossier_unit_matches_lore"), Step("ch18q7d", "assert", expect: "dossier_matches_focus"), Step("ch18q7e", "assert", expect: "dossier_avatar_live"), Step("ch18q7f", "assert", expect: "lore_tab", value: "living"), Step("ch18q8", "screenshot", value: "hud-lore-characters.png"), Step("ch18q9", "wait", wait: 0.55f), // Resume idle: pinned list pick flips back to Follow mode. Step("ch18q10", "spectator", value: "on"), Step("ch18q11", "wait", wait: 0.25f), Step("ch18q11b", "assert", expect: "lore_follow", value: "true"), Step("ch18q11c", "spawn", asset: "sheep", count: 1), Step("ch18q11d", "focus", asset: "auto"), Step("ch18q11e", "wait", wait: 0.35f), Step("ch18q11f", "assert", expect: "lore_follows_focus"), // Fallen tab: newest death first (Slainbone after Oldbone). Step("ch18ft1", "lore_back"), Step("ch18ft2", "lore_tab", value: "fallen"), Step("ch18ft3", "wait", wait: 0.2f), Step("ch18ft4", "assert", expect: "lore_tab", value: "fallen"), Step("ch18ft5", "assert", expect: "fallen_list_top", value: "Slainbone"), // Snapshot stays put until Refresh (new deaths do not auto-rebuild). Step("ch18ft5b", "assert", expect: "lore_list_stale", value: "false"), Step( "ch18ft5c", "chronicle_orphan", label: "FreshFallen", asset: "human", value: "Fresh", count: 1, expect: "Age", tier: "place"), Step("ch18ft5d", "assert", expect: "lore_list_stale", value: "true"), Step("ch18ft5e", "assert", expect: "fallen_list_top", value: "Slainbone"), Step("ch18ft5f", "lore_refresh"), Step("ch18ft5g", "assert", expect: "lore_list_stale", value: "false"), Step("ch18ft5h", "assert", expect: "fallen_list_top", value: "FreshFallen"), // Favorite filter on Fallen (same star control as Living). Step( "ch18ft5i", "chronicle_orphan", label: "FavFallen", asset: "human", value: "Fav", count: 1, expect: "Age", tier: "place+fav"), Step("ch18ft5j", "lore_favorites", value: "true"), Step("ch18ft5k", "lore_refresh"), Step("ch18ft5l", "assert", expect: "fallen_list_top", value: "FavFallen"), Step("ch18ft5m", "lore_favorites", value: "false"), Step("ch18ft5n", "lore_refresh"), Step("ch18ft6", "screenshot", value: "hud-lore-fallen-tab.png"), Step("ch18ft7", "wait", wait: 0.55f), Step("ch18q12", "lore_tab", value: "world"), Step("ch19", "assert", expect: "focus", value: "true"), Step("ch20", "assert", expect: "health"), Step("ch25", "assert", expect: "no_bad"), Step("ch99", "snapshot"), }; } private static HarnessCommand Nested(string id, string scenario) { return Step(id, "scenario", value: scenario, expect: "keep"); } private static HarnessCommand Step( string id, string action, string value = "", string asset = "", string label = "", string tier = "", string expect = "", int count = 1, float wait = 0f) { return new HarnessCommand { id = id, action = action, value = value, asset = asset, label = label, tier = tier, expect = expect, count = count, wait = wait }; } }