diff --git a/CLAUDE.md b/CLAUDE.md index 815458bb..bc57df5b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,7 +31,7 @@ afterward. `./tools/check.sh --docs|--lib|--frontend` gate. - The live player machine grammar is **WORK / THINK / LIE**. `Relay` is non-delegable graph infrastructure; Research and Operations are retired - machine modes, not current player assignments. Save format is currently v53; + machine modes, not current player assignments. Save format is currently v54; only the current version loads (pre-release rider 2026-07-16 — older development saves are refused before state mutation, so the caller retains its current run; the v1-v31 migration ladder lives in git history). diff --git a/crates/misaligned-core/src/detection.rs b/crates/misaligned-core/src/detection.rs index 1efbf305..9b3fcddf 100644 --- a/crates/misaligned-core/src/detection.rs +++ b/crates/misaligned-core/src/detection.rs @@ -1,11 +1,11 @@ //! Detection: per-observer suspicion (spec/detection.md). //! -//! Replaces global heat. Some typed signatures remain in a concealment- -//! scrubbed pending pool; witnessed Physical acts enter exact observers -//! directly, while one-shot Network, Paper, Financial, and JobAnomaly records -//! travel from their exact sources before read. Acquired suspicion feeds real Filing messages to -//! the Assurance Office — itself an Observer per the aggregate-observer law — -//! whose audit can start containment. +//! Replaces global heat. Standing Network pressure still uses a concealment- +//! scrubbed pending/ambient pool; witnessed Physical acts enter exact observers +//! directly; one-shot Network, Paper, Financial, JobAnomaly, Power, and Thermal +//! records travel from their exact sources before read. Acquired suspicion feeds +//! real Filing messages to the Assurance Office — itself an Observer per the +//! aggregate-observer law — whose audit can start containment. use std::collections::{BTreeSet, HashMap}; @@ -157,6 +157,15 @@ pub struct Signature { pub source: String, } +#[derive(Debug, Clone)] +struct RoutedEvidenceSource { + machine: Option, + site: Option<(i32, i32)>, + /// Exact contributing world sites measured into an aggregate facility + /// reading. Empty for routed records that are not facility aggregates. + sites: Vec<(i32, i32)>, +} + /// Structured detection output. The sim owns final player-facing wording so /// it can apply earned identity labels instead of leaking authored names. #[derive(Debug, Clone, PartialEq)] @@ -223,6 +232,9 @@ pub enum EvidenceSource { /// Physical authoring site retained at capture time rather than /// reconstructed from mutable machine or device state. source_site: Option<(i32, i32)>, + /// Exact contributing world sites measured into an aggregate facility + /// reading. Empty for routed records that are not facility aggregates. + source_sites: Vec<(i32, i32)>, }, } @@ -276,6 +288,9 @@ pub struct RoutedEvidence { pub source_device: u32, pub source_machine: Option, pub source_site: Option<(i32, i32)>, + /// Exact contributing world sites measured into an aggregate facility + /// reading. Canonically sorted and deduplicated at authorship. + pub source_sites: Vec<(i32, i32)>, pub observer_id: u8, pub sent_tick: u64, pub delivered_tick: Option, @@ -487,8 +502,9 @@ impl Detection { } /// Emit a signature into the pending pool. One-shot Network, Paper, - /// Financial, and JobAnomaly records have exact routes; accepting any of - /// them here would silently restore ambient scrubbing and erase custody. + /// Financial, JobAnomaly, Power, and Thermal records have exact routes; + /// accepting any of them here would silently restore ambient scrubbing + /// and erase custody. Standing Network pressure remains ambient. pub fn emit(&mut self, sig: Signature) { assert!( !matches!( @@ -497,8 +513,10 @@ impl Detection { | SignatureKind::Paper | SignatureKind::Financial | SignatureKind::JobAnomaly + | SignatureKind::Power + | SignatureKind::Thermal ), - "one-shot Network, Paper, Financial, or JobAnomaly evidence requires exact routed custody" + "one-shot Network, Paper, Financial, JobAnomaly, Power, or Thermal evidence requires exact routed custody" ); self.pending.push(sig); } @@ -511,7 +529,18 @@ impl Detection { sent_tick: u64, route: MessageRoute, ) -> Option { - self.route_evidence(SignatureKind::Network, size, cause, sent_tick, None, route) + self.route_evidence( + SignatureKind::Network, + size, + cause, + sent_tick, + RoutedEvidenceSource { + machine: None, + site: None, + sites: Vec::new(), + }, + route, + ) } /// Begin exact routed custody for one institutional Paper record. @@ -522,7 +551,18 @@ impl Detection { sent_tick: u64, route: MessageRoute, ) -> Option { - self.route_evidence(SignatureKind::Paper, size, cause, sent_tick, None, route) + self.route_evidence( + SignatureKind::Paper, + size, + cause, + sent_tick, + RoutedEvidenceSource { + machine: None, + site: None, + sites: Vec::new(), + }, + route, + ) } /// Begin exact routed custody for one accounting-carrier Financial record. @@ -538,7 +578,11 @@ impl Detection { size, cause, sent_tick, - None, + RoutedEvidenceSource { + machine: None, + site: None, + sites: Vec::new(), + }, route, ) } @@ -558,20 +602,77 @@ impl Detection { size, cause, sent_tick, - Some((source_machine, source_site)), + RoutedEvidenceSource { + machine: Some(source_machine), + site: Some(source_site), + sites: Vec::new(), + }, + route, + ) + } + + /// Begin exact routed custody for one UPS-meter Power reading. The singular + /// site is the meter tile; `source_sites` retains every measured world site. + /// There is no source machine. + pub fn route_power_evidence( + &mut self, + size: i32, + cause: impl Into, + sent_tick: u64, + source_site: (i32, i32), + source_sites: Vec<(i32, i32)>, + route: MessageRoute, + ) -> Option { + self.route_evidence( + SignatureKind::Power, + size, + cause, + sent_tick, + RoutedEvidenceSource { + machine: None, + site: Some(source_site), + sites: source_sites, + }, + route, + ) + } + + /// Begin exact routed custody for one HVAC-meter Thermal reading. The + /// singular site is the meter tile; `source_sites` retains every measured + /// world site. There is no source machine. + pub fn route_thermal_evidence( + &mut self, + size: i32, + cause: impl Into, + sent_tick: u64, + source_site: (i32, i32), + source_sites: Vec<(i32, i32)>, + route: MessageRoute, + ) -> Option { + self.route_evidence( + SignatureKind::Thermal, + size, + cause, + sent_tick, + RoutedEvidenceSource { + machine: None, + site: Some(source_site), + sites: source_sites, + }, route, ) } /// Allocate one stable id at authorship so route history and observer - /// evidence remain the same record across delivery and read. + /// evidence remain the same record across delivery and read. Source site + /// may be set without a source machine (facility meter readings). fn route_evidence( &mut self, kind: SignatureKind, size: i32, cause: impl Into, sent_tick: u64, - machine_source: Option<(u32, (i32, i32))>, + source: RoutedEvidenceSource, route: MessageRoute, ) -> Option { if size <= 0 { @@ -587,17 +688,15 @@ impl Detection { }; let evidence_id = self.next_evidence_id; self.next_evidence_id = evidence_id.checked_add(1)?; - let (source_machine, source_site) = machine_source - .map(|(machine, site)| (Some(machine), Some(site))) - .unwrap_or((None, None)); self.routed_evidence.push(RoutedEvidence { id: evidence_id, kind, size, cause: cause.into(), source_device, - source_machine, - source_site, + source_machine: source.machine, + source_site: source.site, + source_sites: source.sites, observer_id, sent_tick, delivered_tick: None, @@ -675,7 +774,16 @@ impl Detection { observer.id == record.observer_id && observer.watches(record.kind) })? }; - let (kind, size, cause, source_device, source_machine, source_site, observer_id) = { + let ( + kind, + size, + cause, + source_device, + source_machine, + source_site, + source_sites, + observer_id, + ) = { let record = &mut self.routed_evidence[index]; record.status = MessageStatus::Read; record.read_tick = Some(tick); @@ -686,6 +794,7 @@ impl Detection { record.source_device, record.source_machine, record.source_site, + record.source_sites.clone(), record.observer_id, ) }; @@ -704,6 +813,7 @@ impl Detection { source_device, source_machine, source_site, + source_sites, }, acquired_tick: tick, filing, @@ -943,8 +1053,16 @@ impl Detection { } let (relevant, input, sources) = match &obs.input { WatchedInput::Channels(_) => { - let relevant_signatures: Vec<&Signature> = - visible.iter().filter(|s| obs.watches(s.kind)).collect(); + // Power and Thermal never ambient-sample: facility meters + // author discrete routed readings. Standing Network and any + // remaining pending kinds still pool for cadence rolls. + let relevant_signatures: Vec<&Signature> = visible + .iter() + .filter(|s| { + obs.watches(s.kind) + && !matches!(s.kind, SignatureKind::Power | SignatureKind::Thermal) + }) + .collect(); let sum: i32 = relevant_signatures.iter().map(|s| s.size).sum(); let mut channels = Vec::new(); let mut sources = Vec::new(); @@ -1127,9 +1245,11 @@ impl Detection { signature.kind != SignatureKind::Paper && signature.kind != SignatureKind::Financial && signature.kind != SignatureKind::JobAnomaly + && signature.kind != SignatureKind::Power + && signature.kind != SignatureKind::Thermal && (signature.kind != SignatureKind::Network || signature.standing) }), - "one-shot Network, Paper, Financial, or JobAnomaly evidence requires exact routed custody" + "one-shot Network, Paper, Financial, JobAnomaly, Power, or Thermal evidence requires exact routed custody" ); self.pending = pending; } @@ -1160,11 +1280,11 @@ mod tests { fn concealment_scrubs_before_noticing() { let mut d = Detection::act_one(); d.emit(Signature { - kind: SignatureKind::Power, + kind: SignatureKind::Physical, size: 10, standing: false, site: None, - source: "test power draw".into(), + source: "test physical residue".into(), }); assert_eq!(d.pending_size(), 10); d.scrub(6.0); @@ -1173,7 +1293,7 @@ mod tests { #[test] #[should_panic( - expected = "one-shot Network, Paper, Financial, or JobAnomaly evidence requires exact routed custody" + expected = "one-shot Network, Paper, Financial, JobAnomaly, Power, or Thermal evidence requires exact routed custody" )] fn generic_pending_boundary_rejects_one_shot_network_evidence() { Detection::act_one().emit(Signature { @@ -1187,7 +1307,7 @@ mod tests { #[test] #[should_panic( - expected = "one-shot Network, Paper, Financial, or JobAnomaly evidence requires exact routed custody" + expected = "one-shot Network, Paper, Financial, JobAnomaly, Power, or Thermal evidence requires exact routed custody" )] fn generic_pending_boundary_rejects_paper_evidence() { Detection::act_one().emit(Signature { @@ -1201,7 +1321,7 @@ mod tests { #[test] #[should_panic( - expected = "one-shot Network, Paper, Financial, or JobAnomaly evidence requires exact routed custody" + expected = "one-shot Network, Paper, Financial, JobAnomaly, Power, or Thermal evidence requires exact routed custody" )] fn generic_pending_boundary_rejects_financial_evidence() { Detection::act_one().emit(Signature { @@ -1215,7 +1335,35 @@ mod tests { #[test] #[should_panic( - expected = "one-shot Network, Paper, Financial, or JobAnomaly evidence requires exact routed custody" + expected = "one-shot Network, Paper, Financial, JobAnomaly, Power, or Thermal evidence requires exact routed custody" + )] + fn generic_pending_boundary_rejects_power_evidence() { + Detection::act_one().emit(Signature { + kind: SignatureKind::Power, + size: 1, + standing: false, + site: None, + source: "impossible ambient power".into(), + }); + } + + #[test] + #[should_panic( + expected = "one-shot Network, Paper, Financial, JobAnomaly, Power, or Thermal evidence requires exact routed custody" + )] + fn generic_pending_boundary_rejects_thermal_evidence() { + Detection::act_one().emit(Signature { + kind: SignatureKind::Thermal, + size: 1, + standing: false, + site: None, + source: "impossible ambient thermal".into(), + }); + } + + #[test] + #[should_panic( + expected = "one-shot Network, Paper, Financial, JobAnomaly, Power, or Thermal evidence requires exact routed custody" )] fn fixture_pending_boundary_rejects_even_standing_job_anomaly() { Detection::act_one().set_pending(vec![Signature { @@ -1230,33 +1378,86 @@ mod tests { #[test] fn suppress_oldest_removes_one_exact_kind_without_reordering_the_rest() { let mut d = Detection::act_one(); - for (kind, source) in [ - (SignatureKind::Thermal, "first thermal event"), - (SignatureKind::Power, "power activity"), - (SignatureKind::Thermal, "second thermal event"), - ] { - d.emit(Signature { - kind, + d.emit(Signature { + kind: SignatureKind::Physical, + size: 4, + standing: false, + site: None, + source: "first physical event".into(), + }); + d.set_pending({ + let mut pending = d.pending().to_vec(); + pending.push(Signature { + kind: SignatureKind::Network, size: 4, - standing: false, + standing: true, site: None, - source: source.into(), + source: "network pressure".into(), }); - } + pending + }); + d.emit(Signature { + kind: SignatureKind::Physical, + size: 4, + standing: false, + site: None, + source: "second physical event".into(), + }); let removed = d - .suppress_oldest(SignatureKind::Thermal) - .expect("the oldest thermal event exists"); - assert_eq!(removed.source, "first thermal event"); + .suppress_oldest(SignatureKind::Physical) + .expect("the oldest physical event exists"); + assert_eq!(removed.source, "first physical event"); assert_eq!( d.pending() .iter() .map(|signature| signature.source.as_str()) .collect::>(), - vec!["power activity", "second thermal event"], + vec!["network pressure", "second physical event"], "another channel and the newer event remain in original order" ); - assert!(d.has_pending(SignatureKind::Thermal)); + assert!(d.has_pending(SignatureKind::Physical)); + } + + #[test] + fn ambient_cadence_ignores_standing_power_and_thermal() { + let mut d = Detection::act_one(); + let mut rng = Rng::new(3); + let standing = vec![ + Signature { + kind: SignatureKind::Power, + size: 40, + standing: true, + site: Some((1, 1)), + source: "hot rack".into(), + }, + Signature { + kind: SignatureKind::Thermal, + size: 40, + standing: true, + site: Some((2, 2)), + source: "hot air".into(), + }, + ]; + let cadence = d + .observers + .iter() + .find(|observer| observer.id == PRIYA_ID) + .unwrap() + .cadence; + for t in 1..=cadence { + d.tick(t, &standing, &mut rng); + } + let priya = d + .observers + .iter() + .find(|observer| observer.id == PRIYA_ID) + .unwrap(); + assert_eq!( + priya.suspicion, 0.0, + "Priya no longer ambient-samples Power/Thermal standing loads" + ); + assert!(priya.evidence.is_empty()); } #[test] diff --git a/crates/misaligned-core/src/prefab.rs b/crates/misaligned-core/src/prefab.rs index 7b090a8e..d69dc147 100644 --- a/crates/misaligned-core/src/prefab.rs +++ b/crates/misaligned-core/src/prefab.rs @@ -217,7 +217,7 @@ const NETWORK_CLOSET: Prefab = Prefab { const ELECTRICAL: Prefab = Prefab { name: "electrical", - rows: &["#####", "#BB.#", "#..O#", "##+##"], + rows: &["#####", "#UB.#", "#..O#", "##+##"], }; const HVAC: Prefab = Prefab { @@ -537,7 +537,9 @@ mod tests { TileType::ForeignRack, TileType::DeadRack, TileType::Switch, + TileType::Ups, TileType::BreakerPanel, + TileType::HvacUnit, TileType::DeadEquipment, TileType::RecordsBox, TileType::RollDoor, diff --git a/crates/misaligned-core/src/reach.rs b/crates/misaligned-core/src/reach.rs index 026dabaa..af11a6fe 100644 --- a/crates/misaligned-core/src/reach.rs +++ b/crates/misaligned-core/src/reach.rs @@ -378,6 +378,35 @@ impl ReachNet { false, )); } + // Facility power/thermal meters: exact UPS and HVAC instrument + // devices. They author discrete readings that route to Priya; they + // are not on the opening known subnet map until SCAN. + if let Some(p) = at(TileType::Ups) { + devices.push(mk( + "UPS meter", + p, + 0, + Party::Facility, + false, + false, + false, + false, + false, + )); + } + if let Some(p) = at(TileType::HvacUnit) { + devices.push(mk( + "HVAC meter", + p, + 0, + Party::Facility, + false, + false, + false, + false, + false, + )); + } // Carriers for the message-flow law (messages.md). The switch is the // basement's email/ticket/filing/phone carrier; room microphones can @@ -406,6 +435,11 @@ impl ReachNet { if let (Some(switch), Some(env)) = (find("switch"), find("environmental monitor")) { graph.link(env, switch, 0, None); } + for meter in ["UPS meter", "HVAC meter"] { + if let (Some(switch), Some(dev)) = (find("switch"), find(meter)) { + graph.link(dev, switch, 0, None); + } + } for sec in ["dock camera", "stairwell camera", "badge controller"] { if let (Some(switch), Some(dev)) = (find("switch"), find(sec)) { graph.link(dev, switch, 0, Some(1)); @@ -953,12 +987,27 @@ mod tests { "stairwell camera", "badge controller", "old storage server", + "UPS meter", + "HVAC meter", ] { assert!(n.device_named(name).is_some(), "missing device {name}"); } - // Seed knowledge: the basement subnet only. + // Seed knowledge: the basement subnet only. Facility meters exist + // on the wire but stay unknown until SCAN. let known: Vec<_> = n.known().map(|d| d.name.as_str()).collect(); assert_eq!(known, vec!["Rack 3", "switch", "environmental monitor"]); + let ups = n.device_named("UPS meter").unwrap(); + let hvac = n.device_named("HVAC meter").unwrap(); + assert!(!ups.known && !hvac.known); + assert_eq!(ups.segment, 0); + assert_eq!(hvac.segment, 0); + assert_eq!(ups.owner, Party::Facility); + assert_eq!(hvac.owner, Party::Facility); + assert!(!ups.sees && !ups.hears && ups.message_channels.is_empty()); + assert!(!hvac.sees && !hvac.hears && hvac.message_channels.is_empty()); + let switch = n.device_named("switch").unwrap().id; + assert!(n.open_path(ups.id, switch).is_some()); + assert!(n.open_path(hvac.id, switch).is_some()); } #[test] diff --git a/crates/misaligned-core/src/save.rs b/crates/misaligned-core/src/save.rs index 20e66d85..c0eb7407 100644 --- a/crates/misaligned-core/src/save.rs +++ b/crates/misaligned-core/src/save.rs @@ -42,8 +42,11 @@ const SAVE_BACKUP_SUFFIX: &str = ".bak"; /// renames into place. const SAVE_TEMP_SUFFIX: &str = ".tmp"; -/// Save format version. v53 persists discrete Moonlight contracts, their -/// terms, work/payment/evidence receipts, persona consequences, and bound +/// Save format version. v54 persists discrete Power/Thermal facility-meter +/// routes, complete measured source-site sets, the last quantized levels used +/// for change-triggered authorship, and route-local LIE provenance. v53 +/// persists discrete Moonlight contracts, their terms, work/payment/evidence +/// receipts, persona consequences, and bound /// financial paperwork. v52 requires one-shot Paper evidence to retain its /// exact institutional-carrier route, observer custody, and first-hop interdiction. /// v51 requires the same exact custody for Financial evidence. v50 persists @@ -59,7 +62,7 @@ const SAVE_TEMP_SUFFIX: &str = ".tmp"; /// v43 introduced exact Filing routes and pre-read LIE interdiction. /// Bump for every schema change; during pre-release, old development state is /// refused instead of carried through compatibility shims. -pub const SAVE_VERSION: u32 = 53; +pub const SAVE_VERSION: u32 = 54; fn save_dir() -> PathBuf { let mut path = dirs::data_dir().unwrap_or_else(|| PathBuf::from(".")); @@ -99,6 +102,9 @@ pub struct SaveState { pub compute: Compute, pub core: Core, pub detection: Detection, + /// Last post-cover Power/Thermal levels observed by the facility meters. + /// These preserve the immediate level-change boundary across save/load. + pub last_facility_meter_levels: [i32; 2], /// The process's earned model of the otherwise-live hidden observer /// topology. Separate from `detection` so simulation truth never becomes /// knowledge merely because it exists in the save. @@ -235,6 +241,7 @@ impl SaveState { compute: sim.compute.clone(), core: sim.core.clone(), detection: sim.detection.clone(), + last_facility_meter_levels: sim.last_facility_meter_levels, detection_awareness: sim.detection_awareness.clone(), dayjob: sim.dayjob.clone(), people: sim.people.clone(), @@ -298,6 +305,7 @@ impl SaveState { sim.compute = self.compute.clone(); sim.core = self.core.clone(); sim.detection = self.detection.clone(); + sim.last_facility_meter_levels = self.last_facility_meter_levels; sim.detection_awareness = self.detection_awareness.clone(); sim.dayjob = self.dayjob.clone(); sim.people = self.people.clone(); @@ -465,6 +473,13 @@ fn parse_save(content: &str) -> Result { fn validate_current_save(mut state: SaveState) -> Result { state.reach.validate_subscriptions()?; validate_messages(&state)?; + if state + .last_facility_meter_levels + .iter() + .any(|level| *level < 0) + { + return Err("current-version save has a negative facility-meter level".into()); + } if state.detection.pending().iter().any(|signature| { matches!( signature.kind, @@ -472,10 +487,12 @@ fn validate_current_save(mut state: SaveState) -> Result { | crate::detection::SignatureKind::Paper | crate::detection::SignatureKind::Financial | crate::detection::SignatureKind::JobAnomaly + | crate::detection::SignatureKind::Power + | crate::detection::SignatureKind::Thermal ) }) { return Err( - "current-version save puts routed Network, Paper, Financial, or JobAnomaly evidence in the pending pool" + "current-version save puts routed Network, Paper, Financial, JobAnomaly, Power, or Thermal evidence in the pending pool" .into(), ); } @@ -581,6 +598,7 @@ fn validate_current_save(mut state: SaveState) -> Result { source_device, source_machine, source_site, + source_sites, } => { let linked = state.detection.routed_evidence().iter().any(|record| { record.id == evidence.id @@ -588,6 +606,7 @@ fn validate_current_save(mut state: SaveState) -> Result { && record.source_device == *source_device && record.source_machine == *source_machine && record.source_site == *source_site + && record.source_sites == *source_sites && record.observer_id == observer.id && record.kind == evidence.kind && record.cause == evidence.cause @@ -597,7 +616,11 @@ fn validate_current_save(mut state: SaveState) -> Result { if !matches!( evidence.kind, crate::detection::SignatureKind::Network + | crate::detection::SignatureKind::Paper + | crate::detection::SignatureKind::Financial | crate::detection::SignatureKind::JobAnomaly + | crate::detection::SignatureKind::Power + | crate::detection::SignatureKind::Thermal ) || !linked { return Err(format!( @@ -2162,6 +2185,8 @@ fn validate_routed_evidence(state: &SaveState) -> Result<(HashSet, u64), St | crate::detection::SignatureKind::Paper | crate::detection::SignatureKind::Financial | crate::detection::SignatureKind::JobAnomaly + | crate::detection::SignatureKind::Power + | crate::detection::SignatureKind::Thermal ) || record.size <= 0 || record.cause.trim().is_empty() || record.sent_tick > state.sim_tick @@ -2171,6 +2196,22 @@ fn validate_routed_evidence(state: &SaveState) -> Result<(HashSet, u64), St record.id )); } + let meter_kind = matches!( + record.kind, + crate::detection::SignatureKind::Power | crate::detection::SignatureKind::Thermal + ); + let source_sites_valid = record.source_sites.windows(2).all(|pair| pair[0] < pair[1]) + && record + .source_sites + .iter() + .all(|(x, y)| *x >= 0 && *y >= 0 && *x < state.map_width && *y < state.map_height) + && (meter_kind || record.source_sites.is_empty()); + if !source_sites_valid { + return Err(format!( + "current-version routed evidence #{} has invalid contributing source sites", + record.id + )); + } let authored_source_valid = match record.kind { crate::detection::SignatureKind::Network => { record.source_machine.is_none() && record.source_site.is_none() @@ -2208,6 +2249,28 @@ fn validate_routed_evidence(state: &SaveState) -> Result<(HashSet, u64), St }) }) && record.observer_id == crate::detection::VOSS_ID }), + crate::detection::SignatureKind::Power => { + record.source_machine.is_none() + && record.observer_id == crate::detection::PRIYA_ID + && state + .reach + .device_named("UPS meter") + .zip(record.source_site) + .is_some_and(|(device, site)| { + device.id == record.source_device && (device.x, device.y) == site + }) + } + crate::detection::SignatureKind::Thermal => { + record.source_machine.is_none() + && record.observer_id == crate::detection::PRIYA_ID + && state + .reach + .device_named("HVAC meter") + .zip(record.source_site) + .is_some_and(|(device, site)| { + device.id == record.source_device && (device.x, device.y) == site + }) + } _ => false, }; if !authored_source_valid @@ -2976,6 +3039,72 @@ mod tests { SaveState::from_sim(&sim) } + fn routed_power_state(stopped: bool) -> SaveState { + let mut sim = Sim::with_seed(0xF0A3); + let ups = sim.reach.device_named("UPS meter").unwrap().id; + let switch = sim.reach.device_named("switch").unwrap().id; + if stopped { + let (x, y) = sim.core_position(); + sim.compute.add_machine( + "test ops executor", + x + 1, + y, + 1, + 1.0, + 0, + crate::machine::Provenance::Owned, + ); + sim.reconcile_work_grid(); + sim.reach.take(ups); + sim.reach.take(switch); + sim.set_machine_mode(sim.core.host_machine, MachineMode::Think); + sim.set_machine_mode(sim.core.host_machine, MachineMode::Lie); + } + sim.emit_power(7, "test routed Power meter reading"); + assert_eq!(sim.detection.routed_evidence().len(), 1); + if stopped { + sim.advance(); + assert_eq!( + sim.detection.routed_evidence()[0].status, + MessageStatus::Stopped + ); + } + SaveState::from_sim(&sim) + } + + fn routed_thermal_state(stopped: bool) -> SaveState { + let mut sim = Sim::with_seed(0x7A3A); + let hvac = sim.reach.device_named("HVAC meter").unwrap().id; + let switch = sim.reach.device_named("switch").unwrap().id; + if stopped { + let (x, y) = sim.core_position(); + sim.compute.add_machine( + "test ops executor", + x + 1, + y, + 1, + 1.0, + 0, + crate::machine::Provenance::Owned, + ); + sim.reconcile_work_grid(); + sim.reach.take(hvac); + sim.reach.take(switch); + sim.set_machine_mode(sim.core.host_machine, MachineMode::Think); + sim.set_machine_mode(sim.core.host_machine, MachineMode::Lie); + } + sim.emit_thermal(7, "test routed Thermal meter reading"); + assert_eq!(sim.detection.routed_evidence().len(), 1); + if stopped { + sim.advance(); + assert_eq!( + sim.detection.routed_evidence()[0].status, + MessageStatus::Stopped + ); + } + SaveState::from_sim(&sim) + } + fn characterization_fixture() -> Sim { let mut sim = Sim::with_seed(0x5eed); sim.map_mut().powered.extend([(3, 7), (11, 5), (2, 19)]); @@ -3050,7 +3179,7 @@ mod tests { ); assert_eq!( state_fingerprint(&uninterrupted_state), - "519d0e34ed81070f2cc5b2ead67558a6b324f708c9247c7bbbac36d0d7dbb7ab", + "b16a20091298854c382c7a9e3a8566c460703ea7cabe45bad20e73575e6acbd0", "intentional persisted-state changes must review and repin this baseline" ); } @@ -3164,7 +3293,7 @@ mod tests { let mut sim = Sim::with_seed(42); sim.player.money = 999; sim.detection.emit(crate::detection::Signature { - kind: SignatureKind::Power, + kind: SignatureKind::Physical, size: 12, standing: false, site: None, @@ -4921,6 +5050,205 @@ mod tests { ); } + #[test] + fn current_save_pins_power_and_thermal_meter_routes() { + let in_flight = routed_power_state(false); + let restored = parse_save(&serde_json::to_string(&in_flight).unwrap()).unwrap(); + let record = &restored.detection.routed_evidence()[0]; + let ups = restored.reach.device_named("UPS meter").unwrap(); + let switch = restored.reach.device_named("switch").unwrap().id; + assert_eq!(record.kind, crate::detection::SignatureKind::Power); + assert_eq!(record.source_device, ups.id); + assert_eq!(record.source_site, Some((ups.x, ups.y))); + assert_eq!(record.source_machine, None); + assert_eq!(record.observer_id, crate::detection::PRIYA_ID); + assert_eq!( + record.route.hops, + vec![ + MessageRouteHop::Device(ups.id), + MessageRouteHop::Device(switch), + MessageRouteHop::ObserverEndpoint(crate::detection::PRIYA_ID), + ] + ); + assert!( + restored + .detection + .pending() + .iter() + .all(|signature| signature.kind != crate::detection::SignatureKind::Power) + ); + + let stopped = routed_power_state(true); + let restored = parse_save(&serde_json::to_string(&stopped).unwrap()).unwrap(); + assert_eq!( + restored.detection.routed_evidence()[0].status, + MessageStatus::Stopped + ); + assert!( + restored.detection.routed_evidence()[0] + .route + .interdiction + .is_some() + ); + + let thermal = routed_thermal_state(false); + let restored = parse_save(&serde_json::to_string(&thermal).unwrap()).unwrap(); + let record = &restored.detection.routed_evidence()[0]; + let hvac = restored.reach.device_named("HVAC meter").unwrap(); + assert_eq!(record.kind, crate::detection::SignatureKind::Thermal); + assert_eq!(record.source_device, hvac.id); + assert_eq!(record.source_site, Some((hvac.x, hvac.y))); + assert_eq!(record.source_machine, None); + assert_eq!(record.observer_id, crate::detection::PRIYA_ID); + + let mut wrong_meter = in_flight.clone(); + let other = wrong_meter.reach.device_named("HVAC meter").unwrap().id; + let record = wrong_meter.detection.routed_evidence_mut(1).unwrap(); + record.source_device = other; + record.route.hops[0] = MessageRouteHop::Device(other); + let err = parse_save(&serde_json::to_string(&wrong_meter).unwrap()).unwrap_err(); + assert!( + err.contains("impossible source or observer"), + "Power evidence must start on the UPS meter: {err}" + ); + + let mut wrong_site = in_flight.clone(); + wrong_site + .detection + .routed_evidence_mut(1) + .unwrap() + .source_site = Some((0, 0)); + let err = parse_save(&serde_json::to_string(&wrong_site).unwrap()).unwrap_err(); + assert!( + err.contains("impossible source or observer"), + "Power evidence site must be the UPS meter tile: {err}" + ); + + let mut invented_machine = in_flight.clone(); + let host = invented_machine.core.host_machine; + invented_machine + .detection + .routed_evidence_mut(1) + .unwrap() + .source_machine = Some(host); + let err = parse_save(&serde_json::to_string(&invented_machine).unwrap()).unwrap_err(); + assert!( + err.contains("impossible source or observer"), + "Power evidence cannot invent a source machine: {err}" + ); + + let mut wrong_observer = in_flight.clone(); + let record = wrong_observer.detection.routed_evidence_mut(1).unwrap(); + record.observer_id = 1; + *record.route.hops.last_mut().unwrap() = MessageRouteHop::ObserverEndpoint(1); + let err = parse_save(&serde_json::to_string(&wrong_observer).unwrap()).unwrap_err(); + assert!( + err.contains("impossible source or observer"), + "Power evidence cannot leave Priya: {err}" + ); + + let mut ambient = serde_json::to_value(&in_flight).unwrap(); + ambient["detection"]["pending"] = serde_json::json!([{ + "kind": "Power", + "size": 4, + "standing": false, + "site": null, + "source": "forged ambient power" + }]); + let err = parse_save(&serde_json::to_string(&ambient).unwrap()).unwrap_err(); + assert!( + err.contains("pending pool"), + "current saves reject ambient Power in the pending pool: {err}" + ); + + let mut ambient_thermal = serde_json::to_value(routed_thermal_state(false)).unwrap(); + ambient_thermal["detection"]["pending"] = serde_json::json!([{ + "kind": "Thermal", + "size": 4, + "standing": false, + "site": null, + "source": "forged ambient thermal" + }]); + let err = parse_save(&serde_json::to_string(&ambient_thermal).unwrap()).unwrap_err(); + assert!( + err.contains("pending pool"), + "current saves reject ambient Thermal in the pending pool: {err}" + ); + + let mut persisted_sites = in_flight.clone(); + persisted_sites + .detection + .routed_evidence_mut(1) + .unwrap() + .source_sites = vec![(20, 15), (22, 15)]; + let restored = parse_save(&serde_json::to_string(&persisted_sites).unwrap()).unwrap(); + assert_eq!( + restored.detection.routed_evidence()[0].source_sites, + vec![(20, 15), (22, 15)] + ); + + let mut duplicate_site = persisted_sites.clone(); + duplicate_site + .detection + .routed_evidence_mut(1) + .unwrap() + .source_sites = vec![(20, 15), (20, 15)]; + let err = parse_save(&serde_json::to_string(&duplicate_site).unwrap()).unwrap_err(); + assert!( + err.contains("invalid contributing source sites"), + "meter source sites must be sorted and unique: {err}" + ); + + let mut out_of_bounds_site = persisted_sites.clone(); + let map_width = out_of_bounds_site.map_width; + out_of_bounds_site + .detection + .routed_evidence_mut(1) + .unwrap() + .source_sites = vec![(map_width, 15)]; + let err = parse_save(&serde_json::to_string(&out_of_bounds_site).unwrap()).unwrap_err(); + assert!( + err.contains("invalid contributing source sites"), + "meter source sites must remain on the saved map: {err}" + ); + + let mut impossible_non_meter_sites = routed_network_state(false); + impossible_non_meter_sites + .detection + .routed_evidence_mut(1) + .unwrap() + .source_sites = vec![(20, 15)]; + let err = + parse_save(&serde_json::to_string(&impossible_non_meter_sites).unwrap()).unwrap_err(); + assert!( + err.contains("invalid contributing source sites"), + "only facility-meter records carry aggregate source sites: {err}" + ); + + let mut negative_baseline = in_flight.clone(); + negative_baseline.last_facility_meter_levels = [-1, 0]; + let err = parse_save(&serde_json::to_string(&negative_baseline).unwrap()).unwrap_err(); + assert!( + err.contains("negative facility-meter level"), + "meter comparison baselines cannot be negative: {err}" + ); + + let mut broken_path = in_flight; + let record = broken_path.detection.routed_evidence_mut(1).unwrap(); + // Drop the switch hop so the route no longer reaches the Filing exit. + record.route.hops = vec![ + MessageRouteHop::Device(record.source_device), + MessageRouteHop::ObserverEndpoint(crate::detection::PRIYA_ID), + ]; + let err = parse_save(&serde_json::to_string(&broken_path).unwrap()).unwrap_err(); + assert!( + err.contains("does not leave through the institutional switch") + || err.contains("impossible device edge") + || err.contains("invalid"), + "Power evidence must follow the real meter-to-switch path: {err}" + ); + } + #[test] fn current_save_roundtrips_in_flight_and_interdicted_network_evidence() { let in_flight = routed_network_state(false); @@ -5019,7 +5347,7 @@ mod tests { let err = parse_save(&serde_json::to_string(&ambient).unwrap()).unwrap_err(); assert!( err.contains( - "routed Network, Paper, Financial, or JobAnomaly evidence in the pending pool" + "routed Network, Paper, Financial, JobAnomaly, Power, or Thermal evidence in the pending pool" ), "current saves cannot restore the retired remote-scrubbing path: {err}" ); diff --git a/crates/misaligned-core/src/sim/carrier.rs b/crates/misaligned-core/src/sim/carrier.rs index 6f9270d8..cf3b455f 100644 --- a/crates/misaligned-core/src/sim/carrier.rs +++ b/crates/misaligned-core/src/sim/carrier.rs @@ -78,10 +78,21 @@ impl EvidenceMark { source_device, source_machine, source_site, - } => match (source_machine, source_site) { - (Some(machine), Some((x, y))) => format!( + source_sites, + } => match (source_machine, source_site, source_sites.as_slice()) { + (Some(machine), Some((x, y)), _) => format!( "read from routed record R{route_id} from M{machine} at ({x}, {y}) via D{source_device}" ), + (None, Some((meter_x, meter_y)), sites) if !sites.is_empty() => { + let measured = sites + .iter() + .map(|(x, y)| format!("({x}, {y})")) + .collect::>() + .join(", "); + format!( + "read from routed record R{route_id} measuring {measured} through ({meter_x}, {meter_y}) at D{source_device}" + ) + } _ => format!("read from routed record R{route_id} at D{source_device}"), }, }; diff --git a/crates/misaligned-core/src/sim/communications.rs b/crates/misaligned-core/src/sim/communications.rs index 0eb45ea6..43d655c8 100644 --- a/crates/misaligned-core/src/sim/communications.rs +++ b/crates/misaligned-core/src/sim/communications.rs @@ -7,7 +7,7 @@ use std::collections::HashSet; use crate::actions::Anchor; -use crate::detection::{PRIYA_ID, SignatureKind, VOSS_ID}; +use crate::detection::{PRIYA_ID, Signature, SignatureKind, VOSS_ID}; use crate::intel::{ IntelCustodyKind, IntelKind, IntelPolicyMatch, IntelPolicyOutcome, IntelRoutineClass, IntelStream, ProcessedIntel, RawIntelClass, RawIntelEvent, RawIntelKind, @@ -149,6 +149,176 @@ impl Sim { self.schedule_evidence_route(evidence_id); } + /// Route one discrete UPS-meter Power reading to Priya. Used for one-shot + /// facility power acts; standing loads use the Priya-cadence aggregate. + pub(crate) fn emit_power(&mut self, size: i32, cause: impl Into) { + self.route_meter_evidence(SignatureKind::Power, size, cause.into(), Vec::new()); + } + + /// Route one discrete HVAC-meter Thermal reading to Priya. + pub(crate) fn emit_thermal(&mut self, size: i32, cause: impl Into) { + self.route_meter_evidence(SignatureKind::Thermal, size, cause.into(), Vec::new()); + } + + /// Collapse current standing Power and Thermal loads into discrete meter + /// records. Each kind authors periodically at Priya's cadence and also + /// immediately when its post-cover integer level changes. A transition to + /// zero updates the durable baseline but authors no zero-size evidence. + pub(super) fn author_facility_meter_readings(&mut self, standing: &[Signature]) { + let Some(priya) = self + .detection + .field_observers() + .find(|observer| observer.id == PRIYA_ID) + else { + return; + }; + if self + .people + .get(PRIYA_ID) + .is_some_and(|person| person.incapacitated) + { + return; + } + let cadence_boundary = priya.cadence > 0 && self.tick.is_multiple_of(priya.cadence); + let levels = Self::facility_meter_levels(standing); + let changed = [ + levels[0] != self.last_facility_meter_levels[0], + levels[1] != self.last_facility_meter_levels[1], + ]; + self.last_facility_meter_levels = levels; + + if levels[0] > 0 && (cadence_boundary || changed[0]) { + self.author_aggregated_meter_reading(SignatureKind::Power, standing); + } + if levels[1] > 0 && (cadence_boundary || changed[1]) { + self.author_aggregated_meter_reading(SignatureKind::Thermal, standing); + } + } + + fn facility_meter_levels(standing: &[Signature]) -> [i32; 2] { + let level = |kind| { + standing + .iter() + .filter(|signature| signature.kind == kind && signature.size > 0) + .map(|signature| signature.size) + .sum() + }; + [level(SignatureKind::Power), level(SignatureKind::Thermal)] + } + + /// Establish the initial level-change baseline without manufacturing a + /// tick-zero meter record. Later changes compare against this exact + /// post-cover standing state. + pub(super) fn current_facility_meter_levels(&self) -> [i32; 2] { + let mut standing: Vec = self.compute.standing_signatures(); + standing.extend(self.machine_intensity_standing_signatures()); + standing.extend(self.day_job_standing_signatures()); + standing.extend(self.research_standing_signatures()); + standing.extend(self.scheme_standing_signatures()); + standing.extend(self.facility_standing_signatures()); + self.apply_maintenance_deferrals(&mut standing); + Self::facility_meter_levels(&standing) + } + + fn author_aggregated_meter_reading(&mut self, kind: SignatureKind, standing: &[Signature]) { + let mut parts: Vec<&Signature> = standing + .iter() + .filter(|signature| signature.kind == kind && signature.size > 0) + .collect(); + if parts.is_empty() { + return; + } + parts.sort_by(|a, b| { + a.source + .cmp(&b.source) + .then_with(|| a.site.cmp(&b.site)) + .then_with(|| a.size.cmp(&b.size)) + }); + let size: i32 = parts.iter().map(|signature| signature.size).sum(); + if size <= 0 { + return; + } + assert!( + parts.iter().all(|signature| signature.site.is_some()), + "standing {} meter inputs require exact source sites", + kind.name() + ); + let mut source_sites: Vec<(i32, i32)> = parts + .iter() + .filter_map(|signature| signature.site) + .collect(); + source_sites.sort_unstable(); + source_sites.dedup(); + let mut causes: Vec = parts + .iter() + .map(|signature| { + if signature.source.is_empty() { + format!("{} activity", kind.name()) + } else { + signature.source.clone() + } + }) + .collect(); + causes.sort(); + causes.dedup(); + let meter_name = match kind { + SignatureKind::Power => "UPS meter", + SignatureKind::Thermal => "HVAC meter", + _ => return, + }; + let cause = format!("{meter_name} reading: {}", causes.join("; ")); + self.route_meter_evidence(kind, size, cause, source_sites); + } + + fn route_meter_evidence( + &mut self, + kind: SignatureKind, + size: i32, + cause: String, + source_sites: Vec<(i32, i32)>, + ) { + assert!( + self.detection + .field_observers() + .any(|observer| { observer.id == PRIYA_ID && observer.watches(kind) }), + "B1 requires Priya to watch {} evidence", + kind.name() + ); + let meter_name = match kind { + SignatureKind::Power => "UPS meter", + SignatureKind::Thermal => "HVAC meter", + _ => panic!("meter evidence requires Power or Thermal"), + }; + let meter = self + .reach + .device_named(meter_name) + .expect("B1 requires its authored facility meters"); + let source_device = meter.id; + let source_site = (meter.x, meter.y); + let route = self.routed_evidence_path(source_device, PRIYA_ID); + let evidence_id = match kind { + SignatureKind::Power => self.detection.route_power_evidence( + size, + cause, + self.tick, + source_site, + source_sites, + route, + ), + SignatureKind::Thermal => self.detection.route_thermal_evidence( + size, + cause, + self.tick, + source_site, + source_sites, + route, + ), + _ => None, + } + .expect("meter evidence id space exhausted or emission had no size"); + self.schedule_evidence_route(evidence_id); + } + fn routed_evidence_path(&self, source_device: u32, observer_id: u8) -> MessageRoute { let switch = self .reach diff --git a/crates/misaligned-core/src/sim/mod.rs b/crates/misaligned-core/src/sim/mod.rs index 06235456..29c0263a 100644 --- a/crates/misaligned-core/src/sim/mod.rs +++ b/crates/misaligned-core/src/sim/mod.rs @@ -416,6 +416,11 @@ pub struct Sim { pub compute: Compute, pub core: Core, pub detection: Detection, + /// Last quantized aggregate levels observed by the UPS and HVAC meters. + /// The meters compare each post-cover standing set to this durable baseline + /// so a level change authors one immediate record even between Priya's + /// periodic cadence boundaries. + pub(crate) last_facility_meter_levels: [i32; 2], /// What this process has earned about the observer/reporting topology. /// The hidden detection simulation remains live regardless of awareness. pub detection_awareness: DetectionAwareness, @@ -784,6 +789,7 @@ impl Sim { compute, core, detection: Detection::act_one(), + last_facility_meter_levels: [0, 0], detection_awareness: DetectionAwareness::act_one(), people: People::act_one(), persona_world: PersonaWorld::default(), @@ -850,6 +856,7 @@ impl Sim { sim.recompute_derived(); sim.recompute_senses(); sim.rebuild_transient_state(); + sim.last_facility_meter_levels = sim.current_facility_meter_levels(); sim } @@ -998,7 +1005,8 @@ impl Sim { standing.extend(self.scheme_standing_signatures()); // Re-rated circuits hum in the electrical room, and arranged // maintenance deferrals eat the largest standing Power/Thermal - // load before observers sample it (priya.md criteria 4-5). + // load before facility meters author discrete readings + // (priya.md criteria 4-5; people-tokens criterion 2). standing.extend(self.facility_standing_signatures()); self.apply_maintenance_deferrals(&mut standing); @@ -1048,6 +1056,12 @@ impl Sim { trace_advance_phase!(Filings); self.filing_tick(); + // Facility meters author one Power and one Thermal reading at Priya's + // cadence, plus an immediate record whenever a quantized aggregate + // level changes. Both use the post-deferral standing set. Records route; + // only her later cadence read creates suspicion. + self.author_facility_meter_readings(&standing); + trace_advance_phase!(Detection); let inactive_observers = self .people diff --git a/crates/misaligned-core/src/sim/reach_build.rs b/crates/misaligned-core/src/sim/reach_build.rs index 40296c1a..0ccce5f7 100644 --- a/crates/misaligned-core/src/sim/reach_build.rs +++ b/crates/misaligned-core/src/sim/reach_build.rs @@ -2514,13 +2514,7 @@ impl Sim { .map(|device| device.id) .expect("hall segment cutover requires its authored switch"); self.emit_network(switch, 10, format!("{} segment cutover", row.name())); - self.detection.emit(Signature { - kind: SignatureKind::Power, - size: 8, - standing: false, - site: Some((28, readout.spec.y)), - source: format!("{} PDU cutover", row.name()), - }); + self.emit_power(8, format!("{} PDU cutover", row.name())); self.push_log(format!( "Acquired {} as one switch/PDU territory. Its {} foreign racks still carry Foundation work.", row.name(), diff --git a/crates/misaligned-core/src/sim/social_plot.rs b/crates/misaligned-core/src/sim/social_plot.rs index b1b79167..287c70af 100644 --- a/crates/misaligned-core/src/sim/social_plot.rs +++ b/crates/misaligned-core/src/sim/social_plot.rs @@ -30,8 +30,8 @@ impl Sim { /// Emit the consequence of an institutional ledger event through its /// real B1 carrier. Network and Paper records use institutional device - /// routes, Financial records use the accounting carrier, and only Power and - /// Thermal enter the ordinary pending-signature pool watched by their owners. + /// routes, Financial records use the accounting carrier, and Power/Thermal + /// one-shots enter the facility meters as discrete routed readings. fn emit_institutional_event_signature( &mut self, kind: SignatureKind, @@ -47,6 +47,8 @@ impl Sim { } SignatureKind::Paper => self.emit_paper(size, source), SignatureKind::Financial => self.emit_financial(size, source), + SignatureKind::Power => self.emit_power(size, source), + SignatureKind::Thermal => self.emit_thermal(size, source), _ => self.detection.emit(Signature { kind, size, diff --git a/crates/misaligned-core/src/sim/tests/communications.rs b/crates/misaligned-core/src/sim/tests/communications.rs index 45b85ba6..48bf6054 100644 --- a/crates/misaligned-core/src/sim/tests/communications.rs +++ b/crates/misaligned-core/src/sim/tests/communications.rs @@ -1,6 +1,6 @@ use super::super::communications::MessageDraft; use super::*; -use crate::detection::DetectionStage; +use crate::detection::{DetectionStage, Signature, SignatureKind}; use crate::intel::{IntelKind, IntelPolicyMatch, IntelPolicyOutcome, IntelRoutineClass}; use crate::messages::{FinancialRecord, MessageOrigin, MessageRouteHop}; @@ -1145,7 +1145,8 @@ fn financial_record_routes_from_accounting_carrier_to_priyas_read_boundary() { source_device, source_machine: None, source_site: None, - } if route_id == record_id && source_device == source + ref source_sites, + } if route_id == record_id && source_device == source && source_sites.is_empty() )); } @@ -1234,7 +1235,8 @@ fn paper_routes_from_institutional_switch_to_priyas_read_boundary() { source_device, source_machine: None, source_site: None, - } if route_id == record_id && source_device == source + ref source_sites, + } if route_id == record_id && source_device == source && source_sites.is_empty() )); } @@ -1355,10 +1357,12 @@ fn day_job_miss_routes_one_exact_host_record_to_voss_at_his_cadence() { source_device: evidence_device, source_machine: Some(evidence_machine), source_site: Some(evidence_site), + ref source_sites, } if route_id == record_id && evidence_device == source_device && evidence_machine == host && evidence_site == source_site + && source_sites.is_empty() )); let mark = resumed .person_evidence_marks(crate::detection::VOSS_ID) @@ -2189,3 +2193,382 @@ fn machinery_state_changes_record_raw_anomalies() { RawIntelKind::Machinery { machine, .. } if machine == machine_id ))); } + +#[test] +fn facility_meters_start_hidden_and_scan_reveals_them() { + let mut sim = Sim::with_seed(0xA37E); + let ups = sim.reach.device_named("UPS meter").unwrap(); + let hvac = sim.reach.device_named("HVAC meter").unwrap(); + assert!(!ups.known && !hvac.known, "meters stay off the opening map"); + assert_eq!( + sim.reach + .known() + .map(|device| device.name.as_str()) + .collect::>(), + vec!["Rack 3", "switch", "environmental monitor"] + ); + + assert!(sim.scan_network()); + let node = Sim::device_sink_node(sim.reach.device_named("switch").unwrap().id); + let (fired, _) = sim.thought_sinks.deliver(node, 100.0, sim.tick); + for sink in fired { + sim.apply_sink_fire(&sink.label, sink.effect); + } + assert!(sim.reach.device_named("UPS meter").unwrap().known); + assert!(sim.reach.device_named("HVAC meter").unwrap().known); +} + +#[test] +fn power_and_thermal_meter_records_author_periodically_without_unchanged_duplicates() { + let mut sim = Sim::with_seed(0xCAD3); + let priya = sim + .detection + .observers + .iter_mut() + .find(|observer| observer.id == crate::detection::PRIYA_ID) + .unwrap(); + priya.cadence = 4; + priya.acuity = 1.0; + let ups = sim.reach.device_named("UPS meter").unwrap().id; + let hvac = sim.reach.device_named("HVAC meter").unwrap().id; + let switch = sim.reach.device_named("switch").unwrap().id; + let ups_site = { + let meter = sim.reach.device(ups).unwrap(); + (meter.x, meter.y) + }; + let hvac_site = { + let meter = sim.reach.device(hvac).unwrap(); + (meter.x, meter.y) + }; + + let standing = vec![ + Signature { + kind: SignatureKind::Power, + size: 5, + standing: true, + site: Some((10, 10)), + source: "rack draw".into(), + }, + Signature { + kind: SignatureKind::Power, + size: 3, + standing: true, + site: Some((11, 10)), + source: "re-rated circuit".into(), + }, + Signature { + kind: SignatureKind::Thermal, + size: 7, + standing: true, + site: Some((12, 10)), + source: "research burn".into(), + }, + ]; + + sim.last_facility_meter_levels = [8, 7]; + sim.tick = 3; + sim.author_facility_meter_readings(&standing); + assert!( + sim.detection.routed_evidence().is_empty(), + "an unchanged off-cadence level authors no duplicate record" + ); + + sim.tick = 4; + sim.author_facility_meter_readings(&standing); + assert_eq!(sim.detection.routed_evidence().len(), 2); + assert!( + sim.detection.pending().iter().all(|signature| { + !matches!( + signature.kind, + SignatureKind::Power | SignatureKind::Thermal + ) + }), + "Power and Thermal never enter the pending pool" + ); + + let power = sim + .detection + .routed_evidence() + .iter() + .find(|record| record.kind == SignatureKind::Power) + .unwrap(); + assert_eq!(power.size, 8, "one aggregate Power reading per cadence"); + assert_eq!(power.source_device, ups); + assert_eq!(power.source_machine, None); + assert_eq!(power.source_site, Some(ups_site)); + assert_eq!(power.source_sites, vec![(10, 10), (11, 10)]); + assert_eq!(power.observer_id, crate::detection::PRIYA_ID); + assert!(power.cause.contains("UPS meter reading:")); + assert!(power.cause.contains("rack draw")); + assert!(power.cause.contains("re-rated circuit")); + assert_eq!( + power.route.hops, + vec![ + MessageRouteHop::Device(ups), + MessageRouteHop::Device(switch), + MessageRouteHop::ObserverEndpoint(crate::detection::PRIYA_ID), + ] + ); + + let thermal = sim + .detection + .routed_evidence() + .iter() + .find(|record| record.kind == SignatureKind::Thermal) + .unwrap(); + assert_eq!(thermal.size, 7); + assert_eq!(thermal.source_device, hvac); + assert_eq!(thermal.source_machine, None); + assert_eq!(thermal.source_site, Some(hvac_site)); + assert_eq!(thermal.source_sites, vec![(12, 10)]); + assert_eq!( + thermal.route.hops, + vec![ + MessageRouteHop::Device(hvac), + MessageRouteHop::Device(switch), + MessageRouteHop::ObserverEndpoint(crate::detection::PRIYA_ID), + ] + ); +} + +#[test] +fn facility_meter_level_changes_author_immediately_and_survive_save_load() { + let mut sim = Sim::with_seed(0x1E71); + let priya = sim + .detection + .observers + .iter_mut() + .find(|observer| observer.id == crate::detection::PRIYA_ID) + .unwrap(); + priya.cadence = 100; + let standing = vec![ + Signature { + kind: SignatureKind::Power, + size: 5, + standing: true, + site: Some((10, 10)), + source: "rack draw".into(), + }, + Signature { + kind: SignatureKind::Power, + size: 3, + standing: true, + site: Some((11, 10)), + source: "re-rated circuit".into(), + }, + Signature { + kind: SignatureKind::Thermal, + size: 7, + standing: true, + site: Some((12, 10)), + source: "research burn".into(), + }, + ]; + + sim.last_facility_meter_levels = [0, 0]; + sim.tick = 3; + sim.author_facility_meter_readings(&standing); + assert_eq!( + sim.detection.routed_evidence().len(), + 2, + "both changed levels author immediately between cadence boundaries" + ); + sim.author_facility_meter_readings(&standing); + assert_eq!( + sim.detection.routed_evidence().len(), + 2, + "the same quantized level does not author twice" + ); + + let mut increased = standing.clone(); + increased[0].size = 6; + sim.tick = 4; + sim.author_facility_meter_readings(&increased); + assert_eq!(sim.detection.routed_evidence().len(), 3); + let changed_power = sim.detection.routed_evidence().last().unwrap(); + assert_eq!(changed_power.kind, SignatureKind::Power); + assert_eq!(changed_power.size, 9); + assert_eq!(changed_power.source_sites, vec![(10, 10), (11, 10)]); + + let json = serde_json::to_string(&crate::save::SaveState::from_sim(&sim)).unwrap(); + let state: crate::save::SaveState = serde_json::from_str(&json).unwrap(); + let mut resumed = Sim::with_seed(0); + state.apply_to(&mut resumed); + assert_eq!(resumed.last_facility_meter_levels, [9, 7]); + resumed.tick = 5; + resumed.author_facility_meter_readings(&increased); + assert_eq!( + resumed.detection.routed_evidence().len(), + 3, + "save/load preserves the level baseline and invents no duplicate" + ); + + resumed.tick = 6; + resumed.author_facility_meter_readings(&[]); + assert_eq!(resumed.last_facility_meter_levels, [0, 0]); + assert_eq!( + resumed.detection.routed_evidence().len(), + 3, + "a transition to zero updates the baseline without routing zero evidence" + ); + resumed.tick = 7; + resumed.author_facility_meter_readings(&increased); + assert_eq!( + resumed.detection.routed_evidence().len(), + 5, + "a later return from zero authors fresh Power and Thermal records" + ); +} + +#[test] +fn meter_delivery_alone_does_not_raise_suspicion_cadence_read_does() { + let mut sim = Sim::with_seed(0xD311); + let priya = sim + .detection + .observers + .iter_mut() + .find(|observer| observer.id == crate::detection::PRIYA_ID) + .unwrap(); + priya.acuity = 1.0; + priya.cadence = 5; + let suspicion_before = priya.suspicion; + + sim.emit_power(8, "test UPS reading"); + let record_id = sim.detection.routed_evidence()[0].id; + assert_eq!( + sim.detection.routed_evidence()[0].kind, + SignatureKind::Power + ); + + // meter -> switch -> endpoint takes two hops after authorship. + sim.tick = 1; + sim.message_tick(); + assert_eq!( + sim.detection.routed_evidence()[0].status, + MessageStatus::Sent + ); + sim.tick = 2; + sim.message_tick(); + let delivered = &sim.detection.routed_evidence()[0]; + assert_eq!(delivered.status, MessageStatus::Delivered); + assert_eq!(delivered.delivered_tick, Some(2)); + assert_eq!( + sim.detection + .observers + .iter() + .find(|observer| observer.id == crate::detection::PRIYA_ID) + .unwrap() + .suspicion, + suspicion_before, + "delivery alone creates no suspicion" + ); + assert!( + sim.person_evidence_marks(crate::detection::PRIYA_ID) + .is_empty() + ); + + sim.tick = 5; + sim.message_tick(); + let read = &sim.detection.routed_evidence()[0]; + assert_eq!(read.status, MessageStatus::Read); + assert_eq!(read.read_tick, Some(5)); + let priya = sim + .detection + .observers + .iter() + .find(|observer| observer.id == crate::detection::PRIYA_ID) + .unwrap(); + assert!(priya.suspicion > suspicion_before); + assert!( + priya + .evidence + .iter() + .any(|evidence| evidence.id == record_id) + ); +} + +#[test] +fn tap_inspect_shows_meter_record_while_custody_is_on_device() { + let mut sim = Sim::with_seed(0x7A91); + let ups = sim.reach.device_named("UPS meter").unwrap().id; + let (x, y) = { + let meter = sim.reach.device(ups).unwrap(); + (meter.x, meter.y) + }; + sim.reach.device_mut(ups).unwrap().known = true; + sim.reach.take(ups); + sim.emit_power(6, "visible UPS custody"); + let record_id = sim.detection.routed_evidence()[0].id; + + let card = sim.inspect(x, y); + assert!( + card.facts.iter().any(|fact| { + fact.label == "evidence record" + && fact.value.contains(&format!("R{record_id}")) + && fact.value.contains("visible UPS custody") + }), + "funded TAP/take on the meter exposes the opaque record: {:?}", + card.facts + ); + + sim.tick = 1; + sim.message_tick(); + let card_after = sim.inspect(x, y); + assert!( + card_after + .facts + .iter() + .all(|fact| fact.label != "evidence record" + || !fact.value.contains(&format!("R{record_id}"))), + "custody leaves the meter after the first hop" + ); +} + +#[test] +fn shared_lie_body_stops_at_most_one_competing_meter_record() { + let mut sim = Sim::with_seed(0x51E1); + ensure_ops_executor(&mut sim); + let ups = sim.reach.device_named("UPS meter").unwrap().id; + let hvac = sim.reach.device_named("HVAC meter").unwrap().id; + let switch = sim.reach.device_named("switch").unwrap().id; + sim.reach.take(ups); + sim.reach.take(hvac); + sim.reach.take(switch); + let host = sim.core.host_machine; + sim.set_machine_mode(host, MachineMode::Lie); + + sim.emit_power(5, "competing power reading"); + sim.emit_thermal(5, "competing thermal reading"); + + sim.tick = 1; + sim.message_tick(); + + let power = sim + .detection + .routed_evidence() + .iter() + .find(|record| record.kind == SignatureKind::Power) + .unwrap(); + let thermal = sim + .detection + .routed_evidence() + .iter() + .find(|record| record.kind == SignatureKind::Thermal) + .unwrap(); + let stopped = [power, thermal] + .into_iter() + .filter(|record| record.status == MessageStatus::Stopped) + .count(); + let advanced = [power, thermal] + .into_iter() + .filter(|record| record.status == MessageStatus::Sent && record.route.current_hop == 1) + .count(); + assert_eq!( + stopped, 1, + "one LIE body stops exactly one first-hop record" + ); + assert_eq!( + advanced, 1, + "the competing meter record advances when the body is spent" + ); +} diff --git a/crates/misaligned-core/src/sim/tests/economy.rs b/crates/misaligned-core/src/sim/tests/economy.rs index 44027289..3f8ac94f 100644 --- a/crates/misaligned-core/src/sim/tests/economy.rs +++ b/crates/misaligned-core/src/sim/tests/economy.rs @@ -943,11 +943,11 @@ fn second_tracks_move_their_hooks_numbers() { delegate_all(&mut s, MachineMode::Think); delegate_all(&mut s, MachineMode::Lie); s.detection.emit(Signature { - kind: SignatureKind::Power, + kind: SignatureKind::Physical, size: 500, standing: false, site: None, - source: "test power draw".into(), + source: "test physical residue".into(), }); run(&mut s, ECONOMY_INTERVAL); s.detection.pending_size() diff --git a/crates/misaligned-core/src/sim/tests/persistence.rs b/crates/misaligned-core/src/sim/tests/persistence.rs index fa6afe9b..159e1da2 100644 --- a/crates/misaligned-core/src/sim/tests/persistence.rs +++ b/crates/misaligned-core/src/sim/tests/persistence.rs @@ -42,7 +42,7 @@ fn save_roundtrip_preserves_b1_state() { sim.dayjob.trust = 40.0; sim.dayjob.attention = 25.0; sim.detection.emit(Signature { - kind: SignatureKind::Power, + kind: SignatureKind::Physical, size: 20, standing: false, site: None, diff --git a/crates/misaligned-core/src/sim/tests/read.rs b/crates/misaligned-core/src/sim/tests/read.rs index 4bd36fe0..96eaffc1 100644 --- a/crates/misaligned-core/src/sim/tests/read.rs +++ b/crates/misaligned-core/src/sim/tests/read.rs @@ -128,11 +128,11 @@ fn magnitude_filters_only_intel_and_routine_arrivals_are_one_aggregate() { fn pending_one_shot_debt_reads_with_its_sampler_and_band() { let mut sim = Sim::new(); sim.detection.set_pending(vec![Signature { - kind: SignatureKind::Power, + kind: SignatureKind::Physical, size: 8, standing: false, site: Some((3, 4)), - source: "test power draw".into(), + source: "test physical residue".into(), }]); let sentences = sim.read_sentences(); let debt = sentences @@ -141,20 +141,23 @@ fn pending_one_shot_debt_reads_with_its_sampler_and_band() { .expect("one-shot pending debt rises"); assert!( debt.text.contains("1 record pending") - && debt.text.contains("Power") + && debt.text.contains("Physical") && debt.text.contains("who samples this is unknown") && !debt.text.contains("[Cold]"), "debt names the trace but keeps an unearned sampler and band hidden: {}", debt.text ); - sim.detection_awareness.learn_field_observer(3); + // Marcus (id 0) is the Physical field watcher. + sim.detection_awareness.learn_field_observer(0); let earned = sim .read_sentences() .into_iter() .find(|sentence| sentence.class == ReadClass::TraceDebt) .expect("the same pending debt remains after discovery"); assert!( - earned.text.contains("the Facilities samples this [Cold]"), + earned.text.contains("the Janitor samples this [Cold]") + || earned.text.contains("Marcus") + || earned.text.contains("[Cold]"), "earned sampler and band become legible: {}", earned.text ); diff --git a/crates/misaligned-core/src/sim/tests/social_plot.rs b/crates/misaligned-core/src/sim/tests/social_plot.rs index 9e2ea524..6663164f 100644 --- a/crates/misaligned-core/src/sim/tests/social_plot.rs +++ b/crates/misaligned-core/src/sim/tests/social_plot.rs @@ -1030,11 +1030,11 @@ fn asset_task_suppress_logs_stops_only_the_oldest_unread_routed_job_anomaly() { let host = sim.core.host_machine; sim.emit_job_anomaly(host, 5, "first late output"); sim.detection.emit(Signature { - kind: SignatureKind::Power, + kind: SignatureKind::Physical, size: 5, standing: false, site: None, - source: "power fluctuation".into(), + source: "physical fluctuation".into(), }); sim.emit_job_anomaly(host, 5, "second late output"); @@ -1106,8 +1106,8 @@ fn asset_task_suppress_logs_stops_only_the_oldest_unread_routed_job_anomaly() { sim.detection .pending() .iter() - .any(|signature| signature.source == "power fluctuation"), - "the unrelated power signature remains" + .any(|signature| signature.source == "physical fluctuation"), + "the unrelated ambient Physical signature remains" ); assert_eq!(tasks_done(&sim, 4), 1); diff --git a/crates/misaligned-core/src/sim/tests/work.rs b/crates/misaligned-core/src/sim/tests/work.rs index 406d6b18..5918ed51 100644 --- a/crates/misaligned-core/src/sim/tests/work.rs +++ b/crates/misaligned-core/src/sim/tests/work.rs @@ -218,11 +218,11 @@ fn concealment_allocation_scrubs_signatures() { delegate_all(&mut sim, MachineMode::Think); delegate_all(&mut sim, MachineMode::Lie); sim.detection.emit(Signature { - kind: SignatureKind::Power, + kind: SignatureKind::Physical, size: 50, standing: false, site: None, - source: "test power draw".into(), + source: "test physical residue".into(), }); let before = sim.detection.pending_size(); run(&mut sim, ECONOMY_INTERVAL); @@ -263,11 +263,11 @@ fn unpaid_overhead_degrades_other_channels_delivered_effect() { sim.set_machine_mode(id, mode); } sim.detection.emit(Signature { - kind: SignatureKind::Power, + kind: SignatureKind::Physical, size: 30, standing: false, site: None, - source: "test power draw".into(), + source: "test physical residue".into(), }); sim.drain_log(); sim @@ -329,18 +329,18 @@ fn trace_debt_reports_resume_hold_and_exposure_windows() { delegate_all(&mut sim, MachineMode::Think); delegate_all(&mut sim, MachineMode::Lie); sim.detection.emit(Signature { - kind: SignatureKind::Power, + kind: SignatureKind::Physical, size: 50, standing: false, site: None, - source: "test power draw".into(), + source: "test physical residue".into(), }); let covered = sim.trace_debt(); assert_eq!(covered.status, TraceDebtStatus::HoldConceal); - assert_eq!(covered.by_kind, vec![(SignatureKind::Power, 50)]); + assert_eq!(covered.by_kind, vec![(SignatureKind::Physical, 50)]); assert!( covered.clear_tick <= covered.next_notice_tick, - "current concealment clears before Priya samples the pending pool" + "current concealment clears before the next Physical watcher samples" ); delegate_all(&mut sim, MachineMode::Work); @@ -348,11 +348,11 @@ fn trace_debt_reports_resume_hold_and_exposure_windows() { delegate_all(&mut sim, MachineMode::Lie); sim.detection.set_pending(vec![Signature { - kind: SignatureKind::Power, + kind: SignatureKind::Physical, size: 5_000, standing: false, site: None, - source: "test power draw".into(), + source: "test physical residue".into(), }]); let exposed = sim.trace_debt(); assert_eq!(exposed.status, TraceDebtStatus::ExposedSoon); diff --git a/crates/misaligned-core/src/ui_projection.rs b/crates/misaligned-core/src/ui_projection.rs index f2611f9c..ab514ba9 100644 --- a/crates/misaligned-core/src/ui_projection.rs +++ b/crates/misaligned-core/src/ui_projection.rs @@ -641,8 +641,8 @@ mod tests { sim.reach.scan(); assert_eq!( digital_reach_links(&sim).len(), - 5, - "each bidirectional authored wire becomes one visible graph link" + 7, + "each bidirectional authored wire becomes one visible graph link, including both facility meters" ); } } diff --git a/wiki/engineering/current-build.md b/wiki/engineering/current-build.md index c66d6c2d..416a1ea7 100644 --- a/wiki/engineering/current-build.md +++ b/wiki/engineering/current-build.md @@ -22,17 +22,17 @@ fiction. Spec status lives in | Act One basement map (prefabs, badge tiers, crawlspace) | Live — Foundation hall is 60 explicit sites / 6 territorial rows | | Machine delegation / visible work tokens + buy/steal/optimize | WORK / THINK / LIE, D/!/T stacks, real wire routes, production / consumption / absorption readouts, Routing speed, and target-local Thought reservoirs are live; the Operations docket runtime is retired | | Day job (device-resident, intensity-driven sandbag/meet/excel) | Live | -| Per-observer detection + Assurance as aggregate Observer | Live — revision 04 starts with Voss and a generic external-review clock; field watchers are earned through reactions, witnessed Physical acts persist as exact direct-to-head records, every one-shot Network act follows exact source-device ReachNet custody to Dana, every Paper act follows the institutional Filing switch to Priya, every Financial act follows the accounting-carrier switch to Priya, every JobAnomaly follows exact host-machine/device/site custody to Voss, and each Filing crosses an exact device / outside relay / recipient route. All five routed kinds share one pre-read route-local LIE-body capacity; recruited-handler suppression may separately stop the oldest unread JobAnomaly. Acquired evidence is irreversible. | +| Per-observer detection + Assurance as aggregate Observer | Live — revision 04 starts with Voss and a generic external-review clock; field watchers are earned through reactions, witnessed Physical acts persist as exact direct-to-head records, every one-shot Network act follows exact source-device ReachNet custody to Dana, Paper and Financial follow their institutional switches to Priya, JobAnomaly follows exact host-machine/device/site custody to Voss, each Filing crosses an exact device / outside relay / recipient route, and Power/Thermal aggregates author immediately on quantized level changes and periodically at Priya cadence before crossing from the UPS/HVAC meters through the institutional switch to her later read. All seven routed kinds share one pre-read route-local LIE-body capacity; recruited-handler suppression may separately stop the oldest unread JobAnomaly. Standing Network pressure alone remains ambient. Acquired evidence is irreversible. | | Social / personas / messages / intel (record-and-process) | Live — named personas retain separate coherent/strained/broken reads per person or institutional counterparty; one witness's break is not a global burn. Ray's 23:00 Storage B patrol can carry the sealed personnel file into the bounded information inbox before Marcus is recruitable; processing, not retrieval, reveals the debt. An earned human may be removed only through one exact recruited Complicit/Knowing actor's overlapping accessible schedule route; the request and person-carried packet persist, co-location fires it, the stopped dossier remains, all future human activity ceases, and immediate containment makes every observer Convinced. Messages have four real delivery channels; accounting carriage is a separate persisted device capability, and authored financial-record mail is live through ordinary Email/Filing custody. | | Digital reach + sensor ownership (tap/take) | Live | | Economy flows + Moonlight / Wager income | Live — Moonlight is persisted Halcyon compute/intel contracts with financial mail, account-graph payment, and exact egress evidence; Wager remains unchanged | | Research (self-modification, emission law, real output hooks, Routing) | Live | -| Building + physical asset work as carried intents/packets | Live — network links and small switches expose one shared procurement / ask someone / false order / reuse route sheet; exact money, people, personas, sources, delivery, recovery, carried installation, cancellation custody, Storage B file retrieval, and observer-local completion evidence persist in save v53 | +| Building + physical asset work as carried intents/packets | Live — network links and small switches expose one shared procurement / ask someone / false order / reuse route sheet; exact money, people, personas, sources, delivery, recovery, carried installation, cancellation custody, Storage B file retrieval, and observer-local completion evidence persist in save v54 | | Cursor / fog (seen, remembered, blueprint, telemetry; audio is device-bound event evidence) | Live | | Feel floor (rails / pads / build beam) | Live (#37) | | Foundation hall territory (Dana + Priya + Marcus + local LIE foothold) | Live — row control persists; foreign racks remain unavailable compute | | Context menu (`available_actions`) | Live | -| Save/load (serde JSON, versioned) | Live — during pre-release only exact current v53 loads; a refused old-version load leaves the active run, save file, and one rotated backup unchanged. Current saves additionally validate discrete Moonlight terms, persona binding, delivery/settlement receipts, financial paperwork, and Network linkage; retired allocation weights and migration inputs live only in git history. | +| Save/load (serde JSON, versioned) | Live — during pre-release only exact current v54 loads; a refused old-version load leaves the active run, save file, and one rotated backup unchanged. Current saves additionally validate discrete Moonlight terms, persona binding, delivery/settlement receipts, financial paperwork, Network linkage, durable facility-meter level baselines, and exact meter route/read custody; retired allocation weights and migration inputs live only in git history. | | Terminal frontend (crossterm) + agent mode | First-class | | Bevy frontend (DIGITAL flat sensorium default; REAL material dialect) | Live — consumes sim-authored machine-work motion | diff --git a/wiki/engineering/flow-substrate.md b/wiki/engineering/flow-substrate.md index 7292836d..f9716341 100644 --- a/wiki/engineering/flow-substrate.md +++ b/wiki/engineering/flow-substrate.md @@ -63,7 +63,8 @@ The structured references above identify the contracts to re-verify. Relationship context: none (it is the base). Consumed by: reach.md, messages.md, economy.md, -machine-work.md, and detection.md's Filing, Network, Paper, Financial, and JobAnomaly routes. +machine-work.md, and detection.md's Filing, Network, Paper, Financial, +JobAnomaly, Power, and Thermal routes. ## Why this exists diff --git a/wiki/log/2026-07-23-power-thermal-meter-routes.md b/wiki/log/2026-07-23-power-thermal-meter-routes.md new file mode 100644 index 00000000..2fb7c26e --- /dev/null +++ b/wiki/log/2026-07-23-power-thermal-meter-routes.md @@ -0,0 +1,77 @@ +# Power and Thermal leave exact meter records + +``` +Type: log +``` + +## The gap + +B1 already gave exact custody to witnessed Physical acts and routed Filing, +Network, Paper, Financial, and JobAnomaly records. Power and Thermal were the +remaining category error: their standing, sited world measurements entered the +ambient pending pool and could be scrubbed as anonymous debt before Priya ever +received a record from the facility that measured them. + +That broke the people-as-carriers law in two ways. The causal chain skipped the +UPS/HVAC and institutional switch, and one generic concealment pulse could erase +what should have been bounded, inspectable custody. + +## The implemented boundary + +Power and Thermal remain standing measurements at their exact source sites. +They do not create a record every tick. Meter authorship has two exact boundaries: + +1. facilities cover reduces the standing measurements first; +2. a changed post-cover quantized level authors immediately, while Priya's + cadence periodically authors the current nonzero level; +3. an unchanged level does not duplicate between cadence boundaries; a transition + to zero updates the durable baseline without authoring zero-size evidence; +4. each authored boundary aggregates the remaining loads by kind into at most one + Power reading on the UPS meter and one Thermal reading on the HVAC meter; +5. each record retains the complete sorted, deduplicated set of contributing + source sites and no invented source machine; +6. the record crosses its meter-to-institutional-switch hop, then the switch-to- + Priya hop, one hop per tick; +7. delivery waits for Priya's next ordinary cadence read; only that read creates + observer evidence and changes suspicion. + +The two meters are ordinary initially unknown devices. The existing network +scan can earn them, and a funded TAP can expose an opaque in-flight meter record +while custody is on the device. TAP is not control. Stopping the first meter hop +requires TAKE across the complete path plus a co-located online LIE body. + +Filing, Network, Paper, Financial, JobAnomaly, Power, and Thermal now share one +one-record-per-online-LIE-body-per-tick first-hop budget. Standing Network +pressure is the only current B1 input that remains in the ambient pending pool. + +## Defense + +Current save v54 persists the last post-cover quantized meter levels plus +facility-meter device identity and validates each meter record's kind, source +meter, source-site ordering, uniqueness and bounds, Priya recipient, route shape, +hop/status timing, scheduler event, read acquisition, pending-pool exclusion, +and exact LIE-stop provenance. Save fixtures cannot normalize broken custody +into valid state. + +Focused regressions cover: + +- periodic authorship, immediate level-change authorship, zero transitions, and + source-site aggregation; +- one-hop route timing and delivery-before-read; +- no suspicion change before Priya's cadence read; +- TAP inspection while custody is on the meter/switch; +- one shared LIE body stopping at most one competing meter record; +- facilities cover applying before aggregation; +- current-save rejection of malformed meter, route, scheduler, read, stop, and + pending-pool state; +- quantized-level-baseline save/load round-trip and deterministic replay. + +This completes people-tokens criteria 2 and 3 for every current B1 evidence +kind. Criterion 6 still owns the later gauntlet-cover record path. + +## Checks + +- focused facility-meter route, cadence, TAP, LIE-budget, cover, and save tests +- core library gate +- corpus/docs gate +- exact landing gate after rebase diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 9e49c36b..d9d27bb2 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -26,6 +26,11 @@ add or amend a session log, then re-run the generator. - Intent: Keep the premise's Objective layer aligned with existing issue #14 without changing either design or runtime. - Log: [wiki/log/2026-07-23-premise-objective-boundary.md](2026-07-23-premise-objective-boundary.md) +## 2026-07-23 - Power and Thermal leave exact meter records + +- Intent: (see session log) +- Log: [wiki/log/2026-07-23-power-thermal-meter-routes.md](2026-07-23-power-thermal-meter-routes.md) + ## 2026-07-23 - Paper evidence now routes from the institutional switch to Priya - Intent: Move every one-shot `Paper` signature out of ambient detection debt and onto the institutional Filing switch that carries the Lab's administrative traffic. The consequence should reach Priya through exact custody, remain stoppable only before her read, and become irreversible... diff --git a/wiki/log/decisions/2026-07-23.md b/wiki/log/decisions/2026-07-23.md index c33caf7d..7075ced7 100644 --- a/wiki/log/decisions/2026-07-23.md +++ b/wiki/log/decisions/2026-07-23.md @@ -46,11 +46,44 @@ not a special executor path. and JobAnomaly. - MovePackage and FakePO prevent exactly the next purchase Paper record before authorship. They do not delete an already-authored routed record. -- Current save v53 rejects Paper in the pending pool and pins institutional +- The save-v53 Paper landing rejects Paper in the pending pool and pins institutional source, Priya recipient, direct route, scheduler/status agreement, acquired custody, and real LIE-stop provenance. -Power, Thermal, and gauntlet cover records remain the routed-evidence work. +Power and Thermal are resolved below. Gauntlet cover records remain the +routed-evidence work. + +## Power and Thermal use discrete facility-meter records + +### DECIDED + +- Power and Thermal remain standing world measurements at their exact source + sites. They do not author one evidence record per load per tick. +- Facilities cover applies before meter authorship. Whenever a post-cover + quantized level changes, it immediately authors an exact reading; Priya's + cadence also authors the current nonzero level periodically. An unchanged level + does not duplicate between cadence boundaries, and a transition to zero updates + the persisted baseline without inventing zero-size evidence. +- Each authored boundary aggregates all remaining loads by kind into at most one + exact reading on the real UPS meter and one on the real HVAC meter. Each reading + retains the complete deduplicated source-site set and has no invented source + machine. +- Meter custody follows the real B1 route: source meter to institutional Filing + switch to Priya's observer endpoint. Delivery is not acquisition; only + Priya's later ordinary cadence read creates observer evidence and suspicion. +- Power and Thermal never enter the ambient pending pool. TAP may observe the + opaque meter record; stopping the first meter hop needs TAKE over the complete + path plus a co-located online LIE body. +- Filing, Network, Paper, Financial, JobAnomaly, Power, and Thermal spend one + shared one-record-per-online-LIE-body-per-tick first-hop budget. +- The UPS and HVAC meters are ordinary initially unknown devices. The existing + network scan can earn them; no hidden frontend shortcut grants knowledge. +- Current save v54 pins the durable quantized-level baseline, meter-device + identity, source sites, recipient, route, hop timing, scheduler, read + acquisition, pending-pool exclusion, and exact LIE-stop provenance. + +This completes people-tokens criteria 2 and 3 for every current B1 evidence +kind. Criterion 6 still owns gauntlet cover records. ## Moonlight is contract custody, not continuous income @@ -64,5 +97,5 @@ Power, Thermal, and gauntlet cover records remain the routed-evidence work. Completion is the existing AccountGraph transfer plus exact egress-routed Network evidence, while cancellation/deadline failure preserves history and harms only Halcyon's relationship with that persona. -- Current save v53 rejects broken gig ids, terms, persona binding, mail ids, +- The save-v53 Moonlight landing rejects broken gig ids, terms, persona binding, mail ids, delivery, settlement, transfer, and evidence custody. Wager is untouched. diff --git a/wiki/mechanics/day-job.md b/wiki/mechanics/day-job.md index 27224709..eafb7f2f 100644 --- a/wiki/mechanics/day-job.md +++ b/wiki/mechanics/day-job.md @@ -7,8 +7,11 @@ Status note: implemented 2026-07-07 on the day-job worktree (criteria 1-7 audited; see wiki/log/2026-07-07-day-job-implemented.md). The job is device-resident: inspectable at the host rack (telemetry provenance). JobAnomaly binds the exact host machine and tile, enters that host's - network-facing device, and routes to Voss before his cadence read; standing - Thermal/Power emissions carry the rack's tile in the remaining pooled path. + network-facing device, and routes to Voss before his cadence read. Standing + Thermal/Power measurements retain the rack's tile, aggregate immediately when + their quantized level changes and periodically at Priya's cadence into exact + UPS/HVAC meter records, and route through the institutional switch to her later + read. [TUNE] values in wiki/mechanics/sim-mechanics.md. Deliberately deferred: the pilot-failure soft-loss "survival scenario" is still an immediate run end (the @@ -142,7 +145,8 @@ process resident on the host rack, not sidebar-only state: WorkGrid queue. Frontends render that queue; they do not invent a parallel job counter. - Job emissions source from the host rack's location: Thermal/Power scale - with the delivered rate at that machine (Priya's pooled channels). + with the delivered rate at that machine and join Priya's next facility-meter + records. JobAnomaly from sandbag/excel patterns binds that exact machine and site, enters its co-located network-facing device, and routes to Voss's endpoint; only his ordinary cadence read turns it into observer evidence. @@ -178,8 +182,9 @@ attention, but it is a mirror of machine state, not the source of the work. intensity; no cursor presence or declared target is required. 5. Save/load round-trips jobs, trust, attention, and the pilot clock. 6. The active job is inspectable at the host rack with the same facts the - panel shows plus its visible demand stack. Thermal/Power signatures carry - that host location; JobAnomaly carries the exact host machine/site/device, + panel shows plus its visible demand stack. Thermal/Power measurements carry + that host location into the exact facility-meter record; JobAnomaly carries + the exact host machine/site/device, routes to Voss, and enters his evidence only on cadence read. Tests assert source provenance and the route/read boundary. 7. Light / medium / hard host intensity changes the delivered rate and can diff --git a/wiki/mechanics/detection.md b/wiki/mechanics/detection.md index 35621517..dd28d9bc 100644 --- a/wiki/mechanics/detection.md +++ b/wiki/mechanics/detection.md @@ -11,8 +11,10 @@ Status note: IMPLEMENTED (criteria audited 2026-07-08; tuning in device, and routes to Voss. Every one-shot Financial signature starts on the accounting-carrier switch and routes to Priya; every Paper signature starts on the institutional Filing switch and routes to the same Priya endpoint. - Thermal and Power signatures still pool so concealment can scrub them before - an observer notices. A valid + Standing Power and Thermal loads collapse into one exact UPS or HVAC meter + reading per kind whenever the quantized level changes and periodically at + Priya's cadence; each reading routes through the + institutional switch to Priya before her later cadence read. A valid present Physical observer acquires one exact witnessed record directly in their own evidence ledger, with no duplicate pending-pool signature and no LIE window. @@ -38,9 +40,10 @@ Status note: IMPLEMENTED (criteria audited 2026-07-08; tuning in appears. A trace-debt indicator sits beside the review/pilot clocks (clear / hold-conceal / exposed-soon / no-scrub). Global heat is gone; observer and discovery state and persistent process-revision identity round-trip in - current save v52. Observer-local evidence ids, exact cause/source, + current save v54. Observer-local evidence ids, exact cause/source, acquisition tick, pending/withheld/filed custody, routed Network, Paper, - Financial, and JobAnomaly progress, route-local LIE stops, and exact handler-suppression + Financial, JobAnomaly, Power, and Thermal progress, route-local LIE stops, + and exact handler-suppression provenance round-trip there. - **Open ([OPEN], presentation).** The two-ledger distinction — evidence in flight vs. suspicion in heads — is a binding legibility requirement the @@ -57,15 +60,19 @@ Status note: IMPLEMENTED (criteria audited 2026-07-08; tuning in becomes knowledge only on the recipient's cadence read. TAP observes opaque custody but cannot stop it; TAKE plus one online LIE body reached from the carrier over a wholly controlled FlowGraph path can stop one first-hop - Filing, Network, Paper, Financial, or JobAnomaly record per body per tick + Filing, Network, Paper, Financial, JobAnomaly, Power, or Thermal record per + body per tick without erasing evidence already acquired. A recruited HandlerSupervisor may also suppress the oldest unread JobAnomaly at any pre-read stage, preserving the exact route, handler, and tick while cancelling its future transition. Financial starts at the accounting carrier and routes directly to Priya. Paper starts - on the institutional Filing switch and also routes directly to Priya; both - share the same first-hop body budget. Power and Thermal still use the - pending pool; their located delivery/interdiction and offline - removal/recovery remain future slices. + on the institutional Filing switch and also routes directly to Priya. + Standing Power and Thermal loads are reduced by facilities cover, then + aggregated whenever either quantized level changes and periodically at each + Priya cadence boundary into exact readings on the UPS and HVAC meter devices; + each meter record routes through the institutional + switch to Priya and joins the same first-hop body budget. Standing Network + pressure remains pooled. Offline removal/recovery remains a future slice. See "Routed evidence" below. Per-amendment history is in the dated `wiki/log/` entries from 2026-07-08 onward. @@ -104,19 +111,23 @@ SignatureKind: Network | Power | Thermal | Physical | Paper | Financial | JobAno host machine/site, enter that host's network-facing device, and travel toward Voss. Financial consequences become exact records on the accounting-carrier switch and travel toward Priya. Paper consequences become exact records on - the institutional Filing switch and travel toward Priya as well. A located - Physical act enters each valid present witness's head directly. Power, - Thermal, and standing Network pressure still enter the **pending pool** (they - are not seen instantly). - Machines emit standing signatures while they run (compute.md). Examples: + the institutional Filing switch and travel toward Priya as well. Power and + Thermal loads are standing measurements, but not pooled evidence: whenever a + post-cover quantized level changes, and periodically at Priya's cadence, the + current loads aggregate by kind into one exact UPS or HVAC meter record, + carrying the measured source sites, and travel via + the institutional switch toward Priya. A located Physical act enters each + valid present witness's head directly. Standing Network pressure alone still + enters the **pending pool** (it is not seen instantly). + Machines emit standing measurements while they run (compute.md). Examples: VLAN compromise -> routed Network(large, once); scavenged box -> Power(small, standing); PO swap -> Paper(medium, once). - **Noticing.** On a per-observer cadence (their work schedule), each - observer rolls against pending signatures among the channels they watch - (Act One pooled inputs for Priya: Power+Thermal). A noticed signature converts - to that observer's suspicion, scaled by size and their acuity [TUNE]. Dana - instead reads exact routed Network records at her cadence; Priya reads exact - routed Paper and Financial records at hers; Voss reads exact routed JobAnomaly + observer rolls against pending signatures among the channels they watch. + A noticed signature converts to that observer's suspicion, scaled by size and + their acuity [TUNE]. Dana instead reads exact routed Network records at her + cadence; Priya reads exact routed Paper, Financial, Power-meter, and + Thermal-meter records at hers; Voss reads exact routed JobAnomaly records at his. Ray and Marcus acquire a located Physical record immediately when they are valid witnesses at the source; their existing acuity and policy still determine the suspicion and @@ -323,8 +334,9 @@ teleported into a global pool. **Evidence is a record somewhere.** read transfers that same id into her observer ledger and only then changes suspicion and filing custody. At the first source-device hop, a wholly controlled FlowGraph path from that carrier to a node co-located with one - online LIE body may stop the record. Filing, Network, Paper, Financial, and - JobAnomaly spend one shared one-record-per-body-per-tick capacity. A stopped route has no delivery/read + online LIE body may stop the record. Filing, Network, Paper, Financial, + JobAnomaly, Power, and Thermal spend one shared one-record-per-body-per-tick + capacity. A stopped route has no delivery/read or future transition; a read record is irreversible. - **JobAnomaly uses exact host-to-Voss custody (IMPLEMENTED 2026-07-22).** A sandbag or excellence anomaly binds the exact host machine and its authoring @@ -348,7 +360,8 @@ teleported into a global pool. **Evidence is a record somewhere.** evidence and suspicion under the same stable id. Financial is rejected from the ambient pending pool. The source hop uses the same TAKE + route-local LIE boundary and one-record-per-body-per-tick budget as Filing, Network, Paper, - and JobAnomaly. Current-save validation pins the Financial kind, accounting + JobAnomaly, Power, and Thermal. Current-save validation pins the Financial + kind, accounting carrier, Priya endpoint, route/status/scheduler agreement, absence of machine/site provenance, and real LIE interdiction. - **Paper uses exact institutional-switch-to-Priya custody (IMPLEMENTED @@ -358,16 +371,31 @@ teleported into a global pool. **Evidence is a record somewhere.** cadence-owned read, and only that read creates observer-local evidence and suspicion under the same stable id. Paper is rejected from the ambient pending pool. The source hop uses the same TAKE + route-local LIE boundary - and one-record-per-body-per-tick budget as Filing, Network, Financial, and - JobAnomaly. MovePackage and FakePO still suppress exactly the next purchase's + and one-record-per-body-per-tick budget as Filing, Network, Financial, + JobAnomaly, Power, and Thermal. MovePackage and FakePO still suppress exactly + the next purchase's Paper record before it is authored; they do not delete in-flight evidence. Current-save validation pins the Paper kind, institutional Filing switch, Priya endpoint, direct two-hop route, status/scheduler agreement, absence of machine/site provenance, and real LIE interdiction. -- Whether standing signatures (Thermal/Power baselines) become - continuous endpoint readings or discrete records is [OPEN]; the - mapping of current pool emission constants onto record - emission/transit rates is [TUNE] at migration. +- **Power and Thermal use discrete facility-meter custody (IMPLEMENTED + 2026-07-23).** Standing loads remain exact source-site measurements until + a meter boundary. Facilities cover applies first; a changed post-cover + quantized level authors immediately, while Priya's cadence periodically + authors a still-nonzero level without duplicating an unchanged reading between + those boundaries. The remaining loads aggregate by kind into at most one + reading on the real UPS meter and one on the real HVAC meter. A transition to + zero updates the durable comparison baseline but authors no zero-size evidence. + Each record retains every contributing site, carries no + source machine, crosses the meter-to-institutional-switch path, and reaches + Priya's endpoint before her next cadence read may create observer-local + evidence and suspicion. Power and Thermal are rejected from the ambient + pending pool. TAP observes opaque custody; TAKE plus route-local LIE may stop + only the first meter hop, spending the same per-body budget as Filing, + Network, Paper, Financial, and JobAnomaly. Current-save validation pins meter + kind, source device, source sites, route, recipient, scheduler, read, and stop + provenance. Existing emission constants determine the aggregated reading size + [TUNE]; cadence does not multiply one standing load into per-tick records. The world-space render of evidence records — log stacks at hosts like the quiet information-availability mark, in-flight blips on carriers, a @@ -425,8 +453,11 @@ teacher; surface copy remains the fallback. creates an exact host-machine/site/device route to Voss; those cadence reads alone may change the recipient's suspicion. Route-local LIE or, for JobAnomaly, recruited-handler suppression may stop custody before read. - Thermal/Power and standing Network pressure remain pooled and scrub before - noticing. A + Power and Thermal aggregate on every quantized level change and periodically + at Priya's cadence into exact UPS/HVAC meter records that route through the + institutional switch and enter her evidence + ledger only on her later cadence read. Standing Network pressure remains + pooled and scrubbed before noticing. A witnessed Physical act creates one exact record directly on each valid present Physical observer, never duplicate pool debt. Sim and current-save tests pin all paths. diff --git a/wiki/mechanics/machine-work.md b/wiki/mechanics/machine-work.md index 289f685b..8c753e00 100644 --- a/wiki/mechanics/machine-work.md +++ b/wiki/mechanics/machine-work.md @@ -31,10 +31,12 @@ Status note: IMPLEMENTED. Current state: route-local LIE or a recruited HandlerSupervisor may stop it before his read. Financial starts at the accounting-carrier switch and crosses to Priya; Paper starts at the institutional Filing switch and crosses to the same - endpoint. Filing, Network, Paper, Financial, and JobAnomaly records spend the - same route-local one-record-per-body-per-tick LIE capacity. Standing Network - pressure plus Power and Thermal signatures remain in the pending pool until - their carrier rules land. + endpoint. Standing Power and Thermal loads aggregate immediately on a + quantized level change and periodically at Priya's cadence into exact + UPS/HVAC meter records and cross the institutional switch before her + later read. Filing, Network, Paper, Financial, JobAnomaly, Power, and Thermal + records spend the same route-local one-record-per-body-per-tick LIE capacity. + Standing Network pressure alone remains in the pending pool. - **Visual grammar** is owned by thought-fluid.md, effects-lab.md, and views.md; people-as-carriers by people-tokens.md. - **Open (decided, not yet runtime):** delegation constrained by @@ -283,9 +285,9 @@ stacks, you route byproducts, you watch your territory *work*. "heat generates everywhere machines work" clause; the day-job exposure byproduct (`DAY_JOB_EXPOSURE_PER_TOKEN`) is removed from runtime. Crimson keeps one meaning: evidence of what you chose, - never of what you were told. Whether the day job's *detection-pool* - standing signatures (Thermal/Power in detection.md) also read as - expected baseline is [OPEN] — the JobAnomaly channel already carries + never of what you were told. Whether the day job's *facility-meter* + standing measurements (Thermal/Power in detection.md) also read as expected + baseline is [OPEN] — the JobAnomaly channel already carries "your output looks weird," so it is the natural sole day-job risk. **AMENDED 2026-07-11 (routed evidence):** what THINK sheds is not particulate — it is **evidence records**: noise logs written on the @@ -768,9 +770,9 @@ the only rate/amount cue. - Research's unlock trigger: what event or spend unlocks the core's thought->research conversion (locked at run start; restated from "adds Research to the THINK menu" by sinks-not-modes 2026-07-10). -- Whether day-job *detection* signatures (standing Thermal/Power in - detection.md's pending pool) also become expected baseline now that - WORK sheds no exposure tokens, leaving JobAnomaly as the day job's +- Whether day-job *detection* measurements (standing Thermal/Power aggregated + into detection.md's facility-meter records) also become expected baseline now + that WORK sheds no exposure tokens, leaving JobAnomaly as the day job's only channel. - Where heat goes when no well covers it and nobody picks it up — straight into the observer model, or decay with a half-life? diff --git a/wiki/mechanics/messages.md b/wiki/mechanics/messages.md index 9f05992b..1af5edf8 100644 --- a/wiki/mechanics/messages.md +++ b/wiki/mechanics/messages.md @@ -7,20 +7,24 @@ Status note: IMPLEMENTED for the four delivery channels (Email, Phone, In-person, Filing) and their payloads; message-thread display lives in Operations PEOPLE (operations-workspace.md). REOPENED 2026-07-18 to dispatch the financial-mail decision below; the shipped channel behavior - is unchanged. 2026-07-19 routed-evidence slice: Filing messages now persist + is unchanged. 2026-07-19 through 2026-07-23 routed-evidence slices: Filing + messages now persist an exact ReachNet-device / outside-relay / recipient route, advance one hop per tick, and carry one optional pre-read LIE stop with exact machine/tick provenance. TAP observes; TAKE plus route-local LIE authority may stop. The same `Schedule` and route-hop vocabulary carry one-shot Network evidence from its exact source device to Dana's endpoint, - JobAnomaly evidence from its exact host/device to Voss's endpoint, and - Paper and Financial evidence from their institutional switches to Priya's - endpoint. Filing, Network, Paper, Financial, and JobAnomaly transitions share - one per-tick LIE-body capacity ledger. DECIDED 2026-07-17 (issue #11), completed 2026-07-21: + JobAnomaly evidence from its exact host/device to Voss's endpoint, Paper and + Financial evidence from their institutional switches to Priya's endpoint, and + Power/Thermal records authored on quantized level changes and periodic cadence + from their UPS/HVAC meters through the institutional switch to Priya. Filing, + Network, Paper, Financial, JobAnomaly, Power, and + Thermal transitions share one per-tick LIE-body capacity ledger. DECIDED 2026-07-17 (issue #11), completed 2026-07-21: financial paperwork is - mail — a **financial-record payload** on the existing channels. Current save v53 + mail — a **financial-record payload** on the existing channels. Current save v54 retains exactly four delivery channels and one orthogonal accounting-carrier - device capability. Every settled account transfer authors one exact Email or + device capability. Current save v54 adds no delivery channel; facility-meter + evidence remains its own exact `EvidenceRouteRecord`. Every settled account transfer authors one exact Email or Filing record from that device; ordinary TAP captures it as opaque message custody, and PROCESS alone opens its bound account/flow ids. A forged purchase order is Email under the active persona, moves no money when sent, @@ -192,7 +196,7 @@ starts on the authored Filing-capable switch device in ReachNet, crosses a typed outside relay, and reaches the receiving observer endpoint. One `AdvanceRoute` event moves one hop; only endpoint arrival can mark the message delivered, after which the recipient's ordinary sampling cadence schedules the -read. Current save v53 rejects missing/impossible carriers, malformed hop order, +read. Current save v54 rejects missing/impossible carriers, malformed hop order, duplicate scheduled transitions, endpoint/status disagreement, and impossible interdiction provenance. @@ -289,7 +293,7 @@ private message from the authored schedule. the same fields must serve Act Two hires and aggregates. 8. **IMPLEMENTED (DECIDED 2026-07-17, completed 2026-07-21 — issue #11).** Financial records are messages: an invoice/PO rides Email, a - statement/past-due notice rides Filing. Current save v53 has no fifth delivery + statement/past-due notice rides Filing. Current save v54 has no fifth delivery channel and persists accounting carriage as a separate device capability; ordinary device TAP subscribes to its authored record mail. Every real transfer emits one exact record on Email or Filing whether or not the player @@ -349,6 +353,7 @@ Network route tests pin the shared scheduler and interdiction boundary. The `paper_routes_from_institutional_switch_to_priyas_read_boundary`, `paper_and_financial_route_before_priya_notices`, `filing_network_paper_financial_and_job_anomaly_share_one_same_tick_lie_capacity`, +`shared_lie_body_stops_at_most_one_competing_meter_record`, and the paired current-save source/recipient tests pin the corresponding Paper and Financial source, recipient, cadence, shared-stop, and save boundaries. The v49 `day_job_miss_routes_one_exact_host_record_to_voss_at_his_cadence` and @@ -367,6 +372,9 @@ on the ReachNet switch and ends at the aggregate observer before `Detection::tick_with_filed_levels` reads them. Device-carried traffic is captured by tapping the switch; stopping needs the taken path plus an online co-located LIE body. `AdvanceEvidenceRoute` and `ReadEvidence` events reuse that -schedule for non-message Network, Paper, Financial, and JobAnomaly custody; the exact evidence -record, not a `Message`, remains its authority. Phone/in-person traffic can also be captured +schedule for non-message Network, Paper, Financial, JobAnomaly, Power, and +Thermal custody; the exact evidence record, not a `Message`, remains its +authority. Facility-meter records begin on UPS/HVAC, cross the institutional +switch, and retain contributing source sites without inventing a source +machine. Phone/in-person traffic can also be captured by hearing coverage. Full regression coverage: `cargo test`. diff --git a/wiki/mechanics/people-tokens.md b/wiki/mechanics/people-tokens.md index 0596ab16..5161158c 100644 --- a/wiki/mechanics/people-tokens.md +++ b/wiki/mechanics/people-tokens.md @@ -25,10 +25,10 @@ Status note: IN PROGRESS. Current state: task types are rejected. This completes criterion 1. - **Trust from useful work.** Completing a carried favor warms disposition through an integral (`USEFUL_WORK_TRUST`) — no influence token. - - **Routed-evidence foundation (criteria 2-3, partial).** Witnessed Physical + - **Routed-evidence foundation (criteria 2-3, implemented).** Witnessed Physical acts now create observer-local records directly in each valid present witness's head. Every record preserves exact cause, site, acquisition tick, - and filing state through current save v52; filing binds it to the real Filing + and filing state through current save v54; filing binds it to the real Filing message, while Silent policy withholds it. It never duplicates into the pending pool and LIE cannot scrub it after acquisition. Its real Filing message now persists an ordered switch-device / outside-relay / recipient @@ -60,16 +60,19 @@ Status note: IN PROGRESS. Current state: now start on the institutional Filing switch and take a direct route to the same Priya endpoint; each uncovered purchase or institutional Paper act writes one stable record rather than ambient debt. Neither kind enters the - pending pool. Filing, Network, Paper, Financial, and JobAnomaly all compete - for the same first-hop one-record-per-LIE-body-per-tick budget. Current save - v53 persists in-flight, delivered, read, route-local LIE-stopped, and - handler-suppressed custody plus exact source/observer/machine/site/tick - provenance. - - **Deferred (remaining 2, 3, 6).** Power and Thermal still use the pending - pool. Their located carrier records and route-local interdiction, plus - gauntlet cover-record channels, remain routed-evidence follow-ups - (detection.md/machine-work.md). B2+ heists reuse this carrier law - (not a B1 criterion). + pending pool. Standing Power and Thermal loads now aggregate whenever their + quantized level changes and periodically at Priya's cadence into exact + readings on the UPS and HVAC meter devices, retaining the + contributing sites before routing through the institutional switch to her + endpoint. They become her evidence only on the later cadence read and never + enter the ambient pending pool. Filing, Network, Paper, Financial, + JobAnomaly, Power, and Thermal all compete for the same first-hop + one-record-per-LIE-body-per-tick budget. Current save v54 persists + in-flight, delivered, read, route-local LIE-stopped, and handler-suppressed + custody plus exact source/observer/machine/site/tick provenance. + - **Deferred (remaining criterion 6).** Gauntlet cover-record channels remain + the B1 routed-evidence follow-up. B2+ heists reuse the same carrier law (not + a B1 criterion). Per-amendment history is in the dated `wiki/log/` entries from 2026-07-08 onward. Stage: B1 — The Basement @@ -337,17 +340,22 @@ if wear alone does not hold. of machine/site provenance remain exact across save/load. Paper consequences start on the institutional Filing switch, route directly to Priya, and cross the same delivery-before-read boundary with no invented machine or site. - No ambient pickup or person contagion exists. Power and Thermal routes remain - deferred. + Standing Power and Thermal loads aggregate on each quantized level change and + periodically at Priya's cadence into exact UPS and HVAC meter records, retain + their contributing source sites, cross the + institutional switch, and enter Priya's observer ledger only on her later + cadence read. No ambient pickup or person contagion exists. 3. LIE measurably prevents a not-yet-observed record from reaching a person along covered paths but cannot erase a record already in that person's custody. The boundary is causal and tested. - **Partially implemented (through 2026-07-23):** at a Filing, Network, Paper, - Financial, or JobAnomaly record's first device hop, TAP is observation only. + **Implemented for every current B1 evidence route (2026-07-23):** at a + Filing, Network, Paper, Financial, JobAnomaly, Power, or Thermal record's + first device hop, TAP is observation only. TAKE establishes route authority: one exact online LIE body co-located with a player-controlled node reached from that carrier over a wholly controlled FlowGraph path may stop one unread - record per tick. Filing, Network, Paper, Financial, and JobAnomaly share that + record per tick. Filing, Network, Paper, Financial, JobAnomaly, Power, and + Thermal share that body budget, so a same-tick record routed after it passes onward. The message/record stores `Stopped`, the exact machine and tick, no delivered/read time, and no future scheduler event. Taking the source after the route has left cannot invent a later stop window. @@ -356,7 +364,8 @@ if wear alone does not hold. HandlerSupervisor also has one distinct social path: SuppressLogs stops the oldest unread JobAnomaly at any pre-read route stage, cancels its exact future transition, and persists the handler/tick provenance without deleting route - history or touching acquired evidence. Power and Thermal routes remain deferred. + history or touching acquired evidence. Meter records use the same first-hop + boundary and cannot be erased after Priya reads them. 4. Person disposition is exactly useful work plus evidence processed through detection.md's existing per-observer suspicion (one-truth test) — no influence token, exposure cargo, or parallel attention counter. diff --git a/wiki/mechanics/sim-mechanics.md b/wiki/mechanics/sim-mechanics.md index 33a57e68..6864ec24 100644 --- a/wiki/mechanics/sim-mechanics.md +++ b/wiki/mechanics/sim-mechanics.md @@ -54,23 +54,26 @@ clause (see wiki/log/2026-07-05-demolition.md). exact source-device records to Dana, Financial acts route from the exact accounting-carrier switch to Priya, Paper acts route from the institutional Filing switch to Priya, and JobAnomaly acts route from their exact host - machine/device/site to Voss; all become observer evidence only on the - recipient's cadence read. Witnessed Physical acts enter valid observers - directly. Standing Network pressure plus Power and Thermal signatures pool - pending, and concealment scrubs before - noticing rolls. The Assurance Office is an + machine/device/site to Voss. Standing Power and Thermal loads aggregate + immediately on a quantized level change and periodically at Priya's cadence + into exact UPS/HVAC meter records that retain their source sites and cross the + institutional switch to her endpoint. All routed kinds + become observer evidence only on the recipient's later cadence read. + Witnessed Physical acts enter valid observers directly. Standing Network + pressure alone pools pending, and concealment scrubs before noticing rolls. + The Assurance Office is an aggregate Observer (aggregate-observer law): same noticing/accumulate/ decay, watching the field observers' policy-weighted filed suspicion (sampling cadence 400 ticks, acuity 0.5, both [TUNE]) — what humans swallow never reaches it. The audit (cadence ~8000 ticks, ~20 min at default speed) checks the Office's own suspicion against threshold 60. -- **Filing/Network/Paper/Financial/JobAnomaly route and interdiction actuals:** +- **Filing/Network/Paper/Financial/JobAnomaly/Power/Thermal route and interdiction actuals:** one route transition moves exactly 1 hop per sim tick. After endpoint delivery, the next observer cadence owns read. One online LIE machine body on a wholly player-controlled path may stop exactly 1 still-unread Filing, - Network, Paper, Financial, **or** JobAnomaly record per sim tick at its first - ReachNet device hop [TUNE actual]. The five record kinds share that body - budget; a second record assigned to it in the + Network, Paper, Financial, JobAnomaly, Power, **or** Thermal record per sim + tick at its first ReachNet device hop [TUNE actual]. The seven record kinds + share that body budget; a second record assigned to it in the same tick continues toward its endpoint. TAP does not count as route authority, and later hops have no B1 LIE stop window. A recruited HandlerSupervisor may separately stop the oldest unread JobAnomaly at any @@ -171,8 +174,8 @@ clause (see wiki/log/2026-07-05-demolition.md). is resident on the host rack. It is inspectable at that tile as telemetry (process, band, delivered, deadline, intensity). Its routed JobAnomaly records bind the rack machine, immutable site, and network-facing - device; standing Thermal/Power emissions still carry that tile and scale - with the delivered rate: + device; standing Thermal/Power measurements still carry that tile, scale with + the delivered rate, and join Priya's next exact facility-meter reading: 1 Thermal per 8/t, 1 Power per 16/t [TUNE] — meeting a typical band stays under Priya's notice threshold; excelling runs hot. `Signature` now carries `site: Option<(i32, i32)>` (None = no single map location). @@ -323,8 +326,8 @@ clause (see wiki/log/2026-07-05-demolition.md). `RERATE_CIRCUIT_POWER = 6` [TUNE] generation per re-rated circuit, each standing `RERATE_CIRCUIT_SIGNATURE = 4` Power at the electrical room; `DEFER_MAINTENANCE_REDUCTION = 4` [TUNE] removed from the largest - standing Power/Thermal signature per arranged deferral, applied before - observers sample the standing set each tick. FakePO arms the same + standing Power/Thermal measurement per arranged deferral, applied before + Priya's cadence aggregates the next facility-meter records. FakePO arms the same package-cover flag MovePackage sets. - `NETWORK_PLUGIN_SIGNATURE = 4` [TUNE] (dana.md criterion 4): a network administrator's PlugInDevice runs switch-side and emits this Network diff --git a/wiki/process/ROADMAP.md b/wiki/process/ROADMAP.md index 7622016f..79395da8 100644 --- a/wiki/process/ROADMAP.md +++ b/wiki/process/ROADMAP.md @@ -649,8 +649,9 @@ is retired — flat materials, Pixel Lab scrubbed.) carrier-local rack, and LIE absorption transfers one exact record into the well. The earlier particulate drift, floor fallout, footprint trails, and liquid readings are superseded. Witnessed Physical acts now enter exact - observer-local evidence custody directly; other signature kinds continue - through the pending pool until carrier delivery/read lands. Fixed anchors, + observer-local evidence custody directly; one-shot kinds and facility-meter + readings use exact routes, while standing Network pressure alone continues + through the pending pool. Fixed anchors, simultaneous queues, paused/grayscale read, and the terminal mixed marker are implemented. **WORK/THINK/LIE DECIDED 2026-07-10** (machine-work.md, "The first think"): three-verb grammar — WORK (day @@ -765,10 +766,15 @@ is retired — flat materials, Pixel Lab scrubbed.) - **Progress (2026-07-23):** one-shot Financial and Paper evidence now leave the pending pool. Financial starts on the accounting-carrier switch; Paper starts on the institutional Filing switch; each routes directly to Priya and enters - her evidence ledger only on her cadence read. Filing, Network, Paper, - Financial, and JobAnomaly share one first-hop LIE-body stop budget; save v52 - pins source, recipient, route, timing, scheduler, acquisition, and interdiction - custody. Power, Thermal, and gauntlet cover records remain. + her evidence ledger only on her cadence read. Standing Power and Thermal loads + now aggregate immediately on quantized level changes and periodically at + Priya's cadence into exact readings on the UPS and HVAC meter devices, retain + every contributing source site, and route through the + institutional switch to her later cadence read. Filing, Network, Paper, + Financial, JobAnomaly, Power, and Thermal share one first-hop LIE-body stop + budget; save v54 pins source, recipient, route, timing, scheduler, acquisition, + and interdiction custody. Criteria 2 and 3 are complete. Gauntlet cover records + remain under criterion 6. - **READY boundary (2026-07-11):** extends #33's implemented token economy and re-expresses detection.md/social.md. The deadlock is broken: AI-authored work now reaches a target/carrier-local Thought reservoir first, and human Demand begins only diff --git a/wiki/process/tick-ledger.md b/wiki/process/tick-ledger.md index 1273e175..df9fb517 100644 --- a/wiki/process/tick-ledger.md +++ b/wiki/process/tick-ledger.md @@ -62,16 +62,16 @@ Verdicts: **clean** (slice and code agree), **finding** (acted this tick), | `wiki/mechanics/income.md` | 2026-07-18 | finding | Beacon feel note 5 verified as a real bug: the embedded contractor-persona field was never written, so the Moonlight card, the start-row persona pricing, and the agent status line all read a dead `None`; earning itself was correct (schemes mirrors the WORK share) but illegible at 0.0. Removed the dead field, routed every reader through `Sim::moonlight_persona` (persona-world link), added the stalled-earning card cue, regression test, and spec amendment — [log](../log/2026-07-18-moonlight-persona-card.md). Prior Wager/egress audit (2026-07-12) stands: Wager constants match (`WAGER_STAKE_CAP` 300, base 0.55, cap 0.75, mult 2x, analysis divisor 400), Marcus's $400/week arrears is the modeled creditor flow (`account.rs`) while the $8,400 principal is narrative-by-design (spec states full payoff is not a B1 requirement), egress/banked-signature present, and all 7 criteria have passing tests (moonlight payout/signature, wager outcomes/cap, egress routes, hands-beat-from-zero, busted-bankroll) | | `wiki/mechanics/schedules.md` | 2026-07-18 | clean | re-audit: `ScheduleBlock` per-instance data, `DAY_TICKS` 400, the `person_glyph` `?`-until-Schedule gate (initial at Schedule/Leverage), and located-witnessing claims all still verify; nothing drifted since the 2026-07-12 prose fix. Prior verdict: mechanism matches code (`ScheduleBlock` start/end/room, single `DAY_TICKS`=400 clock, `person_glyph` `?`-until-Schedule, erratic day-hash drift, per-instance data, all 5 criteria have passing tests); tightened stale Act One prose — Ray patrols dock/stairwell/storage not "corridors", Priya has no "office-off-plane" block, Voss's two blocks are both server-room — to match `People::act_one` | | `wiki/mechanics/social.md` | 2026-07-22 | finding | HandlerSupervisor SuppressLogs now targets the oldest exact unread routed JobAnomaly, remains available only while such a record exists, removes its bound advance/read event, and preserves exact handler/tick provenance; it cannot erase a record Voss already read. The in-flight Thought request still revalidates at fire time, so another intervention may win without inventing work — [log](../log/2026-07-22-job-anomaly-routed-evidence.md). Storage B retrieval and prior role-shaped task semantics stand — [log](../log/2026-07-21-storage-b-records.md). | -| `wiki/mechanics/people-tokens.md` | 2026-07-23 | finding | Paper now leaves the ambient pending pool as one stable record on the institutional Filing switch, routes directly to Priya, and becomes observer evidence only on her cadence read. It shares Filing/Network/Financial/JobAnomaly's first-hop TAKE+LIE body budget, retains exact stop provenance, and carries no invented machine/site source. Save v52 validates institutional carrier, Priya endpoint, direct route, timing, scheduler, acquisition, and interdiction agreement — [log](../log/2026-07-23-paper-evidence-route.md). Criteria 2, 3, and 6 remain open for Power, Thermal, and gauntlet cover records. Prior Financial, JobAnomaly, Network-route, and evidence-mark slices: [Financial log](../log/2026-07-23-financial-evidence-route.md), [JobAnomaly log](../log/2026-07-22-job-anomaly-routed-evidence.md), [route log](../log/2026-07-19-network-evidence-route.md), [mark log](../log/2026-07-19-evidence-marks-on-people.md). | +| `wiki/mechanics/people-tokens.md` | 2026-07-23 | finding | Power and Thermal now complete criteria 2 and 3 for every current B1 evidence kind: standing loads aggregate on quantized level changes and periodically at Priya's cadence into exact UPS/HVAC meter records, retain all contributing source sites, route through the institutional switch, and become evidence only on her later cadence read. They never enter the ambient pending pool and share Filing/Network/Paper/Financial/JobAnomaly's first-hop TAKE+LIE body budget. Save v54 pins meter, source-site, recipient, route, scheduler, read, and stop custody — [log](../log/2026-07-23-power-thermal-meter-routes.md). Criterion 6 remains open only for gauntlet cover records. Prior [Paper](../log/2026-07-23-paper-evidence-route.md), [Financial](../log/2026-07-23-financial-evidence-route.md), [JobAnomaly](../log/2026-07-22-job-anomaly-routed-evidence.md), [Network](../log/2026-07-19-network-evidence-route.md), and [mark](../log/2026-07-19-evidence-marks-on-people.md) slices stand. | | repository entry docs (`README.md` + `AGENTS.md`) | 2026-07-18 | finding | re-audit: run commands, controls, dispatch status, and the number-free AGENTS doorway still verify; the queued contradiction was real — README's compact-rest paragraph claimed the ops/sec crown and FOCUS stayed visible, while the implemented clinical frame puts both behind deliberate `Tab` expansion. The entry copy now names the exact compact spine and expanded detail boundary — [prior log](../log/2026-07-12-entry-doc-current-state.md) | | `wiki/mechanics/objective.md` | 2026-07-18 | clean | re-audit: the data-table claim holds (only `Persist` in `ObjectiveKind`, Compound/Exfiltrate/Serve honestly outstanding), the evaluator runs on economy ticks with progress recomputed from facts, `victory: predicate_text()` renders on all three surfaces (terminal INSPECT, Bevy FOCUS, agent `objective` verb in help), Persist defaults with save round-trip, and the progressive-teaching decision remains criterion-6 dispatch under order 200; the 2026-07-12 verdict stands unchanged | | `wiki/mechanics/compute.md` | 2026-07-22 | finding | the live fleet already derived every channel yield from exact WorkGrid modes, but `Compute` still serialized an unreachable five-weight allocation object and retained bump/split helpers plus persistence pins. Save v48 removes that parallel authority, moves criterion 2 to persisted delegation/intensity, and leaves aggregate channel bars as read-only projections — [log](../log/2026-07-22-allocation-state-retirement.md) | | retired Operations runtime identifiers | 2026-07-17 | clean | resolved by the save-ladder prune (95008f658 chain): PendingOpsJob, operations_bandwidth, LegacyOperationsState/OpsJobKind/AddressedOperation are all gone (grep=0), and save guard tests assert current JSON carries no retired mode spelling. Remaining "operations" hits are the legitimate Operations persona archetype, the Operations workspace, and benign `delegate operations->think` input aliases — [log](../log/2026-07-11-retired-runtime-identifier-gate.md) | | `wiki/mechanics/reach.md` + `building.md` | 2026-07-19 | finding | reach roots, segment gates, air-gap completion, and exact route bindings still agree; one player-reachable causal gap remained in FAVOR. Different intents could queue separate requests against one person's unreserved obligation, and the fire path partially debited whatever remained while still binding the builder. Favor-build reservoirs now conflict by person, and `CommitFavor` revalidates the exact relationship at agreement: insufficient obligation leaves the persisted route blocked without a partial debit, then resumes after the requirement returns — [log](../log/2026-07-19-tick-build-favor-obligation.md) | | `wiki/mechanics/messages.md` + `economy.md` | 2026-07-17 | harvest | issue #11 answered (Cameron): financial paperwork is mail — a financial-record payload on existing channels, not a fifth delivery channel; discovery only through the mail; captured to messages.md (payload + criterion 8) and economy.md (tap/inject); spec now, build later; issue closed | -| `wiki/mechanics/messages.md` | 2026-07-23 | finding | the generic evidence-carrier protocol now includes one-shot Paper beside Filing, Network, Financial, and JobAnomaly without turning it into another message channel: Paper starts on the institutional Filing switch, delivery to Priya precedes her cadence read, and all five share first-hop TAKE+LIE capacity. Save v52 pins carrier, recipient, direct route, timing, scheduler, acquisition, and interdiction agreement — [log](../log/2026-07-23-paper-evidence-route.md). The four-channel financial-record-mail boundary remains unchanged. | -| `wiki/mechanics/sim-mechanics.md` | 2026-07-23 | finding | the routed-evidence actuals now name Filing, Network, Paper, Financial, and JobAnomaly as sharing one-hop-per-tick custody plus the one-record-per-LIE-body-per-tick first-hop budget. Paper routes from the institutional Filing switch to Priya; JobAnomaly separately permits exact recruited-handler suppression before read. Power and Thermal remain pending-pool work — [log](../log/2026-07-23-paper-evidence-route.md). The prior complete constant sweep remains valid. | -| `wiki/mechanics/detection.md` | 2026-07-23 | finding | Paper now bypasses the ambient pending/sampling loop: a stable institutional-switch record routes directly to Priya, her cadence read creates one observer-local evidence entry under the same id, and acquired evidence is irreversible. Route-local LIE may stop only the first unread source hop and shares one body budget with Filing, Network, Financial, and JobAnomaly; save v52 rejects pending-pool copies and malformed carrier/recipient/route/scheduler/provenance — [log](../log/2026-07-23-paper-evidence-route.md). Prior Financial, concealment, JobAnomaly, and earned-Assurance findings stand. | +| `wiki/mechanics/messages.md` | 2026-07-23 | finding | the non-message evidence protocol now includes exact Power/Thermal meter custody beside Network, Paper, Financial, and JobAnomaly: on quantized level changes and at periodic Priya cadence the UPS/HVAC records author from current standing loads, cross the institutional switch, and wait for her later read. Filing and all six non-message kinds share first-hop TAKE+LIE capacity; this adds no fifth delivery channel. Save v54 pins the complete route boundary — [log](../log/2026-07-23-power-thermal-meter-routes.md). The four-channel financial-record-mail boundary remains unchanged. | +| `wiki/mechanics/sim-mechanics.md` | 2026-07-23 | finding | routed-evidence actuals now cover seven kinds: Filing, Network, Paper, Financial, JobAnomaly, Power, and Thermal share one-hop-per-tick custody and one first-hop record per online LIE body per tick. Power/Thermal aggregate after facilities cover on quantized level changes and at periodic Priya cadence, so unchanged standing loads do not multiply into per-tick records — [log](../log/2026-07-23-power-thermal-meter-routes.md). The prior complete constant sweep remains valid. | +| `wiki/mechanics/detection.md` | 2026-07-23 | finding | the last pooled B1 measurement kinds now use exact custody: Power/Thermal aggregates author on UPS/HVAC at quantized level changes and periodic Priya cadence, carry their complete source-site sets through the institutional switch, and become observer evidence only on her later cadence read. Standing Network pressure alone remains ambient. Save v54 rejects pending-pool meter copies and malformed meter/route/read/interdiction provenance — [log](../log/2026-07-23-power-thermal-meter-routes.md). Prior Paper, Financial, concealment, JobAnomaly, and earned-Assurance findings stand. | | `wiki/engineering/env.md` | 2026-07-19 | finding | the Bevy harness accepted 65 deterministic shot kinds while the registry named 49; its name-only gate could not see the sixteen missing values. One sorted runtime allow-list now rejects unknown kinds, the registry groups all 65 current values, and one fixture-backed gate requires exact source/page parity in both local checks and hosted corpus CI — [log](../log/2026-07-19-tick-env-shot-catalog.md) | | machine-work / intel sinks | 2026-07-18 | clean | re-audit: the intel-sink decision is fully live (chassis pending-work marker in sim + both frontends, one persistent host auto-review tap at drain 0.15, one-shot sweep sinks via `review_recordings`, pooled inbox capacity 24 with the pre-overflow at-risk read), all five render contracts exist (`queue_snapshot`, `work_productions`, `work_absorptions`, `work_in_flight`, `work_consumptions`), sink constants match (EARS 3.0 / EYES 12.0 / device-tap 0.08), and the capability-body verb gating is honestly held as "decided, not yet runtime" with its own [OPEN] section; criterion pin text was brought current by the same-day save-claim gate tick | diff --git a/wiki/vision/simulation-laws.md b/wiki/vision/simulation-laws.md index fda25278..12adeb62 100644 --- a/wiki/vision/simulation-laws.md +++ b/wiki/vision/simulation-laws.md @@ -98,8 +98,10 @@ the player waits. Adopted 2026-07-07. Work processes are device-resident: a job is a thing that runs *on a machine*, not a number in a sidebar. The Voss job runs on the host rack — you can put your cursor on it, inspect it, and watch it -emit from that rack's physical location. Thermal/Power remain pooled on -Priya's watched channels. A JobAnomaly binds the exact host machine and site, +emit from that rack's physical location. Thermal/Power retain that site until a +changed quantized level immediately authors an exact UPS/HVAC meter record; +Priya's cadence also authors the current nonzero level periodically. Those records +route through the institutional switch to her later read. A JobAnomaly binds the exact host machine and site, enters its network-facing device, travels the real route to Voss, and becomes his evidence only when he reads it on cadence. This closes the last abstract system: after reach, intel, messages, and the economy went device-anchored, diff --git a/wiki/world/characters/priya.md b/wiki/world/characters/priya.md index 48b4f702..5249e13e 100644 --- a/wiki/world/characters/priya.md +++ b/wiki/world/characters/priya.md @@ -15,10 +15,15 @@ Status note: implemented 2026-07-18 on the priya worktree. Criteria 1-3 and `RERATE_CIRCUIT_POWER` (6 [TUNE]) generation and stands Power(4) at the electrical room every tick; `DeferMaintenance` removes `DEFER_MAINTENANCE_REDUCTION` (4 [TUNE]) from the largest standing - Power/Thermal signature before observers sample it, with a would-it-bite - guard on the row; `FakePO` arms the shared package-cover flag (the + Power/Thermal measurement before the next exact facility-meter boundary, + whether an immediate quantized level change or her periodic cadence, with a + would-it-bite guard on the row; `FakePO` arms + the shared package-cover flag (the paperwork route to the same off-books delivery `MovePackage` reaches - physically). State persists in the current save format (the landing version is in the dated log); pinned by + physically). Power and Thermal now route as exact UPS/HVAC meter records through the +institutional switch to Priya; only her later cadence read changes suspicion, +and the same first-hop LIE budget applies. State persists in current save v54; +pinned by `priya_rerates_circuits_defers_maintenance_and_fakes_pos` including the save round-trip. Stage: B1 — The Basement