Expose building source selector bytes

This commit is contained in:
Jan Petykiewicz 2026-04-19 12:11:58 -07:00
commit f690a1ebd0
3 changed files with 133 additions and 19 deletions

View file

@ -156,25 +156,26 @@ rebuild a substantial portion of each live candidate record:
`[candidate+0x79c/+0x7a0/+0x78c/+0x790/+0x794/+0x7b0]` `[candidate+0x79c/+0x7a0/+0x78c/+0x790/+0x794/+0x7b0]`
- reads the fixed per-record stream fields at - reads the fixed per-record stream fields at
`[candidate+0x04/+0x22/+0x26/+0x28/+0x2a/+0x2e/+0x32/+0x33]` `[candidate+0x04/+0x22/+0x26/+0x28/+0x2a/+0x2e/+0x32/+0x33]`
- restores the selector-bank bytes
`[candidate+0xb9/+0xba/+0xbb]`
- allocates and streams the packed `0xbc` descriptor array into `[candidate+0x37]` - allocates and streams the packed `0xbc` descriptor array into `[candidate+0x37]`
But in the checked `0x004120b0` body there is still **no** write to:
- `[candidate+0xba]`
- `[candidate+0xbb]`
So the current strongest ownership split is now: So the current strongest ownership split is now:
- direct named-availability table `[state+0x66b2]` is not the missing differentiator by itself - direct named-availability table `[state+0x66b2]` is not the missing differentiator by itself
- per-record stream-load `0x004120b0` is also not where the port-versus-warehouse availability - both source-record import `0x00414490` and per-record stream-load `0x004120b0` do carry the
bytes are authored relevant selector-bank bytes from persisted/source state into the live candidate family
- the surviving writer-side frontier is the later template-bank path in `0x00412d70`, where the - but the stock `Data/BuildingTypes/*.bca` corpus currently keeps `[record+0xb8/+0xb9/+0xba/+0xbb]`
imported record is cloned or reused against one bank-qualified live candidate before the runtime at zero across every observed file, including `Warehouse.bca` and `Port.bca`
descriptor and membership rebuilds run - so the surviving frontier is no longer “does the lower loader import `[candidate+0xba/+0xbb]`?”
but rather which later owner or alternate content path makes the live bank-qualified split differ
from that all-zero shipped BCA corpus before `0x00412d70` clones or reuses one bank-qualified
live candidate
That makes the next Tier 2 question more concrete still: That makes the next Tier 2 question more concrete still:
- how the bank-qualified template source selected under `[candidate+0xba]` versus `[candidate+0xbb]` - how any nonzero bank-qualified template source under `[candidate+0xba]` versus `[candidate+0xbb]`
seeds the later `Warehouse%02d` side in `Louisiana.gmp` is actually seeded above the stock all-zero BCA corpus, and then
drives the later `Warehouse%02d` side in `Louisiana.gmp`
- and whether that preserved bank/template state is the real bridge from the minimal recipe cluster - and whether that preserved bank/template state is the real bridge from the minimal recipe cluster
to the shipped `5200 :: [7:0]` `Add Building Warehouse05` row to the shipped `5200 :: [7:0]` `Add Building Warehouse05` row

View file

