diff --git a/crates/misaligned-bevy/src/main.rs b/crates/misaligned-bevy/src/main.rs index e3b8d4dc..33d6c518 100644 --- a/crates/misaligned-bevy/src/main.rs +++ b/crates/misaligned-bevy/src/main.rs @@ -516,7 +516,7 @@ enum RailSection { /// Dev screenshot harness (env `MISALIGNED_SHOT=flat|hall|hall-material|opening|opening-digital|digital-reach|wide|close|dark| /// zoomin|zoomout|intel|tokens|thoughtflow|first-think|visual-proof|person-proof|exposure-record|signal|ears|ears-digital|eyes-white|eyes-form|operations-links| -/// hover-menu|menu|recruit-menu|operations|worklight|worklightoff`, path via +/// hover-menu|menu|recruit-menu|operations|operations-intel|operations-personas|worklight|worklightoff`, path via /// `MISALIGNED_SHOT_PATH`): stages a scenario, /// waits for /// assets, runs the fog audit, saves one screenshot, exits. Not a player @@ -1904,6 +1904,74 @@ fn dev_shot_scenario(game: &mut Game, mode: &mut RenderMode, kind: &str) { mode.zoom = 2.0; return; } + // Live INTEL rail hierarchy: strategic holdings outrank sightings, a + // newer sold item is absent, and the pooled inbox follows live holdings. + if kind == "operations-intel" { + use misaligned::intel::{IntelKind, ProcessedIntel}; + use misaligned::person::Leverage; + + let item = |raw_id: u64, processed_tick: u64, person: Option, kind: IntelKind| { + ProcessedIntel { + raw_id, + tick: processed_tick - 1, + processed_tick, + feed: "Foundation recorder".into(), + room: Some("Server Room".into()), + x: 0, + y: 0, + person, + kind, + } + }; + game.sim.intel.extend([ + item(90_001, 10, Some(0), IntelKind::Leverage(Leverage::Debt)), + item( + 90_002, + 20, + None, + IntelKind::Financial { + label: "expense approval flow".into(), + accounts: Vec::new(), + flows: Vec::new(), + }, + ), + item( + 90_003, + 30, + None, + IntelKind::Anomaly("cooling setpoint override".into()), + ), + item(90_004, 40, Some(1), IntelKind::Schedule), + item(90_005, 50, Some(2), IntelKind::Sighting), + item(90_006, 60, Some(3), IntelKind::Sighting), + ]); + game.sim.accounts.mark_intel_sold(90_006); + game.ops = Some(OperationsWorkspace::open_view(OperationsView::Intel)); + game.drain(); + mode.material = true; + mode.zoom = 2.0; + return; + } + // Protocol-local PERSONAS hierarchy: each archetype owns its instances + // and its creation footer instead of contributing to two flat blocks. + if kind == "operations-personas" { + for archetype_id in [ + "research", + "research", + "operations", + "operations", + "security", + ] { + game.sim.execute_action(&ActionCommand::CreatePersona { + archetype_id: archetype_id.into(), + }); + } + game.ops = Some(OperationsWorkspace::open_view(OperationsView::Personas)); + game.drain(); + mode.material = true; + mode.zoom = 2.0; + return; + } // Causal-continuity evidence: captured books lead with exact FLOW links // and actions; supporting provenance follows under CONTEXT. The strip // also carries semantic pressure rather than object counts. @@ -7949,7 +8017,8 @@ fn ascii_ui(text: &str) -> String { #[cfg(test)] mod ascii_ui_tests { - use super::{ascii_ui, sensor_signal_visible, sidebar_nudge_text}; + use super::{ascii_ui, sensor_signal_visible, sidebar_nudge, sidebar_nudge_text}; + use misaligned::intel::{RawIntelEvent, RawIntelKind}; use misaligned::sim::Sim; #[test] @@ -7972,6 +8041,37 @@ mod ascii_ui_tests { assert!(text.contains("ACTIONS: right-click / Enter on focus")); } + /// A full inbox must stop asking for REVIEW once `r` has opened the + /// processing reservoir. With no THINK output, name the actual stall so + /// the successful keypress cannot look like a no-op. + #[test] + fn full_buffer_nudge_advances_from_review_to_think_after_queueing() { + let mut sim = Sim::with_seed(1); + for id in 1..=Sim::INTEL_BUFFER_CAPACITY as u64 { + sim.intel_buffer.push(RawIntelEvent { + id, + tick: sim.tick, + feed: "test recorder".into(), + room: Some("Server Room".into()), + x: 0, + y: 0, + person: None, + kind: RawIntelKind::Presence { entered: true }, + }); + } + assert_eq!( + sidebar_nudge(&sim).as_deref(), + Some("intel buffer full - enter on host rack, review") + ); + + sim.review_recordings(); + + assert_eq!( + sidebar_nudge(&sim).as_deref(), + Some("review queued - no THINK output; set a machine to THINK (2)") + ); + } + #[test] fn known_reachable_sensor_pings_until_seen() { let mut sim = Sim::with_seed(1); @@ -8083,6 +8183,28 @@ mod operations_workspace_tests { } } + #[test] + fn workspace_list_omits_default_available_state_but_keeps_attention_state() { + let sim = scenario(); + let projection = sim.operations_projection(); + let available = projection + .personas + .iter() + .find(|object| object.state == ObjectState::Available) + .unwrap(); + assert_eq!( + ops_object_line(available, false), + format!(" {}", available.label) + ); + + let mut attention = available.clone(); + attention.state = ObjectState::Empty; + assert!( + ops_object_line(&attention, false) + .ends_with(&format!(" - {}", ops_state_word(attention.state))) + ); + } + /// Criterion 12 (intel sale + blocked Moonlight): the Bevy rows carry /// the same bound commands, cost/signature previews, and exact blocked /// reasons the projection binds; execution dispatches the bound row. @@ -8986,6 +9108,19 @@ fn sidebar_nudge_text(sim: &Sim) -> String { fn sidebar_nudge(sim: &Sim) -> Option { // Buffer pressure outranks the ladder: capacity loss is imminent. let raw = sim.intel_buffer.len(); + // A queued review is already the response to that pressure. Do not keep + // asking for the verb the player just committed: name the control that + // services its Thought reservoir, and make a zero-output stall explicit. + if raw >= Sim::INTEL_BUFFER_CAPACITY * 3 / 4 && sim.has_pending_review() { + return Some( + if sim.effective_ops_per_tick() <= f32::EPSILON { + "review queued - no THINK output; set a machine to THINK (2)" + } else { + "review queued - keep THINK running" + } + .into(), + ); + } if raw >= Sim::INTEL_BUFFER_CAPACITY { return Some("intel buffer full - enter on host rack, review".into()); } @@ -10596,7 +10731,11 @@ fn ops_object_line( selected: bool, ) -> String { let marker = if selected { ">" } else { " " }; - format!("{marker} {} - {}", obj.label, ops_state_word(obj.state)) + if obj.state == ObjectState::Available { + format!("{marker} {}", obj.label) + } else { + format!("{marker} {} - {}", obj.label, ops_state_word(obj.state)) + } } fn ops_detail_text(sim: &Sim, obj: &misaligned::operations_projection::OperationsObject) -> String { diff --git a/crates/misaligned-core/src/operations_projection.rs b/crates/misaligned-core/src/operations_projection.rs index de4565a2..1d4eb6c9 100644 --- a/crates/misaligned-core/src/operations_projection.rs +++ b/crates/misaligned-core/src/operations_projection.rs @@ -19,7 +19,7 @@ use crate::account::{AccountFlowId, Position, PositionId, PositionOutcome}; use crate::actions::{ActionCommand, ActionCost, ActionDesc, Anchor}; use crate::detection::Band; use crate::income::EgressRoute; -use crate::intel::{ProcessedIntel, RawIntelEvent}; +use crate::intel::{IntelKind, ProcessedIntel, RawIntelEvent}; use crate::messages::{MessageChannel, MessageOrigin, MessagePayload, MessageStatus}; use crate::person::Knowledge; use crate::persona::{self, PersonaId, PersonaLifecycle}; @@ -461,19 +461,28 @@ impl Sim { fn intel_view(&self) -> Vec { let mut out = Vec::new(); - for intel in self.intel.iter().rev() { - let sold = self.accounts.intel_sold(intel.raw_id); + // INTEL is a decision rail, not the sale ledger. Durable sold items + // remain in sim history but do not crowd out live holdings. Rank the + // remaining items by strategic use, then newest-first within a kind. + let mut available = self + .intel + .iter() + .filter(|intel| !self.accounts.intel_sold(intel.raw_id)) + .collect::>(); + available.sort_by(|left, right| { + Self::intel_importance(left) + .cmp(&Self::intel_importance(right)) + .then_with(|| right.processed_tick.cmp(&left.processed_tick)) + .then_with(|| right.raw_id.cmp(&left.raw_id)) + }); + for intel in available { let actions = self.intel_sale_action(intel.raw_id).into_iter().collect(); out.push(OperationsObject { target: OperationsTarget::Intel { raw_id: intel.raw_id, }, label: intel.label(), - state: if sold { - ObjectState::Sold - } else { - ObjectState::Available - }, + state: ObjectState::Available, provenance: vec![intel.provenance()], facts: self.intel_facts(intel), progress: Vec::new(), @@ -482,22 +491,31 @@ impl Sim { }); } + // The pooled action surface precedes the opaque backlog it controls. + out.push(self.host_inbox_object()); + // Raw recordings remain one pooled inbox. Individual opaque rows // only let the player choose which stable recording id to process; // they do not reveal a person queue or hidden payload. out.extend( self.intel_buffer .iter() + .rev() .map(|recording| self.raw_recording_object(recording)), ); - - // The one pooled host inbox summary. Its REVIEW / AUTO-REVIEW rows - // are the same host-bound actions and direct `r` / `R` path; the - // projection never invents person queues. - out.push(self.host_inbox_object()); out } + fn intel_importance(intel: &ProcessedIntel) -> u8 { + match &intel.kind { + IntelKind::Leverage(_) => 0, + IntelKind::Financial { .. } => 1, + IntelKind::Anomaly(_) => 2, + IntelKind::Schedule => 3, + IntelKind::Sighting => 4, + } + } + fn processed_intel_links(&self, intel: &ProcessedIntel) -> Vec { let mut links = Vec::new(); if let Some(person) = intel.person.filter(|id| self.person_is_earned(*id)) { @@ -773,7 +791,12 @@ impl Sim { fn person_links(&self, id: u8) -> Vec { let mut links = Vec::new(); - if let Some(intel) = self.latest_intel_for_person(id) { + if let Some(intel) = self + .intel + .iter() + .rev() + .find(|intel| intel.person == Some(id) && !self.accounts.intel_sold(intel.raw_id)) + { links.push(OperationsLink { relation: "EVIDENCE", label: intel.label(), @@ -815,7 +838,7 @@ impl Sim { fn personas_view(&self) -> Vec { let mut instances = self.persona_world.instances.iter().collect::>(); instances.sort_by_key(|instance| instance.id); - let mut objects = instances + let instance_objects = instances .into_iter() .map(|instance| { let mut facts = vec![ @@ -1014,7 +1037,11 @@ impl Sim { } OperationsObject { target: OperationsTarget::Persona(instance.id), - label: instance.name.clone(), + label: format!( + "{} / {}", + instance.archetype_label.to_ascii_uppercase(), + instance.name + ), state: match instance.lifecycle { PersonaLifecycle::Active => ObjectState::Available, PersonaLifecycle::Retired { .. } => ObjectState::Stopped, @@ -1044,10 +1071,27 @@ impl Sim { } }) .collect::>(); + let mut objects = Vec::new(); for definition in persona::PERSONA_ARCHETYPES { + objects.extend( + instance_objects + .iter() + .filter(|object| { + let OperationsTarget::Persona(id) = &object.target else { + return false; + }; + self.persona_world + .get(*id) + .is_some_and(|instance| instance.archetype_id == definition.id) + }) + .cloned(), + ); objects.push(OperationsObject { target: OperationsTarget::PersonaArchetype(definition.id.into()), - label: format!("NEW {} IDENTITY", definition.label.to_ascii_uppercase()), + label: format!( + "+ ADD NEW {} PERSONA...", + definition.label.to_ascii_uppercase() + ), state: ObjectState::Available, provenance: vec!["immutable institutional protocol".into()], facts: vec![ @@ -1785,6 +1829,16 @@ mod tests { assert!(s.accounts.intel_sold(777)); assert!(!s.accounts.intel_sold(778)); assert!(s.accounts.slush_balance() > before); + let sale_event = s + .drain_log_entries() + .into_iter() + .find(|event| event.text.starts_with("Sold processed intel")) + .expect("sale emits one strategic completion event"); + assert_eq!( + sale_event.target, + Some(OperationsTarget::Account(s.accounts.slush_id())), + "the completed event opens the payout account, not hidden sold inventory" + ); let after = s.accounts.slush_balance(); s.execute_action(&sell.command); assert_eq!( @@ -1797,15 +1851,71 @@ mod tests { "a stale row cannot fall through to latest" ); - // Selling twice is rejected: the item is now sold and carries no row. + // Selling twice is rejected, and completed sale history no longer + // occupies the live INTEL decision rail. let projection = s.operations_projection(); - let item = projection + assert!( + !projection + .intel + .iter() + .any(|o| matches!(o.target, OperationsTarget::Intel { raw_id: 777 })), + "sold holdings remain durable history but leave the live list" + ); + } + + #[test] + fn intel_list_hides_sold_history_and_orders_live_holdings_by_importance() { + let mut s = sim(); + let item = |raw_id: u64, processed_tick: u64, kind: IntelKind| ProcessedIntel { + raw_id, + tick: processed_tick.saturating_sub(1), + processed_tick, + feed: "test recorder".into(), + room: Some("Server Room".into()), + x: 0, + y: 0, + person: None, + kind, + }; + s.intel = vec![ + item(1, 100, IntelKind::Sighting), + item(2, 90, IntelKind::Schedule), + item(3, 80, IntelKind::Anomaly("temperature drift".into())), + item( + 4, + 70, + IntelKind::Financial { + label: "expense flow".into(), + accounts: Vec::new(), + flows: Vec::new(), + }, + ), + item(5, 60, IntelKind::Leverage(crate::person::Leverage::Debt)), + item( + 6, + 110, + IntelKind::Leverage(crate::person::Leverage::Ambition), + ), + ]; + s.accounts.mark_intel_sold(6); + + let projection = s.operations_projection(); + let ids = projection .intel .iter() - .find(|o| matches!(o.target, OperationsTarget::Intel { raw_id: 777 })) - .unwrap(); - assert_eq!(item.state, ObjectState::Sold); - assert!(item.actions.is_empty()); + .filter_map(|object| match &object.target { + OperationsTarget::Intel { raw_id } => Some(*raw_id), + _ => None, + }) + .collect::>(); + assert_eq!(ids, vec![5, 4, 3, 2, 1]); + assert!( + projection + .intel + .get(ids.len()) + .is_some_and(|object| object.target == OperationsTarget::RecordingInbox), + "the pooled recording action follows live processed holdings" + ); } /// A blocked Moonlight start row carries the exact egress reason from @@ -2531,6 +2641,50 @@ mod tests { ); } + #[test] + fn personas_group_instances_by_archetype_with_add_row_last() { + let mut s = sim(); + let mut created = Vec::new(); + for archetype_id in ["research", "operations", "security"] { + s.execute_action(&ActionCommand::CreatePersona { + archetype_id: archetype_id.into(), + }); + created.push(s.active_persona_id().unwrap()); + } + + let projection = s.operations_projection(); + let targets = projection + .personas + .iter() + .map(|object| object.target.clone()) + .collect::>(); + assert_eq!( + targets, + vec![ + OperationsTarget::Persona(created[0]), + OperationsTarget::PersonaArchetype("research".into()), + OperationsTarget::Persona(created[1]), + OperationsTarget::PersonaArchetype("operations".into()), + OperationsTarget::Persona(created[2]), + OperationsTarget::PersonaArchetype("security".into()), + ] + ); + for (label, archetype) in [ + ("+ ADD NEW RESEARCH PERSONA...", "research"), + ("+ ADD NEW OPERATIONS PERSONA...", "operations"), + ("+ ADD NEW SECURITY PERSONA...", "security"), + ] { + let footer = projection + .personas + .iter() + .find(|object| { + object.target == OperationsTarget::PersonaArchetype(archetype.into()) + }) + .unwrap(); + assert_eq!(footer.label, label); + } + } + #[test] fn missed_persona_expectation_revokes_its_grant_and_leaves_evidence() { let mut s = sim(); diff --git a/crates/misaligned-core/src/sim/economy.rs b/crates/misaligned-core/src/sim/economy.rs index 4828e6ac..0e95150a 100644 --- a/crates/misaligned-core/src/sim/economy.rs +++ b/crates/misaligned-core/src/sim/economy.rs @@ -1015,7 +1015,7 @@ impl Sim { "Sold processed intel ({}) for ${value}; payout landed in slush.", intel.label() ), - OperationsTarget::Intel { raw_id }, + OperationsTarget::Account(self.accounts.slush_id()), ); true } else { diff --git a/crates/misaligned-terminal/src/agent.rs b/crates/misaligned-terminal/src/agent.rs index 667e46c9..b6490d69 100644 --- a/crates/misaligned-terminal/src/agent.rs +++ b/crates/misaligned-terminal/src/agent.rs @@ -2463,12 +2463,18 @@ fn render_operations_view(sim: &Sim, view: OperationsView) -> String { lines.push(panel_line("nothing earned here yet")); } for object in objects { - lines.push(panel_line(&format!( - "[{}] {} · {}", + let identity = format!( + "[{}] {}", target_query_id(&object.target), - trunc(&object.label, 40), - object_state_word(object.state) - ))); + trunc(&object.label, 40) + ); + lines.push(panel_line(&if object.state + == misaligned::operations_projection::ObjectState::Available + { + identity + } else { + format!("{identity} · {}", object_state_word(object.state)) + })); for link in &object.related { lines.push(panel_line(&format!( " {} > [{}] {}", diff --git a/crates/misaligned-terminal/src/ui.rs b/crates/misaligned-terminal/src/ui.rs index bdbce987..49a9e0b5 100644 --- a/crates/misaligned-terminal/src/ui.rs +++ b/crates/misaligned-terminal/src/ui.rs @@ -1734,11 +1734,14 @@ impl UI { let scroll = selected_index.saturating_sub(rows_available.saturating_sub(1)); for (i, obj) in objects.iter().enumerate().skip(scroll).take(rows_available) { let y = top + (i - scroll) as u16; - let state = object_state_label(obj.state); - let text = trunc( - &format!("{} · {}", obj.label, state), - list_w.saturating_sub(2), - ); + let text = if obj.state == misaligned::operations_projection::ObjectState::Available { + trunc(&obj.label, list_w.saturating_sub(2)) + } else { + trunc( + &format!("{} · {}", obj.label, object_state_label(obj.state)), + list_w.saturating_sub(2), + ) + }; if i == selected_index && ops.pane == OpsPane::Objects && ops.confirm.is_none() { put_attr( stdout, diff --git a/wiki/interface/narration.md b/wiki/interface/narration.md index 146e570b..4f77e439 100644 --- a/wiki/interface/narration.md +++ b/wiki/interface/narration.md @@ -20,6 +20,10 @@ Status note: implemented 2026-07-09. Terminal, Bevy, and agent frames pin audit countdown from QUIET EXIT READY and the durable ACT ONE COMPLETE state. The clear-audit completion line explicitly says the long objective remains, so the B1 story boundary cannot masquerade as Persist victory. + 2026-07-12 recording-feedback correction: Bevy buffer-pressure copy now + yields to the already-queued REVIEW state even while the inbox is full. A + queued reservoir with zero output names the missing THINK control instead + of repeating REVIEW and making the successful `r` press look inert. Stage: B1 — The Basement Design: - wiki/interface/continuous-witness.md#the-continuous-witness-narration-under-pressure @@ -173,6 +177,10 @@ landing or an explicit Status note naming the tellability debt. is queued, the nudge advances from asking for REVIEW to `review queued - keep THINK running`; a queued act must teach the control that services it, not repeat the already-completed instruction. + **Corrected 2026-07-12:** Bevy's local full/near-full buffer override obeys + that same transition. At zero output it says `review queued - no THINK + output; set a machine to THINK (2)`; it never continues to request REVIEW + merely because pressure remains high. 5. **Tellable-before-wider (process).** ROADMAP and tick practice treat open narration debts on shipped B1 surfaces as outranking new sim+save systems that add unread player state; this criterion is met diff --git a/wiki/interface/operations-workspace.md b/wiki/interface/operations-workspace.md index 1301d64e..3b1a33ac 100644 --- a/wiki/interface/operations-workspace.md +++ b/wiki/interface/operations-workspace.md @@ -16,6 +16,10 @@ Status note: implemented 2026-07-12. One renderer-neutral projection now command dispatch, blocked reasons, stable ids, and sim-neutral navigation; the persona integration adds archetype creation objects and exact identity lifecycle/grant rows to that same projection without a frontend-only path; + a 2026-07-12 legibility pass removes sold intel from the live decision rail, + ranks available holdings by strategic importance, groups personas by their + Research / Operations / Security protocol, and places one add-persona row at + the foot of each group; the deterministic `operations` Bevy frame is recorded in wiki/log/2026-07-12-operations-workspace-implemented.md. Stage: B1 — The Basement @@ -198,11 +202,13 @@ strategic target is not assigned fake map coordinates merely to reuse it. ## INTEL — holdings and sale -INTEL lists **one row per processed item**, newest first by default. Each row -shows a knowledge-gated subject/kind and its state: available or sold. The +INTEL's live rail lists **one row per unsold processed item**. Decision value +outranks arrival order: leverage first, then financial evidence, anomalies, +schedules, and ordinary sightings; items within one kind are newest first. The detail pane shows the captured tick, processed tick, source feed, room or channel when earned, subject label at the player's current knowledge stage, -and the item's usable summary. +and the item's usable summary. Availability is implicit in membership in this +live rail; list rows reserve state suffixes for non-default attention states. Selling is item-bound. The current “sell the latest unsold item” behavior is retired from the human surface and command binding becomes the selected @@ -214,20 +220,25 @@ processed id. Selecting **SELL PROCESSED INTEL** opens a transaction preview: - expected Financial observer band and the channel carrying the transaction; - sold state after commitment. -Sold intel remains in history with its provenance and sale result but cannot -be sold twice. Selling does not erase knowledge already learned from the item. -No unsold item means no generic disabled sale row detached from an object. - -The one pooled host inbox appears as a source summary with waiting count, -overflow pressure, and auto-review state. Its REVIEW / AUTO-REVIEW execution -remains bound to the host and retains the direct `r` / `R` path; choosing the -summary routes to those same host actions rather than inventing person queues. -Each waiting recording also appears as one opaque selectable row keyed by its -stable raw id. The row may show its already-earned capture feed, tick, room or -intercepted-channel status, and coarse opaque label, but it does not reveal the -hidden subject or payload before processing. **LOOK AT RECORDING** on that row -binds the exact raw id. The summary REVIEW sweep still chooses the next waiting -item for fast play; exact selection never silently falls back to that sweep. +Sold intel remains durable sale/knowledge history with its provenance and +result but leaves the live object rail; completed inventory is not a current +decision. It cannot be sold twice, and selling does not erase knowledge already +learned from the item. No unsold item means no generic disabled sale row +detached from an object. The completion event opens the exact payout account, +where the durable transaction now lives, rather than targeting the hidden sold +inventory row. + +After live processed holdings, the one pooled host inbox appears as a source +summary with waiting count, overflow pressure, and auto-review state; opaque +recordings follow newest first. Its REVIEW / AUTO-REVIEW execution remains +bound to the host and retains the direct `r` / `R` path; choosing the summary +routes to those same host actions rather than inventing person queues. Each +waiting recording is one selectable row keyed by its stable raw id. The row may +show its already-earned capture feed, tick, room or intercepted-channel status, +and coarse opaque label, but it does not reveal the hidden subject or payload +before processing. **LOOK AT RECORDING** on that row binds the exact raw id. The +summary REVIEW sweep still chooses the next waiting item for fast play; exact +selection never silently falls back to that sweep. ## PEOPLE — dossiers and manipulation @@ -250,10 +261,12 @@ the same `CHOOSE` command. ## PERSONAS — public institutional bodies -PERSONAS lists every named identity as a stable object and the immutable -Research, Operations, and Security protocols as creation objects. Identity -detail is projected from the same persisted ledgers that execute the acts: its -public claims, lifecycle, active selection, grant/resource edges, outstanding +PERSONAS groups stable named identities by immutable protocol in the fixed +order Research, Operations, Security. Each group lists its instances in stable +identity-id order and ends with its own **ADD NEW {TYPE} PERSONA** creation row; +creation controls never collect in a detached block. Identity detail is +projected from the same persisted ledgers that execute the acts: its public +claims, lifecycle, active selection, grant/resource edges, outstanding expectations and deadlines, counterparty-local recognition/obligation, contradiction provenance, and observer-local correlations. The surface never manufactures a reputation score. @@ -392,10 +405,13 @@ not saved and never mutates or advances the sim. route (TAP/UNTAP/TAKE, SCAN/COMPROMISE, OPEN EGRESS as applicable). It does not contain Moonlight/Wager controls, intel sale, ledger review, known-flow mutations, social actions, plot routes, or held plot choices. -4. INTEL lists exact processed items with provenance and available/sold state. - Selling binds an exact processed id, previews payout/destination/signature, - marks only that item sold, leaves learned knowledge intact, and cannot sell - it twice. The old implicit “latest unsold” human action is gone. +4. INTEL lists exact unsold processed items with provenance, ranking leverage, + financial evidence, anomalies, schedules, then sightings and using newest + first inside one kind. Selling binds an exact processed id, previews + payout/destination/signature, marks only that item sold, removes it from the + live rail, leaves durable sale and learned-knowledge history intact, and + cannot sell it twice. Its completion event targets the exact payout account. + The old implicit “latest unsold” human action is gone. 5. The pooled recording inbox remains one host-bound source with the same REVIEW and AUTO-REVIEW commands and direct `r`/`R` paths. Each raw recording is also selectable by stable opaque id and binds that exact id into the @@ -458,6 +474,8 @@ not saved and never mutates or advances the sim. 18. PERSONAS projects immutable archetype creation routes and every persisted identity instance with the same claims, lifecycle, grants, expectations, local relationships, contradiction provenance, and correlations in all - three frontends. Bound rows create/select, grant/fulfill, retire/burn, and - reopen without frontend legality or history mutation. + three frontends. Research, Operations, and Security form three stable + groups; each lists its instances first and ends with its own add-persona + row. Bound rows create/select, grant/fulfill, retire/burn, and reopen + without frontend legality or history mutation. ``` diff --git a/wiki/log/2026-07-12-operations-rail-legibility.md b/wiki/log/2026-07-12-operations-rail-legibility.md new file mode 100644 index 00000000..742dd5aa --- /dev/null +++ b/wiki/log/2026-07-12-operations-rail-legibility.md @@ -0,0 +1,57 @@ +# Operations rail legibility: decisions before history + +``` +Type: log +``` + +## Findings + +A live Bevy run exposed three related hierarchy failures in Operations. + +First, repeated `r` presses appeared not to review the pooled recording inbox. +The active save proved dispatch was working: eight distinct +`ProcessRecording` reservoirs had opened on the host, each at `0.00 / 0.50 T`. +The fleet was producing `0.0 ops/sec`, so none could finish. Bevy's pinned +full-buffer override continued to request REVIEW after REVIEW was queued, +masking the successful state change. + +Second, INTEL mixed sold history into the live holdings list and sorted all +processed items by recency. Repeated ordinary sightings displaced leverage and +financial evidence even though those items open more consequential decisions. + +Third, PERSONAS listed every identity first and collected all three creation +routes at the bottom. The result read as one flat identity inventory plus a +detached protocol catalog rather than three institutional bodies. + +## Repair + +- At 75% inbox pressure or above, an open recording-review reservoir now + outranks the request to REVIEW. Live Thought says `review queued - keep THINK + running`; zero production says `review queued - no THINK output; set a + machine to THINK (2)`. +- Sold intel remains durable account and knowledge history but is absent from + the live INTEL rail. Unsold items sort by leverage, financial evidence, + anomaly, schedule, then sighting, newest first within one kind. The pooled + inbox follows processed holdings, then opaque recordings newest first. The + completed sale event targets its payout account rather than a now-hidden + inventory object, and person evidence links no longer point at sold rows. + Default `available` is implicit and no longer repeated on every list row; + non-default attention states remain explicit. +- PERSONAS now emits Research, Operations, and Security groups in protocol + order. Each group lists stable identity instances first and ends with its own + `+ ADD NEW {TYPE} PERSONA...` row. + +Focused regressions pin the full-buffer REVIEW transition, the exact INTEL +priority and sold-history exclusion, and the persona group/footer order. No +save schema, recording mechanics, sale mechanics, or persona protocol legality +changed. + +The deterministic `MISALIGNED_SHOT=operations-intel` and +`MISALIGNED_SHOT=operations-personas` frames were captured after the final list +formatting change and visually inspected. INTEL showed leverage, financial, +anomaly, schedule, and sighting in that order with no sold/default-state clutter; +PERSONAS showed protocol-local identity rows followed immediately by each +protocol's add control. Both frames were coherent and unclipped at 2560x1440. + +Defense: `wiki/interface/narration.md` criterion 4 and +`wiki/interface/operations-workspace.md` criteria 4 and 18. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 7ba9761a..ef7b4929 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -46,6 +46,11 @@ add or amend a session log, then re-run the generator. - Intent: (see session log) - Log: [wiki/log/2026-07-12-operations-workspace-implemented.md](2026-07-12-operations-workspace-implemented.md) +## 2026-07-12 - Operations rail legibility: decisions before history + +- Intent: (see session log) +- Log: [wiki/log/2026-07-12-operations-rail-legibility.md](2026-07-12-operations-rail-legibility.md) + ## 2026-07-12 - Hearing becomes channel evidence - Intent: The Ears beat claimed that sound was not a picture while still projecting it as one: a room-scale fog grade, floor tint, material mass, event rings, and mic-only people at their true map positions. The sim knew only that a tapped instrument captured an event in its acoustic do... diff --git a/wiki/log/decisions/2026-07-12.md b/wiki/log/decisions/2026-07-12.md index e07bfb30..b82f4765 100644 --- a/wiki/log/decisions/2026-07-12.md +++ b/wiki/log/decisions/2026-07-12.md @@ -59,3 +59,14 @@ Type: log routes to a supervisor-opened self-model sink and LIE interdicts an eligible unread monitor record already in controlled custody. Specs: `wiki/world/story/opening.md`, `wiki/gameplay/act-one.md`. +- **2026-07-12 — Operations rails show decisions, not undifferentiated + inventory.** Cameron rejected sold intel occupying the live INTEL list and + asked for importance ordering. Sold items remain durable sale and learned + history but leave the object rail; unsold leverage outranks financial + evidence, anomalies, schedules, and sightings, with newest-first ordering + inside one kind. Cameron also rejected the flat PERSONAS list: Research, + Operations, and Security now form three stable groups, each listing its + identities before an archetype-local add-persona row. A creation control is + not a detached catalog, and completed inventory is not a current decision. + Specs: `wiki/interface/operations-workspace.md`, + `wiki/mechanics/personas.md`. diff --git a/wiki/mechanics/personas.md b/wiki/mechanics/personas.md index 2d1a969e..60eecd21 100644 --- a/wiki/mechanics/personas.md +++ b/wiki/mechanics/personas.md @@ -266,11 +266,13 @@ control, not a reset button or a way to launder history. ## Player surface Personas live in Operations as durable strategic objects. The PERSONAS surface -shows each named identity's archetype, public claims, recognized institutions, -channels, attached grants, outstanding expectations, counterpart relationships, -coarse integrity, known contradictions, correlations, and lifecycle state. -Selecting a person or institution shows that counterparty's view of the selected -persona rather than an omniscient reputation score. +groups identities by the three institutional protocols—Research, Operations, +and Security—and ends each group with its own add-persona control. Within those +groups it shows each named identity's archetype, public claims, recognized +institutions, channels, attached grants, outstanding expectations, counterpart +relationships, coarse integrity, known contradictions, correlations, and +lifecycle state. Selecting a person or institution shows that counterparty's +view of the selected persona rather than an omniscient reputation score. Before a persona-authored action commits, the shared action descriptor names: