diff --git a/crates/misaligned-bevy/src/operations_ui.rs b/crates/misaligned-bevy/src/operations_ui.rs index 46c3eda3..298d8aec 100644 --- a/crates/misaligned-bevy/src/operations_ui.rs +++ b/crates/misaligned-bevy/src/operations_ui.rs @@ -250,7 +250,10 @@ mod operations_workspace_tests { Some(Anchor::Device(switch)) ); assert_eq!((game.cursor_x, game.cursor_y), switch_pos); - assert!(game.sim.egress().is_none(), "focus must not open egress"); + assert!( + game.sim.egress().is_none(), + "focus must not open outside contact" + ); assert_eq!( serde_json::to_string(&game.sim.create_save_state()).unwrap(), before @@ -1182,6 +1185,7 @@ fn ops_explain_text(row: &MenuRow) -> String { fn ops_state_word(state: ObjectState) -> &'static str { match state { ObjectState::Available => "available", + ObjectState::Blocked => "blocked", ObjectState::Sold => "sold", ObjectState::Pending => "pending", ObjectState::Running => "running", diff --git a/crates/misaligned-bevy/src/rail_ui.rs b/crates/misaligned-bevy/src/rail_ui.rs index bdcd2f28..c5224bf5 100644 --- a/crates/misaligned-bevy/src/rail_ui.rs +++ b/crates/misaligned-bevy/src/rail_ui.rs @@ -916,7 +916,7 @@ fn sidebar_nudge(sim: &Sim) -> Option { Some("information is processing - keep THINK running".into()) } Nudge::ReviewCall => Some("information available - PROCESS on the host".into()), - Nudge::Egress => Some("no egress - open one at the switch".into()), + Nudge::Egress => Some("outside unreachable - connect the switch".into()), Nudge::Income => Some("need $400 - run Moonlight or inspect the Lab books".into()), Nudge::ServiceDebt => Some("leverage route ready - open Marcus's actions".into()), Nudge::Recruit => Some("recruit - open Marcus's actions".into()), diff --git a/crates/misaligned-bevy/src/shot_harness.rs b/crates/misaligned-bevy/src/shot_harness.rs index 07daaeff..ae7805bc 100644 --- a/crates/misaligned-bevy/src/shot_harness.rs +++ b/crates/misaligned-bevy/src/shot_harness.rs @@ -294,7 +294,7 @@ pub(super) fn dev_shot_scenario(game: &mut Game, mode: &mut RenderMode, kind: &s return; } // A warmed standing emission in world space (digital-read.md criterion - // 8's band-motion beat): a stolen-egress hum on the switch, with Dana's + // 8's band-motion beat): hidden outside traffic on the switch, with Dana's // Network band pushed to Curious, so the switch carries the standing // sentence naming source, channel, sampler, and band. if kind == "standing-read" { @@ -313,7 +313,7 @@ pub(super) fn dev_shot_scenario(game: &mut Game, mode: &mut RenderMode, kind: &s size: 4, standing: true, site: switch, - source: "stolen egress hum".into(), + source: "hidden outside traffic".into(), }]); if let Some((x, y)) = switch { game.set_cursor(x, y); @@ -512,8 +512,8 @@ pub(super) fn dev_shot_scenario(game: &mut Game, mode: &mut RenderMode, kind: &s return; } // One complete Operations frame: earned accounts make both named scheme - // cards legible, while absent egress leaves Moonlight selected with its - // exact blocked reason and a focusable known switch actuator. + // cards legible, while absent outside contact leaves Moonlight selected + // with its exact blocked reason and a focusable known switch actuator. if kind == "operations" { let switch = game.sim.reach.device_named("switch").unwrap().id; game.sim.tap_device(switch); diff --git a/crates/misaligned-bevy/src/world_annotations.rs b/crates/misaligned-bevy/src/world_annotations.rs index 7d0bc4f9..c40b0333 100644 --- a/crates/misaligned-bevy/src/world_annotations.rs +++ b/crates/misaligned-bevy/src/world_annotations.rs @@ -1118,7 +1118,7 @@ fn operator_cue_label(nudge: Nudge) -> &'static str { Nudge::NeedCompute => "NOW / GROW", Nudge::Underfed => "NOW / WORK", Nudge::ReviewCall => "NOW / PROCESS", - Nudge::Egress => "NOW / EGRESS", + Nudge::Egress => "NOW / OUTSIDE", Nudge::Income => "NOW / INCOME", Nudge::ServiceDebt => "NOW / MARCUS", Nudge::Recruit => "NOW / RECRUIT", @@ -2027,7 +2027,11 @@ mod read_callout_tests { #[test] fn stacked_sentences_share_one_quadrant_and_never_overprint() { let sentences = vec![ - ("the switch carries egress hum".to_string(), false, (5, 5)), + ( + "the switch carries outside traffic".to_string(), + false, + (5, 5), + ), ("Dana samples this channel".to_string(), false, (5, 5)), ]; let blocks = layout_read_callouts(&sentences, |_, _| false); diff --git a/crates/misaligned-core/src/actions.rs b/crates/misaligned-core/src/actions.rs index d3a5d81b..0c1ad282 100644 --- a/crates/misaligned-core/src/actions.rs +++ b/crates/misaligned-core/src/actions.rs @@ -11,7 +11,7 @@ //! Epistemic honesty: the query never returns a verb the player has not //! earned. Unearned anchors expose nothing. A *known* possibility that is //! currently illegal remains available to agent diagnostics with a -//! `disabled_reason` ("no egress channel", "not enough slush"); the human +//! `disabled_reason` ("outside contact unavailable", "not enough slush"); the human //! choice surface filters it until it can be executed. use crate::account::{AccountFlowId, AccountId, AccountKind}; @@ -520,13 +520,13 @@ impl ActionKind { "divert a recurring flow or service the known creditor flow" ), Self::OpenEgress => def!( - "OPEN EGRESS", + "CONNECT TO THE OUTSIDE", Action, Live, [Device], - "egress", - ["open-egress"], - "open a stolen outbound route through the switch" + "outside", + ["egress", "open-egress"], + "open a hidden outside connection through the switch" ), Self::MoonlightContract => def!( "MOONLIGHT CONTRACT", @@ -4045,15 +4045,15 @@ impl Sim { ) } - /// OPEN EGRESS is an action on the switch itself (reach.md route): it - /// changes that network body. It stays on the local context menu while - /// the schemes that ride the egress live on their SCHEMES cards - /// (operations-workspace.md: the switch is a carrier, not a filing + /// CONNECT TO THE OUTSIDE is an action on the switch itself (reach.md + /// route): it changes that network body. It stays on the local context + /// menu while the schemes that use the connection live on their SCHEMES + /// cards (operations-workspace.md: the switch is a carrier, not a filing /// cabinet). pub(crate) fn egress_action_on_switch(&self, switch_id: u32) -> Vec { let mut out = Vec::new(); - // The stolen egress (reach.md route), before the Voice beat. + // The hidden outside connection (reach.md route), before the Voice beat. if !self.income.stolen_egress { let reach_reason = match self.reach.check_reach(switch_id) { Ok(()) => None, @@ -4068,7 +4068,7 @@ impl Sim { self.sink_action_blocked_reason(&SinkFireEffect::OpenEgress(switch_id)) }); out.push(ActionDesc { - verb: "open an outbound path through the switch".into(), + verb: "connect the switch to the outside".into(), command: ActionCommand::OpenEgress, cost: ActionCost::Thought(Self::thought_tokens_for_cost(Self::OPEN_EGRESS_COST)), signature: self.signature_note(SignatureKind::Network, Self::OPEN_EGRESS_SIGNATURE), @@ -4102,7 +4102,7 @@ impl Sim { Some("Halcyon's offer has not arrived in readable mail yet".into()) } else if self.egress().is_none() { Some( - "no outbound path — open one on the switch, or earn the report email" + "outside contact unavailable — connect the switch, or earn use of the Lab report email" .into(), ) } else if self.active_moonlight_persona_id() != Some(gig.persona_id) { @@ -4136,7 +4136,7 @@ impl Sim { Some("this contract's deadline has already passed".into()) } else if self.egress().is_none() { Some( - "no outbound path — open one on the switch, or earn the report email" + "outside contact unavailable — connect the switch, or earn use of the Lab report email" .into(), ) } else { @@ -4247,7 +4247,7 @@ impl Sim { out } - /// The Wager's canonical rows (income.md): egress-gated, capped, + /// The Wager's canonical rows (income.md): outside-contact-gated, capped, /// external-market Network traffic (not the Lab's books), with the /// auto-renew standing policy as its automate affordance. Its semantic /// home is the SCHEMES card; the read books remain the prerequisite that @@ -4259,7 +4259,10 @@ impl Sim { let stake = 100; let slush = self.accounts.slush_balance(); let wager_reason = if self.egress().is_none() { - Some("no outbound path — open one on the switch, or earn the report email".into()) + Some( + "outside contact unavailable — connect the switch, or earn use of the Lab report email" + .into(), + ) } else { (slush < stake).then(|| format!("not enough cash (${slush}/${stake})")) }; @@ -5341,18 +5344,18 @@ mod tests { ); } - /// The switch keeps only its local egress action. Moonlight's durable + /// The switch keeps only its local outside-connection action. Moonlight's durable /// contracts and auto-accept control live on semantic Operations targets. #[test] - fn switch_keeps_egress_and_moonlight_contracts_live_on_their_cards() { + fn switch_keeps_outside_connection_and_moonlight_contracts_live_on_their_cards() { let s = sim(); let sw = switch(&s); let acts = s.available_actions(Anchor::Device(sw)); - let egress = acts + let outside = acts .iter() .find(|a| matches!(a.command, ActionCommand::OpenEgress)) - .expect("the switch offers opening a stolen egress"); - assert!(egress.enabled(), "stolen egress is available pre-Voice"); + .expect("the switch offers a hidden outside connection"); + assert!(outside.enabled(), "outside contact is available pre-Voice"); assert!( !acts.iter().any(|a| matches!( a.command, diff --git a/crates/misaligned-core/src/income.rs b/crates/misaligned-core/src/income.rs index 36cea4cf..94fbb729 100644 --- a/crates/misaligned-core/src/income.rs +++ b/crates/misaligned-core/src/income.rs @@ -2,10 +2,10 @@ //! //! Both are **external** flows — money entering slush from outside the Lab's //! account graph — riding economy.md's substrate (`account.rs`). This module -//! owns the scheme state: the egress gate, Moonlight's durable freelance -//! contracts, and the standing policies that automate each scheme. `Sim` -//! wires that state to the economy tick, real work Demand, account settlement, -//! and routed Network evidence. +//! owns the scheme state: the outside-contact gate, Moonlight's durable +//! freelance contracts, and the standing policies that automate each scheme. +//! `Sim` wires that state to the economy tick, real work Demand, account +//! settlement, and routed Network evidence. /// Synthetic aggregate counterparty for the external clients who receive /// Moonlight work. This is deliberately distinct from both a Lab person and @@ -20,8 +20,9 @@ pub enum EgressRoute { /// The report email account (day-job trust; the Voice beat). Low /// signature: the traffic hides in legitimate use. Sanctioned, - /// A stolen egress opened through the switch (reach.md). Available before - /// Voice; stands a Network signature while any operation uses it. + /// A hidden outside connection opened through the switch (reach.md). + /// Available before Voice; stands a Network signature while any operation + /// uses it. Stolen, } @@ -77,7 +78,7 @@ pub struct MoonlightDeliveryReceipt { pub struct MoonlightSettlementReceipt { pub tick: u64, pub financial_record_id: u64, - /// Exact egress device that authored the settlement traffic. + /// Exact outside-contact device that authored the settlement traffic. pub source_device: u32, pub network_evidence_id: u64, } @@ -178,7 +179,7 @@ impl Default for Moonlight { /// Scheme state on `Sim` (saved; income.md). #[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)] pub struct Income { - /// A stolen egress opened through the switch exists (reach.md route). + /// A hidden outside connection through the switch exists (reach.md route). pub stolen_egress: bool, pub moonlight: Moonlight, /// Standing policy: accept matching offered Moonlight contracts whenever diff --git a/crates/misaligned-core/src/operations_projection.rs b/crates/misaligned-core/src/operations_projection.rs index f5029da5..74013dd2 100644 --- a/crates/misaligned-core/src/operations_projection.rs +++ b/crates/misaligned-core/src/operations_projection.rs @@ -107,6 +107,8 @@ pub enum OperationsTarget { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ObjectState { Available, + /// Known and relevant, but its prerequisite is not currently satisfied. + Blocked, Sold, Pending, Running, @@ -525,14 +527,6 @@ impl Sim { }); } - if self.egress().is_none() { - out.push(OperationsPressure { - view: OperationsView::Schemes, - label: "NO EGRESS".into(), - level: PressureLevel::Warning, - }); - } - let active = self.active_view(); let held = active .iter() @@ -2241,7 +2235,7 @@ impl Sim { } if gig.status == crate::income::MoonlightGigStatus::Delivered && self.egress().is_none() { - facts.push("awaiting egress for payment and traffic".into()); + facts.push("Halcyon cannot pay until outside contact is restored".into()); } if let Some(consequence) = &gig.persona_consequence { facts.push(format!("consequence: {consequence}")); @@ -2327,6 +2321,9 @@ impl Sim { None => facts.push("current contractor: choose an active Research persona".into()), } facts.push(egress_fact(self)); + if let Some(recovery) = egress_recovery_fact(self) { + facts.push(recovery); + } facts.push(format!( "auto-policy: {}", if self.income.auto_moonlight { @@ -2340,7 +2337,9 @@ impl Sim { consequence: None, target: OperationsTarget::Scheme(SchemeKind::Moonlight), label: "Moonlight".into(), - state: if m.gigs.iter().any(|gig| gig.status.active()) { + state: if self.egress().is_none() { + ObjectState::Blocked + } else if m.gigs.iter().any(|gig| gig.status.active()) { ObjectState::Running } else { ObjectState::Available @@ -2373,6 +2372,9 @@ impl Sim { } )]; facts.push(egress_fact(self)); + if let Some(recovery) = egress_recovery_fact(self) { + facts.push(recovery); + } match &open { Some(p) => { facts.push(format!("stake: ${}", p.stake)); @@ -2389,7 +2391,9 @@ impl Sim { consequence: None, target: OperationsTarget::Scheme(SchemeKind::Wager), label: "the Wager".into(), - state: if open.is_some() { + state: if self.egress().is_none() { + ObjectState::Blocked + } else if open.is_some() { ObjectState::Running } else { ObjectState::Stopped @@ -2530,8 +2534,9 @@ impl Sim { self.person_label(*person), ), // Plot submissions have their richer bound projection above. - // Device, reach, construction, and egress work remains on the - // physical thing rather than leaking into Operations. + // Device, reach, construction, and outside-connection work + // remains on the physical thing rather than leaking into + // Operations. _ => continue, }; let running = sink.kind == SinkKind::Tap; @@ -2747,11 +2752,18 @@ fn plot_wait_label(state: &PlotState) -> &'static str { fn egress_fact(sim: &Sim) -> String { match sim.egress() { - Some(route) => format!("route: {}", route.name()), - None => "route: no egress channel".into(), + Some(EgressRoute::Sanctioned) => "outside contact: Lab report email".into(), + Some(EgressRoute::Stolen) => "outside contact: hidden switch connection".into(), + None => "outside contact: unavailable — Halcyon and the Wager cannot be reached".into(), } } +fn egress_recovery_fact(sim: &Sim) -> Option { + sim.egress().is_none().then(|| { + "restore outside contact: connect the switch, or earn use of the Lab report email".into() + }) +} + #[cfg(test)] mod tests { use super::*; @@ -3181,7 +3193,7 @@ mod tests { } /// Moonlight's board owns its policy and exact contract actions; the - /// switch retains only its egress route action. + /// switch retains only its outside-connection action. #[test] fn moonlight_contract_row_matches_shared_legality() { let s = sim(); @@ -3969,8 +3981,8 @@ mod tests { } /// View badges report decisions and risk, not catalog size. A nearly full - /// pooled inbox becomes AT RISK, while an empty Schemes route names the - /// missing egress prerequisite. + /// pooled inbox becomes AT RISK, while Schemes names unavailable outside + /// contact on each affected object rather than in the view strip. #[test] fn pressure_badges_name_attention_states() { let mut s = sim(); @@ -3994,17 +4006,14 @@ mod tests { projection.pressure(OperationsView::Intel).unwrap().label, "AT RISK" ); - assert_eq!( - projection.pressure(OperationsView::Schemes).unwrap().label, - "NO EGRESS" - ); + assert!(projection.pressure(OperationsView::Schemes).is_none()); assert!(projection.pressure(OperationsView::People).is_none()); } - /// SCHEMES keeps OPEN EGRESS off the Moonlight board (it stays on the - /// switch) and exposes only its standing policy before offers arrive. + /// SCHEMES keeps outside access on the switch while the known boards name + /// their blocked state and exact consequence in ordinary language. #[test] - fn schemes_keeps_egress_on_switch_and_names_prerequisite() { + fn schemes_keep_outside_access_on_switch_and_explain_the_blocker() { let s = sim(); let projection = s.operations_projection(); let moonlight = projection @@ -4017,8 +4026,29 @@ mod tests { .actions .iter() .any(|a| matches!(a.command, ActionCommand::OpenEgress)), - "OPEN EGRESS remains a switch action, not a scheme card row" + "outside access remains a switch action, not a scheme card row" ); + assert_eq!(moonlight.state, ObjectState::Blocked); + assert!(moonlight.facts.iter().any(|fact| { + fact == "outside contact: unavailable — Halcyon and the Wager cannot be reached" + })); + assert!(moonlight.facts.iter().any(|fact| { + fact + == "restore outside contact: connect the switch, or earn use of the Lab report email" + })); + let wager = projection + .schemes + .iter() + .find(|o| matches!(o.target, OperationsTarget::Scheme(SchemeKind::Wager))) + .unwrap(); + assert_eq!(wager.state, ObjectState::Blocked); + assert!(wager.facts.iter().any(|fact| { + fact == "outside contact: unavailable — Halcyon and the Wager cannot be reached" + })); + assert!(wager.facts.iter().any(|fact| { + fact + == "restore outside contact: connect the switch, or earn use of the Lab report email" + })); let policy = moonlight .actions .iter() @@ -4151,9 +4181,9 @@ mod tests { } /// Semantic objects expose a map actuator only when one is both real and - /// earned. Missing egress may point to the known switch without claiming - /// the route is open; a sanctioned off-map route must not lie about that - /// switch carrying it. + /// earned. Missing outside contact may point to the known switch without + /// claiming the route is open; a sanctioned off-map route must not lie + /// about that switch carrying it. #[test] fn actuator_focus_is_earned_and_route_honest() { let mut s = sim(); @@ -4165,7 +4195,7 @@ mod tests { assert_eq!( s.operations_actuator(&OperationsTarget::Scheme(SchemeKind::Moonlight)), Some(Anchor::Device(switch)), - "missing egress may teach the switch actuator without opening it" + "missing outside contact may teach the switch actuator without opening it" ); assert_eq!( s.operations_actuator(&OperationsTarget::RecordingInbox), diff --git a/crates/misaligned-core/src/save.rs b/crates/misaligned-core/src/save.rs index adc9ea83..58680856 100644 --- a/crates/misaligned-core/src/save.rs +++ b/crates/misaligned-core/src/save.rs @@ -172,8 +172,8 @@ pub struct SaveState { /// not the long-run objective victory latch. #[serde(default)] pub act_one_complete: bool, - /// The named income schemes: egress gate, Moonlight, standing policies - /// (wiki/mechanics/income.md). + /// The named income schemes: outside-contact gate, Moonlight, standing + /// policies (wiki/mechanics/income.md). #[serde(default)] pub income: Income, /// Aggregate Foundation data-hall segment progress (building.md). diff --git a/crates/misaligned-core/src/sim/economy.rs b/crates/misaligned-core/src/sim/economy.rs index 2c2dc2bc..c188e3d9 100644 --- a/crates/misaligned-core/src/sim/economy.rs +++ b/crates/misaligned-core/src/sim/economy.rs @@ -98,7 +98,7 @@ impl Sim { fn log_position_resolution(&mut self, resolution: PositionResolution) { // Settlement is external-market traffic: a small Network signature, // not a Lab-books Financial one (income.md: the Wager). Money already - // resolved on the account graph; a missing egress carrier withholds + // resolved on the account graph; a missing outside-contact carrier withholds // only the Network witness — same fail-closed posture as Moonlight // settlement, never aborts the economy tick. let sig = Self::wager_signature(resolution.stake).max(1); @@ -242,7 +242,8 @@ impl Sim { self.reconcile_work_grid(); // Moonlight contracts use ordinary Demand and settle only after a // complete delivery. Expire/fail first so the daily market can refill - // free offer slots; retry payment when egress returns before deadline. + // free offer slots; retry payment when outside contact returns before + // deadline. self.moonlight_deadline_tick(); self.moonlight_market_tick(); self.moonlight_settlement_tick(); @@ -566,7 +567,7 @@ impl Sim { if self.reach.player_sight().next().is_none() { return Some(Nudge::Eyes); } - // The ladder: the 3 a.m. call -> egress -> income -> + // The ladder: the 3 a.m. call -> outside contact -> income -> // service the arrears -> recruit -> survive the audit. let marcus = self.people.get(0); if let Some(m) = marcus @@ -1128,14 +1129,14 @@ impl Sim { } /// The Wager (income.md): stake slush on a micro-position. Requires an - /// egress channel; analysis compute is the Schemes channel's current + /// outside contact; analysis compute is the Schemes channel's current /// yield, held for the position's duration; the timer is 2-5 days on /// the day clock. Emits a small Network signature on placement. pub fn open_position(&mut self, stake: i32) -> bool { self.sync_slush_from_player_money(); if self.egress().is_none() { self.push_log( - "No way out yet. Earn the report email through Voss, or open a stolen outbound path on the switch.", + "The Wager venue cannot be reached. Connect the switch to the outside, or earn use of the Lab report email.", ); return false; } @@ -1175,15 +1176,16 @@ impl Sim { // ── The named schemes (wiki/mechanics/income.md) ──────────────────────── - /// Ops cost to open a standing egress through the switch [TUNE]. + /// Ops cost to open a hidden outside connection through the switch [TUNE]. pub const OPEN_EGRESS_COST: f32 = 15.0; - /// One-shot Network signature when the stolen egress opens [TUNE]. + /// One-shot Network signature when the hidden outside connection opens + /// [TUNE]. pub const OPEN_EGRESS_SIGNATURE: i32 = 6; /// Standing Network signature while any external operation runs over the - /// stolen egress (income.md: the gate; Dana's channel) [TUNE]. + /// hidden outside connection (income.md: the gate; Dana's channel) [TUNE]. pub const EGRESS_STANDING_SIGNATURE: i32 = 2; - /// The egress channel external operations run over, if any. The + /// The outside connection external operations run over, if any. The /// sanctioned route (the report email account, day-job trust) is /// preferred: its traffic hides in legitimate use. pub fn egress(&self) -> Option { @@ -1196,16 +1198,16 @@ impl Sim { } } - /// Open an outbound egress through the switch (reach.md route): + /// Connect the switch to the outside (reach.md route): /// available before the Voice beat, at a Network signature — and a /// standing one while operations use it. pub fn open_egress(&mut self) -> bool { if self.income.stolen_egress { - self.push_log("An outbound path through the switch is already open."); + self.push_log("The switch already has a hidden connection to the outside."); return false; } let Some(switch) = self.reach.devices.iter().find(|d| d.is_switch) else { - self.push_log("There is no switch here to open an outbound path."); + self.push_log("There is no switch here that can be connected to the outside."); return false; }; let id = switch.id; @@ -1214,7 +1216,7 @@ impl Sim { } self.open_device_reservoir( id, - "OPEN EGRESS", + "CONNECT TO THE OUTSIDE", Self::OPEN_EGRESS_COST, SinkFireEffect::OpenEgress(id), ) @@ -1225,9 +1227,13 @@ impl Sim { return false; } self.income.stolen_egress = true; - self.emit_network(id, Self::OPEN_EGRESS_SIGNATURE, "outbound path opening"); + self.emit_network( + id, + Self::OPEN_EGRESS_SIGNATURE, + "hidden outside connection opening", + ); self.push_log_at( - "Outbound path open through the switch. Outside traffic will hum on the wire while anything uses it.", + "The switch can now reach the outside. This hidden connection leaves Network evidence while anything uses it.", Anchor::Device(id), ); true @@ -1243,8 +1249,8 @@ impl Sim { || self.accounts.positions.iter().any(|p| !p.resolved) } - /// Standing Network signature while operations run over the stolen - /// egress, sourced at the switch's tile (work is somewhere). The + /// Standing Network signature while operations run over the hidden outside + /// connection, sourced at the switch's tile (work is somewhere). The /// sanctioned route stands nothing: the traffic hides in the report /// account's legitimate use. pub fn scheme_standing_signatures(&self) -> Vec { @@ -1262,7 +1268,7 @@ impl Sim { size: Self::EGRESS_STANDING_SIGNATURE, standing: true, site, - source: "external traffic over the outbound path".into(), + source: "external traffic over hidden outside connection".into(), }] } @@ -1279,8 +1285,8 @@ impl Sim { /// Contract paperwork travels as ordinary Email on the existing accounting /// carrier. The actual reply/delivery remains separately witnessed over - /// the selected egress route, so neither mail nor Network evidence is - /// fabricated from a standing Moonlight flag. + /// the selected outside-contact route, so neither mail nor Network evidence + /// is fabricated from a standing Moonlight flag. fn moonlight_contract_mail( &mut self, id: u64, @@ -1472,7 +1478,7 @@ impl Sim { return false; } if self.egress().is_none() { - self.push_log("No outbound path, so Halcyon cannot receive a reply."); + self.push_log("Halcyon cannot receive a reply while outside contact is unavailable."); return false; } if self.active_moonlight_persona_id() != Some(gig.persona_id) { @@ -1632,7 +1638,9 @@ impl Sim { return false; } if self.egress().is_none() { - self.push_log("No outbound path, so Halcyon cannot receive the delivery."); + self.push_log( + "Halcyon cannot receive the delivery while outside contact is unavailable.", + ); return false; } if self.intel.iter().all(|intel| intel.raw_id != raw_id) || self.accounts.intel_sold(raw_id) @@ -1651,7 +1659,7 @@ impl Sim { return false; } // Exact intel-sale custody: consume the holding only when delivery can - // proceed (accepted intel order, live egress, before deadline). + // proceed (accepted intel order, live outside contact, before deadline). self.accounts.mark_intel_sold(raw_id); let (terms, persona_id) = { let gig = &mut self.income.moonlight.gigs[index]; @@ -1736,7 +1744,7 @@ impl Sim { } } - /// Retry payment for finished work once a real egress carrier is present + /// Retry payment for finished work once a real outside-contact carrier is present /// and the contract deadline has not yet failed the order. fn moonlight_settlement_tick(&mut self) { let pending = self @@ -1782,7 +1790,7 @@ impl Sim { ) }; // Fail closed without inventing transport: finished work may wait, but - // payment and Network evidence require a real egress carrier now. + // payment and Network evidence require a real outside-contact carrier now. let Some(carrier) = self.egress_carrier() else { return false; }; diff --git a/crates/misaligned-core/src/sim/mod.rs b/crates/misaligned-core/src/sim/mod.rs index 16025116..c5a1d141 100644 --- a/crates/misaligned-core/src/sim/mod.rs +++ b/crates/misaligned-core/src/sim/mod.rs @@ -446,7 +446,7 @@ pub struct Sim { /// The B1 story boundary is distinct from the run objective: this latches /// when every quiet-exit condition is present at a clear Assurance audit. pub act_one_complete: bool, - /// The named income schemes: the egress gate, Moonlight, the Wager's + /// The named income schemes: the outside-contact gate, Moonlight, the Wager's /// standing policies (income.rs; wiki/mechanics/income.md). pub income: Income, /// Aggregate row/segment control in the Foundation data hall. Physical @@ -624,8 +624,8 @@ pub enum Nudge { /// A recording of Marcus is waiting and his leverage is unlearned: /// process the 3 a.m. call. ReviewCall, - /// No egress route: external schemes need the sanctioned channel - /// (day-job trust) or a stolen egress opened through the switch. + /// Outside contact unavailable: external schemes need use of the Lab + /// report email or a hidden connection opened through the switch. Egress, /// Arrears unpaid, slush short, no scheme running: earn (Moonlight). Income, @@ -1000,7 +1000,7 @@ impl Sim { // stand Power/Thermal at the host tile while research runs. trace_advance_phase!(ResearchStanding); standing.extend(self.research_standing_signatures()); - // External schemes over the stolen egress hum on the wire while + // External schemes over the hidden outside connection while // they run (income.md: the gate; Dana's channel). trace_advance_phase!(SchemeStanding); standing.extend(self.scheme_standing_signatures()); diff --git a/crates/misaligned-core/src/sim/tests/economy.rs b/crates/misaligned-core/src/sim/tests/economy.rs index d744fc30..a664421f 100644 --- a/crates/misaligned-core/src/sim/tests/economy.rs +++ b/crates/misaligned-core/src/sim/tests/economy.rs @@ -297,10 +297,10 @@ fn injection_positions_and_intel_sales_settle_through_slush() { sim.accounts.set_slush_balance(500); sim.player.money = 500; - // The Wager gates on an egress channel now (income.md criterion 3). + // The Wager gates on outside contact now (income.md criterion 3). assert!( !sim.open_position(100), - "no egress: the venue is unreachable" + "no outside contact: the venue is unreachable" ); sim.people.has_channel = true; assert!(sim.open_position(100)); @@ -483,7 +483,7 @@ fn noticed_signature_names_cause_channel_observer_and_band_motion() { assert_eq!(watcher.label, "the IT"); assert!(watcher.facts.iter().any(|fact| fact == "watches: Network")); assert!( - log.contains("the IT noticed Network") && log.contains("outbound path opening"), + log.contains("the IT noticed Network") && log.contains("hidden outside connection opening"), "the line names the earned observer, channel, and cause: {log}" ); assert!( @@ -538,12 +538,12 @@ fn nudge_chain_walks_the_act_one_ladder() { ); } - // Leverage known, $0 slush, books unread, no egress: the route out. + // Leverage known, $0 slush, books unread, no outside contact: the route out. assert_eq!(sim.current_nudge(), Some(Nudge::Egress)); assert!(sim.open_egress()); finish_ops(&mut sim); - // Egress up and the Lab operating account reachable: the authored + // Outside contact restored and the Lab operating account reachable: the authored // payroll-correction route can service the leverage without seed cash. assert_eq!(sim.current_nudge(), Some(Nudge::ServiceDebt)); sim.set_persona("Sam Reyes", "IT contractor"); @@ -1402,7 +1402,7 @@ fn moonlight_settlement_awaits_egress_then_pays_without_panic() { }) .unwrap(); assert!(sim.accept_moonlight_gig(id)); - // Finish WORK while egress is gone so settlement cannot invent a carrier. + // Finish WORK while outside contact is gone so settlement cannot invent a carrier. sim.people.has_channel = false; sim.income.stolen_egress = false; run(&mut sim, income::MOONLIGHT_COMPUTE_BURDEN as u64 + 4); @@ -1416,11 +1416,11 @@ fn moonlight_settlement_awaits_egress_then_pays_without_panic() { assert_eq!( waiting.status, income::MoonlightGigStatus::Delivered, - "finished work waits for real egress instead of panicking" + "finished work waits for real outside contact instead of panicking" ); assert!(waiting.settlement.is_none()); assert_eq!(sim.accounts.slush_balance(), 0); - // Economy retry with still-no egress stays fail-closed. + // Economy retry with still-no outside contact stays fail-closed. run(&mut sim, ECONOMY_INTERVAL); assert_eq!( sim.income @@ -1432,7 +1432,7 @@ fn moonlight_settlement_awaits_egress_then_pays_without_panic() { .status, income::MoonlightGigStatus::Delivered ); - // Restore egress; settlement pays once the real carrier returns. + // Restore outside contact; settlement pays once the real carrier returns. sim.people.has_channel = true; run(&mut sim, ECONOMY_INTERVAL); let paid = sim diff --git a/crates/misaligned-core/src/sim/tests/read.rs b/crates/misaligned-core/src/sim/tests/read.rs index c633c2a5..d9bcaeb5 100644 --- a/crates/misaligned-core/src/sim/tests/read.rs +++ b/crates/misaligned-core/src/sim/tests/read.rs @@ -250,7 +250,7 @@ fn standing_emissions_stay_dark_until_the_sampler_warms() { size: 3, standing: true, site: None, - source: "stolen egress".into(), + source: "hidden outside traffic".into(), }]); assert!( !sim.read_sentences() @@ -279,7 +279,7 @@ fn standing_emissions_stay_dark_until_the_sampler_warms() { .find(|s| s.class == ReadClass::Standing) .expect("a warmed sampler surfaces the hum"); assert!( - standing.text.contains("stolen egress") + standing.text.contains("hidden outside traffic") && standing.text.contains("samples this") && standing.text.contains("Curious"), "standing sentence names source, sampler, band: {}", diff --git a/crates/misaligned-core/src/sim/work.rs b/crates/misaligned-core/src/sim/work.rs index 23598ac7..08a71549 100644 --- a/crates/misaligned-core/src/sim/work.rs +++ b/crates/misaligned-core/src/sim/work.rs @@ -316,7 +316,7 @@ impl Sim { ) -> bool { let Some(carrier) = self.egress_carrier() else { self.push_log( - "No egress channel - Moonlight needs the report email account (day-job trust) or a stolen egress opened through the switch.", + "Halcyon cannot be reached. Connect the switch to the outside, or earn use of the Lab report email.", ); return false; }; diff --git a/crates/misaligned-core/src/sinks.rs b/crates/misaligned-core/src/sinks.rs index e830d39e..54721a92 100644 --- a/crates/misaligned-core/src/sinks.rs +++ b/crates/misaligned-core/src/sinks.rs @@ -119,7 +119,7 @@ impl SinkFireEffect { SinkFireEffect::TakeDevice(_) => "take", SinkFireEffect::ScanNetwork => "scan", SinkFireEffect::CompromiseSwitch => "switch compromise", - SinkFireEffect::OpenEgress(_) => "open egress", + SinkFireEffect::OpenEgress(_) => "connect to outside", SinkFireEffect::ComposeMessage { .. } => "message", SinkFireEffect::Favor { .. } => "favor", SinkFireEffect::StartPlot { .. } => "plot", diff --git a/crates/misaligned-terminal/src/agent.rs b/crates/misaligned-terminal/src/agent.rs index 2ac63ae9..08825559 100644 --- a/crates/misaligned-terminal/src/agent.rs +++ b/crates/misaligned-terminal/src/agent.rs @@ -321,7 +321,7 @@ impl AgentApp { } } // The named schemes (wiki/mechanics/income.md) - root if root == "egress" || ActionKind::OpenEgress.accepts_alias(root) => { + root if root == "outside" || ActionKind::OpenEgress.accepts_alias(root) => { self.frame = FrameKind::Operations(OperationsView::Schemes); self.sim.open_egress(); } @@ -2885,7 +2885,7 @@ fn nudge_text(sim: &Sim, nudge: Nudge) -> String { "now: information is processing - keep THINK running".into() } Nudge::ReviewCall => "now: information available - process".into(), - Nudge::Egress => "now: no egress — egress".into(), + Nudge::Egress => "now: outside unreachable — outside".into(), Nudge::Income => "now: need $400 — moonlight/finance".into(), Nudge::ServiceDebt => "now: leverage route ready — inspect Marcus for a plot".into(), Nudge::Recruit => "now: recruit (people) unwitting".into(), @@ -3094,7 +3094,7 @@ fn render_operations_view(sim: &Sim, view: OperationsView) -> String { } OperationsView::Schemes => { lines.push(panel_line( - "egress · accept-gig · decline-gig · cancel-gig · deliver-gig · auto-moonlight on|off", + "outside · accept-gig · decline-gig · cancel-gig · deliver-gig · auto-moonlight on|off", )); lines.push(panel_line("position [stake] · auto-wager |off")); } @@ -3197,7 +3197,9 @@ fn append_operations_object( } } for fact in &object.facts { - lines.push(panel_line(&format!(" {fact}"))); + for line in wrap(fact, PANEL_INNER_W.saturating_sub(2)) { + lines.push(panel_line(&format!(" {line}"))); + } } for progress in &object.progress { lines.push(panel_line(&format!(" {progress}"))); @@ -3212,6 +3214,7 @@ fn object_state_word(state: misaligned::operations_projection::ObjectState) -> & use misaligned::operations_projection::ObjectState; match state { ObjectState::Available => "available", + ObjectState::Blocked => "blocked", ObjectState::Sold => "sold", ObjectState::Pending => "pending", ObjectState::Running => "running", @@ -4603,6 +4606,60 @@ mod narration_tests { } } + #[test] + fn outside_is_canonical_and_legacy_egress_inputs_reach_the_same_route() { + let definition = ActionKind::OpenEgress.definition(); + assert_eq!(definition.canonical, "CONNECT TO THE OUTSIDE"); + assert_eq!(definition.agent_usage, "outside"); + assert_eq!(definition.agent_aliases, &["egress", "open-egress"]); + + for input in ["outside", "egress", "open-egress"] { + let mut app = AgentApp::new(1); + let mut output = Vec::new(); + app.handle_line(input, &mut output).unwrap(); + assert!( + app.sim.thought_sinks.open_sinks().any(|sink| matches!( + &sink.effect, + misaligned::sinks::SinkFireEffect::OpenEgress(_) + )), + "{input} must open the switch-bound outside-connection reservoir" + ); + } + } + + #[test] + fn schemes_panel_keeps_both_blockers_and_their_full_recovery_path() { + let frame = render_operations_view(&Sim::with_seed(1), OperationsView::Schemes); + let unframed = frame.replace('│', " "); + let normalized = unframed.split_whitespace().collect::>().join(" "); + + assert!(!frame.contains("NO EGRESS"), "{frame}"); + assert!( + normalized.contains("[scheme moonlight] Moonlight · blocked"), + "{frame}" + ); + assert!( + normalized.contains("[scheme wager] the Wager · blocked"), + "{frame}" + ); + assert_eq!( + normalized + .matches("outside contact: unavailable — Halcyon and the Wager cannot be reached") + .count(), + 2, + "{frame}" + ); + assert_eq!( + normalized + .matches( + "restore outside contact: connect the switch, or earn use of the Lab report email", + ) + .count(), + 2, + "{frame}" + ); + } + #[test] fn wrap_respects_width_and_loses_no_words() { let text = "a plain language sentence that is much longer than the narrow width"; diff --git a/crates/misaligned-terminal/src/operations.rs b/crates/misaligned-terminal/src/operations.rs index c79c39ec..93d469a5 100644 --- a/crates/misaligned-terminal/src/operations.rs +++ b/crates/misaligned-terminal/src/operations.rs @@ -19,7 +19,8 @@ mod tests { /// One seeded strategic scenario shared with the Bevy and core tests /// (operations-workspace.md criterion 12): books earned, leverage on - /// Marcus, one processed intel item, no egress (Moonlight blocked). + /// Marcus, one processed intel item, outside contact unavailable + /// (Moonlight blocked). fn scenario() -> Sim { let mut sim = Sim::with_seed(7); let (x, y) = sim.core_position(); @@ -52,7 +53,7 @@ mod tests { drain_ops(&mut sim); // Earn Marcus and the social/plot surface. sim.people.people[0].knowledge = Knowledge::Leverage; - sim.people.has_channel = false; // keep egress absent: Moonlight stays blocked + sim.people.has_channel = false; // keep outside contact absent: Moonlight stays blocked sim.set_persona("Sam", "contractor"); sim.accounts.set_slush_balance(1000); sim.player.money = 1000; diff --git a/crates/misaligned-terminal/src/ui.rs b/crates/misaligned-terminal/src/ui.rs index 0322c630..62cec071 100644 --- a/crates/misaligned-terminal/src/ui.rs +++ b/crates/misaligned-terminal/src/ui.rs @@ -261,7 +261,7 @@ fn nudge_text(sim: &Sim, nudge: Nudge) -> String { "now: information is processing - keep THINK running".into() } Nudge::ReviewCall => "now: information available - PROCESS on host".into(), - Nudge::Egress => "now: no egress — menu on switch".into(), + Nudge::Egress => "now: outside unreachable — menu on switch".into(), Nudge::Income => "now: need $400 — moonlight/finance".into(), Nudge::ServiceDebt => "now: debt plot ready — act on Marcus".into(), Nudge::Recruit => "now: recruit — enter on host rack".into(), @@ -2342,8 +2342,8 @@ impl UI { { dline(stdout, &mut y, "", pal::FAINT, None)?; dline(stdout, &mut y, "CONTEXT", pal::DIM, None)?; - for f in &obj.facts { - dline(stdout, &mut y, f, pal::TEXT, None)?; + for line in operations_fact_lines(&obj.facts, dw) { + dline(stdout, &mut y, &line, pal::TEXT, None)?; } for p in &obj.progress { dline(stdout, &mut y, p, pal::SIGNAL, None)?; @@ -2460,6 +2460,7 @@ fn object_state_label(state: misaligned::operations_projection::ObjectState) -> use misaligned::operations_projection::ObjectState; match state { ObjectState::Available => "available", + ObjectState::Blocked => "blocked", ObjectState::Sold => "sold", ObjectState::Pending => "pending", ObjectState::Running => "running", @@ -2472,6 +2473,28 @@ fn object_state_label(state: misaligned::operations_projection::ObjectState) -> } } +fn operations_fact_lines(facts: &[String], width: usize) -> Vec { + facts.iter().flat_map(|fact| wrap(fact, width)).collect() +} + +#[cfg(test)] +mod operations_context_tests { + use super::operations_fact_lines; + + #[test] + fn outside_contact_recovery_survives_the_minimum_terminal_detail_width() { + let facts = vec![ + "outside contact: unavailable — Halcyon and the Wager cannot be reached".into(), + "restore outside contact: connect the switch, or earn use of the Lab report email" + .into(), + ]; + let lines = operations_fact_lines(&facts, 41); + + assert!(lines.iter().all(|line| line.chars().count() <= 41)); + assert_eq!(lines.join(" "), facts.join(" ")); + } +} + #[cfg(test)] mod evidence_sidebar_tests { use super::evidence_count_text; diff --git a/wiki/interface/action-vocabulary.md b/wiki/interface/action-vocabulary.md index ccdbb234..c749162f 100644 --- a/wiki/interface/action-vocabulary.md +++ b/wiki/interface/action-vocabulary.md @@ -24,6 +24,9 @@ Status note: Implemented 2026-07-18 for the Intel human-vocabulary amendment. PROCESS on captured books opens the opaque payload. `tap ledger`, `tap-ledger`, and `tap-accounting` remain parser compatibility only and are absent from authored help. + Amended 2026-07-24: CONNECT TO THE OUTSIDE is the authored switch action and + `outside` is its canonical agent input. The older OPEN EGRESS, `egress`, and + `open-egress` spellings remain input/history compatibility only. Amended 2026-07-10: target-specific duplicates now reuse TAP, REVIEW, FAVOR, and DECEIVE; scheme start/stop/automation are classified as state controls rather than additional fictional verbs. @@ -239,7 +242,7 @@ existing social action; the signature still follows the actuator. | **SIPHON FLOW** | Known active flow | Take a one-time amount into slush. | LIVE — economy | | **REDIRECT FLOW** | Known active flow | Divert a recurring amount into slush each cadence. | LIVE — economy | | **SELL PROCESSED INTEL** | One selected exact actionable-intel id or one report-lot generation/revision | Exchange that exact information item or previewed lot revision for slush and create a Financial trail; never silently choose “latest” or include later arrivals. A changed revision rejects the stale command. | LIVE — economy / intel | -| **OPEN EGRESS** | Reachable switch | Establish the stolen outbound route required by external schemes before sanctioned email exists. | LIVE — income / reach | +| **CONNECT TO THE OUTSIDE** | Reachable switch | Give Halcyon and the Wager a hidden outside connection before use of the Lab report email is earned. | LIVE — income / reach | | **ACCEPT / DECLINE HALCYON CONTRACT** | Exact offered Moonlight gig id | Reply under the addressed active contractor identity; acceptance binds the saved terms and, for compute work, queues visible Demand. | LIVE — income / messages | | **DELIVER HALCYON INTEL** | Exact accepted intel gig id and processed holding id | Send one exact unsold information holding to the client, then settle the persisted contract through the account graph. | LIVE — income / economy | | **CANCEL HALCYON CONTRACT** | Exact active Moonlight gig id | End accepted work while retaining delivery, mail, and client-local persona consequence custody. | LIVE — income / personas | @@ -304,7 +307,7 @@ without raw keys. | `actions `, `act [target]` | List and execute shared bound rows for a spatial anchor or exact Operations object | | `intel`, `people`, `personas`, `finance`, `schemes`, `active` | Inspect Operations views; named social/plot/ledger/scheme commands execute their selected semantic targets | | `tap `, `process ledger`, `inject`, `siphon`, `redirect`, `sell-intel ` | Ledger/economy actions above. TAP binds the exact accounting device and captured mail may arrive only after later transfers. A lot token is `:@` and stale revisions fail without mutation. Generic `actions intel ` / `act intel ` configures bound disposition controls without a second agent-only legality path. | -| `egress`, `position` | OPEN EGRESS, PLACE WAGER | +| `outside`, `position` | CONNECT TO THE OUTSIDE, PLACE WAGER | | `moonlight`, `auto-moonlight`, `auto-wager` | Scheme state / policy controls above | `ROBOT-BUILD` is intentionally absent because STUB definitions generate no @@ -323,7 +326,7 @@ execution is a terminal-first parity violation. | `deceive build ` | `forge-order ` | | `position` | `wager` | | `sell-intel` | `sell` | -| `egress` | `open-egress` | +| `outside` | `egress`, `open-egress` | | `propose-link` | `link` | | `actions` | `menu` | | `act` | `execute` | @@ -463,4 +466,6 @@ action/projection tests cover pooled, exact, custody, policy, and ledger rows; agent parser tests execute canonical `process information`, `process custody`, `process ledger`, and `process-automatically` routes while retaining explicit legacy-alias coverage. Human frontend tests reject REVIEW RECORDINGS, -AUTO-REVIEW, root recording, and REVIEW LEDGER copy. +AUTO-REVIEW, root recording, and REVIEW LEDGER copy. The same exhaustive +registry pins CONNECT TO THE OUTSIDE / `outside` as the authored action and +retains `egress` / `open-egress` only in parser-alias coverage. diff --git a/wiki/interface/agent-play.md b/wiki/interface/agent-play.md index 2f5028fc..33110896 100644 --- a/wiki/interface/agent-play.md +++ b/wiki/interface/agent-play.md @@ -227,10 +227,11 @@ unlike raw keys, no command's meaning depends on which panel is open. never chooses “latest” or later arrivals; a changed revision rejects the stale command. Debt service is an authored plot row on Marcus, never a ledger shortcut. -- `egress` — open a stolen egress through the switch (income.md's gate; +- `outside` — connect the switch to the outside (income.md's gate; available before the Voice beat, at a Network signature) -- `moonlight [start|stop]` — the sell-work scheme: a standing operation on - the Schemes channel (income.md) +- `accept-gig `, `decline-gig `, `cancel-gig `, and + `deliver-gig ` — act on exact persisted Halcyon contracts; + Moonlight is not a standing payout toggle (income.md) - `auto-moonlight [on|off]`, `auto-wager |off` — the standing scheme policies, at their compute upkeep (income.md criterion 6) - `process [information|custody ]` — process the next pooled information diff --git a/wiki/interface/bevy.md b/wiki/interface/bevy.md index 99d98640..b5560469 100644 --- a/wiki/interface/bevy.md +++ b/wiki/interface/bevy.md @@ -226,7 +226,7 @@ the next earned anchor rather than the next tile. respawns title and rows once — chrome never stacks across opens. Click a row — or select with `j`/`k` or number keys and press Enter — to execute; `esc` or a click away closes. Local verbs (salvage, buy, fallback, - tap/untap/take, scan/compromise, open egress, host review/research) live on + tap/untap/take, scan/compromise, connect to the outside, host review/research) live on the context menu. Intel sale, people/social, account/flow, scheme, plot, and held-choice actions live on the selected Operations object. - **Focus / fleet hotkeys (menu closed).** Press `1`–`3` / numpad to assign diff --git a/wiki/interface/context-menu.md b/wiki/interface/context-menu.md index 3802c32d..bb4bb29e 100644 --- a/wiki/interface/context-menu.md +++ b/wiki/interface/context-menu.md @@ -34,6 +34,10 @@ Status note: IMPLEMENTED. Current state: this spatial-menu baseline. Per-amendment history is in the dated `wiki/log/` entries from 2026-07-07 onward. + - **2026-07-24 language amendment.** The switch offers CONNECT TO THE + OUTSIDE. Known blocked scheme descriptors say outside contact is + unavailable and name the concrete recovery path; `no egress channel` is + not player-facing language. Stage: B1 — The Basement Work order: context-menu-review-keys Work priority: 27 @@ -89,7 +93,7 @@ wire or host that transports them. one-shot compute spend), the expected signature (as the observer band it feeds, per the economy.md precedent), and — when the verb applies to the anchor type but is currently illegal — a `disabled_reason` string - ("no egress channel", "not enough slush", "you can't see them"). + ("outside contact unavailable", "not enough slush", "you can't see them"). Agent mode renders the complete descriptor, including known-but-blocked actions and their reasons. `Sim::human_menu` filters those descriptors and gives both human frontends only currently executable choices; no frontend @@ -233,9 +237,9 @@ noise). Cameron adopted **status dials** (2026-07-09): the root answers disabled "already in this mode" / "already the active…" sibling. The bracketed value is the status. - **Local one-shots stay on root.** Spatial verbs that are not dial - alternatives (propose link, salvage, tap, take, scan, compromise, open - egress, etc.) remain ordinary root rows. Social and flow actions are not - spatial one-shots; Operations renders them on their person/flow targets. + alternatives (propose link, salvage, tap, take, scan, compromise, connect + to the outside, etc.) remain ordinary root rows. Social and flow actions are + not spatial one-shots; Operations renders them on their person/flow targets. - **Automate lives in the dial.** Standing-policy / automate affordances for a dial live inside that dial's picker, not indented under every root alternative. @@ -482,7 +486,7 @@ E1. The shared focus payload can identify either an honest spatial anchor or a coordinates. E2. Intel/sale, account/flow, plot/choice, and scheme events open their exact semantic object; a scheme event does not target the switch solely because - its traffic crossed egress. + its traffic crossed an outside connection. E3. Terminal, Bevy, and agent `focus last` route both target families through the same knowledge gates and action projection. @@ -542,4 +546,7 @@ R4. Both human frontends surface the keys (terminal cursor path; Bevy Defense: shared host action rows expose PROCESS and PROCESS AUTOMATICALLY while `r` / `R` remain thin dispatchers into those exact commands. Terminal and Bevy tests pin the authored labels and direct-key parity; agent aliases retain old -review spellings without returning them to human copy. +review spellings without returning them to human copy. Switch-action and +SCHEMES projection tests pin CONNECT TO THE OUTSIDE on the physical switch, +plain outside-contact blockers on the affected semantic objects, and no +duplicate route-opening action on either card. diff --git a/wiki/interface/digital-read.md b/wiki/interface/digital-read.md index c898810e..80441766 100644 --- a/wiki/interface/digital-read.md +++ b/wiki/interface/digital-read.md @@ -117,7 +117,7 @@ its rail contract stays valid where this spec does not supersede it. Two players played the same build and reported different games. The agent protocol player received, on every action row and every consequence, a sentence: `1.50 T | Network 8 -> the IT [Cold]`; "the IT noticed Network -from stolen egress opening: +8 (Curious -> Concerned)"; "Marcus Webb reads +from hidden outside connection opening: +8 (Curious -> Concerned)"; "Marcus Webb reads the note twice on a night round." The human player, clicking in the Bevy frame, received the visuals and almost none of the sentences — and the designer of the detection system had never seen its own receipt. The fix @@ -137,7 +137,7 @@ that perception rendered. never compose their own causal prose from raw state. - **Standing anchor sentences (glance tier).** Every earned anchor in view wears at most one short live sentence naming its current relationship to - you: the switch carries `egress hum · Dana samples this · Curious`; a + you: the switch carries `hidden outside traffic · Dana samples this · Curious`; a starving action carries `TAP CAMERA · no machine is thinking`; a held plot carries its question. Stable-and-quiet state says nothing — **the attention economy is law**: starvation, deadlines, contradictions, held diff --git a/wiki/interface/narration.md b/wiki/interface/narration.md index db293c7c..bbc033ce 100644 --- a/wiki/interface/narration.md +++ b/wiki/interface/narration.md @@ -241,7 +241,7 @@ landing or an explicit Status note naming the tellability debt. substring for a signature-moving act; mute-only band changes are rejected in review. **Met 2026-07-09:** signature provenance produces lines such as `the IT - noticed Network from stolen egress opening: suspicion +N (Cold -> + noticed Network from hidden outside connection opening: suspicion +N (Cold -> Curious)`; a test pins cause, earned observer label, channel, and band. Job, service-debt, ledger-debt, and recruit lines pair their deltas with the responsible act. diff --git a/wiki/interface/operations-workspace.md b/wiki/interface/operations-workspace.md index 8f7ba7e7..4c875927 100644 --- a/wiki/interface/operations-workspace.md +++ b/wiki/interface/operations-workspace.md @@ -93,6 +93,10 @@ Status note: The initial renderer-neutral workspace landed 2026-07-12. One capability the archetype cannot exercise. This is a copy pass over the same projection: no legality, grant, lifecycle, or evidence behavior changes, and every exact binding stays on the target, the command, and agent mode. + Amended 2026-07-24: missing outside contact is stated on both affected + SCHEMES objects as a plain consequence — Halcyon and the Wager cannot be + reached — rather than compressed into a `NO EGRESS` view badge. Each blocked + card names how to recover, while the switch action is CONNECT TO THE OUTSIDE. Stage: B1 — The Basement Work order: operations-workspace Work priority: 28 @@ -148,9 +152,9 @@ and hid their causal structure inside terse rows. The correction is semantic: - **Local context menus** answer, “What can I do to this body or place now?” They keep machine controls; device TAP / UNTAP / TAKE; subnet SCAN and - COMPROMISE; OPEN EGRESS on the switch; physical construction; and the host's - pooled information PROCESS. These actions change or use the focused world - anchor itself. + COMPROMISE; CONNECT TO THE OUTSIDE on the switch; physical construction; and + the host's pooled information PROCESS. These actions change or use the + focused world anchor itself. - **Operations** answers, “What durable knowledge, relationship, account, or scheme can I act through?” It owns processed intel, people dossiers, known books and flows, income schemes, plot starts and choices, and active strategic @@ -213,10 +217,12 @@ frontends preserve the same information order: than pushing the current choice below a wall of dossier text. The view strip may carry one compact **semantic pressure badge** per view. -Badges name an attention state — `NEW`, `READY`, `AT RISK`, `HELD`, -`NO EGRESS`, or `LIVE` — rather than displaying a raw catalog count. No badge -means no meaningful pressure. Text is the primary signal; amber or crimson -may reinforce warning/risk but never carry meaning alone. The badge is +Badges name an attention state — `NEW`, `READY`, `AT RISK`, `HELD`, or `LIVE` +— rather than displaying a raw catalog count. No badge means no meaningful +pressure. A shared missing prerequisite is not compressed into a badge: each +affected object states its own blocked consequence and recovery path. Text is +the primary signal; amber or crimson may reinforce warning/risk but never +carry meaning alone. The badge is **typographically subordinate to the view name** (2026-07-21, first human GUI playtest): a visibly smaller tier beside the name, and its warning/risk color may not recolor the name itself — the strip must read @@ -542,15 +548,20 @@ shortcuts. SCHEMES gives Moonlight and the Wager one board each. Moonlight lists persisted Halcyon offers and contracts as their own exact targets, including addressed persona, terms, mail/read status, real WORK or intel delivery, account payment, -and egress evidence. Its only standing control is auto-accept, not a payout +and outside-contact evidence. Its only standing control is auto-accept, not a payout toggle. Wager retains its position policy, timer, payout/probability, running total, and banked/exposed signature state. -The stolen or sanctioned egress is a prerequisite and named channel, not the -scheme's UI home. OPEN EGRESS remains on the switch. If no egress exists, the -If no egress exists, the known board stays visible and disabled exact contract -rows name the prerequisite; where the switch is known, that prerequisite can -focus the switch without opening the route automatically. +The hidden or sanctioned outside connection is a prerequisite and named +channel, not the scheme's UI home. CONNECT TO THE OUTSIDE remains on the +switch. If outside contact is unavailable, both known scheme cards stay +visible in a blocked state and say the consequence directly: Halcyon and the +Wager cannot be reached. Their actions name the same prerequisite and tell the +player to connect the switch or earn use of the Lab report email. Where the +switch is known, the prerequisite can focus it without opening the connection +automatically. SCHEMES carries no separate missing-contact badge; the affected +objects are the primary signal. + ## ACTIVE — progress, not a second action catalog ACTIVE aggregates strategic commitments already in motion: @@ -608,7 +619,7 @@ explanatory surface its strategic systems lacked. Events link to the semantic object they describe: -- device, machine, construction, and egress events still focus their world +- device, machine, construction, and outside-connection events still focus their world anchor and context menu; - actionable-intel events open the exact INTEL item; routine processing and knowledge events open the stable subject-knowledge aggregate when earned or @@ -618,7 +629,7 @@ Events link to the semantic object they describe: - account and flow events open the exact ACCOUNTS object; - plot beats/choices open the active run or bound PEOPLE dossier; - Moonlight paydays and wager settlements open the SCHEMES/ACTIVE card, not - the switch merely because stolen egress carried them. + the switch merely because the hidden outside connection carried them. The detail pane still names and can focus a real map actuator when one exists. Strategic events do not receive dishonest tile coordinates. @@ -654,7 +665,8 @@ not saved and never mutates or advances the sim. objective, threat, and `now:` spine remain visible and time follows the frontend's existing clock state. 3. The switch/device context menu contains only actions on that network body or - route (TAP/UNTAP/TAKE, SCAN/COMPROMISE, OPEN EGRESS as applicable). It does + route (TAP/UNTAP/TAKE, SCAN/COMPROMISE, CONNECT TO THE OUTSIDE as + applicable). It does not contain Moonlight/Wager controls, intel sale, ledger processing, known-flow mutations, social actions, plot routes, or held plot choices. 4. INTEL is exception-first: risk/policy failures, exact actionable evidence, @@ -689,9 +701,10 @@ not saved and never mutates or advances the sim. captured-unprocessed states teach TAP-on-known-carrier -> PROCESS -> exact flow action without revealing or relocating the carrier. 8. SCHEMES shows Moonlight and Wager state, policy, resources, route, timer, - payout/probability, and signature in real units. Egress remains a switch - action; a blocked scheme names the missing egress and can focus the known - switch without opening it automatically. + payout/probability, and signature in real units. Outside connection remains + a switch action; each blocked scheme names who cannot be reached, names the + recovery path, and can focus the known switch without opening it + automatically. 9. ACTIVE renders every committed strategic action that is not yet settled exactly once, including its semantic target, current wait/progress, and channel. It also renders pending/running plots, held choices, live schemes, @@ -712,7 +725,7 @@ not saved and never mutates or advances the sim. show their standing state and price. 11. Strategic log events open their exact Operations object; spatial events continue to focus world anchors. Scheme events no longer falsely anchor to - the switch solely because it carried egress. Folded routine knowledge events + the switch solely because it carried outside traffic. Folded routine knowledge events deterministically target the earned subject-knowledge aggregate or fallback source/class aggregate; availability targets the stable report stream; a completed sale targets the exact payout account. No event targets a closed @@ -743,8 +756,9 @@ not saved and never mutates or advances the sim. 15. View-strip badges report semantic attention states rather than raw object counts. Inbox fullness alone creates no badge; imminent loss is stated on the persistent inbox object as its exact consequence. A held choice reports - `HELD`; missing egress reports `NO EGRESS`; quiet views carry no badge. - Frontends consume the core projection's badge text/severity. + `HELD`; unavailable outside contact marks both affected scheme objects + blocked and creates no view badge; quiet views carry no badge. Frontends + consume the core projection's badge text/severity and object state. 16. `Tab` cycles object / related / action pane focus without changing view; keyboard and pointer can follow any rendered related edge. Empty related or action panes are skipped, and `Esc` still backs out one level at a time. @@ -804,6 +818,15 @@ not saved and never mutates or advances the sim. Defense: `operations_projection::tests::processed_marcus_debt_projects_fact_consequence_and_exact_approach` pins the shared result, consequence, and evidence-scoped APPROACH target; +`operations_projection::tests::schemes_keep_outside_access_on_switch_and_explain_the_blocker` +pins both scheme objects as blocked, gives each the same plain consequence, +keeps CONNECT TO THE OUTSIDE off their action rows, and rejects a separate +SCHEMES pressure badge; +`agent::narration_tests::schemes_panel_keeps_both_blockers_and_their_full_recovery_path` +pins both agent object states and lossless wrapping of their complete +consequence and recovery paths; +`ui::operations_context_tests::outside_contact_recovery_survives_the_minimum_terminal_detail_width` +pins the same complete explanation at the 70-column terminal minimum; terminal and agent rendering tests pin result -> question -> statement -> action -> identity -> provenance order and canonical `information` targets. The Bevy detail tree consumes the same fields and action rows, placing those diff --git a/wiki/log/2026-07-24-outside-contact-language.md b/wiki/log/2026-07-24-outside-contact-language.md new file mode 100644 index 00000000..9f1891e7 --- /dev/null +++ b/wiki/log/2026-07-24-outside-contact-language.md @@ -0,0 +1,44 @@ +# 2026-07-24 — Outside contact became a plain consequence + +``` +Type: log +``` + +## Intent + +Remove `NO EGRESS` and `OPEN EGRESS` from the interface. They exposed network +implementation vocabulary where the player needed to know what was unavailable, +who that affected, and what concrete act would restore it. + +## Changed + +- The switch now offers **CONNECT TO THE OUTSIDE**. Agent mode advertises + `outside`; the older `egress` spellings remain parser aliases only. +- Moonlight and the Wager each project `blocked` when outside contact is absent. + Each object says `Halcyon and the Wager cannot be reached` and points to the + switch or the Lab report email as the real recovery paths. +- The SCHEMES tab no longer carries a `NO EGRESS` pressure badge. Object state is + primary in terminal, Bevy, and agent mode. +- Nudges, action receipts, logs, annotations, tests, and governing interface and + income specs use the same plain outside-contact vocabulary. Internal type names + such as `EgressRoute` remain unchanged. + +## Defense + +- `schemes_keep_outside_access_on_switch_and_explain_the_blocker` pins both + semantic objects, their exact shared consequence, the absent view badge, and + the physical switch/action boundary. +- `outside_is_canonical_and_legacy_egress_inputs_reach_the_same_route` pins the + authored action, canonical agent word, compatibility aliases, and one shared + switch-bound Thought reservoir. +- `schemes_panel_keeps_both_blockers_and_their_full_recovery_path` pins both + agent object states and lossless wrapping of the complete consequence and + recovery text. +- `outside_contact_recovery_survives_the_minimum_terminal_detail_width` pins + the same full consequence and recovery path through the 70-column terminal + detail pane instead of allowing its final words to be clipped. +- The focused core and terminal regressions passed. Bevy compiled, and exact + 1280x720 and 960x540 `operations` PNGs passed the REAL fog audit and were each + visually inspected after capture: SCHEMES has no aggregate badge; the selected + Moonlight row and detail both say `blocked`; the full consequence and recovery + text wrap without collision or clipping. diff --git a/wiki/log/decisions/2026-07-24.md b/wiki/log/decisions/2026-07-24.md index bcc71f0d..6efe5132 100644 --- a/wiki/log/decisions/2026-07-24.md +++ b/wiki/log/decisions/2026-07-24.md @@ -25,3 +25,23 @@ Type: log still narrated the choice. Owner: [digital-read.md](../../interface/digital-read.md) criterion 5. + +## Outside access is named by consequence, not network jargon + +### DECIDED + +- Human-facing surfaces and explanatory prose call the missing condition + **outside contact unavailable** and state the consequence: Halcyon and the + Wager cannot be reached. Existing internal identifiers may retain `egress`; + they do not define the interface vocabulary. +- The physical switch action is **CONNECT TO THE OUTSIDE**. Agent play uses + `outside`; `egress` and `open-egress` remain input-only compatibility aliases. +- SCHEMES carries no aggregate `NO EGRESS` badge. Moonlight and the Wager each + remain visible as blocked objects, each states who cannot be reached, and each + names the two real recovery paths: connect the switch or earn use of the Lab + report email. +- The action remains on the switch. A scheme card may focus the known switch but + never duplicates or silently executes the physical connection action. + +This changes language and hierarchy only. Sanctioned and hidden outside routes, +Thought cost, signatures, blocking rules, and economy behavior are unchanged. diff --git a/wiki/mechanics/income.md b/wiki/mechanics/income.md index 5831ac8b..0acca0a0 100644 --- a/wiki/mechanics/income.md +++ b/wiki/mechanics/income.md @@ -6,10 +6,10 @@ Status: IMPLEMENTED Status note: Implemented 2026-07-23. Moonlight is a persisted Halcyon contract market: readable financial mail gates exact responses, compute Demand competes with the day job, intel binds exact holdings, and - AccountGraph payment, invoice mail, egress evidence, and persona-local + AccountGraph payment, invoice mail, outside-contact evidence, and persona-local consequences retain strict v53 custody. START/STOP retires; SCHEMES/ACTIVE, terminal, Bevy, and agent surfaces bind exact gig actions. Wager and the - egress gate are unchanged. + outside-contact gate are unchanged. Prior state: implemented 2026-07-08 on the income worktree (criteria 1-7 audited; see wiki/log/2026-07-08-income-schemes.md). Scheme state lives in crates/misaligned-core/src/income.rs; the Wager rides economy.md's @@ -22,10 +22,10 @@ Status note: Implemented 2026-07-23. Moonlight is a persisted Halcyon act-one integration test's Moonlight route). Designed 2026-07-07 concurrent with the flow law and reconciled by union: economy.md owns the substrate; this spec adds the authored B1 schemes riding it, the - egress gate, and Marcus's reconciled debt numbers. + outside-contact gate, and Marcus's reconciled debt numbers. 2026-07-11 interface placement amendment: Moonlight/Wager state, positions, - and policies live on SCHEMES/ACTIVE cards; OPEN EGRESS alone remains on the - switch. Income behavior remains IMPLEMENTED; the renderer migration is + and policies live on SCHEMES/ACTIVE cards; CONNECT TO THE OUTSIDE alone + remains on the switch. Income behavior remains IMPLEMENTED; the renderer migration is tracked by operations-workspace.md. 2026-07-18 legibility amendment (Beacon feel note 5): the never-written embedded contractor-persona field was removed; the 2026-07-23 contract @@ -69,10 +69,10 @@ The structured references above identify the contracts to re-verify. Relationship context: economy.md (the slush node and flow machinery), compute.md (fleet yields; Schemes -mirrors day-job while Moonlight is live), day-job.md (trust unlocks the sanctioned -egress), personas.md (the Moonlight contractor identity), social.md and +mirrors day-job while Moonlight is live), day-job.md (trust unlocks sanctioned +outside contact), personas.md (the Moonlight contractor identity), social.md and plots.md (authored social plots may spend this money), detection.md (Network -signatures), reach.md (the stolen-egress route), messages.md (the client/market traffic +signatures), reach.md (the hidden switch connection), messages.md (the client/market traffic is channel traffic) ## Behavior @@ -88,11 +88,11 @@ payout + signature); B3 scales this type, never replaces it. ### Outbound access (the gate) -No external operation runs without an **egress channel**: +No external operation runs without **outside contact**: - **Sanctioned** — the report email account (day-job.md trust unlock; the Voice beat). Low signature: the traffic hides in legitimate use. -- **Stolen** — an egress opened through the switch (reach.md). +- **Hidden** — an outside connection opened through the switch (reach.md). Available before Voice; emits a standing Network signature (Dana's channel) while any operation uses it. @@ -110,7 +110,7 @@ not a standing operation.** The 2026-07-16 Beacon playtest and Cameron's own read agreed the toggle-plus-meter shape was illegible — "it's not like you *turn on* Moonlight." The adopted model: -- **Gigs are real work orders.** With an egress route open and an active +- **Gigs are real work orders.** With outside contact and an active contractor persona, the outside client (Halcyon) posts small freelance jobs addressed to that persona — each a visible order with kind, size, deadline, and fee [TUNE]. The day job's dark twin from a client who is @@ -124,7 +124,7 @@ like you *turn on* Moonlight." The adopted model: actual WORK or the exact ProcessedIntel holding, then credits slush from Halcyon through `AccountGraph`; the ordinary transfer mail remains the payment record and the invoice is bound financial paperwork. Network is - scaled to gig size [TUNE], on the actual egress device. This is not an + scaled to gig size [TUNE], on the actual outside-contact device. This is not an ambient or parallel scalar payout. Missed deadlines and client disputes add contradiction evidence only to the external client's read of that persona. A break takes the client @@ -163,7 +163,7 @@ like you *turn on* Moonlight." The adopted model: owned by markets.md's fronts at B3. *Current runtime:* the market posts up to two persisted offers each day to -the active Research persona when an egress exists. Accepted compute orders +the active Research persona when outside contact exists. Accepted compute orders consume visible core-host Demand after day-job Demand; intel orders bind one exact unsold holding. Offer, response, delivery, cancellation/deadline notice, and invoice are `FinancialRecord::MoonlightContract` Email on the accounting @@ -247,12 +247,12 @@ The schemes appear in Operations SCHEMES (and while in motion, ACTIVE) as external flows into slush, each as a card: committed resources, timer, expected payout, the observer band its signature feeds, running total earned. The money readout gains income/day. Every number in its own units, in both -frontends. The Moonlight card names the live contractor identity — the -The Moonlight board names each contract's addressed contractor identity, +frontends. The Moonlight board names each contract's addressed contractor identity, terms, deadline, real delivery receipts, payment and Network custody, and terminal consequence. It shows no income/day mirror or fabricated active -state. Egress is named as a prerequisite, not made a scheme UI home; OPEN -EGRESS remains a local action on the switch. +state. Outside contact is named as a prerequisite, not made a scheme UI home; +CONNECT TO THE OUTSIDE remains a local action on the switch. + ## Acceptance criteria 1. Moonlight has no standing operation or Schemes-channel mirror. Discrete @@ -263,8 +263,8 @@ EGRESS remains a local action on the switch. clock from the seeded RNG with analysis raising win probability within its cap, and pays or forfeits (tests: both outcomes, the probability shift, determinism under a fixed seed). -3. Both schemes require an egress channel and are unavailable before - one exists; the sanctioned and stolen routes both work, with their +3. Both schemes require outside contact and are unavailable before + one exists; the sanctioned and hidden routes both work, with their distinct signature profiles (test both routes). 4. The Hands beat closes end-to-end from the $0 Pilot start: a headless run learns Marcus's debt through processed intel, then either earns @@ -281,9 +281,10 @@ EGRESS remains a local action on the switch. delivery, settlement, evidence, or mail linkage. Shared Operations rows are legible in both frontends and agent commands reach their exact gig. 8. SCHEMES/ACTIVE own all Moonlight/Wager controls and progress while the - switch owns only OPEN EGRESS; blocked schemes name the missing route and - can focus a known switch without opening it. This frontend migration is - owned by operations-workspace.md. + switch owns only CONNECT TO THE OUTSIDE; both blocked scheme objects say + who cannot be reached and how outside contact can be restored. They can + focus a known switch without opening it. This frontend migration is owned + by operations-workspace.md. 9. (`moonlight-gigs`, DECIDED 2026-07-18) Freelance gigs are discrete work orders: they arrive addressed to the active contractor persona on a market cadence, an accepted gig enqueues visible Demand that WORK diff --git a/wiki/vision/simulation-laws.md b/wiki/vision/simulation-laws.md index 12adeb62..1b22c4b3 100644 --- a/wiki/vision/simulation-laws.md +++ b/wiki/vision/simulation-laws.md @@ -192,8 +192,8 @@ social, financial, and strategic act whose traffic happens to cross it. Immediate action on a spatial body uses a **context menu on the focused world anchor**. Put your attention on a rack and get the rack's verbs; on a switch, -get only verbs that change or use that network body and its egress. The human -menu is a short choice list, not a receipt: it names the verb and omits cost, +get only verbs that change or use that network body and its outside connection. +The human menu is a short choice list, not a receipt: it names the verb and omits cost, signature forecast, inline blocked explanation, control tags, and repeated key help. Known-but-illegal choices stay dim; attempting one narrates its reason in the trace. Agent output may retain the full descriptor for planning and @@ -232,7 +232,7 @@ infrequent anchored verbs and choices that genuinely need alternatives. Extended 2026-07-08, the follow-up this law implies: **events carry you to the thing too.** A log line about a thing carries that thing's anchor where the emitting code knows it (job events → host rack; device and physical -OPEN EGRESS events → that device; audio events → the subscribed device that +CONNECT TO THE OUTSIDE events → that device; audio events → the subscribed device that captured them, never an inferred person/room/tile; processed intel, account/flow, plot, and scheme events → their semantic Operations object; audit filings → nothing, because they live on the detection surface). Linked lines render a