@ -17,6 +17,10 @@ pub struct BuildingTypeSourceFile {
pub raw_stem: String, pub raw_stem: String,
pub canonical_stem: String, pub canonical_stem: String,
pub source_kind: BuildingTypeSourceKind, pub source_kind: BuildingTypeSourceKind,
#[serde(default)]
pub byte_len: Option<usize>,
#[serde(default)]
pub bca_selector_probe: Option<BuildingTypeBcaSelectorProbe>,
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -27,6 +31,29 @@ pub struct BuildingTypeSourceEntry {
pub file_names: Vec<String>, pub file_names: Vec<String>,
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BuildingTypeBcaSelectorProbe {
pub byte_0xb8: u8,
pub byte_0xb8_hex: String,
pub byte_0xb9: u8,
pub byte_0xb9_hex: String,
pub byte_0xba: u8,
pub byte_0xba_hex: String,
pub byte_0xbb: u8,
pub byte_0xbb_hex: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BuildingTypeBcaSelectorPatternSummary {
pub byte_len: usize,
pub byte_0xb8_hex: String,
pub byte_0xb9_hex: String,
pub byte_0xba_hex: String,
pub byte_0xbb_hex: String,
pub file_count: usize,
pub sample_file_names: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BuildingTypeNamedBindingComparison { pub struct BuildingTypeNamedBindingComparison {
pub bindings_path: String, pub bindings_path: String,
@ -42,9 +69,11 @@ pub struct BuildingTypeSourceReport {
pub bca_file_count: usize, pub bca_file_count: usize,
pub bty_file_count: usize, pub bty_file_count: usize,
pub unique_canonical_stem_count: usize, pub unique_canonical_stem_count: usize,
pub bca_selector_pattern_count: usize,
#[serde(default)] #[serde(default)]
pub named_binding_comparison: Option<BuildingTypeNamedBindingComparison>, pub named_binding_comparison: Option<BuildingTypeNamedBindingComparison>,
pub notes: Vec<String>, pub notes: Vec<String>,
pub bca_selector_patterns: Vec<BuildingTypeBcaSelectorPatternSummary>,
pub files: Vec<BuildingTypeSourceFile>, pub files: Vec<BuildingTypeSourceFile>,
pub entries: Vec<BuildingTypeSourceEntry>, pub entries: Vec<BuildingTypeSourceEntry>,
} }
@ -78,6 +107,7 @@ pub fn inspect_building_types_dir_with_bindings(
"bty" => BuildingTypeSourceKind::Bty, "bty" => BuildingTypeSourceKind::Bty,
_ => continue, _ => continue,
}; };
let bytes = fs::read(entry.path())?;
let raw_stem = Path::new(&file_name) let raw_stem = Path::new(&file_name)
.file_stem() .file_stem()
.and_then(|stem| stem.to_str()) .and_then(|stem| stem.to_str())
@ -90,7 +120,12 @@ pub fn inspect_building_types_dir_with_bindings(
file_name, file_name,
canonical_stem: canonicalize_building_stem(&raw_stem), canonical_stem: canonicalize_building_stem(&raw_stem),
raw_stem, raw_stem,
source_kind, source_kind: source_kind.clone(),
byte_len: Some(bytes.len()),
bca_selector_probe: match source_kind {
BuildingTypeSourceKind::Bca => Some(probe_bca_selector_bytes(&bytes)),
BuildingTypeSourceKind::Bty => None,
},
}); });
} }
@ -141,10 +176,45 @@ pub fn inspect_building_types_dir_with_bindings(
.iter() .iter()
.filter(|file| matches!(file.source_kind, BuildingTypeSourceKind::Bty)) .filter(|file| matches!(file.source_kind, BuildingTypeSourceKind::Bty))
.count(); .count();
let mut grouped_selector_patterns =
BTreeMap::<(usize, String, String, String, String), Vec<String>>::new();
for file in &files {
let Some(probe) = &file.bca_selector_probe else {
continue;
};
grouped_selector_patterns
.entry((
file.byte_len.unwrap_or_default(),
probe.byte_0xb8_hex.clone(),
probe.byte_0xb9_hex.clone(),
probe.byte_0xba_hex.clone(),
probe.byte_0xbb_hex.clone(),
))
.or_default()
.push(file.file_name.clone());
}
let bca_selector_patterns = grouped_selector_patterns
.into_iter()
.map(
|(
(byte_len, byte_0xb8_hex, byte_0xb9_hex, byte_0xba_hex, byte_0xbb_hex),
file_names,
)| BuildingTypeBcaSelectorPatternSummary {
byte_len,
byte_0xb8_hex,
byte_0xb9_hex,
byte_0xba_hex,
byte_0xbb_hex,
file_count: file_names.len(),
sample_file_names: file_names.into_iter().take(12).collect(),
},
)
.collect::<Vec<_>>();
let notes = vec![ let notes = vec![
"BuildingTypes sources are grouped by a canonical stem that lowercases and strips spaces, underscores, and hyphens so paired .bca/.bty variants collapse onto one asset token.".to_string(), "BuildingTypes sources are grouped by a canonical stem that lowercases and strips spaces, underscores, and hyphens so paired .bca/.bty variants collapse onto one asset token.".to_string(),
"This report is an offline asset-pool view only; it does not by itself assign live candidate ids or prove scenario candidate-table availability.".to_string(), "This report is an offline asset-pool view only; it does not by itself assign live candidate ids or prove scenario candidate-table availability.".to_string(),
"For .bca files, the report also exposes the narrow selector-byte window at offsets 0xb8..0xbb used by the grounded aux-candidate and live-candidate stream decoders.".to_string(),
]; ];
let named_binding_comparison = if let Some(bindings_path) = bindings_path { let named_binding_comparison = if let Some(bindings_path) = bindings_path {
@ -158,13 +228,32 @@ pub fn inspect_building_types_dir_with_bindings(
bca_file_count, bca_file_count,
bty_file_count, bty_file_count,
unique_canonical_stem_count: entries.len(), unique_canonical_stem_count: entries.len(),
bca_selector_pattern_count: bca_selector_patterns.len(),
named_binding_comparison, named_binding_comparison,
notes, notes,
bca_selector_patterns,
files, files,
entries, entries,
}) })
} }
fn probe_bca_selector_bytes(bytes: &[u8]) -> BuildingTypeBcaSelectorProbe {
let byte_0xb8 = bytes.get(0xb8).copied().unwrap_or(0);
let byte_0xb9 = bytes.get(0xb9).copied().unwrap_or(0);
let byte_0xba = bytes.get(0xba).copied().unwrap_or(0);
let byte_0xbb = bytes.get(0xbb).copied().unwrap_or(0);
BuildingTypeBcaSelectorProbe {
byte_0xb8,
byte_0xb8_hex: format!("0x{byte_0xb8:02x}"),
byte_0xb9,
byte_0xb9_hex: format!("0x{byte_0xb9:02x}"),
byte_0xba,
byte_0xba_hex: format!("0x{byte_0xba:02x}"),
byte_0xbb,
byte_0xbb_hex: format!("0x{byte_0xbb:02x}"),
}
}
fn load_named_binding_comparison( fn load_named_binding_comparison(
bindings_path: &Path, bindings_path: &Path,
entries: &[BuildingTypeSourceEntry], entries: &[BuildingTypeSourceEntry],
@ -214,3 +303,24 @@ struct BuildingBindingRow {
#[serde(default)] #[serde(default)]
candidate_name: Option<String>, candidate_name: Option<String>,
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn probes_bca_selector_bytes_from_fixed_offsets() {
let mut bytes = vec![0u8; 0xbc + 1];
bytes[0xb8] = 0x12;
bytes[0xb9] = 0x34;
bytes[0xba] = 0x56;
bytes[0xbb] = 0x78;
let probe = probe_bca_selector_bytes(&bytes);
assert_eq!(probe.byte_0xb8, 0x12);
assert_eq!(probe.byte_0xb9, 0x34);
assert_eq!(probe.byte_0xba, 0x56);
assert_eq!(probe.byte_0xbb, 0x78);
assert_eq!(probe.byte_0xb8_hex, "0x12");
assert_eq!(probe.byte_0xbb_hex, "0x78");
}
}

View file

@ -678,12 +678,15 @@ Working rule:
- the writer-side split is narrower now too: - the writer-side split is narrower now too:
direct disassembly of `0x004120b0` shows that the per-record stream-load helper clears direct disassembly of `0x004120b0` shows that the per-record stream-load helper clears
`[candidate+0x79c/+0x7a0/+0x78c/+0x790/+0x794/+0x7b0]`, restores the fixed fields through `[candidate+0x79c/+0x7a0/+0x78c/+0x790/+0x794/+0x7b0]`, restores the fixed fields through
`[candidate+0x33]`, and streams the packed `0xbc` descriptor array into `[candidate+0x37]`, `[candidate+0x33]`, restores `[candidate+0xb9/+0xba/+0xbb]`, and streams the packed `0xbc`
but does **not** write `[candidate+0xba/+0xbb]` in the checked body. So the next concrete descriptor array into `[candidate+0x37]`. Direct disassembly of upstream source-record import
recovery target is now the bank-qualified template source in `0x00412d70`, not the lower `0x00414490` also restores `[record+0xb8/+0xb9/+0xba/+0xbb]`. The new checked-in building
tagged-record reader: source inspector now shows the stock `Data/BuildingTypes/*.bca` corpus keeps those four bytes
recover how preserved `[candidate+0xba/+0xbb]` bank/template state feeds the zero across every observed file, including `Warehouse.bca` and `Port.bca`. So the next
`Warehouse%02d` runtime side before `0x00411ee0 / 0x00411ce0 / 0x00412c10` run. concrete recovery target is no longer the lower tagged-record reader itself:
recover which later owner or alternate content path makes the live
`[candidate+0xba/+0xbb]` bank/template state diverge from that all-zero shipped BCA corpus
before `0x00411ee0 / 0x00411ce0 / 0x00412c10` run.
kinds”; it is the smaller set of scenario-specific records where that sweep explicitly writes kinds”; it is the smaller set of scenario-specific records where that sweep explicitly writes
`[event+0x7ef]` itself or a still-later owner does. `[event+0x7ef]` itself or a still-later owner does.
- two explicit trigger-kind materializations are now grounded inside that retagger: - two explicit trigger-kind materializations are now grounded inside that retagger: