Class v6 family gate, the 17:00 cut: the harness as a plain diff at 3dc3117c (the founder-strings check refuses the patch form's author lines)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
a421b896f5
commit
bb52ac640f
2 changed files with 193 additions and 426 deletions
|
|
@ -14,7 +14,7 @@ The question. Class v6 (the founder's word at 11:1x BST, 8 October 2026) draws p
|
||||||
| The per-era tests (section 3) | Three rings. Ring A, the chain runs per era at the cut, microseconds: the structural redraw rules (the band membership of every draw, the day-key cost rule, the product-bias rotation class recorded). Ring B, the chain runs per epoch, seconds: the acceptance rule generalised to the family's shape ((a) (b) (a') (c) (c') (c'') (c''') plus the two next-class tests, the per-site largest-256-item-bucket bound and the index-bit bias read, on the same 2^20 pass). Ring C, the gate runs offline per drawn era, box-hours: the F8-form census at 2^24 on 64 seeds, the attempts census, the exhaustion count. Each row carries its known-failed shape and its clean-seed cost so the redraw is priced |
|
| The per-era tests (section 3) | Three rings. Ring A, the chain runs per era at the cut, microseconds: the structural redraw rules (the band membership of every draw, the day-key cost rule, the product-bias rotation class recorded). Ring B, the chain runs per epoch, seconds: the acceptance rule generalised to the family's shape ((a) (b) (a') (c) (c') (c'') (c''') plus the two next-class tests, the per-site largest-256-item-bucket bound and the index-bit bias read, on the same 2^20 pass). Ring C, the gate runs offline per drawn era, box-hours: the F8-form census at 2^24 on 64 seeds, the attempts census, the exhaustion count. Each row carries its known-failed shape and its clean-seed cost so the redraw is priced |
|
||||||
| The board (section 4) | F1 to F10 and the nine lanes classified per-era, per-family or chain-run, with the harness taking the era draw as an input; the proof of testing is one JSON record per drawn era (the draw, the object, the binary, the seeds, every verdict with its statistic, threshold and log sha256, the core-seconds) and one family summary (eras per stratum, the worst era per test, the refuse rate per band, the bound's arithmetic) |
|
| The board (section 4) | F1 to F10 and the nine lanes classified per-era, per-family or chain-run, with the harness taking the era draw as an input; the proof of testing is one JSON record per drawn era (the draw, the object, the binary, the seeds, every verdict with its statistic, threshold and log sha256, the core-seconds) and one family summary (eras per stratum, the worst era per test, the refuse rate per band, the bound's arithmetic) |
|
||||||
| The honest line (section 5) | The gate sees what its tests see. It does not see the diffuse era-stride class below the bit-bias read's resolution (a bias under 0.3 percent at 2^20), a mechanism that lives in a joint corner no stratum names, a mixer weakness below adv-mixer-3's bands, or an era whose draw sits in the untested 1 in 64. A GPU-like chip loses nothing on any era; what a corner buys it is a per-era hot set bounded at 1.002x (the floor) or 1.0024x (the diffuse class), or, at `m = 4`, a mixer with 2 applications of measured margin instead of 6 |
|
| The honest line (section 5) | The gate sees what its tests see. It does not see the diffuse era-stride class below the bit-bias read's resolution (a bias under 0.3 percent at 2^20), a mechanism that lives in a joint corner no stratum names, a mixer weakness below adv-mixer-3's bands, or an era whose draw sits in the untested 1 in 64. A GPU-like chip loses nothing on any era; what a corner buys it is a per-era hot set bounded at 1.002x (the floor) or 1.0024x (the diffuse class), or, at `m = 4`, a mixer with 2 applications of measured margin instead of 6 |
|
||||||
| Measured (section 6) | 24,000 drawn eras through the ring-B tests in 54 core-hours (1.7 box-hours) on build-1 and build-3, 11:16 to 12:13 BST, the harness as a patch series in `logs/`. Four measured rows move the design: the lossy corner of B = 4 exhausts the 256 cap in 1.2 percent of eras (61 of 5,000) against 0 of 19,000 elsewhere, so the band is B = 4 on the injecting families with or/mul/mulhi never raised (0 of 3,000 under it); the (c''') floor at 0.995 costs 4.5 to 6.5 percent of candidates at width 4 against 0.6 to 1.0 at width 1 (a per-width calibration); the shape axis moves the draw from 1.8 attempts (64 x 108) to 3.8 (256 x 27); the era-stride bias sits at over 6 sigma on one address bit (bit R in 73 percent of cases) in about half the eras of every stratum and at over 100 sigma in a third, falling to 7.5 percent under R in 28..31, a structural fact for the research lane's layer 4 rather than a per-epoch refusal |
|
| Measured (section 6) | 24,000 drawn eras through the ring-B tests in 54 core-hours (1.7 box-hours) on build-1 and build-3, 11:16 to 12:13 BST, the harness as a diff in `logs/`. Four measured rows move the design: the lossy corner of B = 4 exhausts the 256 cap in 1.2 percent of eras (61 of 5,000) against 0 of 19,000 elsewhere, so the band is B = 4 on the injecting families with or/mul/mulhi never raised (0 of 3,000 under it); the (c''') floor at 0.995 costs 4.5 to 6.5 percent of candidates at width 4 against 0.6 to 1.0 at width 1 (a per-width calibration); the shape axis moves the draw from 1.8 attempts (64 x 108) to 3.8 (256 x 27); the era-stride bias sits at over 6 sigma on one address bit (bit R in 73 percent of cases) in about half the eras of every stratum and at over 100 sigma in a third, falling to 7.5 percent under R in 28..31, a structural fact for the research lane's layer 4 rather than a per-epoch refusal |
|
||||||
|
|
||||||
## 1. The parameter space
|
## 1. The parameter space
|
||||||
|
|
||||||
|
|
@ -191,7 +191,7 @@ A GPU-like chip (the `f = 1` stored-dataset chip with a programmable core, chip-
|
||||||
|
|
||||||
## 6. The coverage measured (24,000 drawn eras, 11:16 to 12:13 BST, 8 October 2026)
|
## 6. The coverage measured (24,000 drawn eras, 11:16 to 12:13 BST, 8 October 2026)
|
||||||
|
|
||||||
The harness: branch `family-gate-v5` at 236ef3a5 (the class v5 crate 8f481459 plus `accept::tests::family_gate_era_census`; the patch series is `logs/harness-family-gate-v5-236ef3a5.patch` in this directory, since the branch's push to the mirror was refused by the class v5 tree's own pre-push hook and is held). What it does per era `k` of a label space: the era bytes from `igneum-family-gate[/stratum]/era/k` and the epoch bytes from `.../program/k` (the F8 label space's form); from the era's own stream, in a fixed order and consumed whether pinned or not, the shadow block shape in {64 x 108, 128 x 54, 256 x 27}, the mixer multiplier in {4, 8, 16} (recorded: the acceptance never runs the mixer), the ten non-load weights perturbed by `below(2B + 1) - B` with shfl and mulhi never raised and renormalised to 75 by largest remainder (B = 4), the read width through the era draw over {1, 4} words (the mix one-hot on the drawn width); then the chain draw of that era's epoch through the real rule keyed on the family's shape (`is_family_shape`, the acceptance's `is_class_v4_shape` generalised behind `IGNEUM_FAMILY_GATE`; the draw's source rule reads the same predicate), every candidate's first failing part recorded, the cap 256 and the last resort as shipped; on the accepted program, on the rule's own 2^20 sample (4,096 units), per site: the (c'') and (c''') ratio with the window divided by the width, the largest 4,096-word (256-item) bucket in sigma of its expectation, and the largest index-bit one-count excess in sigma over the free bits. One TSV row per era (`logs/<stratum>-family_gate_era_census-<from>-<n>.tsv`, the run's log beside it with the binary's sha256 and the lease line); every binary a pinned copy under `/srv/builds/_adv-family-gate/bin/<tag>/` (fg2 9b7f764a for the first corners, fg5 e7a50f8d for the width-4 and lossy-base strata). Cost: about 10 core-seconds per era; 54.4 core-hours in all (1.7 box-hours of 32-core slots) on build-1 (random, lossy cap, width 4) and build-3 (shape 64, lossy base, width 4 plus lossy cap), every run under `lease pool` at class measure. The control: `v5_attempts_census` on the shipped class v5 draw over 10,000 seeds of the F8 label space (build-1, 24 cores, 1,534 s, binary 4bec799c, `logs/base-v5_attempts_census-1000-10000.log`): r = 0.6854, mean attempt 2.179, 0 exhaustions, (a') 82.5 percent of rejections, (c''') 0.9 percent, the row-90 reading reproduced.
|
The harness: branch `family-gate-v5` at 3dc3117c (the class v5 crate 8f481459 plus `accept::tests::family_gate_era_census`; the diff is `logs/harness-family-gate-v5-3dc3117c.diff` (a plain diff of igneum-pow/src: the gate refuses the patch form's author lines) in this directory, since the branch's push to the mirror was refused by the class v5 tree's own pre-push hook and is held). What it does per era `k` of a label space: the era bytes from `igneum-family-gate[/stratum]/era/k` and the epoch bytes from `.../program/k` (the F8 label space's form); from the era's own stream, in a fixed order and consumed whether pinned or not, the shadow block shape in {64 x 108, 128 x 54, 256 x 27}, the mixer multiplier in {4, 8, 16} (recorded: the acceptance never runs the mixer), the ten non-load weights perturbed by `below(2B + 1) - B` with shfl and mulhi never raised and renormalised to 75 by largest remainder (B = 4), the read width through the era draw over {1, 4} words (the mix one-hot on the drawn width); then the chain draw of that era's epoch through the real rule keyed on the family's shape (`is_family_shape`, the acceptance's `is_class_v4_shape` generalised behind `IGNEUM_FAMILY_GATE`; the draw's source rule reads the same predicate), every candidate's first failing part recorded, the cap 256 and the last resort as shipped; on the accepted program, on the rule's own 2^20 sample (4,096 units), per site: the (c'') and (c''') ratio with the window divided by the width, the largest 4,096-word (256-item) bucket in sigma of its expectation, and the largest index-bit one-count excess in sigma over the free bits. One TSV row per era (`logs/<stratum>-family_gate_era_census-<from>-<n>.tsv`, the run's log beside it with the binary's sha256 and the lease line); every binary a pinned copy under `/srv/builds/_adv-family-gate/bin/<tag>/` (fg2 9b7f764a for the first corners, fg5 e7a50f8d for the width-4 and lossy-base strata). Cost: about 10 core-seconds per era; 54.4 core-hours in all (1.7 box-hours of 32-core slots) on build-1 (random, lossy cap, width 4) and build-3 (shape 64, lossy base, width 4 plus lossy cap), every run under `lease pool` at class measure. The control: `v5_attempts_census` on the shipped class v5 draw over 10,000 seeds of the F8 label space (build-1, 24 cores, 1,534 s, binary 4bec799c, `logs/base-v5_attempts_census-1000-10000.log`): r = 0.6854, mean attempt 2.179, 0 exhaustions, (a') 82.5 percent of rejections, (c''') 0.9 percent, the row-90 reading reproduced.
|
||||||
|
|
||||||
Voided and stated: the first width-4 stratum (3,000 eras, 11:2x to 11:4x BST) ran at width 1, because `LoadClass::era` with a one-entry allowed set is the pinned path and redraws to the base's widest width; the fix builds the pinned class by hand (the era drawn over the one-entry set, the mix one-hot); the void rows are not in this directory and the stratum was re-run under its own label space. The first corner strata (lossy cap, shape 64) share the random stratum's label space, so where the random draw happened to land on the pinned value the era is the same program in both; the later strata use per-stratum labels.
|
Voided and stated: the first width-4 stratum (3,000 eras, 11:2x to 11:4x BST) ran at width 1, because `LoadClass::era` with a one-entry allowed set is the pinned path and redraws to the base's widest width; the fix builds the pinned class by hand (the era drawn over the one-entry set, the mix one-hot); the void rows are not in this directory and the stratum was re-run under its own label space. The first corner strata (lossy cap, shape 64) share the random stratum's label space, so where the random draw happened to land on the pinned value the era is the same program in both; the later strata use per-stratum labels.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,8 @@
|
||||||
From 4c408ef180a2f9a321756e10de224b7610c37cbc Mon Sep 17 00:00:00 2001
|
|
||||||
From: igneum-labs <337424239+[removed]>
|
|
||||||
Date: Thu, 8 Oct 2026 11:15:23 +0100
|
|
||||||
Subject: [PATCH 1/6] class v6 family-gate harness: the acceptance keyed on the
|
|
||||||
family's shapes behind IGNEUM_FAMILY_GATE (never a chain path), per-thread
|
|
||||||
weight tables, family_site_stats (largest 256-item bucket, index-bit bias)
|
|
||||||
and the family_gate_era_census test (one drawn era per seed, every parameter
|
|
||||||
from the era's stream, one TSV row per era)
|
|
||||||
|
|
||||||
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
|
||||||
---
|
|
||||||
igneum-pow/src/accept.rs | 295 +++++++++++++++++++++++++++++++++++-
|
|
||||||
igneum-pow/src/generator.rs | 21 ++-
|
|
||||||
2 files changed, 311 insertions(+), 5 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/igneum-pow/src/accept.rs b/igneum-pow/src/accept.rs
|
diff --git a/igneum-pow/src/accept.rs b/igneum-pow/src/accept.rs
|
||||||
index 808451a4..7035c82e 100644
|
index 808451a4..533ec672 100644
|
||||||
--- a/igneum-pow/src/accept.rs
|
--- a/igneum-pow/src/accept.rs
|
||||||
+++ b/igneum-pow/src/accept.rs
|
+++ b/igneum-pow/src/accept.rs
|
||||||
@@ -241,7 +241,100 @@ pub struct SiteIndexStats {
|
@@ -241,7 +241,105 @@ pub struct SiteIndexStats {
|
||||||
|
|
||||||
/// The window of a load site in words at the rule's dataset: `2^28 >> min(win, 2)`.
|
/// The window of a load site in words at the rule's dataset: `2^28 >> min(win, 2)`.
|
||||||
pub fn site_window_words(ins: &Instr) -> u64 {
|
pub fn site_window_words(ins: &Instr) -> u64 {
|
||||||
|
|
@ -35,7 +20,12 @@ index 808451a4..7035c82e 100644
|
||||||
+pub struct FamilySiteStats {
|
+pub struct FamilySiteStats {
|
||||||
+ pub base: SiteIndexStats,
|
+ pub base: SiteIndexStats,
|
||||||
+ pub bucket_ratio: f64,
|
+ pub bucket_ratio: f64,
|
||||||
|
+ /// the same excess in sigma of the bucket's own expectation (a clean full-window site's maximum over 65,536
|
||||||
|
+ /// Poisson(16) buckets is about +4.4 sigma, a ratio of 2.1; the quarter-bit tail of 8 October read 3.1x to 5.6x)
|
||||||
|
+ pub bucket_z: f64,
|
||||||
+ pub bucket_id: u32,
|
+ pub bucket_id: u32,
|
||||||
|
+ /// the site's window draw `k_off` (0 the dataset, 1 a half, 2 a quarter)
|
||||||
|
+ pub win: u8,
|
||||||
+ pub bit_z: f64,
|
+ pub bit_z: f64,
|
||||||
+ pub bit: u8,
|
+ pub bit: u8,
|
||||||
+}
|
+}
|
||||||
|
|
@ -113,13 +103,13 @@ index 808451a4..7035c82e 100644
|
||||||
+ }
|
+ }
|
||||||
+ i = j;
|
+ i = j;
|
||||||
+ }
|
+ }
|
||||||
+ out.push(FamilySiteStats { base: st, bucket_ratio: bmax as f64 / expect, bucket_id: bid, bit_z, bit });
|
+ out.push(FamilySiteStats { base: st, bucket_ratio: bmax as f64 / expect, bucket_z: (bmax as f64 - expect) / expect.sqrt(), bucket_id: bid, win: k as u8, bit_z, bit });
|
||||||
+ }
|
+ }
|
||||||
+ Ok(out)
|
+ Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One interpreter run over `units` units with every load site's word indices kept, then per site the sorted
|
/// One interpreter run over `units` units with every load site's word indices kept, then per site the sorted
|
||||||
@@ -353,11 +446,38 @@ pub fn check_indices_v5(p: &Program) -> Result<(), Reject> {
|
@@ -353,11 +451,38 @@ pub fn check_indices_v5(p: &Program) -> Result<(), Reject> {
|
||||||
/// Whether `class` is the class v4 shape (the 256-instruction shadow block over the class v3 base, the pass count and
|
/// Whether `class` is the class v4 shape (the 256-instruction shadow block over the class v3 base, the pass count and
|
||||||
/// the era set aside): the shape the sub-version 2 rules (a') and (c') apply to, on every draw path.
|
/// the era set aside): the shape the sub-version 2 rules (a') and (c') apply to, on every draw path.
|
||||||
pub fn is_class_v4_shape(class: &LoadClass) -> bool {
|
pub fn is_class_v4_shape(class: &LoadClass) -> bool {
|
||||||
|
|
@ -158,7 +148,7 @@ index 808451a4..7035c82e 100644
|
||||||
/// One pass of the dataflow freshness over the base program then the shadow block (the order of one iteration),
|
/// One pass of the dataflow freshness over the base program then the shadow block (the order of one iteration),
|
||||||
/// from `fresh`; `check` reports the first load that reads a register that is not fresh. The rule (AP-F8-1,
|
/// from `fresh`; `check` reports the first load that reads a register that is not fresh. The rule (AP-F8-1,
|
||||||
/// `docs/analysis/ca3-v4-uniform.md`): a load leaves its destination fresh only if its source was (a saturated
|
/// `docs/analysis/ca3-v4-uniform.md`): a load leaves its destination fresh only if its source was (a saturated
|
||||||
@@ -1262,4 +1382,177 @@ mod tests {
|
@@ -1262,4 +1387,219 @@ mod tests {
|
||||||
assert!(check(&generate(s)).is_ok());
|
assert!(check(&generate(s)).is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -191,10 +181,15 @@ index 808451a4..7035c82e 100644
|
||||||
+ let pin_mixer = std::env::var("IGNEUM_FG_MIXER").ok().and_then(|v| v.parse::<u8>().ok());
|
+ let pin_mixer = std::env::var("IGNEUM_FG_MIXER").ok().and_then(|v| v.parse::<u8>().ok());
|
||||||
+ let pin_width = std::env::var("IGNEUM_FG_WIDTH").ok().and_then(|v| v.parse::<u8>().ok());
|
+ let pin_width = std::env::var("IGNEUM_FG_WIDTH").ok().and_then(|v| v.parse::<u8>().ok());
|
||||||
+ let lossy_cap = std::env::var_os("IGNEUM_FG_LOSSY_CAP").is_some();
|
+ let lossy_cap = std::env::var_os("IGNEUM_FG_LOSSY_CAP").is_some();
|
||||||
|
+ // the proposed band after the 12:00 BST reading of 8 October: B on the injecting families only, the three lossy
|
||||||
|
+ // families (or, mul, mulhi) never raised above their base
|
||||||
|
+ let lossy_base = std::env::var_os("IGNEUM_FG_LOSSY_BASE").is_some();
|
||||||
|
+ // the lossy-share curve (the full report's item 3): or, mul and mulhi each at exactly +n points
|
||||||
|
+ let lossy_plus: Option<i64> = std::env::var("IGNEUM_FG_LOSSY_PLUS").ok().and_then(|v| v.parse().ok());
|
||||||
+ let out_path = std::env::var("IGNEUM_FG_OUT").unwrap_or_else(|_| "family-gate.tsv".to_string());
|
+ let out_path = std::env::var("IGNEUM_FG_OUT").unwrap_or_else(|_| "family-gate.tsv".to_string());
|
||||||
+ let out = Mutex::new(std::fs::File::create(&out_path).expect("IGNEUM_FG_OUT"));
|
+ let out = Mutex::new(std::fs::File::create(&out_path).expect("IGNEUM_FG_OUT"));
|
||||||
+ let wnames: Vec<String> = NONLOAD_WEIGHTS.iter().map(|(o, _)| format!("w_{o:?}").to_lowercase()).collect();
|
+ let wnames: Vec<String> = NONLOAD_WEIGHTS.iter().map(|(o, _)| format!("w_{o:?}").to_lowercase()).collect();
|
||||||
+ writeln!(out.lock().unwrap(), "k\tshape\treps\tmixer\twidth\tR\tM\tpos\t{}\tattempt\tcandidates\ta_prime\ta\tb\tc_const\tc_lane\tc_sat\tc_bias\tc_distinct\tc1_sat_source\tc2_low_entropy\tc3_hot_item\tmin_ratio\tmin_site\ttop_count_max\tbucket_ratio_max\tbucket_site\tbit_z_max\tbit_site\tbit\tsecs", wnames.join("\t")).unwrap();
|
+ writeln!(out.lock().unwrap(), "k\tshape\treps\tmixer\twidth\tR\tM\tpos\t{}\tattempt\tcandidates\ta_prime\ta\tb\tc_const\tc_lane\tc_sat\tc_bias\tc_distinct\tc1_sat_source\tc2_low_entropy\tc3_hot_item\tmin_ratio\tmin_site\ttop_count_max\tbucket_ratio_max\tbucket_z_max\tbucket_site\tbucket_win\tbit_z_max\tbit_site\tbit_win\tbit\tlast_resort_k\tsecs", wnames.join("\t")).unwrap();
|
||||||
+ let part_ix = |r: &Reject| -> usize {
|
+ let part_ix = |r: &Reject| -> usize {
|
||||||
+ match r {
|
+ match r {
|
||||||
+ Reject::UnfreshLoadSource { .. } => 0,
|
+ Reject::UnfreshLoadSource { .. } => 0,
|
||||||
|
|
@ -241,6 +236,14 @@ index 808451a4..7035c82e 100644
|
||||||
+ if lossy_cap && matches!(op, Op::Or | Op::Mul | Op::MulHi) {
|
+ if lossy_cap && matches!(op, Op::Or | Op::Mul | Op::MulHi) {
|
||||||
+ d = b_pts;
|
+ d = b_pts;
|
||||||
+ }
|
+ }
|
||||||
|
+ if lossy_base && matches!(op, Op::Or | Op::Mul | Op::MulHi) && d > 0 {
|
||||||
|
+ d = 0;
|
||||||
|
+ }
|
||||||
|
+ if let Some(n) = lossy_plus {
|
||||||
|
+ if matches!(op, Op::Or | Op::Mul | Op::MulHi) {
|
||||||
|
+ d = n;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
+ raw[i] = (*wgt as i64 + d).max(1);
|
+ raw[i] = (*wgt as i64 + d).max(1);
|
||||||
+ }
|
+ }
|
||||||
+ // renormalise to 75 by largest remainder
|
+ // renormalise to 75 by largest remainder
|
||||||
|
|
@ -268,12 +271,21 @@ index 808451a4..7035c82e 100644
|
||||||
+ set_family_weights(Some(weights));
|
+ set_family_weights(Some(weights));
|
||||||
+ let reps = (V4_SHADOW_INSTRS as u32 * crate::generator::V4_SHADOW_REPS as u32 / shape as u32) as u16;
|
+ let reps = (V4_SHADOW_INSTRS as u32 * crate::generator::V4_SHADOW_REPS as u32 / shape as u32) as u16;
|
||||||
+ let base = LoadClass { shadow: Some(ShadowClass { instrs: shape, reps }), mixer_mult: mixer, ..V5_CLASS };
|
+ let base = LoadClass { shadow: Some(ShadowClass { instrs: shape, reps }), mixer_mult: mixer, ..V5_CLASS };
|
||||||
+ let allowed: &[u8] = match pin_width {
|
+ // a one-entry set is the pinned-width path of LoadClass::era (the mix stays the base's and the draw is
|
||||||
+ Some(1) => &[1],
|
+ // redrawn to the base's widest, 1 word) and the set must be strictly ascending, so a pinned width is
|
||||||
+ Some(4) => &[4],
|
+ // built by hand: the era drawn over the one-entry set and the mix one-hot on that width, as the
|
||||||
+ _ => &[1, 4],
|
+ // drawn-set path does
|
||||||
|
+ let class = match pin_width {
|
||||||
|
+ Some(w) if w == 1 || w == 4 => {
|
||||||
|
+ let mut c = base;
|
||||||
|
+ let i = crate::generator::WIDTH_WORDS.iter().position(|&x| x == w).unwrap();
|
||||||
|
+ c.mix = [0, 0, 0];
|
||||||
|
+ c.mix[i] = 100;
|
||||||
|
+ c.era = Some(crate::generator::era_draw(&era, &[w]));
|
||||||
|
+ c
|
||||||
|
+ }
|
||||||
|
+ _ => LoadClass::era(base, &era, &[1, 4]),
|
||||||
+ };
|
+ };
|
||||||
+ let class = LoadClass::era(base, &era, allowed);
|
|
||||||
+ let e = class.era.unwrap();
|
+ let e = class.era.unwrap();
|
||||||
+ let lbl = f8_label(&epoch);
|
+ let lbl = f8_label(&epoch);
|
||||||
+ let mut parts = [0u32; 11];
|
+ let mut parts = [0u32; 11];
|
||||||
|
|
@ -288,390 +300,6 @@ index 808451a4..7035c82e 100644
|
||||||
+ Err(r) => parts[part_ix(&r)] += 1,
|
+ Err(r) => parts[part_ix(&r)] += 1,
|
||||||
+ }
|
+ }
|
||||||
+ }
|
+ }
|
||||||
+ set_family_weights(None);
|
|
||||||
+ let candidates: u32 = parts.iter().sum::<u32>() + accepted.is_some() as u32;
|
|
||||||
+ let (attempt, stats_row) = match &accepted {
|
|
||||||
+ Some((att, p)) => {
|
|
||||||
+ let st = family_site_stats(p, ACCEPT_UNITS_DISTINCT_V4).expect("the accepted program runs");
|
|
||||||
+ let n = (ACCEPT_UNITS_DISTINCT_V4 * LANES * ITERATIONS) as f64;
|
|
||||||
+ let loads: Vec<&Instr> = p.instrs.iter().filter(|i| i.op.is_load()).collect();
|
|
||||||
+ let (mut min_r, mut min_s) = (f64::MAX, 0usize);
|
|
||||||
+ let (mut bmax, mut bsite) = (0.0f64, 0usize);
|
|
||||||
+ let (mut zmax, mut zsite, mut zbit) = (0.0f64, 0usize, 0u8);
|
|
||||||
+ let mut top = 0u32;
|
|
||||||
+ for (s, fs) in st.iter().enumerate() {
|
|
||||||
+ let r = site_ratio(fs.base.distinct, n, site_window_words(loads[s]));
|
|
||||||
+ if r < min_r {
|
|
||||||
+ min_r = r;
|
|
||||||
+ min_s = s;
|
|
||||||
+ }
|
|
||||||
+ if fs.bucket_ratio > bmax {
|
|
||||||
+ bmax = fs.bucket_ratio;
|
|
||||||
+ bsite = s;
|
|
||||||
+ }
|
|
||||||
+ if fs.bit_z.abs() > zmax.abs() {
|
|
||||||
+ zmax = fs.bit_z;
|
|
||||||
+ zsite = s;
|
|
||||||
+ zbit = fs.bit;
|
|
||||||
+ }
|
|
||||||
+ top = top.max(fs.base.top_count);
|
|
||||||
+ }
|
|
||||||
+ (*att as i64, format!("{min_r:.5}\t{min_s}\t{top}\t{bmax:.3}\t{bsite}\t{zmax:.2}\t{zsite}\t{zbit}"))
|
|
||||||
+ }
|
|
||||||
+ None => (-1, "\t\t\t\t\t\t\t".to_string()),
|
|
||||||
+ };
|
|
||||||
+ let wcols: Vec<String> = weights.iter().map(|(_, x)| x.to_string()).collect();
|
|
||||||
+ let pcols: Vec<String> = parts.iter().map(|x| x.to_string()).collect();
|
|
||||||
+ let row = format!("{k}\t{shape}\t{reps}\t{mixer}\t{}\t{}\t{:08x}\t{:?}\t{}\t{attempt}\t{candidates}\t{}\t{stats_row}\t{:.1}", e.width_words, e.stride_rot, e.stride_mul, e.pos, wcols.join("\t"), pcols.join("\t"), ts.elapsed().as_secs_f64());
|
|
||||||
+ writeln!(out.lock().unwrap(), "{row}").unwrap();
|
|
||||||
+ let d = done.fetch_add(1, Ordering::Relaxed) + 1;
|
|
||||||
+ if d % 100 == 0 {
|
|
||||||
+ println!("family_gate_era_census: {d} eras in {:.0} s", t0.elapsed().as_secs_f64());
|
|
||||||
+ out.lock().unwrap().flush().unwrap();
|
|
||||||
+ }
|
|
||||||
+ });
|
|
||||||
+ }
|
|
||||||
+ });
|
|
||||||
+ out.lock().unwrap().flush().unwrap();
|
|
||||||
+ println!("family_gate_era_census: {} eras {from}..{} in {:.0} s on {threads} threads -> {out_path}", done.load(Ordering::Relaxed), from + seeds, t0.elapsed().as_secs_f64());
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
diff --git a/igneum-pow/src/generator.rs b/igneum-pow/src/generator.rs
|
|
||||||
index 3f2d6723..00730eac 100644
|
|
||||||
--- a/igneum-pow/src/generator.rs
|
|
||||||
+++ b/igneum-pow/src/generator.rs
|
|
||||||
@@ -1316,6 +1316,18 @@ pub const NONLOAD_WEIGHTS: [(Op, u64); 10] = [
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Version 1 weights (retired). Sum 100, load at 25 percent.
|
|
||||||
+thread_local! {
|
|
||||||
+ static FAMILY_WEIGHTS: std::cell::Cell<Option<[(Op, u64); 10]>> = const { std::cell::Cell::new(None) };
|
|
||||||
+}
|
|
||||||
+/// Class v6 family-gate harness (8 October 2026): the ten non-load weights this thread's draws use when set, else
|
|
||||||
+/// [`NONLOAD_WEIGHTS`]. Set only by the harness census (`accept::tests::family_gate_era_census`), never on a chain path.
|
|
||||||
+pub fn set_family_weights(w: Option<[(Op, u64); 10]>) {
|
|
||||||
+ FAMILY_WEIGHTS.with(|c| c.set(w));
|
|
||||||
+}
|
|
||||||
+pub fn family_weights() -> [(Op, u64); 10] {
|
|
||||||
+ FAMILY_WEIGHTS.with(|c| c.get()).unwrap_or(NONLOAD_WEIGHTS)
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
pub const OP_WEIGHTS: [(Op, u64); 11] = [
|
|
||||||
(Op::Load, 25),
|
|
||||||
(Op::Add, 12),
|
|
||||||
@@ -1436,8 +1448,9 @@ pub fn candidate_from_words_class(
|
|
||||||
// era set aside) on EVERY draw path, era or not, so a census through candidate_class reads the same stream as
|
|
||||||
// the chain; v2, v3 and every other class take no part. The draw order and the stream are otherwise the same.
|
|
||||||
// class v5 (docs/design/class-v5-stored-state.md) draws under the same rule: its state flag is set aside here too
|
|
||||||
- let source_rule_v4 = matches!(class.shadow, Some(ShadowClass { instrs: V4_SHADOW_INSTRS, .. }))
|
|
||||||
- && LoadClass { era: None, shadow: None, state: false, ..class } == LoadClass { shadow: None, ..V4_CLASS };
|
|
||||||
+ // class v6 family-gate harness (8 October 2026): the same predicate as the acceptance's, generalised to the
|
|
||||||
+ // family's shapes only when IGNEUM_FAMILY_GATE is set (never on a chain path; see accept::is_family_shape).
|
|
||||||
+ let source_rule_v4 = crate::accept::is_class_v4_shape(&class);
|
|
||||||
let mut fresh = [false; 8];
|
|
||||||
let mut fresh_value = [true; 8];
|
|
||||||
// the shared-operand idiom (AP-F8-1, sub-version 3): after `or d |= s`, a later `xor d ^= s` or `sub d -= s` with
|
|
||||||
@@ -1449,7 +1462,7 @@ pub fn candidate_from_words_class(
|
|
||||||
for k in 0..INSTR_COUNT {
|
|
||||||
let mut roll = rng.below(75);
|
|
||||||
let mut op = Op::Add;
|
|
||||||
- for &(o, w) in &NONLOAD_WEIGHTS {
|
|
||||||
+ for &(o, w) in &family_weights() {
|
|
||||||
if roll < w {
|
|
||||||
op = o;
|
|
||||||
break;
|
|
||||||
@@ -1556,7 +1569,7 @@ pub fn candidate_from_words_class(
|
|
||||||
for _ in 0..sh.instrs {
|
|
||||||
let mut roll = rng.below(75);
|
|
||||||
let mut op = Op::Add;
|
|
||||||
- for &(o, w) in &NONLOAD_WEIGHTS {
|
|
||||||
+ for &(o, w) in &family_weights() {
|
|
||||||
if roll < w {
|
|
||||||
op = o;
|
|
||||||
break;
|
|
||||||
--
|
|
||||||
2.41.0
|
|
||||||
|
|
||||||
|
|
||||||
From 1d5f551e0bec6840377c5e5bc3b98fe00e2cb5fa Mon Sep 17 00:00:00 2001
|
|
||||||
From: igneum-labs <337424239+[removed]>
|
|
||||||
Date: Thu, 8 Oct 2026 11:22:50 +0100
|
|
||||||
Subject: [PATCH 2/6] family-gate harness: the bucket excess in sigma and each
|
|
||||||
site's window draw beside the ratio (a clean full-window site reads a ratio
|
|
||||||
of 2.1 at +4.4 sigma, so the ratio alone cannot carry a bound); the
|
|
||||||
retired-weights doc comment back on its item
|
|
||||||
|
|
||||||
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
|
||||||
---
|
|
||||||
igneum-pow/src/accept.rs | 22 +++++++++++++++-------
|
|
||||||
igneum-pow/src/generator.rs | 2 +-
|
|
||||||
2 files changed, 16 insertions(+), 8 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/igneum-pow/src/accept.rs b/igneum-pow/src/accept.rs
|
|
||||||
index 7035c82e..f9febaab 100644
|
|
||||||
--- a/igneum-pow/src/accept.rs
|
|
||||||
+++ b/igneum-pow/src/accept.rs
|
|
||||||
@@ -254,7 +254,12 @@ pub fn site_window_words(ins: &Instr) -> u64 {
|
|
||||||
pub struct FamilySiteStats {
|
|
||||||
pub base: SiteIndexStats,
|
|
||||||
pub bucket_ratio: f64,
|
|
||||||
+ /// the same excess in sigma of the bucket's own expectation (a clean full-window site's maximum over 65,536
|
|
||||||
+ /// Poisson(16) buckets is about +4.4 sigma, a ratio of 2.1; the quarter-bit tail of 8 October read 3.1x to 5.6x)
|
|
||||||
+ pub bucket_z: f64,
|
|
||||||
pub bucket_id: u32,
|
|
||||||
+ /// the site's window draw `k_off` (0 the dataset, 1 a half, 2 a quarter)
|
|
||||||
+ pub win: u8,
|
|
||||||
pub bit_z: f64,
|
|
||||||
pub bit: u8,
|
|
||||||
}
|
|
||||||
@@ -332,7 +337,7 @@ pub fn family_site_stats(p: &Program, units: usize) -> Result<Vec<FamilySiteStat
|
|
||||||
}
|
|
||||||
i = j;
|
|
||||||
}
|
|
||||||
- out.push(FamilySiteStats { base: st, bucket_ratio: bmax as f64 / expect, bucket_id: bid, bit_z, bit });
|
|
||||||
+ out.push(FamilySiteStats { base: st, bucket_ratio: bmax as f64 / expect, bucket_z: (bmax as f64 - expect) / expect.sqrt(), bucket_id: bid, win: k as u8, bit_z, bit });
|
|
||||||
}
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
@@ -1414,7 +1419,7 @@ mod tests {
|
|
||||||
let out_path = std::env::var("IGNEUM_FG_OUT").unwrap_or_else(|_| "family-gate.tsv".to_string());
|
|
||||||
let out = Mutex::new(std::fs::File::create(&out_path).expect("IGNEUM_FG_OUT"));
|
|
||||||
let wnames: Vec<String> = NONLOAD_WEIGHTS.iter().map(|(o, _)| format!("w_{o:?}").to_lowercase()).collect();
|
|
||||||
- writeln!(out.lock().unwrap(), "k\tshape\treps\tmixer\twidth\tR\tM\tpos\t{}\tattempt\tcandidates\ta_prime\ta\tb\tc_const\tc_lane\tc_sat\tc_bias\tc_distinct\tc1_sat_source\tc2_low_entropy\tc3_hot_item\tmin_ratio\tmin_site\ttop_count_max\tbucket_ratio_max\tbucket_site\tbit_z_max\tbit_site\tbit\tsecs", wnames.join("\t")).unwrap();
|
|
||||||
+ writeln!(out.lock().unwrap(), "k\tshape\treps\tmixer\twidth\tR\tM\tpos\t{}\tattempt\tcandidates\ta_prime\ta\tb\tc_const\tc_lane\tc_sat\tc_bias\tc_distinct\tc1_sat_source\tc2_low_entropy\tc3_hot_item\tmin_ratio\tmin_site\ttop_count_max\tbucket_ratio_max\tbucket_z_max\tbucket_site\tbucket_win\tbit_z_max\tbit_site\tbit_win\tbit\tsecs", wnames.join("\t")).unwrap();
|
|
||||||
let part_ix = |r: &Reject| -> usize {
|
|
||||||
match r {
|
|
||||||
Reject::UnfreshLoadSource { .. } => 0,
|
|
||||||
@@ -1516,8 +1521,8 @@ mod tests {
|
|
||||||
let n = (ACCEPT_UNITS_DISTINCT_V4 * LANES * ITERATIONS) as f64;
|
|
||||||
let loads: Vec<&Instr> = p.instrs.iter().filter(|i| i.op.is_load()).collect();
|
|
||||||
let (mut min_r, mut min_s) = (f64::MAX, 0usize);
|
|
||||||
- let (mut bmax, mut bsite) = (0.0f64, 0usize);
|
|
||||||
- let (mut zmax, mut zsite, mut zbit) = (0.0f64, 0usize, 0u8);
|
|
||||||
+ let (mut bmax, mut bz, mut bsite, mut bwin) = (0.0f64, 0.0f64, 0usize, 0u8);
|
|
||||||
+ let (mut zmax, mut zsite, mut zbit, mut zwin) = (0.0f64, 0usize, 0u8, 0u8);
|
|
||||||
let mut top = 0u32;
|
|
||||||
for (s, fs) in st.iter().enumerate() {
|
|
||||||
let r = site_ratio(fs.base.distinct, n, site_window_words(loads[s]));
|
|
||||||
@@ -1525,20 +1530,23 @@ mod tests {
|
|
||||||
min_r = r;
|
|
||||||
min_s = s;
|
|
||||||
}
|
|
||||||
- if fs.bucket_ratio > bmax {
|
|
||||||
+ if fs.bucket_z > bz {
|
|
||||||
bmax = fs.bucket_ratio;
|
|
||||||
+ bz = fs.bucket_z;
|
|
||||||
bsite = s;
|
|
||||||
+ bwin = fs.win;
|
|
||||||
}
|
|
||||||
if fs.bit_z.abs() > zmax.abs() {
|
|
||||||
zmax = fs.bit_z;
|
|
||||||
zsite = s;
|
|
||||||
zbit = fs.bit;
|
|
||||||
+ zwin = fs.win;
|
|
||||||
}
|
|
||||||
top = top.max(fs.base.top_count);
|
|
||||||
}
|
|
||||||
- (*att as i64, format!("{min_r:.5}\t{min_s}\t{top}\t{bmax:.3}\t{bsite}\t{zmax:.2}\t{zsite}\t{zbit}"))
|
|
||||||
+ (*att as i64, format!("{min_r:.5}\t{min_s}\t{top}\t{bmax:.3}\t{bz:.2}\t{bsite}\t{bwin}\t{zmax:.2}\t{zsite}\t{zwin}\t{zbit}"))
|
|
||||||
}
|
|
||||||
- None => (-1, "\t\t\t\t\t\t\t".to_string()),
|
|
||||||
+ None => (-1, "\t\t\t\t\t\t\t\t\t\t".to_string()),
|
|
||||||
};
|
|
||||||
let wcols: Vec<String> = weights.iter().map(|(_, x)| x.to_string()).collect();
|
|
||||||
let pcols: Vec<String> = parts.iter().map(|x| x.to_string()).collect();
|
|
||||||
diff --git a/igneum-pow/src/generator.rs b/igneum-pow/src/generator.rs
|
|
||||||
index 00730eac..6e611d45 100644
|
|
||||||
--- a/igneum-pow/src/generator.rs
|
|
||||||
+++ b/igneum-pow/src/generator.rs
|
|
||||||
@@ -1315,7 +1315,6 @@ pub const NONLOAD_WEIGHTS: [(Op, u64); 10] = [
|
|
||||||
(Op::Or, 4),
|
|
||||||
];
|
|
||||||
|
|
||||||
-/// Version 1 weights (retired). Sum 100, load at 25 percent.
|
|
||||||
thread_local! {
|
|
||||||
static FAMILY_WEIGHTS: std::cell::Cell<Option<[(Op, u64); 10]>> = const { std::cell::Cell::new(None) };
|
|
||||||
}
|
|
||||||
@@ -1328,6 +1327,7 @@ pub fn family_weights() -> [(Op, u64); 10] {
|
|
||||||
FAMILY_WEIGHTS.with(|c| c.get()).unwrap_or(NONLOAD_WEIGHTS)
|
|
||||||
}
|
|
||||||
|
|
||||||
+/// Version 1 weights (retired). Sum 100, load at 25 percent.
|
|
||||||
pub const OP_WEIGHTS: [(Op, u64); 11] = [
|
|
||||||
(Op::Load, 25),
|
|
||||||
(Op::Add, 12),
|
|
||||||
--
|
|
||||||
2.41.0
|
|
||||||
|
|
||||||
|
|
||||||
From adce49deea93678b0165fbaa76349a0b347794c5 Mon Sep 17 00:00:00 2001
|
|
||||||
From: igneum-labs <337424239+[removed]>
|
|
||||||
Date: Thu, 8 Oct 2026 11:33:16 +0100
|
|
||||||
Subject: [PATCH 3/6] family-gate harness: a pinned width is a two-entry
|
|
||||||
allowed set (the one-entry set is LoadClass::era's pinned path and redraws to
|
|
||||||
the base's 1 word: the first width-4 stratum ran at width 1, void)
|
|
||||||
|
|
||||||
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
|
||||||
---
|
|
||||||
igneum-pow/src/accept.rs | 7 +++++--
|
|
||||||
1 file changed, 5 insertions(+), 2 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/igneum-pow/src/accept.rs b/igneum-pow/src/accept.rs
|
|
||||||
index f9febaab..c92a54ce 100644
|
|
||||||
--- a/igneum-pow/src/accept.rs
|
|
||||||
+++ b/igneum-pow/src/accept.rs
|
|
||||||
@@ -1493,9 +1493,12 @@ mod tests {
|
|
||||||
set_family_weights(Some(weights));
|
|
||||||
let reps = (V4_SHADOW_INSTRS as u32 * crate::generator::V4_SHADOW_REPS as u32 / shape as u32) as u16;
|
|
||||||
let base = LoadClass { shadow: Some(ShadowClass { instrs: shape, reps }), mixer_mult: mixer, ..V5_CLASS };
|
|
||||||
+ // a one-entry set is the pinned-width path of LoadClass::era (the mix stays the base's and the draw is
|
|
||||||
+ // redrawn to the base's widest, 1 word), so a pin is a two-entry set of one value: the era draw then
|
|
||||||
+ // sets the mix one-hot on that width as it does for the drawn set
|
|
||||||
let allowed: &[u8] = match pin_width {
|
|
||||||
- Some(1) => &[1],
|
|
||||||
- Some(4) => &[4],
|
|
||||||
+ Some(1) => &[1, 1],
|
|
||||||
+ Some(4) => &[4, 4],
|
|
||||||
_ => &[1, 4],
|
|
||||||
};
|
|
||||||
let class = LoadClass::era(base, &era, allowed);
|
|
||||||
--
|
|
||||||
2.41.0
|
|
||||||
|
|
||||||
|
|
||||||
From a1a7e1318961869b0ef074ec753e1bb45544127b Mon Sep 17 00:00:00 2001
|
|
||||||
From: igneum-labs <337424239+[removed]>
|
|
||||||
Date: Thu, 8 Oct 2026 11:41:57 +0100
|
|
||||||
Subject: [PATCH 4/6] family-gate harness: IGNEUM_FG_LOSSY_BASE, the proposed
|
|
||||||
band (B on the injecting families only, or/mul/mulhi never raised)
|
|
||||||
|
|
||||||
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
|
||||||
---
|
|
||||||
igneum-pow/src/accept.rs | 6 ++++++
|
|
||||||
1 file changed, 6 insertions(+)
|
|
||||||
|
|
||||||
diff --git a/igneum-pow/src/accept.rs b/igneum-pow/src/accept.rs
|
|
||||||
index c92a54ce..d6551ec5 100644
|
|
||||||
--- a/igneum-pow/src/accept.rs
|
|
||||||
+++ b/igneum-pow/src/accept.rs
|
|
||||||
@@ -1416,6 +1416,9 @@ mod tests {
|
|
||||||
let pin_mixer = std::env::var("IGNEUM_FG_MIXER").ok().and_then(|v| v.parse::<u8>().ok());
|
|
||||||
let pin_width = std::env::var("IGNEUM_FG_WIDTH").ok().and_then(|v| v.parse::<u8>().ok());
|
|
||||||
let lossy_cap = std::env::var_os("IGNEUM_FG_LOSSY_CAP").is_some();
|
|
||||||
+ // the proposed band after the 12:00 BST reading of 8 October: B on the injecting families only, the three lossy
|
|
||||||
+ // families (or, mul, mulhi) never raised above their base
|
|
||||||
+ let lossy_base = std::env::var_os("IGNEUM_FG_LOSSY_BASE").is_some();
|
|
||||||
let out_path = std::env::var("IGNEUM_FG_OUT").unwrap_or_else(|_| "family-gate.tsv".to_string());
|
|
||||||
let out = Mutex::new(std::fs::File::create(&out_path).expect("IGNEUM_FG_OUT"));
|
|
||||||
let wnames: Vec<String> = NONLOAD_WEIGHTS.iter().map(|(o, _)| format!("w_{o:?}").to_lowercase()).collect();
|
|
||||||
@@ -1466,6 +1469,9 @@ mod tests {
|
|
||||||
if lossy_cap && matches!(op, Op::Or | Op::Mul | Op::MulHi) {
|
|
||||||
d = b_pts;
|
|
||||||
}
|
|
||||||
+ if lossy_base && matches!(op, Op::Or | Op::Mul | Op::MulHi) && d > 0 {
|
|
||||||
+ d = 0;
|
|
||||||
+ }
|
|
||||||
raw[i] = (*wgt as i64 + d).max(1);
|
|
||||||
}
|
|
||||||
// renormalise to 75 by largest remainder
|
|
||||||
--
|
|
||||||
2.41.0
|
|
||||||
|
|
||||||
|
|
||||||
From e94f4db05dd3fd76e356de8bdb4de70455144e5c Mon Sep 17 00:00:00 2001
|
|
||||||
From: igneum-labs <337424239+[removed]>
|
|
||||||
Date: Thu, 8 Oct 2026 11:43:12 +0100
|
|
||||||
Subject: [PATCH 5/6] family-gate harness: a pinned width builds the class by
|
|
||||||
hand (era_draw over the one-entry set, the mix one-hot); the two-entry set
|
|
||||||
tripped era_draw's ascending assert (w4b exit 101)
|
|
||||||
|
|
||||||
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
|
||||||
---
|
|
||||||
igneum-pow/src/accept.rs | 20 +++++++++++++-------
|
|
||||||
1 file changed, 13 insertions(+), 7 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/igneum-pow/src/accept.rs b/igneum-pow/src/accept.rs
|
|
||||||
index d6551ec5..f38e4399 100644
|
|
||||||
--- a/igneum-pow/src/accept.rs
|
|
||||||
+++ b/igneum-pow/src/accept.rs
|
|
||||||
@@ -1500,14 +1500,20 @@ mod tests {
|
|
||||||
let reps = (V4_SHADOW_INSTRS as u32 * crate::generator::V4_SHADOW_REPS as u32 / shape as u32) as u16;
|
|
||||||
let base = LoadClass { shadow: Some(ShadowClass { instrs: shape, reps }), mixer_mult: mixer, ..V5_CLASS };
|
|
||||||
// a one-entry set is the pinned-width path of LoadClass::era (the mix stays the base's and the draw is
|
|
||||||
- // redrawn to the base's widest, 1 word), so a pin is a two-entry set of one value: the era draw then
|
|
||||||
- // sets the mix one-hot on that width as it does for the drawn set
|
|
||||||
- let allowed: &[u8] = match pin_width {
|
|
||||||
- Some(1) => &[1, 1],
|
|
||||||
- Some(4) => &[4, 4],
|
|
||||||
- _ => &[1, 4],
|
|
||||||
+ // redrawn to the base's widest, 1 word) and the set must be strictly ascending, so a pinned width is
|
|
||||||
+ // built by hand: the era drawn over the one-entry set and the mix one-hot on that width, as the
|
|
||||||
+ // drawn-set path does
|
|
||||||
+ let class = match pin_width {
|
|
||||||
+ Some(w) if w == 1 || w == 4 => {
|
|
||||||
+ let mut c = base;
|
|
||||||
+ let i = crate::generator::WIDTH_WORDS.iter().position(|&x| x == w).unwrap();
|
|
||||||
+ c.mix = [0, 0, 0];
|
|
||||||
+ c.mix[i] = 100;
|
|
||||||
+ c.era = Some(crate::generator::era_draw(&era, &[w]));
|
|
||||||
+ c
|
|
||||||
+ }
|
|
||||||
+ _ => LoadClass::era(base, &era, &[1, 4]),
|
|
||||||
};
|
|
||||||
- let class = LoadClass::era(base, &era, allowed);
|
|
||||||
let e = class.era.unwrap();
|
|
||||||
let lbl = f8_label(&epoch);
|
|
||||||
let mut parts = [0u32; 11];
|
|
||||||
--
|
|
||||||
2.41.0
|
|
||||||
|
|
||||||
|
|
||||||
From 236ef3a5c2ce4bb3f82310d5eabb1b9cadc423c0 Mon Sep 17 00:00:00 2001
|
|
||||||
From: igneum-labs <337424239+[removed]>
|
|
||||||
Date: Thu, 8 Oct 2026 12:26:37 +0100
|
|
||||||
Subject: [PATCH 6/6] family-gate harness: IGNEUM_FG_LOSSY_PLUS=n (the
|
|
||||||
lossy-share curve) and the class v5 last-resort scan on an exhausted era (the
|
|
||||||
first passing k past the cap, or fallback)
|
|
||||||
|
|
||||||
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
|
||||||
---
|
|
||||||
igneum-pow/src/accept.rs | 28 ++++++++++++++++++++++++++--
|
|
||||||
1 file changed, 26 insertions(+), 2 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/igneum-pow/src/accept.rs b/igneum-pow/src/accept.rs
|
|
||||||
index f38e4399..533ec672 100644
|
|
||||||
--- a/igneum-pow/src/accept.rs
|
|
||||||
+++ b/igneum-pow/src/accept.rs
|
|
||||||
@@ -1419,10 +1419,12 @@ mod tests {
|
|
||||||
// the proposed band after the 12:00 BST reading of 8 October: B on the injecting families only, the three lossy
|
|
||||||
// families (or, mul, mulhi) never raised above their base
|
|
||||||
let lossy_base = std::env::var_os("IGNEUM_FG_LOSSY_BASE").is_some();
|
|
||||||
+ // the lossy-share curve (the full report's item 3): or, mul and mulhi each at exactly +n points
|
|
||||||
+ let lossy_plus: Option<i64> = std::env::var("IGNEUM_FG_LOSSY_PLUS").ok().and_then(|v| v.parse().ok());
|
|
||||||
let out_path = std::env::var("IGNEUM_FG_OUT").unwrap_or_else(|_| "family-gate.tsv".to_string());
|
|
||||||
let out = Mutex::new(std::fs::File::create(&out_path).expect("IGNEUM_FG_OUT"));
|
|
||||||
let wnames: Vec<String> = NONLOAD_WEIGHTS.iter().map(|(o, _)| format!("w_{o:?}").to_lowercase()).collect();
|
|
||||||
- writeln!(out.lock().unwrap(), "k\tshape\treps\tmixer\twidth\tR\tM\tpos\t{}\tattempt\tcandidates\ta_prime\ta\tb\tc_const\tc_lane\tc_sat\tc_bias\tc_distinct\tc1_sat_source\tc2_low_entropy\tc3_hot_item\tmin_ratio\tmin_site\ttop_count_max\tbucket_ratio_max\tbucket_z_max\tbucket_site\tbucket_win\tbit_z_max\tbit_site\tbit_win\tbit\tsecs", wnames.join("\t")).unwrap();
|
|
||||||
+ writeln!(out.lock().unwrap(), "k\tshape\treps\tmixer\twidth\tR\tM\tpos\t{}\tattempt\tcandidates\ta_prime\ta\tb\tc_const\tc_lane\tc_sat\tc_bias\tc_distinct\tc1_sat_source\tc2_low_entropy\tc3_hot_item\tmin_ratio\tmin_site\ttop_count_max\tbucket_ratio_max\tbucket_z_max\tbucket_site\tbucket_win\tbit_z_max\tbit_site\tbit_win\tbit\tlast_resort_k\tsecs", wnames.join("\t")).unwrap();
|
|
||||||
let part_ix = |r: &Reject| -> usize {
|
|
||||||
match r {
|
|
||||||
Reject::UnfreshLoadSource { .. } => 0,
|
|
||||||
@@ -1472,6 +1474,11 @@ mod tests {
|
|
||||||
if lossy_base && matches!(op, Op::Or | Op::Mul | Op::MulHi) && d > 0 {
|
|
||||||
d = 0;
|
|
||||||
}
|
|
||||||
+ if let Some(n) = lossy_plus {
|
|
||||||
+ if matches!(op, Op::Or | Op::Mul | Op::MulHi) {
|
|
||||||
+ d = n;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
raw[i] = (*wgt as i64 + d).max(1);
|
|
||||||
}
|
|
||||||
// renormalise to 75 by largest remainder
|
|
||||||
@@ -1528,6 +1535,23 @@ mod tests {
|
|
||||||
Err(r) => parts[part_ix(&r)] += 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+ // an exhausted era takes class v5's last resort: the scan of 256 more candidates past the cap, each
|
+ // an exhausted era takes class v5's last resort: the scan of 256 more candidates past the cap, each
|
||||||
+ // rewritten (or, mul, mulhi to xor) and its stale loads re-sourced, the first that passes the whole
|
+ // rewritten (or, mul, mulhi to xor) and its stale loads re-sourced, the first that passes the whole
|
||||||
+ // rule; the column records that k (cap + i), or "fallback" when none of the 256 passed
|
+ // rule; the column records that k (cap + i), or "fallback" when none of the 256 passed
|
||||||
|
|
@ -689,18 +317,157 @@ index f38e4399..533ec672 100644
|
||||||
+ } else {
|
+ } else {
|
||||||
+ String::new()
|
+ String::new()
|
||||||
+ };
|
+ };
|
||||||
set_family_weights(None);
|
+ set_family_weights(None);
|
||||||
let candidates: u32 = parts.iter().sum::<u32>() + accepted.is_some() as u32;
|
+ let candidates: u32 = parts.iter().sum::<u32>() + accepted.is_some() as u32;
|
||||||
let (attempt, stats_row) = match &accepted {
|
+ let (attempt, stats_row) = match &accepted {
|
||||||
@@ -1565,7 +1589,7 @@ mod tests {
|
+ Some((att, p)) => {
|
||||||
};
|
+ let st = family_site_stats(p, ACCEPT_UNITS_DISTINCT_V4).expect("the accepted program runs");
|
||||||
let wcols: Vec<String> = weights.iter().map(|(_, x)| x.to_string()).collect();
|
+ let n = (ACCEPT_UNITS_DISTINCT_V4 * LANES * ITERATIONS) as f64;
|
||||||
let pcols: Vec<String> = parts.iter().map(|x| x.to_string()).collect();
|
+ let loads: Vec<&Instr> = p.instrs.iter().filter(|i| i.op.is_load()).collect();
|
||||||
- let row = format!("{k}\t{shape}\t{reps}\t{mixer}\t{}\t{}\t{:08x}\t{:?}\t{}\t{attempt}\t{candidates}\t{}\t{stats_row}\t{:.1}", e.width_words, e.stride_rot, e.stride_mul, e.pos, wcols.join("\t"), pcols.join("\t"), ts.elapsed().as_secs_f64());
|
+ let (mut min_r, mut min_s) = (f64::MAX, 0usize);
|
||||||
|
+ let (mut bmax, mut bz, mut bsite, mut bwin) = (0.0f64, 0.0f64, 0usize, 0u8);
|
||||||
|
+ let (mut zmax, mut zsite, mut zbit, mut zwin) = (0.0f64, 0usize, 0u8, 0u8);
|
||||||
|
+ let mut top = 0u32;
|
||||||
|
+ for (s, fs) in st.iter().enumerate() {
|
||||||
|
+ let r = site_ratio(fs.base.distinct, n, site_window_words(loads[s]));
|
||||||
|
+ if r < min_r {
|
||||||
|
+ min_r = r;
|
||||||
|
+ min_s = s;
|
||||||
|
+ }
|
||||||
|
+ if fs.bucket_z > bz {
|
||||||
|
+ bmax = fs.bucket_ratio;
|
||||||
|
+ bz = fs.bucket_z;
|
||||||
|
+ bsite = s;
|
||||||
|
+ bwin = fs.win;
|
||||||
|
+ }
|
||||||
|
+ if fs.bit_z.abs() > zmax.abs() {
|
||||||
|
+ zmax = fs.bit_z;
|
||||||
|
+ zsite = s;
|
||||||
|
+ zbit = fs.bit;
|
||||||
|
+ zwin = fs.win;
|
||||||
|
+ }
|
||||||
|
+ top = top.max(fs.base.top_count);
|
||||||
|
+ }
|
||||||
|
+ (*att as i64, format!("{min_r:.5}\t{min_s}\t{top}\t{bmax:.3}\t{bz:.2}\t{bsite}\t{bwin}\t{zmax:.2}\t{zsite}\t{zwin}\t{zbit}"))
|
||||||
|
+ }
|
||||||
|
+ None => (-1, "\t\t\t\t\t\t\t\t\t\t".to_string()),
|
||||||
|
+ };
|
||||||
|
+ let wcols: Vec<String> = weights.iter().map(|(_, x)| x.to_string()).collect();
|
||||||
|
+ let pcols: Vec<String> = parts.iter().map(|x| x.to_string()).collect();
|
||||||
+ let row = format!("{k}\t{shape}\t{reps}\t{mixer}\t{}\t{}\t{:08x}\t{:?}\t{}\t{attempt}\t{candidates}\t{}\t{stats_row}\t{last_resort_k}\t{:.1}", e.width_words, e.stride_rot, e.stride_mul, e.pos, wcols.join("\t"), pcols.join("\t"), ts.elapsed().as_secs_f64());
|
+ let row = format!("{k}\t{shape}\t{reps}\t{mixer}\t{}\t{}\t{:08x}\t{:?}\t{}\t{attempt}\t{candidates}\t{}\t{stats_row}\t{last_resort_k}\t{:.1}", e.width_words, e.stride_rot, e.stride_mul, e.pos, wcols.join("\t"), pcols.join("\t"), ts.elapsed().as_secs_f64());
|
||||||
writeln!(out.lock().unwrap(), "{row}").unwrap();
|
+ writeln!(out.lock().unwrap(), "{row}").unwrap();
|
||||||
let d = done.fetch_add(1, Ordering::Relaxed) + 1;
|
+ let d = done.fetch_add(1, Ordering::Relaxed) + 1;
|
||||||
if d % 100 == 0 {
|
+ if d % 100 == 0 {
|
||||||
--
|
+ println!("family_gate_era_census: {d} eras in {:.0} s", t0.elapsed().as_secs_f64());
|
||||||
2.41.0
|
+ out.lock().unwrap().flush().unwrap();
|
||||||
|
+ }
|
||||||
|
+ });
|
||||||
|
+ }
|
||||||
|
+ });
|
||||||
|
+ out.lock().unwrap().flush().unwrap();
|
||||||
|
+ println!("family_gate_era_census: {} eras {from}..{} in {:.0} s on {threads} threads -> {out_path}", done.load(Ordering::Relaxed), from + seeds, t0.elapsed().as_secs_f64());
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
diff --git a/igneum-pow/src/generator.rs b/igneum-pow/src/generator.rs
|
||||||
|
index 3f2d6723..b1db44c9 100644
|
||||||
|
--- a/igneum-pow/src/generator.rs
|
||||||
|
+++ b/igneum-pow/src/generator.rs
|
||||||
|
@@ -943,14 +943,31 @@ pub fn generate_era_generator(seed_string: &str, seed_bytes: &[u8], base: LoadCl
|
||||||
|
p
|
||||||
|
}
|
||||||
|
|
||||||
|
+/// Class v6 family-gate harness: `base` with the shadow block shape from `IGNEUM_FG_SHAPE` (64, 128 or 256 at 6,912 per
|
||||||
|
+/// iteration) under `IGNEUM_FAMILY_GATE`, so an unmodified harness runs the family's shape; `base` otherwise.
|
||||||
|
+fn family_shape_of(base: LoadClass) -> LoadClass {
|
||||||
|
+ if std::env::var_os("IGNEUM_FAMILY_GATE").is_some() {
|
||||||
|
+ if let Some(n) = std::env::var("IGNEUM_FG_SHAPE").ok().and_then(|v| v.parse::<u16>().ok()) {
|
||||||
|
+ assert!(matches!(n, 64 | 128 | 256), "IGNEUM_FG_SHAPE is 64, 128 or 256");
|
||||||
|
+ let reps = (V4_SHADOW_INSTRS as u32 * V4_SHADOW_REPS as u32 / n as u32) as u16;
|
||||||
|
+ return LoadClass { shadow: Some(ShadowClass { instrs: n, reps }), ..base };
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ base
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
impl ProgramClass {
|
||||||
|
/// The load class this program class draws from.
|
||||||
|
pub fn load_class(&self) -> LoadClass {
|
||||||
|
match self {
|
||||||
|
ProgramClass::V2 => LoadClass::V2,
|
||||||
|
ProgramClass::V3 => V3_CLASS,
|
||||||
|
- ProgramClass::V4 => V4_CLASS,
|
||||||
|
- ProgramClass::V5 => V5_CLASS,
|
||||||
|
+ ProgramClass::V4 => family_shape_of(V4_CLASS),
|
||||||
|
+ ProgramClass::V5 => {
|
||||||
|
+ // class v6 family-gate harness: the shadow block shape from IGNEUM_FG_SHAPE (64, 128 or 256 at 6,912
|
||||||
|
+ // per iteration) under IGNEUM_FAMILY_GATE, so an unmodified harness runs the family's shape
|
||||||
|
+ family_shape_of(V5_CLASS)
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -1315,6 +1332,34 @@ pub const NONLOAD_WEIGHTS: [(Op, u64); 10] = [
|
||||||
|
(Op::Or, 4),
|
||||||
|
];
|
||||||
|
|
||||||
|
+thread_local! {
|
||||||
|
+ static FAMILY_WEIGHTS: std::cell::Cell<Option<[(Op, u64); 10]>> = const { std::cell::Cell::new(None) };
|
||||||
|
+}
|
||||||
|
+/// Class v6 family-gate harness (8 October 2026): the ten non-load weights this thread's draws use when set, else
|
||||||
|
+/// [`NONLOAD_WEIGHTS`]. Set only by the harness census (`accept::tests::family_gate_era_census`), never on a chain path.
|
||||||
|
+pub fn set_family_weights(w: Option<[(Op, u64); 10]>) {
|
||||||
|
+ FAMILY_WEIGHTS.with(|c| c.set(w));
|
||||||
|
+}
|
||||||
|
+pub fn family_weights() -> [(Op, u64); 10] {
|
||||||
|
+ FAMILY_WEIGHTS.with(|c| c.get()).unwrap_or_else(|| *FAMILY_WEIGHTS_ENV.get_or_init(family_weights_env))
|
||||||
|
+}
|
||||||
|
+static FAMILY_WEIGHTS_ENV: std::sync::OnceLock<[(Op, u64); 10]> = std::sync::OnceLock::new();
|
||||||
|
+/// The harness's process-wide weight table: `IGNEUM_FG_WEIGHTS=w0,..,w9` (sum 75, the order of [`NONLOAD_WEIGHTS`]),
|
||||||
|
+/// read only with `IGNEUM_FAMILY_GATE` set (an unmodified harness such as attack-f8 then draws the family's weights).
|
||||||
|
+fn family_weights_env() -> [(Op, u64); 10] {
|
||||||
|
+ let mut w = NONLOAD_WEIGHTS;
|
||||||
|
+ if std::env::var_os("IGNEUM_FAMILY_GATE").is_some() {
|
||||||
|
+ if let Ok(s) = std::env::var("IGNEUM_FG_WEIGHTS") {
|
||||||
|
+ let v: Vec<u64> = s.split(',').filter_map(|x| x.trim().parse().ok()).collect();
|
||||||
|
+ assert!(v.len() == 10 && v.iter().sum::<u64>() == 75, "IGNEUM_FG_WEIGHTS: ten weights summing to 75");
|
||||||
|
+ for (i, x) in v.into_iter().enumerate() {
|
||||||
|
+ w[i].1 = x;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ w
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
/// Version 1 weights (retired). Sum 100, load at 25 percent.
|
||||||
|
pub const OP_WEIGHTS: [(Op, u64); 11] = [
|
||||||
|
(Op::Load, 25),
|
||||||
|
@@ -1436,8 +1481,9 @@ pub fn candidate_from_words_class(
|
||||||
|
// era set aside) on EVERY draw path, era or not, so a census through candidate_class reads the same stream as
|
||||||
|
// the chain; v2, v3 and every other class take no part. The draw order and the stream are otherwise the same.
|
||||||
|
// class v5 (docs/design/class-v5-stored-state.md) draws under the same rule: its state flag is set aside here too
|
||||||
|
- let source_rule_v4 = matches!(class.shadow, Some(ShadowClass { instrs: V4_SHADOW_INSTRS, .. }))
|
||||||
|
- && LoadClass { era: None, shadow: None, state: false, ..class } == LoadClass { shadow: None, ..V4_CLASS };
|
||||||
|
+ // class v6 family-gate harness (8 October 2026): the same predicate as the acceptance's, generalised to the
|
||||||
|
+ // family's shapes only when IGNEUM_FAMILY_GATE is set (never on a chain path; see accept::is_family_shape).
|
||||||
|
+ let source_rule_v4 = crate::accept::is_class_v4_shape(&class);
|
||||||
|
let mut fresh = [false; 8];
|
||||||
|
let mut fresh_value = [true; 8];
|
||||||
|
// the shared-operand idiom (AP-F8-1, sub-version 3): after `or d |= s`, a later `xor d ^= s` or `sub d -= s` with
|
||||||
|
@@ -1449,7 +1495,7 @@ pub fn candidate_from_words_class(
|
||||||
|
for k in 0..INSTR_COUNT {
|
||||||
|
let mut roll = rng.below(75);
|
||||||
|
let mut op = Op::Add;
|
||||||
|
- for &(o, w) in &NONLOAD_WEIGHTS {
|
||||||
|
+ for &(o, w) in &family_weights() {
|
||||||
|
if roll < w {
|
||||||
|
op = o;
|
||||||
|
break;
|
||||||
|
@@ -1556,7 +1602,7 @@ pub fn candidate_from_words_class(
|
||||||
|
for _ in 0..sh.instrs {
|
||||||
|
let mut roll = rng.below(75);
|
||||||
|
let mut op = Op::Add;
|
||||||
|
- for &(o, w) in &NONLOAD_WEIGHTS {
|
||||||
|
+ for &(o, w) in &family_weights() {
|
||||||
|
if roll < w {
|
||||||
|
op = o;
|
||||||
|
break;
|
||||||
Loading…
Reference in a new issue