Compare commits

...

6 commits

Author SHA1 Message Date
igneum-labs
febf98e273 Program id derivation: every pack's program.json states its own recipe (the record maker print_every_pack_derivation, ignored in the suite, rebuilds each pack as the pack tests do); the twelve readwidth mix packs keep their text (a class name the parser no longer accepts)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 20:36:54 +00:00
igneum-labs
6f2d63c826 Program id derivation stated exactly (adv-accept-3 documentation finding, 7 October 2026): the id and its printed derivation come from one byte recipe; program.json and spec 1.4.6 name the 'sub/' || sub_version_le16 suffix of generator 4 and the class-rung form; every pinned pack's id re-derived from its own text
generator.rs: IdRecipe (bytes + labelled parts), program_id_recipe and program_id_class_recipe build the bytes the id hashes and the text program.json prints, Program::program_id_derivation picks the form as program_id does. emit.rs prints that text. Spec 01 section 1.4.6 (the "Program id" paragraph) states the suffix, the class v5 form (generator 5, no suffix) and the rung form. tests/derivation.rs re-derives each of the 18 pinned packs' ids from its own derivation string with the pack's fields only (the plain text gives 8aa9f185d63f269e for the devnet v4 pack where the id is a785001687d8688a: the known-failed case). Packs: program.json re-exported for the 7 generator-4 packs and the 9 shadow ladder packs, one line each (the derivation); every other file byte-identical on re-export, ids and fingerprints unchanged. No object change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 20:25:53 +00:00
igneum-labs
57526e05c2 Counter ASIC 3.0 (hash): the efficiency pass's second grid, 1,400 MHz down to the driver floor (the job id selects it; the first 5090 pass found its best MH/W at the lowest lock), and its publish line
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 19:19:13 +00:00
igneum-labs
bfc0f23a88 No PC job raises a UAC prompt (standing rule, the project lead through main, 7 October 2026): publish-jobs.sh refuses --elevated; playbook-quit-check rule 3 fails a script that launches with -Verb RunAs or runas /user
The known-failed case: run-ca3-pc1-v4-eff-5090-20261007 as published at 18:27Z (--elevated) waited two minutes for a click and died with exit 251, the card switched off for nothing. The rights path is the installed app's Igneum Power Helper task (app/igneum-app/src/powertask.rs: Start-ScheduledTask by the owning user, the fixed verbs through its cmd.txt), which tools/ca3-v4-amend/pc1-v4-efficiency.ps1 uses; a job without the task reports the fact and takes its measured-only rows. Self-tests: a -Verb RunAs launch fails, a Power Helper task start passes, the publisher refuses --elevated (exit 3).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 18:52:48 +00:00
igneum-labs
b315203946 Counter ASIC 3.0 (hash): the class v4 efficiency pass takes its clock lock through the installed app's Igneum Power Helper task (no prompt; the 18:31Z elevated run exit 251, UAC unanswered); publish lines -b, not elevated
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 18:35:28 +00:00
igneum-labs
d5d7dc5662 Counter ASIC 3.0 (hash): the PC 1 queue of 7 October 2026, prepared, nothing published: the class v4 efficiency pass (clock locks, v4 against v3, the card alone, elevated), G1 + the AMD ladder on the sub-version 3 kit, item 6's family run e with its re-kit, Ember Tune per card (5080, 9070 XT), the publish lines
tools/ca3-v4-amend: pc1-v4-efficiency.ps1 (the job id names the card; -lgc grid 2,850 to 1,400 with the four Ember caps on it, so the owed 5090 clock rows fall out; the fingerprint checked on every step; nvidia-smi sampler by pid; -rgc in finally; voltage: no nvidia-smi lever, the V/F curve follows the lock), pc1-v4-sub3-amd-g1.ps1 (tools/ca3-pc1-amd's G1 + ladder shape on fetch-ca3-v4-sub3-pc1-20261007, the 13 packs' Mac fingerprints as the table), pc1-amd-family-20261007.ps1 (run e, the new kit id only), pc1-ember-card.ps1 (the installed app tunes the one card named by the job id), pc1-publish-20261007.sh (the exact lines, the order, the kit sha256s). Kits built on build-1 with mingw from this tree. CI checks run locally: playbook-quit, copied-sources, bash-body green. The dr736 derive job is not in the queue (its packs are not in this tree).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 18:03:59 +00:00
49 changed files with 1076 additions and 79 deletions

View file

@ -178,7 +178,7 @@ Implemented (`igneum-pow/src/accept.rs`, `proto-metal/main.swift`; ledger M6 Fix
Attempts. Attempt 0 of a program seed `b` (the 32-byte epoch seed, or the UTF-8 of a seed string) is the candidate drawn from `seed_words_from_bytes(b)`. If it fails, attempt `k = 1, 2, ...` is drawn from `seed_words_from_bytes(b || k_le32)`; the first accepted candidate is the program of the epoch. Measured rejection rate under this generator: 5.14 percent over 100,000 seeds (census section 7) and the 20,000-seed confirmation of `docs/bench-log.md` (4 October 2026), so the probability that 32 consecutive candidates fail is below 2^-136, and an implementation MAY treat 32 consecutive failures as a consensus fault (`MAX_ATTEMPTS`). Attempts. Attempt 0 of a program seed `b` (the 32-byte epoch seed, or the UTF-8 of a seed string) is the candidate drawn from `seed_words_from_bytes(b)`. If it fails, attempt `k = 1, 2, ...` is drawn from `seed_words_from_bytes(b || k_le32)`; the first accepted candidate is the program of the epoch. Measured rejection rate under this generator: 5.14 percent over 100,000 seeds (census section 7) and the 20,000-seed confirmation of `docs/bench-log.md` (4 October 2026), so the probability that 32 consecutive candidates fail is below 2^-136, and an implementation MAY treat 32 consecutive failures as a consensus fault (`MAX_ATTEMPTS`).
Program id. `FNV-1a-64("igneum-program/" || generator_le32 || seed words as little-endian bytes || attempt_le32)` with `generator = 2` under class v2 and `generator = 3` under class v3, written into every pack. Two implementations that agree on the id agree on the generator version, the seed words and the attempt. Program id. `FNV-1a-64("igneum-program/" || generator_le32 || seed words as little-endian bytes || attempt_le32 || suffix)` with `generator = 2` under class v2 and `generator = 3` under class v3 (no suffix), and `generator = 4` under class v4 with the suffix `"sub/" || sub_version_le16` (the class v4 stream's sub-version, 3 since 7 October 2026: `PROGRAM_SUBVERSION_V4` in `generator.rs`, `IGNEUM_PROGRAM_SUBVERSION` in program.h, `"sub_version"` in program.json; without the suffix Devnet 3's epoch-0 id reads 30956569d8f3d8d7 where the chain and the packs say fce15bf61030be57); class v5 is `generator = 5` with no suffix. A program of a non-default class (a read-width, scratch, mixer, derivation, era, shadow or hot rung other than class v4's rung 0) uses the tag `"igneum-program-rw/"` and appends the class's fields after `attempt_le32` (`generator::program_id_class_recipe`). Every pack prints its own derivation as `program_id_derivation` in program.json, built from the same byte recipe the id is hashed from (`generator::IdRecipe`), and `igneum-pow/tests/derivation.rs` re-derives every pinned pack's id from that text. Two implementations that agree on the id agree on the generator version, the seed words, the attempt and, under class v4, the sub-version.
Why the closed form: the test is then a pure function of the program (no cache, no day), costs 1.3 to 3.4 ms on one core, and the census checked on 100,000 programs that its verdict agrees with the memory-hard dataset's on all but 39 threshold-edge cases (section 7.3). What the three parts catch: (a) the empty-list fallback of 1.4.3; (b) registers that saturate to all ones (2.4 percent of candidates); (c) zero-absorbing register sets, lane-constant load sites, output bias and value-level address repeats (2.1 percent). Not in the rule, and why: a contraction as the last write (80 percent of programs) and the `or` count are too common and (c) already catches the cases that matter; the load critical path is a hash-rate question, not a weakness. Why the closed form: the test is then a pure function of the program (no cache, no day), costs 1.3 to 3.4 ms on one core, and the census checked on 100,000 programs that its verdict agrees with the memory-hard dataset's on all but 39 threshold-edge cases (section 7.3). What the three parts catch: (a) the empty-list fallback of 1.4.3; (b) registers that saturate to all ones (2.4 percent of candidates); (c) zero-absorbing register sets, lane-constant load sites, output bias and value-level address repeats (2.1 percent). Not in the rule, and why: a contraction as the last write (80 percent of programs) and the `or` count are too common and (c) already catches the cases that matter; the load critical path is a hash-rate question, not a weakness.

View file

@ -1812,7 +1812,7 @@ pub fn program_json(p: &Program, day: &str, ds: &DatasetSource) -> String {
s.push_str(&format!(" \"generator\": {},\n", p.generator)); s.push_str(&format!(" \"generator\": {},\n", p.generator));
s.push_str(&format!(" \"attempt\": {},\n", p.attempt)); s.push_str(&format!(" \"attempt\": {},\n", p.attempt));
s.push_str(&format!(" \"program_id\": {},\n", jhex64(p.program_id()))); s.push_str(&format!(" \"program_id\": {},\n", jhex64(p.program_id())));
s.push_str(" \"program_id_derivation\": \"FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32\",\n"); s.push_str(&format!(" \"program_id_derivation\": {},\n", jstr(&p.program_id_derivation())));
s.push_str(&format!( s.push_str(&format!(
" \"dataset_mode\": {},\n", " \"dataset_mode\": {},\n",
jstr(if memhard.is_some() { "memory-hard" } else { "closed-form" }) jstr(if memhard.is_some() { "memory-hard" } else { "closed-form" })

View file

@ -1077,6 +1077,17 @@ impl Program {
} }
} }
/// The derivation of [`Program::program_id`] as text, from the same byte recipe (program.json's
/// "program_id_derivation"; spec 01 section 1.4.6).
pub fn program_id_derivation(&self) -> String {
let v4_rung_0 = self.generator == GENERATOR_VERSION_V4 && LoadClass { era: None, ..self.class } == V4_CLASS;
if self.class.is_v2() || self.generator == GENERATOR_VERSION_V3 || v4_rung_0 {
program_id_recipe(self.generator, &self.seed, self.attempt).text()
} else {
program_id_class_recipe(self.generator, &self.seed, self.attempt, &self.class).text()
}
}
/// The program class of this program, from its generator version (3 = v3, 4 = v4, everything else v2). /// The program class of this program, from its generator version (3 = v3, 4 = v4, everything else v2).
pub fn program_class(&self) -> ProgramClass { pub fn program_class(&self) -> ProgramClass {
match self.generator { match self.generator {
@ -1087,24 +1098,61 @@ impl Program {
} }
} }
pub fn program_id(generator: u32, seed: &[u32; 8], attempt: u32) -> u64 { /// The byte recipe of a program id and the text that states it: the bytes FNV-1a 64 hashes and, part by part, the label
let mut b = Vec::with_capacity(PROGRAM_ID_TAG.len() + 4 + 32 + 4 + 6); /// of each part (`'literal'` or `field_le32`), so the derivation printed in program.json is built from the same list the id
b.extend_from_slice(PROGRAM_ID_TAG); /// is hashed from and the two cannot drift (the documentation finding of 7 October 2026: program.json and spec 1.4.6 said
b.extend_from_slice(&generator.to_le_bytes()); /// the plain form while generator 4 appended `'sub/' || sub_version_le16`, so a client written from the text derived another
for w in seed { /// id). `tests/derivation.rs` re-derives every pinned pack's id from its own derivation string.
b.extend_from_slice(&w.to_le_bytes()); #[derive(Clone, Debug, Default)]
pub struct IdRecipe {
pub bytes: Vec<u8>,
parts: Vec<String>,
}
impl IdRecipe {
fn lit(&mut self, s: &[u8]) {
self.bytes.extend_from_slice(s);
self.parts.push(format!("'{}'", String::from_utf8_lossy(s)));
} }
b.extend_from_slice(&attempt.to_le_bytes()); fn field(&mut self, bytes: &[u8], label: &str) {
self.bytes.extend_from_slice(bytes);
self.parts.push(label.to_string());
}
/// The derivation as program.json prints it: `FNV-1a 64 over <part> || <part> || ...`.
pub fn text(&self) -> String {
format!("FNV-1a 64 over {}", self.parts.join(" || "))
}
/// The id: FNV-1a 64 over the bytes.
pub fn id(&self) -> u64 {
fnv1a64(&self.bytes)
}
}
/// The recipe of [`program_id`].
pub fn program_id_recipe(generator: u32, seed: &[u32; 8], attempt: u32) -> IdRecipe {
let mut r = IdRecipe::default();
r.lit(PROGRAM_ID_TAG);
r.field(&generator.to_le_bytes(), "generator_le32");
let mut sw = Vec::with_capacity(32);
for w in seed {
sw.extend_from_slice(&w.to_le_bytes());
}
r.field(&sw, "seed_words as little-endian bytes");
r.field(&attempt.to_le_bytes(), "attempt_le32");
if generator == GENERATOR_VERSION_V4 { if generator == GENERATOR_VERSION_V4 {
// The class v4 sub-version (AP-F8-1 amendment, 7 October 2026): `"sub/" || sub_version as little-endian u16` // The class v4 sub-version (AP-F8-1 amendment, 7 October 2026): `"sub/" || sub_version as little-endian u16`
// appended for generator 4 only, so a binary from before the load-source rule (sub-version 0, no suffix) and // appended for generator 4 only, so a binary from before the load-source rule (sub-version 0, no suffix) and
// one after it never share a program id for one seed; the node's id check then catches a split. v2 and v3 // one after it never share a program id for one seed; the node's id check then catches a split. v2 and v3
// ids are byte-identical. The node reads the sub-version from [`PROGRAM_SUBVERSION_V4`]; packs carry it as // ids are byte-identical. The node reads the sub-version from [`PROGRAM_SUBVERSION_V4`]; packs carry it as
// IGNEUM_PROGRAM_SUBVERSION and program.json "sub_version". // IGNEUM_PROGRAM_SUBVERSION and program.json "sub_version".
b.extend_from_slice(b"sub/"); r.lit(b"sub/");
b.extend_from_slice(&PROGRAM_SUBVERSION_V4.to_le_bytes()); r.field(&PROGRAM_SUBVERSION_V4.to_le_bytes(), "sub_version_le16");
} }
fnv1a64(&b) r
}
pub fn program_id(generator: u32, seed: &[u32; 8], attempt: u32) -> u64 {
program_id_recipe(generator, seed, attempt).id()
} }
/// The sub-version of class v4's program stream, in every generator-4 program id and pack (AP-F8-3: 3 = the /// The sub-version of class v4's program stream, in every generator-4 program id and pack (AP-F8-3: 3 = the
@ -1118,53 +1166,60 @@ pub const PROGRAM_ID_TAG_RW: &[u8] = b"igneum-program-rw/";
/// The program id of a non-default class: the tag, then the same fields as [`program_id`], then the three mix /// The program id of a non-default class: the tag, then the same fields as [`program_id`], then the three mix
/// percentages and the slot count as bytes. /// percentages and the slot count as bytes.
pub fn program_id_class(generator: u32, seed: &[u32; 8], attempt: u32, class: &LoadClass) -> u64 { /// The recipe of [`program_id_class`].
let mut b = Vec::with_capacity(PROGRAM_ID_TAG_RW.len() + 4 + 32 + 4 + 4); pub fn program_id_class_recipe(generator: u32, seed: &[u32; 8], attempt: u32, class: &LoadClass) -> IdRecipe {
b.extend_from_slice(PROGRAM_ID_TAG_RW); let mut r = IdRecipe::default();
b.extend_from_slice(&generator.to_le_bytes()); r.lit(PROGRAM_ID_TAG_RW);
r.field(&generator.to_le_bytes(), "generator_le32");
let mut sw = Vec::with_capacity(32);
for w in seed { for w in seed {
b.extend_from_slice(&w.to_le_bytes()); sw.extend_from_slice(&w.to_le_bytes());
} }
b.extend_from_slice(&attempt.to_le_bytes()); r.field(&sw, "seed_words as little-endian bytes");
b.extend_from_slice(&class.mix); r.field(&attempt.to_le_bytes(), "attempt_le32");
b.push(class.load_slots); r.field(&class.mix, "mix[3]");
r.field(&[class.load_slots], "load_slots_u8");
if let Some(k) = class.scratch { if let Some(k) = class.scratch {
b.extend_from_slice(b"scratch/"); r.lit(b"scratch/");
b.push(k); r.field(&[k], "scratch_k_u8");
b.push(class.scratch_kb); r.field(&[class.scratch_kb], "scratch_kb_u8");
} }
if class.mixer_mult != 1 || class.growth { if class.mixer_mult != 1 || class.growth {
// Counter ASIC 2.0: the mixer multiplier and the growth rule are part of the construction, so a program of // Counter ASIC 2.0: the mixer multiplier and the growth rule are part of the construction, so a program of
// the same seed under a different mixer carries a different id (under the v3 seam the id is // the same seed under a different mixer carries a different id (under the v3 seam the id is
// program_id(3, seed, attempt) and this branch is not taken) // program_id(3, seed, attempt) and this branch is not taken)
b.extend_from_slice(b"mixer/"); r.lit(b"mixer/");
b.push(class.mixer_mult); r.field(&[class.mixer_mult], "mixer_mult_u8");
b.push(class.growth as u8); r.field(&[class.growth as u8], "growth_u8");
} }
if class.derive_len != 0 { if class.derive_len != 0 {
// Counter ASIC 3.0 item 2: the derivation program's length is part of the construction // Counter ASIC 3.0 item 2: the derivation program's length is part of the construction
b.extend_from_slice(b"derive/"); r.lit(b"derive/");
b.extend_from_slice(&class.derive_len.to_le_bytes()); r.field(&class.derive_len.to_le_bytes(), "derive_len_le16");
} }
if let Some(e) = class.era { if let Some(e) = class.era {
b.extend_from_slice(b"era/"); r.lit(b"era/");
b.extend_from_slice(&e.id_bytes()); r.field(&e.id_bytes(), "era_id_bytes (allowed[3] || width_words_u8 || stride_mul_le32 || stride_rot_le32 || interleave[4])");
} }
if let Some(sh) = class.shadow { if let Some(sh) = class.shadow {
// Counter ASIC 3.0 item 8: the shadow block's size and repeat count are part of the construction // Counter ASIC 3.0 item 8: the shadow block's size and repeat count are part of the construction
b.extend_from_slice(b"shadow/"); r.lit(b"shadow/");
b.extend_from_slice(&sh.instrs.to_le_bytes()); r.field(&sh.instrs.to_le_bytes(), "shadow_instrs_le16");
b.extend_from_slice(&sh.reps.to_le_bytes()); r.field(&sh.reps.to_le_bytes(), "shadow_reps_le16");
} }
if let Some(h) = class.hot { if let Some(h) = class.hot {
b.extend_from_slice(b"hot/"); r.lit(b"hot/");
b.push(h.mb); r.field(&[h.mb], "hot_mb_u8");
b.push(h.k); r.field(&[h.k], "hot_k_u8");
if h.added { if h.added {
b.extend_from_slice(b"added"); r.lit(b"added");
} }
} }
fnv1a64(&b) r
}
pub fn program_id_class(generator: u32, seed: &[u32; 8], attempt: u32, class: &LoadClass) -> u64 {
program_id_class_recipe(generator, seed, attempt, class).id()
} }
/// Weights of the ten non-load families under version 2, in draw order. Sum 75. The load family has no /// Weights of the ten non-load families under version 2, in draw order. Sum 75. The load family has no

View file

@ -0,0 +1,142 @@
//! Every pinned pack's program id re-derived from its own `program_id_derivation` text (the documentation finding of
//! 7 October 2026, the in-house pass adv-accept-3: program.json and spec 1.4.6 stated the plain form while generator 4
//! appends `'sub/' || sub_version_le16`, so a client written from the text derived 30956569d8f3d8d7 for Devnet 3's epoch
//! 0 where the chain says fce15bf61030be57). The text is parsed part by part and the bytes come from the pack's own fields
//! (generator, attempt, seed_words, sub_version, load_class), never from the code's id function, so the test fails the
//! moment the printed derivation and the hashed bytes disagree.
use igneum_pow::generator::LoadClass;
use serde_json::Value;
use std::path::{Path, PathBuf};
fn fnv1a64(b: &[u8]) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for &x in b {
h ^= x as u64;
h = h.wrapping_mul(0x0000_0100_0000_01b3);
}
h
}
fn hex64(v: &Value) -> u64 {
u64::from_str_radix(v.as_str().unwrap().trim_start_matches("0x"), 16).unwrap()
}
/// The bytes a derivation part names, from the pack's own fields.
fn part_bytes(part: &str, j: &Value, class: Option<&LoadClass>) -> Vec<u8> {
if let Some(lit) = part.strip_prefix('\'').and_then(|p| p.strip_suffix('\'')) {
return lit.as_bytes().to_vec();
}
let c = || class.unwrap_or_else(|| panic!("part {part} needs the pack's load_class"));
match part {
"generator_le32" => (j["generator"].as_u64().unwrap() as u32).to_le_bytes().to_vec(),
"attempt_le32" => (j["attempt"].as_u64().unwrap() as u32).to_le_bytes().to_vec(),
"seed_words as little-endian bytes" => j["seed_words"].as_array().unwrap().iter().flat_map(|w| (hex64(w) as u32).to_le_bytes()).collect(),
"sub_version_le16" => (j["sub_version"].as_u64().expect("a generator-4 pack carries sub_version") as u16).to_le_bytes().to_vec(),
"mix[3]" => c().mix.to_vec(),
"load_slots_u8" => vec![c().load_slots],
"scratch_k_u8" => vec![c().scratch.unwrap()],
"scratch_kb_u8" => vec![c().scratch_kb],
"mixer_mult_u8" => vec![c().mixer_mult],
"growth_u8" => vec![c().growth as u8],
"derive_len_le16" => c().derive_len.to_le_bytes().to_vec(),
"shadow_instrs_le16" => c().shadow.unwrap().instrs.to_le_bytes().to_vec(),
"shadow_reps_le16" => c().shadow.unwrap().reps.to_le_bytes().to_vec(),
"hot_mb_u8" => vec![c().hot.unwrap().mb],
"hot_k_u8" => vec![c().hot.unwrap().k],
other => panic!("derivation part {other:?} is not one this test knows; teach it the field before pinning such a pack"),
}
}
fn rederive(dir: &Path) -> (u64, u64, String) {
let j: Value = serde_json::from_str(&std::fs::read_to_string(dir.join("program.json")).unwrap()).unwrap();
let text = j["program_id_derivation"].as_str().unwrap().to_string();
let parts = text.strip_prefix("FNV-1a 64 over ").unwrap_or_else(|| panic!("{}: the derivation does not start with 'FNV-1a 64 over '", dir.display()));
let class = j["load_class"].as_str().and_then(LoadClass::parse);
let mut b = Vec::new();
for part in parts.split(" || ") {
b.extend(part_bytes(part.trim(), &j, class.as_ref()));
}
(fnv1a64(&b), hex64(&j["program_id"]), text)
}
fn pinned_packs() -> Vec<PathBuf> {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../proto-cuda");
let mut out = Vec::new();
for sub in ["packs-ca3-v4", "packs-ca3-shadow"] {
let mut v: Vec<_> = std::fs::read_dir(root.join(sub)).unwrap().map(|e| e.unwrap().path()).filter(|p| p.join("program.json").is_file()).collect();
v.sort();
out.extend(v);
}
out.push(root.join("packs/igneum-devnet-v4-epoch0"));
assert!(out.len() >= 18, "the pinned packs are missing; this test never skips ({} found)", out.len());
out
}
#[test]
fn every_pinned_pack_id_rederives_from_its_own_derivation_text() {
let mut seen_sub = false;
for dir in pinned_packs() {
let (derived, pinned, text) = rederive(&dir);
assert_eq!(derived, pinned, "{}: the id re-derived from \"{text}\" is {derived:016x}, the pack says {pinned:016x}", dir.display());
if text.contains("'sub/'") {
seen_sub = true;
}
}
assert!(seen_sub, "at least one generator-4 pack states the 'sub/' || sub_version_le16 suffix");
}
#[test]
fn the_class_v4_derivation_states_the_suffix_and_the_plain_text_derives_another_id() {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../proto-cuda/packs-ca3-v4/v4-devnet-epoch0");
let j: Value = serde_json::from_str(&std::fs::read_to_string(root.join("program.json")).unwrap()).unwrap();
let text = j["program_id_derivation"].as_str().unwrap();
assert!(text.ends_with("attempt_le32 || 'sub/' || sub_version_le16"), "{text}");
// the known-failed case: the text as printed before 7 October 2026 (no suffix) derives a different id
let plain = "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32";
let mut b = Vec::new();
for part in plain.strip_prefix("FNV-1a 64 over ").unwrap().split(" || ") {
b.extend(part_bytes(part, &j, None));
}
assert_ne!(fnv1a64(&b), hex64(&j["program_id"]), "the plain text must not derive the pinned id (it did before the fix)");
assert_eq!(hex64(&j["program_id"]), 0xa785_0016_87d8_688a);
}
/// The record maker (ignored in the suite): prints `DERIVATION <dir> <id> <text>` for every pack directory under proto-cuda,
/// the program rebuilt as the pack tests rebuild it (generator 3 with era bytes under class v2's era layout for packs-ca2-era,
/// generator 3 through the v3 seam otherwise, generator 2 with its load class, plain generator 2), so program.json's line can
/// be set from the code's own recipe after a derivation change. `cargo test --release --test derivation -- --ignored --nocapture`.
#[test]
#[ignore]
fn print_every_pack_derivation() {
use igneum_pow::generator::{generate_era, generate_from_seed_bytes, generate_from_seed_bytes_class, generate_from_seed_bytes_program_class, ProgramClass, GENERATOR_VERSION_V3};
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../proto-cuda");
let mut dirs: Vec<PathBuf> = std::fs::read_dir(&root).unwrap().map(|e| e.unwrap().path()).filter(|p| p.file_name().unwrap().to_str().unwrap().starts_with("packs")).flat_map(|g| std::fs::read_dir(g).unwrap().map(|e| e.unwrap().path())).filter(|p| p.join("program.json").is_file()).collect();
dirs.sort();
for dir in dirs {
let j: Value = serde_json::from_str(&std::fs::read_to_string(dir.join("program.json")).unwrap()).unwrap();
let seed = j["seed"].as_str().unwrap();
let seed_bytes = igneum_pow::bind::unhex(j["seed_bytes"].as_str().unwrap()).unwrap();
let gen = j["generator"].as_u64().unwrap() as u32;
let era = j.get("era_seed_bytes").and_then(|v| v.as_str()).map(|h| igneum_pow::bind::unhex(h).unwrap());
let lc = j.get("load_class").and_then(|c| c.as_str());
let program = if gen == GENERATOR_VERSION_V3 && dir.parent().unwrap().file_name().unwrap() == "packs-ca2-era" {
let allowed: Vec<u8> = j["era"]["allowed_widths"].as_array().unwrap().iter().map(|v| v.as_u64().unwrap() as u8).collect();
generate_era(seed, &seed_bytes, LoadClass::V2, era.as_deref().unwrap(), &allowed)
} else if gen == GENERATOR_VERSION_V3 {
generate_from_seed_bytes_program_class(seed, &seed_bytes, ProgramClass::V3, era.as_deref())
} else if gen == 4 {
generate_from_seed_bytes_program_class(seed, &seed_bytes, ProgramClass::V4, era.as_deref())
} else if let Some(c) = lc {
let Some(class) = LoadClass::parse(c) else {
println!("SKIP {} load_class {c:?} is a name the parser no longer accepts (an old experiment's pack); its derivation line stands as written", dir.strip_prefix(&root).unwrap().display());
continue;
};
generate_from_seed_bytes_class(seed, &seed_bytes, class)
} else {
generate_from_seed_bytes(seed, &seed_bytes)
};
println!("DERIVATION {} {:016x} {}", dir.strip_prefix(&root).unwrap().display(), program.program_id(), program.program_id_derivation());
assert_eq!(program.program_id(), hex64(&j["program_id"]), "{}: the rebuilt program's id is the pack's", dir.display());
}
}

View file

@ -9,7 +9,7 @@
# public key compiled into src/manifest.rs, runs each job that targets it ONCE per id, and reports to the log intake # public key compiled into src/manifest.rs, runs each job that targets it ONCE per id, and reports to the log intake
# as run_id job-<id>-<machine id8> (read back with tools/jobs.mjs). # as run_id job-<id>-<machine id8> (read back with tools/jobs.mjs).
# #
# packaging/ota/publish-jobs.sh add --kind run --target 1ccfe586 --script path.ps1 [--elevated] [--stop-miners] \ # packaging/ota/publish-jobs.sh add --kind run --target 1ccfe586 --script path.ps1 [--stop-miners] \ (--elevated is REFUSED: the 7 Oct 2026 no-prompt rule)
# [--cards-off key,key] [--timeout-minutes 60] [--shell powershell|bash] --title "..." [--expires-hours 48] [--deploy] # [--cards-off key,key] [--timeout-minutes 60] [--shell powershell|bash] --title "..." [--expires-hours 48] [--deploy]
# (--cards-off: the RUNNER switches these cards off through the app's own card path before the script and puts them # (--cards-off: the RUNNER switches these cards off through the app's own card path before the script and puts them
# back exactly on any exit, done, failed, timeout, aborted or the app quitting; keys with or without the device # back exactly on any exit, done, failed, timeout, aborted or the app quitting; keys with or without the device
@ -70,7 +70,15 @@ while [ $# -gt 0 ]; do
--expires-hours) EXPIRES_H="$2"; shift 2 ;; --expires-hours) EXPIRES_H="$2"; shift 2 ;;
--script) SCRIPT="$2"; shift 2 ;; --script) SCRIPT="$2"; shift 2 ;;
--shell) SHELL_KIND="$2"; shift 2 ;; --shell) SHELL_KIND="$2"; shift 2 ;;
--elevated) ELEVATED=1; shift ;; --elevated)
# STANDING RULE (the project lead through main, 7 October 2026, 18:5x UTC): no PC job may raise a UAC prompt or need a click, ever. A run
# job published --elevated launches the script through an administrator prompt on the PC (the 18:27Z job
# run-ca3-pc1-v4-eff-5090-20261007 waited 2 minutes for a click and died with exit 251, the card switched off for nothing).
# Anything needing rights goes through the installed app's Igneum Power Helper task (app/igneum-app/src/powertask.rs,
# present when Power control is on); a job reads the task's presence first and, without it, reports
# "no elevation path: Power control off on <machine>" with the measured-only rows it can still take. The flag is refused here.
echo "publish-jobs: --elevated is refused (standing rule of 7 October 2026: no PC job raises a UAC prompt; use the Igneum Power Helper task from the script, see app/igneum-app/src/powertask.rs and tools/ca3-v4-amend/pc1-v4-efficiency.ps1)" >&2
exit 3 ;;
--stop-miners) STOP_MINERS=1; shift ;; --stop-miners) STOP_MINERS=1; shift ;;
--cards-off) CARDS_OFF="$2"; shift 2 ;; --cards-off) CARDS_OFF="$2"; shift 2 ;;
--timeout-minutes) TIMEOUT_MIN="$2"; shift 2 ;; --timeout-minutes) TIMEOUT_MIN="$2"; shift 2 ;;

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0x3370a4466e5f322c", "program_id": "0x3370a4466e5f322c",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'hot/' || hot_mb_u8 || hot_k_u8",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0xd40c36ab07d82002", "program_id": "0xd40c36ab07d82002",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'hot/' || hot_mb_u8 || hot_k_u8 || 'added'",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0x322a62466d49ff66", "program_id": "0x322a62466d49ff66",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'hot/' || hot_mb_u8 || hot_k_u8",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0x322a64466d4a02cc", "program_id": "0x322a64466d4a02cc",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'hot/' || hot_mb_u8 || hot_k_u8",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0x7f581216e95eb062", "program_id": "0x7f581216e95eb062",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'hot/' || hot_mb_u8 || hot_k_u8 || 'added'",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0x322a68466d4a0998", "program_id": "0x322a68466d4a0998",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'hot/' || hot_mb_u8 || hot_k_u8",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0x329724466da667ec", "program_id": "0x329724466da667ec",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'hot/' || hot_mb_u8 || hot_k_u8",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0x2ff8e52c952a62c2", "program_id": "0x2ff8e52c952a62c2",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'hot/' || hot_mb_u8 || hot_k_u8 || 'added'",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0xa6892c64d9ddd7f7", "program_id": "0xa6892c64d9ddd7f7",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000", "seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000",
"seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07", "seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0x951b89584750bd75", "program_id": "0x951b89584750bd75",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0x7f4a5ca0a3637820", "program_id": "0x7f4a5ca0a3637820",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'derive/' || derive_len_le16",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000", "seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000",
"seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07", "seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0x72c1d8048aef9542", "program_id": "0x72c1d8048aef9542",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'derive/' || derive_len_le16",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0xb42f8c8549967581", "program_id": "0xb42f8c8549967581",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'shadow/' || shadow_instrs_le16 || shadow_reps_le16",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0x9a5be3853b011f6c", "program_id": "0x9a5be3853b011f6c",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'shadow/' || shadow_instrs_le16 || shadow_reps_le16",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0x9a3d4d853ae722fb", "program_id": "0x9a3d4d853ae722fb",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'shadow/' || shadow_instrs_le16 || shadow_reps_le16",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0x9a8b77853b298baa", "program_id": "0x9a8b77853b298baa",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'shadow/' || shadow_instrs_le16 || shadow_reps_le16",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0x9abe71853b54db11", "program_id": "0x9abe71853b54db11",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'shadow/' || shadow_instrs_le16 || shadow_reps_le16",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0x9ae3d3853b749dd4", "program_id": "0x9ae3d3853b749dd4",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'shadow/' || shadow_instrs_le16 || shadow_reps_le16",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0x9a477f853aefcc76", "program_id": "0x9a477f853aefcc76",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'shadow/' || shadow_instrs_le16 || shadow_reps_le16",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0x9b6191853bdf72c1", "program_id": "0x9b6191853bdf72c1",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'shadow/' || shadow_instrs_le16 || shadow_reps_le16",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0x90cd909ac3e78d4a", "program_id": "0x90cd909ac3e78d4a",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'mixer/' || mixer_mult_u8 || growth_u8 || 'shadow/' || shadow_instrs_le16 || shadow_reps_le16",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 4, "generator": 4,
"attempt": 1, "attempt": 1,
"program_id": "0xa785001687d8688a", "program_id": "0xa785001687d8688a",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || 'sub/' || sub_version_le16",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000", "seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000",
"seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07", "seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07",

View file

@ -3,7 +3,7 @@
"generator": 4, "generator": 4,
"attempt": 1, "attempt": 1,
"program_id": "0xa785001687d8688a", "program_id": "0xa785001687d8688a",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || 'sub/' || sub_version_le16",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000", "seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000",
"seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07", "seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07",

View file

@ -3,7 +3,7 @@
"generator": 4, "generator": 4,
"attempt": 1, "attempt": 1,
"program_id": "0xa785001687d8688a", "program_id": "0xa785001687d8688a",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || 'sub/' || sub_version_le16",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000", "seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000",
"seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07", "seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07",

View file

@ -3,7 +3,7 @@
"generator": 4, "generator": 4,
"attempt": 1, "attempt": 1,
"program_id": "0xa785001687d8688a", "program_id": "0xa785001687d8688a",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || 'sub/' || sub_version_le16",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000", "seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000",
"seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07", "seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07",

View file

@ -3,7 +3,7 @@
"generator": 4, "generator": 4,
"attempt": 1, "attempt": 1,
"program_id": "0xa785001687d8688a", "program_id": "0xa785001687d8688a",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || 'sub/' || sub_version_le16",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000", "seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000",
"seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07", "seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07",

View file

@ -3,7 +3,7 @@
"generator": 4, "generator": 4,
"attempt": 1, "attempt": 1,
"program_id": "0xa785001687d8688a", "program_id": "0xa785001687d8688a",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || 'sub/' || sub_version_le16",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000", "seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000",
"seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07", "seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07",

View file

@ -3,7 +3,7 @@
"generator": 4, "generator": 4,
"attempt": 1, "attempt": 1,
"program_id": "0xa785001687d8688a", "program_id": "0xa785001687d8688a",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || 'sub/' || sub_version_le16",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000", "seed": "igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07/day/69676e65756d2d6461792ffa50000000000000",
"seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07", "seed_bytes": "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0xe0b70cd155c28f4b", "program_id": "0xe0b70cd155c28f4b",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'scratch/' || scratch_k_u8 || scratch_kb_u8",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0xe0afe0d155bc25d9", "program_id": "0xe0afe0d155bc25d9",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'scratch/' || scratch_k_u8 || scratch_kb_u8",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0xe0b080d155bd35b9", "program_id": "0xe0b080d155bd35b9",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'scratch/' || scratch_k_u8 || scratch_kb_u8",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0xe0c444d155cd78cf", "program_id": "0xe0c444d155cd78cf",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'scratch/' || scratch_k_u8 || scratch_kb_u8",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0xe0c4a4d155ce1bef", "program_id": "0xe0c4a4d155ce1bef",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'scratch/' || scratch_k_u8 || scratch_kb_u8",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0xe0d1dcd155d90573", "program_id": "0xe0d1dcd155d90573",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'scratch/' || scratch_k_u8 || scratch_kb_u8",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0xe0d27cd155da1553", "program_id": "0xe0d27cd155da1553",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8 || 'scratch/' || scratch_k_u8 || scratch_kb_u8",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0x6a183165e23fd2bc", "program_id": "0x6a183165e23fd2bc",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0x5c427d666b4ed4f4", "program_id": "0x5c427d666b4ed4f4",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -3,7 +3,7 @@
"generator": 2, "generator": 2,
"attempt": 0, "attempt": 0,
"program_id": "0x5c4269666b4eb2f8", "program_id": "0x5c4269666b4eb2f8",
"program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", "program_id_derivation": "FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed_words as little-endian bytes || attempt_le32 || mix[3] || load_slots_u8",
"dataset_mode": "memory-hard", "dataset_mode": "memory-hard",
"seed": "igneum-genesis", "seed": "igneum-genesis",
"seed_bytes": "69676e65756d2d67656e65736973", "seed_bytes": "69676e65756d2d67656e65736973",

View file

@ -0,0 +1,149 @@
# 7 October 2026 copy for the PC 1 queue: tools/ca3-pc1-amd/pc1-amd-family.ps1 (run e) unchanged except the kit id, since a machine
# runs a fetch id once and the 0.3.21 host build wipes the jobs folder: fetch-ca3-pc1-amd-family-20261007 (the probe rebuilt on
# build-1 from this tree's proto-opencl/family-probe.c).
# Counter ASIC 3.0, PC 1 AMD job 2 (6 October 2026, run e: the 9070 XT ALONE): item 6's step cost of every reserve candidate family of spec
# 1.13.2 on PC 1's RX 9070 XT (machine ae432dc7, gfx1201), docs/plans/counter-asic-3-reserve.md, status file section
# 3 "Item 6" (the OWED AMD column). Run a (run-ca3-pc1-amd-family-20261006, 150 s, exit 0) measured ordinal 0, PC 1's
# integrated gfx1036 (one CU: alu 40.59 G steps/s against the 9070 XT's expected 1,000 to 2,000): the device list parse
# ran a second -match after the capturing one, which overwrote $Matches, so every device read idx 0 and an empty name,
# and the probe took a bare ordinal. Run b: the probe picks the card BY NAME (`--device-name gfx1201`, the match on the
# newest AMD platform by driver version, the kit worker's dedup rule; `RESULT device_choice name= index= platform= driver=
# cus=`), prints the device's name, CUs and platform on every RESULT line, and refuses with `RESULT error` when no
# gfx1201 is listed; the older-platform duplicate and the gfx1036 run after it by explicit ordinal, as their own labelled
# columns. Run d (17:17Z, the card by name, every variant built) showed a dependent-chain ratio does NOT survive the
# card's own miner: the alu chain read 226 then 195 G steps/s and the ratios swung from 5.9 to 11.8x to 0.17 to 1.3x
# between runs (the loaded card's scheduler, not the ops), while the Mac and 5090 columns were taken with the card alone.
# Run e: the 9070 XT is switched OFF by the RUNNER (`--cards-off amd:gfx1201` at publish, the app's own card path, put
# back on ANY exit with its own enabled flag, identities and cap: rule of 6 October 2026, a script never posts to
# /api/cards) for its three runs; this script only confirms by the process list that the card's opencl worker is gone;
# the 5090 and the 4070 keep mining (NOT --stop-miners). The gfx1036 and the old-platform columns run
# AFTER the restore, beside the miners, as they are not the card under test. mm8 stays exact=unverified: the gfx12 WMMA
# iu8 16x16x16 fragment layout is in no source at hand, and a guessed CPU layout would turn a wrong guess into exact=no. The probe is the family kit's
# family-probe-cl.exe (proto-opencl/family-probe.c of branch ca3-pc1-amd, cross-compiled with mingw, OpenCL.dll at run
# time; the Mac ran the same source bit-exact on Apple OpenCL), three runs per device, each best of 3 with a fresh seed
# per repetition, device event time, bit-exact against the CPU reference on two whole 32-lane groups. Every family is
# tried through every form AMD's OpenCL C offers; a form that does not compile prints build=failed with the first line
# of the build log and the run goes on (run a's finding: AMD's OpenCL C compiles no amd_perm, no sudot4, no WMMA
# builtin; ds_bpermute does compile). Never quits, pauses, resumes or updates the installed app; never writes
# settings.json. Lines: the probe's own `RESULT FAMILY name= ms= gsteps= ratio= exact= path= variant= ... device= cus=
# platform=` and `RESULT FAMILYBEST ...`, each prefixed `RESULT run=<n> dev=<label>`; a `SUMMARY {json}` line at the end.
# Read back with `node tools/jobs.mjs <job id>`.
$ErrorActionPreference = 'Continue'
$jobName = 'pc1-amd-family'
$kitId = 'fetch-ca3-pc1-amd-family-20261007'
$started = Get-Date
function Stamp { (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') }
function Summary([string] $status, [hashtable] $extra) {
$o = [ordered]@{ job = $jobName; status = $status; duration_s = [int]((Get-Date) - $started).TotalSeconds; finished_at = (Stamp) }
foreach ($k in $extra.Keys) { $o[$k] = $extra[$k] }
'SUMMARY ' + ($o | ConvertTo-Json -Compress -Depth 4)
}
"RESULT start $(Stamp) job=$jobName machine=$env:COMPUTERNAME app_version=$env:IGNEUM_APP_VERSION"
$jobs = Split-Path $env:IGNEUM_JOB_DIR
$kit = Join-Path $jobs $kitId
if (-not (Test-Path $kit)) { "RESULT error kit missing at $kit (the fetch job $kitId runs first; republish it after any app update)"; Summary 'failed' @{ error = 'kit missing' }; exit 2 }
$probe = Join-Path $kit 'bin\family-probe-cl.exe'
$src = Join-Path $kit 'src\family-probe.c'
if (-not (Test-Path $probe)) { "RESULT error probe missing at $probe"; Summary 'failed' @{ error = 'probe missing' }; exit 2 }
"RESULT probe $probe sha256 $((Get-FileHash -Algorithm SHA256 $probe).Hash.ToLower()) bytes $((Get-Item $probe).Length)"
if (Test-Path $src) { "RESULT source family-probe.c sha256 $((Get-FileHash -Algorithm SHA256 $src).Hash.ToLower()) bytes $((Get-Item $src).Length)" }
# what the card carries during the run (read only; nothing is switched)
function Workers { @(Get-CimInstance Win32_Process -Filter "Name = 'igneum-worker-opencl.exe' OR Name = 'igneum-worker-cuda.exe'" -ErrorAction SilentlyContinue | ForEach-Object { "$($_.Name):$($_.ProcessId):[$($_.CommandLine -replace '\s+', ' ')]" }) }
$w = @(Workers)
$amdLoaded = ($w | Where-Object { $_ -match 'igneum-worker-opencl' }).Count -gt 0
$cardState = if ($amdLoaded) { 'loaded' } else { 'quiet' }
"RESULT workers_before $(Stamp) $($w -join ' ')"
"RESULT context card_state=$(if ($amdLoaded) { 'LOADED (an igneum-worker-opencl process mines on an AMD card: absolutes are shared-card figures, ratios stand)' } else { 'quiet (no igneum-worker-opencl process)' })"
# ---- the installed app and the card's settings.json entries (READ ONLY), for the card-alone runs ----
$inst = @("$env:LOCALAPPDATA\Programs\Igneum Miner", "$env:ProgramFiles\Igneum Miner") | Where-Object { Test-Path (Join-Path $_ 'igneum-app.exe') } | Select-Object -First 1
$appDir = if ($env:IGNEUM_APP_DIR) { $env:IGNEUM_APP_DIR } else { Join-Path $env:LOCALAPPDATA 'igneum\app' }
$urlFile = Join-Path $appDir 'app.url'
$base = $null
if (Test-Path $urlFile) { $base = (Get-Content -LiteralPath $urlFile -Raw).Trim().TrimEnd('/') }
"RESULT app install=$inst app_dir=$appDir url_present=$([bool]$base)"
# the 9070 XT's OpenCL device index as the APP numbers it (the installed worker's --list; the probe's own list below
# numbers devices differently), for the process-list check of its worker
$dev = $null
if ($inst -and (Test-Path (Join-Path $inst 'igneum-worker-opencl.exe'))) {
$wl = @(& (Join-Path $inst 'igneum-worker-opencl.exe') --list 2>&1 | ForEach-Object { "$_" })
foreach ($l in $wl) { if ($l -match '^\s*\[(\d+)\].*gfx1201' -and $l -notmatch 'dup') { $dev = [int]$Matches[1]; break } }
}
"RESULT app_device $(if ($null -ne $dev) { $dev } else { 'unknown (no installed worker list: the quiet check falls back to any opencl worker)' })"
# the card is switched off by the RUNNER (`--cards-off amd:gfx1201` at publish), never by this script (rule of 6 October
# 2026, tools/ci/playbook-quit-check.sh rule 2); below only the read-only confirmation that its worker is gone
function CardWorkers {
$procs = @(Get-CimInstance Win32_Process -Filter "Name = 'igneum-worker-opencl.exe'" -ErrorAction SilentlyContinue)
if ($null -ne $dev) { $procs = @($procs | Where-Object { $_.CommandLine -match "--device\s+$dev(\s|$)" }) }
@($procs | ForEach-Object { $_.ProcessId })
}
# the probe's own device list: the 9070 XT by name first (the probe picks the newest platform itself), then the
# older-platform duplicate and the gfx1036 by explicit ordinal as their own columns
$list = @(& $probe --list 2>&1 | ForEach-Object { "$_" })
$list | ForEach-Object { "RESULT list $_" }
$extra = @()
$gfx1201Count = 0; $newest = ''
foreach ($l in $list) {
if ($l -match '^\[(\d+)\] (.*?) \| (.*?) \| driver (\S+)') {
$idx = [int] $Matches[1]; $name = $Matches[2]; $plat = $Matches[3]; $drv = $Matches[4]
if ($name -match 'gfx1201') { $gfx1201Count++; if ($drv -gt $newest) { $newest = $drv } }
if ($name -match 'gfx1201|gfx1036|Radeon' -and $name -notmatch 'NVIDIA') { $extra += [pscustomobject]@{ idx = $idx; name = $name; platform = $plat; driver = $drv } }
}
}
if ($gfx1201Count -eq 0) { "RESULT error no gfx1201 in the probe's list (the eGPU is off the bus: the 9070 XT column stays OWED); listed AMD devices: $(($extra | ForEach-Object { "[$($_.idx)] $($_.name) on $($_.platform) driver $($_.driver)" }) -join ' ; ')"; Summary 'failed' @{ error = 'no gfx1201' }; exit 2 }
# the by-ordinal runs after the by-name one: the gfx1201 entries NOT on the newest driver (the older platform), then the rest
$byOrdinal = @($extra | Where-Object { -not ($_.name -match 'gfx1201' -and $_.driver -eq $newest) })
"RESULT devices by_name=gfx1201 (newest driver $newest, $gfx1201Count gfx1201 entries) then by_ordinal=[$(($byOrdinal | ForEach-Object { "[$($_.idx)] $($_.name) on $($_.platform) driver $($_.driver)" }) -join ' ; ')]"
$runs = 0; $exactRows = 0; $rowsTotal = 0; $built = 0; $failedBuilds = 0; $probeErrors = 0; $mainRows = 0
function RunProbe([string] $label, [string[]] $probeArgs, [int] $r) {
# never name a parameter after the automatic variable for unbound arguments: a splat of it is then empty (run b, 17:10Z)
"RESULT run=$r dev=$label start $(Stamp) args=[$($probeArgs -join ' ')] card_state=$cardState"
$t0 = Get-Date
$lines = @(& $probe @probeArgs --reps 3 2>&1 | ForEach-Object { "$_" })
$rc = $LASTEXITCODE
foreach ($l in $lines) {
if ($l -match '^RESULT ') { "RESULT run=$r dev=$label $($l.Substring(7))" } else { "RESULT run=$r dev=$label text $l" }
if ($l -match '^RESULT FAMILY name=\S+ ms=') { $script:rowsTotal++; if ($l -match ' exact=yes ') { $script:exactRows++ }; if ($label -eq '9070xt') { $script:mainRows++ } }
if ($l -match '^RESULT FAMILY .*build=failed') { $script:failedBuilds++ }
if ($l -match '^RESULT FAMILYBEST name=\S+ ms=') { $script:built++ }
if ($l -match '^RESULT error') { $script:probeErrors++ }
}
"RESULT run=$r dev=$label end $(Stamp) exit=$rc wall_s=$([int]((Get-Date) - $t0).TotalSeconds)"
if ($rc -ne 0) { "RESULT run=$r dev=$label error=probe exit $rc" }
$script:runs++
}
# ---- the 9070 XT alone: off in the app, confirmed quiet, three runs, restored whatever happens ----
$cardAlone = $false
try {
$before = @(CardWorkers)
"RESULT card_workers_before pids=[$($before -join ' ')]"
if ($before.Count -eq 0) { "RESULT card already quiet: no opencl worker for the card (the runner's --cards-off took)"; $cardAlone = $true }
else {
$t = 0
while ($t -lt 150) { Start-Sleep -Seconds 5; $t += 5; if ((@(CardWorkers)).Count -eq 0) { break } }
$left = @(CardWorkers)
if ($left.Count -eq 0) { "RESULT card $(Stamp) quiet after $t s: the card's igneum-worker-opencl process is gone (the card to itself)"; $cardAlone = $true }
else { "RESULT card $(Stamp) UNCONFIRMED after $t s: opencl worker pids [$($left -join ' ')] still run (was the job published with --cards-off amd:gfx1201?); the 9070 XT rows below are LOADED-card figures (run d's class)" }
Start-Sleep -Seconds 8
}
$cardState = if ($cardAlone) { 'alone' } else { 'loaded' }
"RESULT workers_during $(Stamp) $((Workers) -join ' ')"
for ($r = 1; $r -le 3; $r++) { RunProbe '9070xt' @('--device-name', 'gfx1201') $r; Start-Sleep -Seconds 2 }
} finally {
"RESULT runs_9070xt_done $(Stamp) (the card comes back through the RUNNER's restore on any exit, not here)"
}
# ---- the other AMD columns beside the miners (not the card under test) ----
$cardState = if ((@(Get-CimInstance Win32_Process -Filter "Name = 'igneum-worker-opencl.exe'" -ErrorAction SilentlyContinue)).Count -gt 0) { 'loaded' } else { 'quiet' }
foreach ($d in $byOrdinal) {
$label = if ($d.name -match 'gfx1201') { 'gfx1201-old-platform' } elseif ($d.name -match 'gfx1036') { 'gfx1036' } else { 'amd-' + $d.idx }
for ($r = 1; $r -le 3; $r++) { RunProbe $label @('--device', "$($d.idx)") $r; Start-Sleep -Seconds 2 }
}
"RESULT workers_after $(Stamp) $((Workers) -join ' ')"
"RESULT end $(Stamp)"
$status = if ($mainRows -gt 0) { 'done' } elseif ($rowsTotal -gt 0) { 'partial' } else { 'failed' }
Summary $status @{ runs = $runs; family_rows = $rowsTotal; rows_9070xt = $mainRows; card_alone_9070xt = $cardAlone; exact_rows = $exactRows; failed_builds = $failedBuilds; familybest_rows = $built; probe_errors = $probeErrors }
if ($status -eq 'failed') { exit 1 }
exit 0

View file

@ -0,0 +1,74 @@
# Counter ASIC 3.0, PC 1 queue (7 October 2026): Ember Tune on ONE card of the installed app, by the job id (an id carrying
# "9070" tunes the RX 9070 XT, "5080" the RTX 5080, otherwise the RTX 5090), the other cards untouched and mining on. The shape
# is relay/playbooks/ember-installed-tune-pc1.ps1 (6 October 2026): the SHIPPED app tunes itself through the registered Power
# Helper task, this script only asks (POST api/tune/goal climb on, POST api/sweep/start for the one card) and watches the TUNE
# rows in the app's own log and api/state, then puts the climb switch back. On 0.3.21 (ember.rs may_set) a step is SET on an
# AMD card only when igneum-gpu-telemetry beside the app gives the card a tune line (ADLX) and Power control is on, else the
# tune is measure-only and the TUNE rows carry the reason; this script prints the card's tune_line before and after so the
# "measure only" reading and its reason are on RESULT lines. Never quits, pauses or resumes the installed app, never posts to
# /api/cards, never writes settings.json. Not elevated (the task carries the rights).
$ErrorActionPreference = 'Continue'
function Say([string] $s) { Write-Output ('[' + (Get-Date -Format 'HH:mm:ss') + '] ' + $s) }
$appDir = Join-Path $env:LOCALAPPDATA 'igneum\app'
$u = Join-Path $appDir 'app.url'
if (-not (Test-Path -LiteralPath $u)) { Write-Output 'RESULT TUNE error=no_app_url (installed app not running)'; exit 1 }
$base = (Get-Content -LiteralPath $u -Raw).Trim()
function State() { try { return ((Invoke-WebRequest -Uri ($base + 'api/state') -UseBasicParsing -TimeoutSec 5).Content | ConvertFrom-Json) } catch { return $null } }
function Post([string] $path, [string] $json) { try { Invoke-WebRequest -Uri ($base + $path) -Method POST -Body $json -ContentType 'application/json' -UseBasicParsing -TimeoutSec 5 | Out-Null; return $true } catch { Say ($path + ': ' + $_.Exception.Message); return $false } }
$s = State
if (-not $s) { Write-Output 'RESULT TUNE error=state_unreadable'; exit 1 }
$ver = [string]$s.version
Write-Output ('RESULT TUNE installed app ' + $ver + ' power_control=' + $s.settings.power_control + ' tuning_off=' + $s.settings.tuning_off + ' climb_before=' + $s.settings.tune_climb + ' goal_before=' + $s.settings.tune_goal)
$t = Get-ScheduledTask -TaskName 'Igneum Power Helper' -ErrorAction SilentlyContinue
Write-Output ('RESULT TUNE task ' + $(if ($t) { $t.Actions[0].Execute + ' state=' + $t.State } else { 'NOT REGISTERED' }))
if (-not $t) { Write-Output 'RESULT TUNE error=task_not_registered (the NVIDIA climbs would need a prompt; stopping)'; exit 1 }
if (-not $s.settings.power_control) { Write-Output 'RESULT TUNE error=power_control_off'; exit 1 }
$climbBefore = [bool]$s.settings.tune_climb
$jobDir = $env:IGNEUM_JOB_DIR; $jobId = if ($jobDir) { Split-Path -Leaf $jobDir } else { '' }
$want = if ($jobId -match '9070') { '9070' } elseif ($jobId -match '5080') { '5080' } else { '5090' }
Write-Output ('RESULT TUNE job_id=' + $jobId + ' card_match=' + $want)
$cards = @($s.mining.cards | Where-Object { $_.enabled -and ($_.vendor -eq 'nvidia' -or $_.vendor -eq 'amd') -and $_.name -notmatch 'Radeon\(TM\) Graphics' -and $_.name -match $want })
if ($cards.Count -eq 0) { Write-Output ('RESULT TUNE error=no enabled card matching ' + $want + ' in api/state (names: ' + (($s.mining.cards | ForEach-Object { $_.name }) -join ', ') + ')'); exit 1 }
Write-Output ('RESULT TUNE cards ' + (($cards | ForEach-Object { $_.key + ' (' + $_.name + ', ' + $_.vendor + ', sweep_state ' + $_.sweep_state + ', line "' + $_.tune_line + '")' }) -join ' | '))
foreach ($c in $cards) { Write-Output ('RESULT TUNE before key=' + $c.key + ' power_w=' + $c.power_w + ' power_pct=' + $c.power_pct + ' clock_cap=' + $c.clock_cap_mhz + ' mhs=' + $c.mhs + ' line=' + $c.tune_line) }
# the climb on (NVIDIA cards; an AMD card has no clock knob and runs its ladder regardless)
if (-not (Post 'api/tune/goal' '{"goal":"balanced","climb":true}')) { Write-Output 'RESULT TUNE error=tune_goal_refused'; exit 1 }
Start-Sleep -Seconds 2
$order = @($cards | Where-Object { $_.vendor -eq 'amd' }) + @($cards | Where-Object { $_.vendor -eq 'nvidia' })
$deadline = (Get-Date).AddMinutes(27)
$logs = Join-Path $appDir 'logs'
function LogMark() { $l = Get-ChildItem -Path $logs -Filter 'app-*.log' -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 1; if ($l) { return @{ f = $l.FullName; n = (Get-Content -LiteralPath $l.FullName).Count } } else { return @{ f = ''; n = 0 } } }
function LogSince($m) { if (-not $m.f) { return @() }; $all = @(Get-Content -LiteralPath $m.f -ErrorAction SilentlyContinue); if ($all.Count -le $m.n) { return @() }; return @($all[$m.n..($all.Count - 1)]) }
$rows = 0
foreach ($c in $order) {
if ((Get-Date) -gt $deadline) { Say 'budget spent before ' + $c.key; break }
$m = LogMark
$body = '{"key":"' + ($c.key -replace '"', '\"') + '"}'
if (-not (Post 'api/sweep/start' $body)) { Write-Output ('RESULT TUNE error=sweep_start_refused key=' + $c.key); continue }
Say ('tune asked for ' + $c.key)
$seen = 0; $done = $false; $started = Get-Date
while (-not $done -and (Get-Date) -lt $deadline) {
Start-Sleep -Seconds 10
$new = @(LogSince $m)
while ($seen -lt $new.Count) { $l = [string]$new[$seen]; $seen++; if ($l -match 'TUNE (card=|chosen|start|aborted|progress)') { if ($l -notmatch 'TUNE progress') { Write-Output ('RESULT ' + ($l -replace '^.*?(TUNE )', 'TUNE ')) }; if ($l -match 'TUNE card=') { $rows++ } } }
$st = State
if ($st) {
$cc = $st.mining.cards | Where-Object { $_.key -eq $c.key } | Select-Object -First 1
if ($cc) {
$running = ($cc.sweep_state -eq 'running')
if (-not $running -and ((Get-Date) - $started).TotalSeconds -gt 180) { $done = $true }
if ($running -and $cc.tune_steps -gt 0 -and ($seen % 6 -eq 0)) { Say ($c.key + ': step ' + $cc.tune_step + ' of ' + $cc.tune_steps + ', ' + $cc.sweep_note) }
}
}
}
$st = State
if ($st) { $cc = $st.mining.cards | Where-Object { $_.key -eq $c.key } | Select-Object -First 1; if ($cc) { Write-Output ('RESULT TUNE after key=' + $c.key + ' power_w=' + $cc.power_w + ' power_pct=' + $cc.power_pct + ' clock_cap=' + $cc.clock_cap_mhz + ' line=' + $cc.tune_line + ' source=' + $cc.tune_source + ' note=' + $cc.sweep_note); if ($cc.tune_curve) { Write-Output ('RESULT TUNE curve key=' + $c.key + ' ' + (($cc.tune_curve | ForEach-Object { ($_.clock_mhz) + 'MHz/' + ($_.mem_mhz) + 'mem/' + ($_.power_pct) + '%:' + $_.mhs + 'MH/s ' + $_.watts + 'W ' + $_.eff + ' ' + $_.mark }) -join ' | ')) } } }
}
# the climb switch back to what it was
Post 'api/tune/goal' ('{"climb":' + $(if ($climbBefore) { 'true' } else { 'false' }) + '}') | Out-Null
Write-Output ('RESULT TUNE climb_restored=' + $climbBefore + ' rows=' + $rows)
$smi = Join-Path $env:ProgramFiles 'NVIDIA Corporation\NVSMI\nvidia-smi.exe'
if (-not (Test-Path $smi)) { $smi = Join-Path $env:SystemRoot 'System32\nvidia-smi.exe' }
if (Test-Path $smi) { Write-Output ('RESULT TUNE nvidia_now ' + ((& $smi --query-gpu=index,name,power.draw,power.limit,clocks.gr,clocks.mem --format=csv,noheader 2>&1 | Out-String) -replace "`r?`n", ' | ')) }
if ($rows -eq 0) { Write-Output 'RESULT TUNE error=no_rows'; exit 1 }
exit 0

View file

@ -0,0 +1,36 @@
#!/usr/bin/env bash
# The PC 1 queue of 7 October 2026 (Counter ASIC 3.0, the hash lane; main's orders through the coordinator): the exact publish
# lines, one job at a time under the PC 1 rule (each job's closing report read before the next; nothing published before the
# coordinator relays the shipper's "PC 1 released" line). PC 1 = machine ae432dc7 (RTX 5090, RTX 5080 on the dock, RTX 4070,
# RX 9070 XT amd:gfx1201). Every measured card alone through the RUNNER's --cards-off (the app's own card path, restored on any
# exit); the other cards mining on; the installed app never quit, paused, resumed or updated; the desk usable.
# tools/ca3-v4-amend/pc1-publish-20261007.sh <step> [--deploy] steps: kit family-kit eff-5090 eff-5090-floor eff-5080 amd-g1 family tune-5080 tune-9070
# tools/ca3-v4-amend/pc1-publish-20261007.sh read <job id> the closing report (node tools/jobs.mjs <id> --all)
# Kits (built 7 October 2026, 17:5x UTC; the exes cross-compiled on build-1 with mingw from this tree):
# igneum-ca3-v4-sub3-pc1-kit.zip sha256 1d441f5a075d72b1b902d23941e57b0fa4bee08f91a14b10a7a545aaac58c413 (896,071 bytes, 167 files)
# igneum-ca3-pc1-amd-family-kit-20261007.zip sha256 9dd82b11ed08ff9270ac7d59f634eaf99267b1bb48a9ae9fdfc0ce7fe41e63ef (39,399 bytes, 3 files)
# The efficiency passes are NOT elevated (the 18:31Z run exit 251: the UAC prompt went unanswered); the clock lock goes through the
# installed app's registered Igneum Power Helper task (no prompt); ids -b because a machine runs an id once.
# Order (main, 7 October 2026 evening): eff-5090, eff-5080 (the class v4 efficiency pass; its clock grid carries the four Ember
# caps, so the owed "5090 clock rows" fall out of it), amd-g1 (G1 + the AMD ladder on the sub-version 3 kit), family (item 6
# on AMD), tune-5080 (the full Ember Tune on the 5080), tune-9070 (the 9070 XT tune pass). The dr736 derive job is NOT in this
# queue: its packs are not in this tree (the 6 October kit carried them from branch ca3-pc1-amd).
set -euo pipefail
ROOT=$(cd "$(dirname "$0")/../.." && pwd); cd "$ROOT"
KITS="${IGNEUM_PC1_KITS:-${TMPDIR:-/tmp}}"
P=packaging/ota/publish-jobs.sh
STEP="${1:-}"; shift || true
DEPLOY=""; for a in "$@"; do [ "$a" = "--deploy" ] && DEPLOY="--deploy"; done
case "$STEP" in
kit) $P add --kind fetch --target ae432dc7 --id fetch-ca3-v4-sub3-pc1-20261007 --file "$KITS/igneum-ca3-v4-sub3-pc1-kit.zip" --dir jobs --extract --title "CA3 PC 1: the sub-version 3 kit (worker, 13 packs)" --expires-hours 36 $DEPLOY ;;
family-kit) $P add --kind fetch --target ae432dc7 --id fetch-ca3-pc1-amd-family-20261007 --file "$KITS/igneum-ca3-pc1-amd-family-kit-20261007.zip" --dir jobs --extract --title "CA3 PC 1: the family probe kit (7 Oct)" --expires-hours 36 $DEPLOY ;;
eff-5090) $P add --kind run --target ae432dc7 --id run-ca3-pc1-v4-eff-5090-20261007-b --cards-off "nvidia:NVIDIA GeForce RTX 5090" --script tools/ca3-v4-amend/pc1-v4-efficiency.ps1 --timeout-minutes 75 --title "CA3 PC 1: class v4 efficiency pass on the RTX 5090 (clock locks, v4 against v3, the card alone)" --expires-hours 36 $DEPLOY ;;
eff-5090-floor) $P add --kind run --target ae432dc7 --id run-ca3-pc1-v4-eff-5090-floor-20261007 --cards-off "nvidia:NVIDIA GeForce RTX 5090" --script tools/ca3-v4-amend/pc1-v4-efficiency.ps1 --timeout-minutes 60 --title "CA3 PC 1: class v4 efficiency pass 2 on the RTX 5090 (1,400 MHz down to the driver floor, v4 against v3, the card alone)" --expires-hours 36 $DEPLOY ;;
eff-5080) $P add --kind run --target ae432dc7 --id run-ca3-pc1-v4-eff-5080-20261007-b --cards-off "nvidia:NVIDIA GeForce RTX 5080" --script tools/ca3-v4-amend/pc1-v4-efficiency.ps1 --timeout-minutes 75 --title "CA3 PC 1: class v4 efficiency pass on the RTX 5080 (clock locks, v4 against v3, the card alone)" --expires-hours 36 $DEPLOY ;;
amd-g1) $P add --kind run --target ae432dc7 --id run-ca3-pc1-v4-sub3-amd-g1-20261007 --cards-off amd:gfx1201 --script tools/ca3-v4-amend/pc1-v4-sub3-amd-g1.ps1 --timeout-minutes 45 --title "CA3 PC 1: G1 on the sub-version 3 kit and the AMD ladder on the 9070 XT (the card alone)" --expires-hours 36 $DEPLOY ;;
family) $P add --kind run --target ae432dc7 --id run-ca3-pc1-amd-family-20261007-e --cards-off amd:gfx1201 --script tools/ca3-v4-amend/pc1-amd-family-20261007.ps1 --timeout-minutes 20 --title "CA3 PC 1: item 6 family step costs on the 9070 XT (run e, the card alone)" --expires-hours 36 $DEPLOY ;;
tune-5080) $P add --kind run --target ae432dc7 --id run-ca3-pc1-ember-5080-20261007 --script tools/ca3-v4-amend/pc1-ember-card.ps1 --timeout-minutes 35 --title "CA3 PC 1: Ember Tune on the RTX 5080 (the installed app tunes the one card)" --expires-hours 36 $DEPLOY ;;
tune-9070) $P add --kind run --target ae432dc7 --id run-ca3-pc1-ember-9070-20261007 --script tools/ca3-v4-amend/pc1-ember-card.ps1 --timeout-minutes 35 --title "CA3 PC 1: Ember Tune pass on the RX 9070 XT (set or measure-only by the probe)" --expires-hours 36 $DEPLOY ;;
read) node tools/jobs.mjs "$1" --all ;;
*) sed -n 2,12p "$0"; exit 2 ;;
esac

View file

@ -0,0 +1,260 @@
# Counter ASIC 3.0, PC 1: the class v4 efficiency pass (7 October 2026, main's question "profitable mining is all about
# efficiency, is there a solution?"). Thesis under test: the latency-shadow work hides in the memory wait, so the core clock
# (and with it the voltage along the driver's V/F curve) can drop until the work just fits the wait with no rate loss, and the
# 5090's +80 W for class v4 at the unlocked 2,850 MHz mostly comes back. One job per card (the job id names the card: an id
# carrying "5080" measures the RTX 5080, any other the RTX 5090), the card ALONE under the RUNNER's --cards-off (the app's own
# card path, restored on any exit; the other cards keep mining), ELEVATED so nvidia-smi -lgc needs no prompt, no restart, the
# desk usable. Per step: nvidia-smi -lgc <c>,<c> on the card, then the INSTALLED igneum-worker-cuda.exe --bench on the class v4
# pack (v4-devnet-epoch0, the sub-version 3 kit) and on the class v3 control (mx8-devnet-epoch0), about 60 s each, the 2^24
# fingerprint checked against the Mac's on every step (bit-exactness under the lock), while nvidia-smi samples power.draw,
# clocks.sm, clocks.mem and the throttle reasons at 1 Hz (a child by pid, ended in finally). Steps: unlocked, then 2,850,
# 2,781, 2,700, 2,550, 2,472, 2,400, 2,250, 2,163, 2,100, 1,950, 1,854, 1,800, 1,650, 1,500, 1,400 MHz (the four Ember caps
# 2,781 / 2,472 / 2,163 / 1,854 are on the grid, so the owed "5090 clock rows" of counter-asic-3-status.md section 7 fall out of
# this pass), then unlocked again as the drift check. The voltage lever: nvidia-smi exposes NO voltage offset (that is NVAPI,
# Afterburner's lever); the clock lock moves the card down the driver's V/F curve, so the voltage falls with the lock and that
# is the lever this pass has; stated on a RESULT line. Lines: `RESULT STEP card= pack= lock= sm_mhz= watts= mhs= mh_per_w=
# fingerprint= match=`, one per step and pack; `RESULT TABLE` lines at the end (v3 best against v4 best, the recovered watts,
# the rate lost); a `SUMMARY {json}` line. Never quits, pauses, resumes or updates the installed app; never posts to
# /api/cards; never writes settings.json; -rgc in finally. Not elevated: the lock is refused, the unlocked rows are
# measured and every lock row says why. The lock path when NOT elevated (the 18:31Z run: the UAC prompt went unanswered, exit 251):
# the installed app's registered `Igneum Power Helper` task (app/igneum-app/src/powertask.rs, one approval ever): the script
# starts it (Start-ScheduledTask by the owning user, no prompt) and talks to it through %LOCALAPPDATA%\igneum\app\sweep\cmd.txt
# with the fixed verbs `<seq> dev <idx>`, `<seq> lgc <MHz>` (the helper runs nvidia-smi -lgc 0,<MHz>: a cap, which under load
# is the lock), `<seq> rgc`, and reads the result from helper.log beside it; the same channel the app's own Ember tune uses,
# so no new rights and no prompt. Kit: fetch job fetch-ca3-v4-sub3-pc1-20261007 (tools/ca3-v4-amend/pc1-publish-20261007.sh).
$ErrorActionPreference = 'Continue'
$jobName = 'pc1-v4-efficiency'
$kitId = 'fetch-ca3-v4-sub3-pc1-20261007'
$started = Get-Date
$deadline = $started.AddMinutes(66)
function Stamp { (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') }
function Summary([string] $status, [hashtable] $extra) {
$o = [ordered]@{ job = $jobName; status = $status; duration_s = [int]((Get-Date) - $started).TotalSeconds; finished_at = (Stamp) }
foreach ($k in $extra.Keys) { $o[$k] = $extra[$k] }
'SUMMARY ' + ($o | ConvertTo-Json -Compress -Depth 4)
}
$jobDir = $env:IGNEUM_JOB_DIR; if (-not $jobDir) { $jobDir = Join-Path $env:TEMP 'igneum-ca3-pc1-eff' }
New-Item -ItemType Directory -Force -Path $jobDir | Out-Null
$jobId = Split-Path -Leaf $jobDir
$cardName = if ($jobId -match '5080') { 'NVIDIA GeForce RTX 5080' } else { 'NVIDIA GeForce RTX 5090' }
$expected = @{ 'mx8-devnet-epoch0' = '90f794dd556f7a3b'; 'v4-devnet-epoch0' = 'e370fb2080b7dbb1' }
$packOrder = @('v4-devnet-epoch0', 'mx8-devnet-epoch0')
# the grid by the job id: an id carrying "floor" runs the second pass, 1,400 MHz down to the driver's floor in 100 MHz steps (the
# first pass found the best MH/W at its lowest lock, 1,400, so the knee sits below it); a lock the driver clamps shows in sm_mhz
$locks = if ($jobId -match 'floor') { @(1400, 1300, 1200, 1100, 1000, 900, 800, 700, 600, 500, 400, 300) } else { @(2850, 2781, 2700, 2550, 2472, 2400, 2250, 2163, 2100, 1950, 1854, 1800, 1650, 1500, 1400) }
$stepSeconds = 60
"RESULT start $(Stamp) job=$jobName job_id=$jobId card=$cardName grid=[$($locks -join ' ')] machine=$env:COMPUTERNAME app_version=$env:IGNEUM_APP_VERSION"
'RESULT lever voltage: nvidia-smi exposes no voltage offset (NVAPI only); the clock lock (-lgc) moves the card along the driver V/F curve, so the voltage falls with the lock; the power cap (-pl) is the other driver lever and is not moved here'
$elevated = $false
try { $elevated = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } catch { }
"RESULT elevated=$elevated"
# ---- the kit (the wiped-jobs-folder rule: tested before use) ----
$jobs = Split-Path $jobDir
$kit = Join-Path $jobs $kitId
$packs = Join-Path $kit 'packs'
if (-not (Test-Path $packs)) { "RESULT error packs missing at $packs (the fetch job $kitId runs first; republish it after any app update)"; Summary 'failed' @{ error = 'kit missing' }; exit 2 }
foreach ($pk in $packOrder) { $d = Join-Path $packs $pk; if (-not (Test-Path $d)) { "RESULT error pack missing at $d"; Summary 'failed' @{ error = "pack $pk missing" }; exit 2 }; foreach ($f in (Get-ChildItem $d -File | Sort-Object Name)) { "RESULT kitfile $pk/$($f.Name) sha256 $((Get-FileHash -Algorithm SHA256 $f.FullName).Hash.ToLower())" } }
# ---- the installed worker (NVRTC, the pack's own kernel text) ----
$inst = @("$env:LOCALAPPDATA\Programs\Igneum Miner", "$env:ProgramFiles\Igneum Miner") | Where-Object { Test-Path (Join-Path $_ 'igneum-worker-cuda.exe') } | Select-Object -First 1
if (-not $inst) { 'RESULT error no installed igneum-worker-cuda.exe'; Summary 'failed' @{ error = 'no worker' }; exit 2 }
$exe = Join-Path $inst 'igneum-worker-cuda.exe'
"RESULT worker $exe sha256 $((Get-FileHash -Algorithm SHA256 $exe).Hash.ToLower()) bytes $((Get-Item $exe).Length)"
$appExe = Join-Path $inst 'igneum-app.exe'
if (Test-Path $appExe) { try { "RESULT app-version $((& $appExe --version 2>&1 | Out-String).Trim())" } catch { } }
# ---- nvidia-smi and the card ----
$smi = $null
$c = Get-Command nvidia-smi -ErrorAction SilentlyContinue; if ($c) { $smi = $c.Source }
if (-not $smi) { foreach ($p in @((Join-Path $env:ProgramFiles 'NVIDIA Corporation\NVSMI\nvidia-smi.exe'), (Join-Path $env:SystemRoot 'System32\nvidia-smi.exe'))) { if (Test-Path $p) { $smi = $p; break } } }
if (-not $smi) { 'RESULT error nvidia-smi not found'; Summary 'failed' @{ error = 'no nvidia-smi' }; exit 2 }
$gpus = @(& $smi --query-gpu=index,name,uuid,pci.bus_id,power.limit,power.max_limit,clocks.max.sm,driver_version --format=csv,noheader 2>&1 | ForEach-Object { "$_" })
$gpus | ForEach-Object { "RESULT gpu $_" }
$row = $gpus | Where-Object { $_ -match [regex]::Escape($cardName) } | Select-Object -First 1
if (-not $row) { "RESULT error card '$cardName' is not in nvidia-smi's list"; Summary 'failed' @{ error = 'card missing' }; exit 2 }
$fld = $row -split ',\s*'
$idx = $fld[0].Trim(); $uuid = $fld[2].Trim(); $bus = $fld[3].Trim(); $maxSm = $fld[6].Trim()
"RESULT card index=$idx uuid=$uuid bus=$bus max_sm_mhz=$maxSm"
# ---- the card quiet (the RUNNER's --cards-off took): no igneum compute app on this bus id ----
function BusApps { @(& $smi --query-compute-apps=gpu_bus_id,pid,process_name --format=csv,noheader 2>$null | ForEach-Object { "$_" } | Where-Object { $_ -match [regex]::Escape($bus) -and $_ -match 'igneum' }) }
$t = 0; $apps = @(BusApps)
while ($t -lt 150 -and $apps.Count -gt 0) { Start-Sleep -Seconds 5; $t += 5; $apps = @(BusApps) }
$alone = ($apps.Count -eq 0)
if ($alone) { "RESULT card $(Stamp) quiet after $t s: no igneum compute app on $bus (the runner's --cards-off took)" }
else { "RESULT card $(Stamp) UNCONFIRMED after $t s: $($apps -join '; ') (was the job published with --cards-off?); the rows below are LOADED-card figures" }
# ---- the sampler: nvidia-smi at 1 Hz on this card, a child by pid, its file read per step ----
$samples = Join-Path $jobDir 'nv-samples.csv'
if (Test-Path $samples) { Remove-Item -LiteralPath $samples -Force }
$sampler = $null
$rows = @()
$lockState = 'unlocked'
function ReadWindow([datetime] $t0, [datetime] $t1) {
$sel = @()
if (Test-Path $samples) {
foreach ($l in (Get-Content -LiteralPath $samples -ErrorAction SilentlyContinue)) {
$p = $l -split ',\s*'
if ($p.Count -lt 5) { continue }
$ts = $null
try { $ts = [datetime]::ParseExact($p[0].Trim(), 'yyyy/MM/dd HH:mm:ss.fff', [cultureinfo]::InvariantCulture) } catch { continue }
if ($ts -ge $t0 -and $ts -le $t1) { $sel += ,$p }
}
}
return ,$sel
}
function Mean($vals) { $v = @($vals | ForEach-Object { [double]$_ }); if ($v.Count -eq 0) { return $null }; return [math]::Round((($v | Measure-Object -Sum).Sum / $v.Count), 1) }
# ---- the Power Helper task (the lock path without a prompt) ----
$helperDir = Join-Path $env:LOCALAPPDATA 'igneum\app\sweep'
$cmdFile = Join-Path $helperDir 'cmd.txt'
$helperLog = Join-Path $helperDir 'helper.log'
$viaHelper = $false
$seq = 0
if (-not $elevated) {
$task = Get-ScheduledTask -TaskName 'Igneum Power Helper' -ErrorAction SilentlyContinue
if ($task) {
try {
if (Test-Path $cmdFile) { $seq = @(Get-Content -LiteralPath $cmdFile -ErrorAction SilentlyContinue | ForEach-Object { if ($_ -match '^(\d{1,6})\s') { [int]$Matches[1] } }) | Measure-Object -Maximum | Select-Object -ExpandProperty Maximum }
if (-not $seq) { $seq = 0 }
$seq = [int]$seq + 100
Start-ScheduledTask -TaskName 'Igneum Power Helper' -ErrorAction Stop
Start-Sleep -Seconds 3
$viaHelper = $true
"RESULT helper task 'Igneum Power Helper' state=$((Get-ScheduledTask -TaskName 'Igneum Power Helper').State) started; commands through $cmdFile from seq $seq; results from $helperLog"
} catch { "RESULT helper task present but Start-ScheduledTask failed: $_" }
} else { 'RESULT helper task NOT REGISTERED (Power control off in the app): no lock path without a prompt' }
}
function HelperSend([string] $verb) {
$script:seq += 2
$s0 = $script:seq; $s1 = $script:seq + 1; $script:seq = $s1
$mark = 0; if (Test-Path $helperLog) { $mark = @(Get-Content -LiteralPath $helperLog -ErrorAction SilentlyContinue).Count }
Set-Content -LiteralPath $cmdFile -Value "$s0 dev $idx`n$s1 $verb`n" -NoNewline -Encoding ascii
$t = 0
while ($t -lt 30) {
Start-Sleep -Seconds 1; $t++
$new = @(Get-Content -LiteralPath $helperLog -ErrorAction SilentlyContinue)
if ($new.Count -gt $mark) {
$hit = $new[$mark..($new.Count - 1)] | Where-Object { $_ -match "\b$s1 nvidia-smi " } | Select-Object -First 1
if ($hit) { return ($hit -replace '^\d+\s+', '') }
}
}
return "no helper.log line for seq $s1 within 30 s (is the task running?)"
}
function SetLock([int] $mhz) {
if ($elevated) {
$out = (& $smi -i $idx -lgc "$mhz,$mhz" 2>&1 | Out-String).Trim() -replace '\s+', ' '
if ($out -match 'not have permission|No permission|Insufficient|not supported|Unable|ERROR') { return "refused: $out" }
return "ok (direct): $out"
}
if ($viaHelper) {
$out = HelperSend "lgc $mhz"
if ($out -match 'not have permission|No permission|Insufficient|not supported|Unable|ERROR|no helper.log') { return "refused: $out" }
return "ok (helper, -lgc 0,$mhz): $out"
}
return 'refused: not elevated and no helper task'
}
function ResetClocks() {
if ($elevated) { return ((& $smi -i $idx -rgc 2>&1 | Out-String).Trim() -replace '\s+', ' ') }
if ($viaHelper) { return (HelperSend 'rgc') }
return 'not attempted (not elevated, no helper)'
}
function Bench([string] $pk, [int] $n) {
$d = Join-Path $packs $pk
$env:CUDA_VISIBLE_DEVICES = $uuid
$t0 = Get-Date
$lines = @(& $exe --bench --pack $d --batches $n --batch-log2 24 --block-warps 1 --device 0 2>&1 | ForEach-Object { "$_" })
$rc = $LASTEXITCODE
$t1 = Get-Date
$res = $lines | Where-Object { $_ -match '^RESULT pack=' } | Select-Object -First 1
$fp = ''; $mhs = $null; $check = ''; $dev = ''
if ($res -and $res -match 'device=(.*?) arch=') { $dev = $Matches[1] }
if ($res -and $res -match 'fingerprint=([0-9a-f]{16})') { $fp = $Matches[1] }
if ($res -and $res -match 'mhs=([\d.]+)') { $mhs = [double]$Matches[1] }
if ($res -and $res -match 'check=(\S+)') { $check = $Matches[1] }
$err = ''
if (-not $res -or $rc -ne 0) { $e = ($lines | Where-Object { $_ -match 'FAIL|error|MISMATCH' } | Select-Object -First 1); $err = if ($e) { ($e -replace '\s+', ' ') } else { "exit $rc, no RESULT line" } }
return [pscustomobject]@{ pack = $pk; t0 = $t0; t1 = $t1; rc = $rc; fp = $fp; mhs = $mhs; check = $check; err = $err; res = $res; batches = $n; dev = $dev }
}
function Step([string] $label, [string] $lockNote) {
foreach ($pk in $packOrder) {
if ((Get-Date) -gt $deadline) { "RESULT STEP card=$cardName pack=$pk lock=$label error=budget spent"; continue }
$n = $nb[$pk]
$b = Bench $pk $n
if ($b.err) { "RESULT STEP card=$cardName pack=$pk lock=$label error=$($b.err) lock_note=$lockNote"; continue }
$w = ReadWindow $b.t0.AddSeconds(8) $b.t1
$watts = Mean ($w | ForEach-Object { $_[1] })
$sm = Mean ($w | ForEach-Object { $_[2] })
$mem = Mean ($w | ForEach-Object { $_[3] })
$tmax = ($w | ForEach-Object { [int]$_[4] } | Measure-Object -Maximum).Maximum
$thr = ($w | ForEach-Object { $_[5].Trim() } | Group-Object | Sort-Object Count -Descending | Select-Object -First 1).Name
$eff = if ($watts -and $watts -gt 0) { [math]::Round($b.mhs / $watts, 4) } else { $null }
$want = $expected[$pk]
$match = if ($want -and $b.fp -eq $want) { 'yes' } elseif ($want) { 'no' } else { 'unknown' }
$wall = [int]($b.t1 - $b.t0).TotalSeconds
"RESULT STEP card=$cardName pack=$pk lock=$label sm_mhz=$sm mem_mhz=$mem watts=$watts temp_max=$tmax throttle=$thr mhs=$($b.mhs) mh_per_w=$eff fingerprint=$($b.fp) match=$match expected=$want check=$($b.check) batches=$n wall_s=$wall samples=$($w.Count) device=$($b.dev) lock_note=$lockNote alone=$alone"
$script:rows += [pscustomobject]@{ pack = $pk; lock = $label; sm = $sm; watts = $watts; mhs = $b.mhs; eff = $eff; match = $match }
}
}
try {
$sampler = Start-Process -FilePath $smi -ArgumentList @('-i', $idx, '--query-gpu=timestamp,power.draw,clocks.sm,clocks.mem,temperature.gpu,clocks_throttle_reasons.active', '--format=csv,noheader,nounits', '-lms', '1000') -RedirectStandardOutput $samples -WindowStyle Hidden -PassThru
Start-Sleep -Seconds 6
$first = @(Get-Content -LiteralPath $samples -ErrorAction SilentlyContinue | Select-Object -First 2)
"RESULT sampler pid=$($sampler.Id) first=[$($first -join ' | ')]"
if ($first.Count -eq 0) { 'RESULT sampler error=no samples in 6 s; watts will read empty on every row' }
# the unlocked start: -rgc so the baseline is the driver's own choice, then the batch counts from a short probe per pack
$r0 = ResetClocks
"RESULT unlock start $r0"
$nb = @{}
foreach ($pk in $packOrder) {
$p = Bench $pk 5
if ($p.err) { "RESULT probe pack=$pk error=$($p.err)"; $nb[$pk] = 40 } else {
$n = [int][math]::Ceiling($stepSeconds * $p.mhs * 1e6 / 16777216.0)
$nb[$pk] = [math]::Max(20, [math]::Min(2000, $n))
"RESULT probe pack=$pk mhs=$($p.mhs) fingerprint=$($p.fp) batches_for_${stepSeconds}s=$($nb[$pk])"
}
}
Step 'unlocked' 'driver default (-rgc)'
if (-not $elevated -and -not $viaHelper) {
'RESULT locks skipped: the job is not elevated and the Power Helper task is not registered; the unlocked rows above are the measurement'
foreach ($c in $locks) { foreach ($pk in $packOrder) { "RESULT STEP card=$cardName pack=$pk lock=$c error=no lock path (not elevated, no helper task)" } }
} else {
foreach ($c in $locks) {
if ((Get-Date) -gt $deadline) { "RESULT STEP card=$cardName pack=* lock=$c error=budget spent"; continue }
$note = SetLock $c
$lockState = "locked $c"
if ($note -match '^refused') { foreach ($pk in $packOrder) { "RESULT STEP card=$cardName pack=$pk lock=$c error=lock $note" }; if ($c -eq $locks[0]) { 'RESULT locks refused on the first step: the remaining locks are skipped'; break } else { continue } }
Start-Sleep -Seconds 3
Step "$c" $note
}
$r1 = ResetClocks
$lockState = 'unlocked'
"RESULT unlock end $r1"
Start-Sleep -Seconds 3
Step 'unlocked-end' 'driver default (-rgc), the drift check'
}
# ---- the table ----
$tab = @{}
foreach ($pk in $packOrder) {
$mine = @($rows | Where-Object { $_.pack -eq $pk -and $_.mhs })
$un = $mine | Where-Object { $_.lock -eq 'unlocked' } | Select-Object -First 1
$withW = @($mine | Where-Object { $_.eff })
$best = $withW | Sort-Object eff -Descending | Select-Object -First 1
$tab[$pk] = @{ un = $un; best = $best }
if ($un -and $best) {
$rec = [math]::Round($un.watts - $best.watts, 1)
$lost = if ($un.mhs -gt 0) { [math]::Round(100.0 * ($un.mhs - $best.mhs) / $un.mhs, 2) } else { $null }
"RESULT TABLE card=$cardName pack=$pk unlocked_sm_mhz=$($un.sm) unlocked_mhs=$($un.mhs) unlocked_w=$($un.watts) unlocked_mh_per_w=$($un.eff) best_lock=$($best.lock) best_sm_mhz=$($best.sm) best_mhs=$($best.mhs) best_w=$($best.watts) best_mh_per_w=$($best.eff) recovered_w=$rec rate_lost_pct=$lost fingerprints_all_match=$((@($mine | Where-Object { $_.match -ne 'yes' })).Count -eq 0)"
} else { "RESULT TABLE card=$cardName pack=$pk error=no unlocked row or no row with watts" }
}
$u4 = $tab['v4-devnet-epoch0'].un; $u3 = $tab['mx8-devnet-epoch0'].un; $b4 = $tab['v4-devnet-epoch0'].best; $b3 = $tab['mx8-devnet-epoch0'].best
if ($u4 -and $u3 -and $b4 -and $b3) {
"RESULT TABLE card=$cardName v3_vs_v4 v4_extra_w_unlocked=$([math]::Round($u4.watts - $u3.watts, 1)) v4_extra_w_at_best=$([math]::Round($b4.watts - $b3.watts, 1)) v4_rate_vs_v3_unlocked_pct=$([math]::Round(100.0 * ($u4.mhs - $u3.mhs) / $u3.mhs, 2)) v4_rate_vs_v3_at_best_pct=$([math]::Round(100.0 * ($b4.mhs - $b3.mhs) / $b3.mhs, 2)) v4_best_lock=$($b4.lock) v3_best_lock=$($b3.lock)"
}
Summary 'done' @{ card = $cardName; elevated = $elevated; via_helper = $viaHelper; alone = $alone; steps = $rows.Count; mismatches = (@($rows | Where-Object { $_.match -eq 'no' })).Count }
exit 0
} finally {
try { $r = ResetClocks; "RESULT restore clocks reset: $r (was $lockState)" } catch { "RESULT restore clocks reset failed: $_" }
if ($sampler -and -not $sampler.HasExited) { try { Stop-Process -Id $sampler.Id -Force -ErrorAction SilentlyContinue; "RESULT restore sampler pid $($sampler.Id) stopped" } catch { } }
"RESULT gpus_after $(Stamp) $((& $smi --query-gpu=index,name,power.draw,clocks.sm,clocks.applications.graphics --format=csv,noheader 2>&1 | Out-String).Trim() -replace "`r?`n", ' | ')"
}

View file

@ -0,0 +1,260 @@
# Counter ASIC 3.0, PC 1 queue job (7 October 2026): gate G1 on the sub-version 3 kit and item 8's AMD ladder rows on PC 1's
# RX 9070 XT (machine ae432dc7, gfx1201 on the eGPU). The shape is tools/ca3-pc1-amd/pc1-amd-g1-shadow.ps1 (6 October 2026)
# with the kit of 7 October: fetch job fetch-ca3-v4-sub3-pc1-20261007 (bin/igneum-worker-opencl.exe from this tree's
# proto-opencl/host.c, cross-compiled on build-1 with mingw as proto-cuda/nvrtc/build-windows.sh builds the shipped worker;
# packs/: the class v3 control mx8-devnet-epoch0, the seven sub-version 3 class v4 packs v4-devnet-epoch0, v4-era-0 .. v4-era-5
# (program id a785001687d8688a), and the ladder packs sh256x13, sh256x27, sh256x53, sh256x88, sh64x52 re-exported under the
# amended stream; src/; SHA256SUMS). Published with the RUNNER's --cards-off amd:gfx1201 (the index-free key form; the runner
# matches the live card with or without the device index and restores it on ANY exit); the 5090, 5080 and 4070 keep mining.
# The script never touches /api/cards, never quits, pauses, resumes or updates the installed app, never writes settings.json;
# it confirms the card quiet by the process list, then runs the kit worker --bench-pack on every pack while the AMD helper
# samples board watts (the sampler by its own pid). Per pack: the self-test, the 2^24 fingerprint at base nonce 0 against the
# Mac's (G1: Metal = Apple OpenCL on 7 October 2026, 17:5x UTC), and a timed rate for the ladder row (MH/s, board watts,
# microjoules per hash). Lines: `RESULT G1 pack= fingerprint= match=`, `RESULT LADDER pack= ops= mhs= watts= uj=`, a
# `SUMMARY {json}` line. Read back with `node tools/jobs.mjs <job id>`.
$ErrorActionPreference = 'Continue'
$jobName = 'pc1-v4-sub3-amd-g1'
$kitId = 'fetch-ca3-v4-sub3-pc1-20261007'
$started = Get-Date
function Stamp { (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') }
function Summary([string] $status, [hashtable] $extra) {
$o = [ordered]@{ job = $jobName; status = $status; duration_s = [int]((Get-Date) - $started).TotalSeconds; finished_at = (Stamp) }
foreach ($k in $extra.Keys) { $o[$k] = $extra[$k] }
'SUMMARY ' + ($o | ConvertTo-Json -Compress -Depth 4)
}
# the expected 2^24 fingerprints at base nonce 0 (the Mac on Metal and Apple OpenCL, the 5090 on CUDA: status file
# section 3, docs/analysis/latency-shadow-2026-10-06.md sections 2 and 5, counter-asic-2-status.md 22:16 for the
# control; re-checked on Apple OpenCL through this kit's host.c on 6 October 2026, 15:2x UTC)
$expected = @{
'mx8-devnet-epoch0' = '90f794dd556f7a3b'
'v4-devnet-epoch0' = 'e370fb2080b7dbb1'
'v4-era-0' = 'b7237555d31fc3cf'
'v4-era-1' = 'b6b167fa15dfe2c9'
'v4-era-2' = '28bdf65eff33f2c4'
'v4-era-3' = 'e26d38c46f3f1b16'
'v4-era-4' = 'dd8fdf6ff4f59eed'
'v4-era-5' = '8bf40f5cb858d835'
'sh256x13' = 'ff8f707210659eb1'
'sh256x27' = '892b6d55a7ddcfcb'
'sh256x53' = '663c73c61f62cc54'
'sh256x88' = 'ec7ca430a061fa59'
'sh64x52' = '9dd010f79d8ca9f4'
}
# ops per hash as the status file counts them (x1.83 counted ops per shadow instruction + 930 for the base program);
# the shadow instruction count is read from each pack's program.json at run time and printed beside it
$opsPerHash = @{ 'mx8-devnet-epoch0' = 930; 'v4-devnet-epoch0' = 102100; 'v4-era-0' = 102100; 'v4-era-1' = 102100; 'v4-era-2' = 102100; 'v4-era-3' = 102100; 'v4-era-4' = 102100; 'v4-era-5' = 102100; 'sh256x13' = 49700; 'sh256x27' = 102100; 'sh256x53' = 199600; 'sh256x88' = 330700; 'sh64x52' = 49700 }
# timed dispatches of 2^24 nonces per pack: about 0.9 s each at 19 MH/s, longer where the shadow binds the card
$batches = @{ 'mx8-devnet-epoch0' = 90; 'v4-devnet-epoch0' = 90; 'v4-era-0' = 40; 'v4-era-1' = 40; 'v4-era-2' = 40; 'v4-era-3' = 40; 'v4-era-4' = 40; 'v4-era-5' = 40; 'sh256x13' = 90; 'sh256x27' = 90; 'sh256x53' = 80; 'sh256x88' = 60; 'sh64x52' = 90 }
$order = @('mx8-devnet-epoch0', 'v4-devnet-epoch0', 'v4-era-0', 'v4-era-1', 'v4-era-2', 'v4-era-3', 'v4-era-4', 'v4-era-5', 'sh256x13', 'sh256x27', 'sh256x53', 'sh256x88', 'sh64x52', 'mx8-devnet-epoch0')
"RESULT start $(Stamp) job=$jobName machine=$env:COMPUTERNAME app_version=$env:IGNEUM_APP_VERSION"
# ---- the kit (the wiped-jobs-folder rule: tested before use; republish the fetch after any app update) ----
$jobs = Split-Path $env:IGNEUM_JOB_DIR
$kit = Join-Path $jobs $kitId
if (-not (Test-Path $kit)) { "RESULT error kit missing at $kit (the fetch job $kitId runs first; republish it after any app update)"; Summary 'failed' @{ error = 'kit missing' }; exit 2 }
$exe = Join-Path $kit 'bin\igneum-worker-opencl.exe'
$packs = Join-Path $kit 'packs'
if (-not (Test-Path $exe)) { "RESULT error worker missing at $exe"; Summary 'failed' @{ error = 'worker missing' }; exit 2 }
if (-not (Test-Path $packs)) { "RESULT error packs missing at $packs"; Summary 'failed' @{ error = 'packs missing' }; exit 2 }
"RESULT worker kit $exe sha256 $((Get-FileHash -Algorithm SHA256 $exe).Hash.ToLower()) bytes $((Get-Item $exe).Length)"
foreach ($f in (Get-ChildItem $kit -Recurse -File | Where-Object { $_.Name -match '\.(exe|c|json)$|SHA256SUMS' } | Sort-Object FullName)) { "RESULT kitfile $($f.FullName.Substring($kit.Length + 1).Replace('\', '/')) sha256 $((Get-FileHash -Algorithm SHA256 $f.FullName).Hash.ToLower()) bytes $($f.Length)" }
$job = $env:IGNEUM_JOB_DIR; if (-not $job) { $job = Join-Path $env:TEMP 'igneum-ca3-pc1-amd' }; New-Item -ItemType Directory -Force -Path $job | Out-Null
# ---- the installed app: install folder (the installed worker for the cross-check and the AMD helper), app dir, URL ----
$inst = @("$env:LOCALAPPDATA\Programs\Igneum Miner", "$env:ProgramFiles\Igneum Miner") | Where-Object { Test-Path (Join-Path $_ 'igneum-app.exe') } | Select-Object -First 1
$appDir = if ($env:IGNEUM_APP_DIR) { $env:IGNEUM_APP_DIR } else { Join-Path $env:LOCALAPPDATA 'igneum\app' }
$urlFile = Join-Path $appDir 'app.url'
$base = $null
if (Test-Path $urlFile) { $base = (Get-Content -LiteralPath $urlFile -Raw).Trim().TrimEnd('/') }
"RESULT app install=$inst app_dir=$appDir url_present=$([bool]$base)"
# ---- the OpenCL device index of the 9070 XT, from the worker's own list (the current platform; the older platform's duplicate is marked dup) ----
$listExe = $exe
if ($inst -and (Test-Path (Join-Path $inst 'igneum-worker-opencl.exe'))) { $listExe = Join-Path $inst 'igneum-worker-opencl.exe' }
$list = @(& $listExe --list 2>&1 | ForEach-Object { "$_" })
$list | ForEach-Object { "RESULT list $_" }
$dev = $null
foreach ($l in $list) { if ($l -match '^\s*\[(\d+)\].*gfx1201' -and $l -notmatch 'dup') { $dev = [int]$Matches[1]; break } }
if ($null -eq $dev) { "RESULT error no gfx1201 device in --list (the eGPU is off the bus: every row OWED)"; Summary 'failed' @{ error = 'no gfx1201' }; exit 2 }
"RESULT device $dev gfx1201 (list from $listExe)"
# ---- the card is switched off by the RUNNER (`--cards-off amd:gfx1201` at publish), never by this script (rule of
# 6 October 2026, tools/ci/playbook-quit-check.sh rule 2); here only the read-only confirmation that its worker is gone ----
function Workers {
@(Get-CimInstance Win32_Process -Filter "Name = 'igneum-worker-opencl.exe' OR Name = 'igneum-worker-cuda.exe' OR Name = 'igneum-worker-metal.exe'" -ErrorAction SilentlyContinue | ForEach-Object { "$($_.Name):$($_.ProcessId):[$($_.CommandLine -replace '\s+', ' ')]" })
}
function CardWorkers { @(Get-CimInstance Win32_Process -Filter "Name = 'igneum-worker-opencl.exe'" -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -match "--device\s+$dev(\s|$)" } | ForEach-Object { $_.ProcessId }) }
"RESULT workers_before $(Stamp) $((Workers) -join ' ')"
# ---- the AMD helper: the installed app's igneum-gpu-telemetry.exe (0.3.10+), else the newest one under an amd-kit job folder ----
$tele = $null
if ($inst -and (Test-Path (Join-Path $inst 'igneum-gpu-telemetry.exe'))) { $tele = Join-Path $inst 'igneum-gpu-telemetry.exe' }
if (-not $tele) { $found = Get-ChildItem -Path (Join-Path $appDir 'jobs') -Recurse -Filter 'igneum-gpu-telemetry.exe' -ErrorAction SilentlyContinue | Where-Object { $_.FullName -match 'amd-kit' } | Sort-Object LastWriteTime -Descending | Select-Object -First 1; if ($found) { $tele = $found.FullName } }
$wattsSource = 'owed'
if ($tele) {
$one = @(& $tele 2>&1 | ForEach-Object { "$_" })
$one | ForEach-Object { "RESULT tele_once $_" }
$card9070 = $one | Where-Object { $_ -match '^amd \d+ .*name "[^"]*9070[^"]*" watts (\S+) .* source (\S+)' } | Select-Object -First 1
if ($card9070 -and $card9070 -match ' watts (\S+) .* source (\S+)$') { if ($Matches[1] -ne '-') { $wattsSource = $Matches[2] } }
"RESULT tele helper=$tele sha256=$((Get-FileHash -Algorithm SHA256 $tele).Hash.ToLower()) watts_source=$wattsSource (board watts through ADLX GPUPower, GPUTotalBoardPower as the fallback; 'owed' = the helper gives no watts for the 9070 XT)"
} else { "RESULT tele none (no igneum-gpu-telemetry.exe in the install folder or under an amd-kit job folder): watts OWED" }
$samples = Join-Path $job 'amd-samples.log'
$sampler = $null
$windows = @()
$results = @()
$cardAlone = $false
try {
# ---- the card quiet? read-only: the runner switched it off before this script started; its worker must be gone ----
$before = @(CardWorkers)
"RESULT card_workers_before pids=[$($before -join ' ')]"
if ($before.Count -eq 0) { "RESULT card already quiet: no opencl worker with --device $dev (the runner's --cards-off took, or --stop-miners)"; $cardAlone = $true }
else {
$t = 0
while ($t -lt 150) { Start-Sleep -Seconds 5; $t += 5; if ((@(CardWorkers)).Count -eq 0) { break } }
$left = @(CardWorkers)
if ($left.Count -eq 0) { "RESULT card $(Stamp) quiet after $t s: no igneum-worker-opencl process with --device $dev (the card to itself)"; $cardAlone = $true }
else { "RESULT card $(Stamp) UNCONFIRMED after $t s: opencl worker pids [$($left -join ' ')] still carry --device $dev (was the job published with --cards-off amd:gfx1201?); the rows below are LOADED-card figures" }
Start-Sleep -Seconds 5
}
"RESULT workers_during $(Stamp) $((Workers) -join ' ')"
# ---- the sampler: the helper at 1 Hz through a wrapper; its paths through the ENVIRONMENT (inherited by the child,
# never on a command line: Start-Process -ArgumentList in Windows PowerShell 5.1 joins arguments WITHOUT quoting,
# and "Igneum Miner" holds a space, which is why the 15:46Z run wrote no samples), the -File path quoted; ended
# by its own pid in finally. The sampler PROVES itself in the 12 idle seconds: the first three raw lines are
# printed, and without a 9070 XT line with watts the ladder is NOT run blind (the rates are on record from 15:46Z). ----
$samplerOk = $false
if ($tele -and $wattsSource -ne 'owed') {
$wrapper = Join-Path $job 'sampler.ps1'
$wrapperText = @'
$exe = $env:IGNEUM_SAMPLER_EXE
$out = $env:IGNEUM_SAMPLER_OUT
Add-Content -LiteralPath $out -Value ('wrapper start ' + (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fff') + ' exe=' + $exe + ' exists=' + (Test-Path -LiteralPath $exe))
& $exe -l 1 2>&1 | ForEach-Object { Add-Content -LiteralPath $out -Value ((Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fff') + ' ' + $_) }
Add-Content -LiteralPath $out -Value ('wrapper end ' + (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fff') + ' exit=' + $LASTEXITCODE)
'@
[IO.File]::WriteAllText($wrapper, $wrapperText, (New-Object System.Text.UTF8Encoding $false))
Remove-Item -LiteralPath $samples -Force -ErrorAction SilentlyContinue
$env:IGNEUM_SAMPLER_EXE = $tele
$env:IGNEUM_SAMPLER_OUT = $samples
$sampler = Start-Process -FilePath 'powershell.exe' -ArgumentList @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', ('"' + $wrapper + '"')) -WindowStyle Hidden -PassThru
"RESULT sampler pid=$($sampler.Id) wrapper=$wrapper file=$samples exe_via_env=$tele"
Start-Sleep -Seconds 12
$raw = @(); if (Test-Path -LiteralPath $samples) { $raw = @(Get-Content -LiteralPath $samples) }
$raw | Select-Object -First 3 | ForEach-Object { "RESULT sampler_raw $_" }
$n9070 = @($raw | Where-Object { $_ -match '^\S+ amd \d+ .*name "[^"]*9070[^"]*" watts [\d.]+ ' }).Count
"RESULT sampler_idle lines=$($raw.Count) lines_9070_with_watts=$n9070 sampler_alive=$(-not $sampler.HasExited)"
if ($n9070 -eq 0) {
$why = if ($raw.Count -eq 0) { 'the sampler wrote nothing in 12 s (file absent or empty)' } else { 'the sampler wrote ' + $raw.Count + ' line(s) but none is a 9070 XT line with a watts number (first lines above)' }
"RESULT watts error=$why; the ladder is NOT run blind"
foreach ($pk in $order) { "RESULT LADDER pack=$pk error=$why" }
} else { $samplerOk = $true; "RESULT idle $(Stamp) sampler proven on the idle window" }
} else { "RESULT watts error=no helper or no watts in its one-shot sample: the ladder runs with watts owed"; $samplerOk = $true }
# ---- the ladder ----
$i = 0
foreach ($pk in $order) {
if (-not $samplerOk) { break }
$i++
$d = Join-Path $packs $pk
if (-not (Test-Path $d)) { "RESULT G1 pack=$pk error=pack missing at $d"; "RESULT LADDER pack=$pk error=pack missing"; continue }
$instrs = 0; $cls = ''
try { $pj = Get-Content -LiteralPath (Join-Path $d 'program.json') -Raw | ConvertFrom-Json; $cls = [string] $pj.load_class; if ($pj.shadow) { $instrs = [int] $pj.shadow.instrs_per_hash } } catch { "RESULT note program.json of $pk unreadable: $($_.Exception.Message)" }
$n = $batches[$pk]
$tag = "$pk#$i"
$t0 = (Get-Date).ToUniversalTime()
"RESULT bench $tag start $(Stamp) batches=$n class=$cls shadow_instrs_per_hash=$instrs"
$lines = @(& $exe --bench-pack --pack $d --batches $n --batch-log2 24 --device $dev 2>&1 | ForEach-Object { "$_" })
$rc = $LASTEXITCODE
$t1 = (Get-Date).ToUniversalTime()
$lines | ForEach-Object { "RESULT bench $tag $_" }
"RESULT bench $tag end $(Stamp) exit=$rc wall_s=$([int]($t1 - $t0).TotalSeconds)"
$res = $lines | Where-Object { $_ -match '^RESULT pack=' } | Select-Object -First 1
$fp = ''; $mhs = ''; $check = ''
if ($res -and $res -match 'fingerprint=([0-9a-f]{16})') { $fp = $Matches[1] }
if ($res -and $res -match 'mhs=([\d.]+)') { $mhs = $Matches[1] }
if ($res -and $res -match 'check=(\S+)') { $check = $Matches[1] }
$buildMs = ''; if (($lines | Where-Object { $_ -match '^build ([\d.]+) ms clBuildProgram' } | Select-Object -First 1) -match '^build ([\d.]+) ms') { $buildMs = $Matches[1] }
$dsMs = ''; if (($lines | Where-Object { $_ -match '^pack .*: cache \d+ dataset (\d+) ' } | Select-Object -First 1) -match ' dataset (\d+) ') { $dsMs = $Matches[1] }
if (-not $res -or $rc -ne 0) {
$err = ($lines | Where-Object { $_ -match 'FAIL|error|MISMATCH' } | Select-Object -First 1); if (-not $err) { $err = "exit $rc, no RESULT line" }
"RESULT G1 pack=$pk error=$($err -replace '\s+', ' ')"
"RESULT LADDER pack=$pk error=$($err -replace '\s+', ' ')"
continue
}
$want = $expected[$pk]
$match = if ($want -and $fp -eq $want) { 'yes' } elseif ($want) { 'no' } else { 'unknown' }
"RESULT G1 pack=$pk fingerprint=$fp match=$match expected=$want check=$check card=gfx1201 harness=opencl-kit"
$results += [pscustomobject]@{ tag = $tag; pack = $pk; instrs = $instrs; ops = $opsPerHash[$pk]; mhs = $mhs; fp = $fp; match = $match; check = $check; build_ms = $buildMs; dataset_ms = $dsMs; t0 = $t0; t1 = $t1 }
Start-Sleep -Seconds 3
}
# ---- the cross-check: the INSTALLED worker on the control pack, if it carries --bench-pack ----
if ($inst -and (Test-Path (Join-Path $inst 'igneum-worker-opencl.exe'))) {
$iexe = Join-Path $inst 'igneum-worker-opencl.exe'
$help = (& $iexe --help 2>&1 | Out-String)
if ($help -match '--bench-pack') {
$d = Join-Path $packs 'mx8-devnet-epoch0'
$t0 = (Get-Date).ToUniversalTime()
"RESULT xcheck start $(Stamp) worker=installed sha256=$((Get-FileHash -Algorithm SHA256 $iexe).Hash.ToLower())"
$lines = @(& $iexe --bench-pack --pack $d --batches 40 --batch-log2 24 --device $dev 2>&1 | ForEach-Object { "$_" })
$rc = $LASTEXITCODE
$t1 = (Get-Date).ToUniversalTime()
$lines | Where-Object { $_ -match '^RESULT|^pack |^warm-up|FAIL|error' } | ForEach-Object { "RESULT xcheck $_" }
$res = $lines | Where-Object { $_ -match '^RESULT pack=' } | Select-Object -First 1
$fp = ''; $mhs = ''
if ($res -and $res -match 'fingerprint=([0-9a-f]{16})') { $fp = $Matches[1] }
if ($res -and $res -match 'mhs=([\d.]+)') { $mhs = $Matches[1] }
if ($res) { "RESULT XCHECK worker=installed pack=mx8-devnet-epoch0 fingerprint=$fp match=$(if ($fp -eq $expected['mx8-devnet-epoch0']) { 'yes' } else { 'no' }) mhs=$mhs exit=$rc (the kit worker's control rows are the LADDER lines)"; $windows += [pscustomobject]@{ tag = 'xcheck-installed'; t0 = $t0; t1 = $t1 } }
else { "RESULT XCHECK worker=installed error=exit $rc, no RESULT line" }
} else { "RESULT XCHECK worker=installed skipped: the installed igneum-worker-opencl.exe has no --bench-pack" }
}
} finally {
# ---- whatever happened: the sampler ended by its own pid (never by name: the app runs its own copy of the helper), the card back ----
if ($sampler) { try { & taskkill /T /F /PID $sampler.Id 2>&1 | Out-Null; "RESULT sampler ended pid=$($sampler.Id)" } catch { "RESULT sampler end error=$($_.Exception.Message)" } }
# the card comes back through the RUNNER (its restore on any exit), not here
"RESULT workers_after $(Stamp) $((Workers) -join ' ')"
}
# ---- the samples: board watts and clocks of the 9070 XT inside each bench window (after its first 12 s, before its last 2 s) ----
$rows = @()
if (Test-Path $samples) {
foreach ($l in (Get-Content -LiteralPath $samples)) {
if ($l -match '^(\S+) amd (\d+) bus (\S+) kind (\S+) name "([^"]*)" watts (\S+) temp_c (\S+) fan_rpm (\S+) fan_pct (\S+) mclk_mhz (\S+) gclk_mhz (\S+) util_pct (\S+) source (\S+)') {
if ($Matches[5] -notmatch '9070') { continue }
try { $ts = [datetime]::ParseExact($Matches[1], 'yyyy-MM-ddTHH:mm:ss.fff', [Globalization.CultureInfo]::InvariantCulture) } catch { continue }
$w = if ($Matches[6] -eq '-') { $null } else { [double] $Matches[6] }
$rows += [pscustomobject]@{ ts = $ts; w = $w; temp = $Matches[7]; fan = $Matches[9]; mclk = $Matches[10]; gclk = $Matches[11]; util = $Matches[12]; src = $Matches[13] }
}
}
}
"RESULT samples total $($rows.Count) file $samples (9070 XT lines)"
function Window([datetime] $t0, [datetime] $t1) { @($rows | Where-Object { $_.ts -gt $t0.AddSeconds(12) -and $_.ts -lt $t1.AddSeconds(-2) -and $null -ne $_.w }) }
$g1Match = 0; $ladderRows = 0
foreach ($r in $results) {
$in = @(Window $r.t0 $r.t1)
$watts = 'owed'; $uj = 'owed'; $gclk = 'owed'; $mclk = 'owed'; $tmax = 'owed'; $wmin = ''; $wmax = ''
if ($in.Count -gt 0) {
$m = $in | Measure-Object -Property w -Average -Minimum -Maximum
$watts = [math]::Round($m.Average, 1); $wmin = $m.Minimum; $wmax = $m.Maximum
$gclk = [math]::Round((($in | ForEach-Object { [double] $_.gclk }) | Measure-Object -Average).Average)
$mclk = [math]::Round((($in | ForEach-Object { [double] $_.mclk }) | Measure-Object -Average).Average)
$tmax = (($in | ForEach-Object { [double] $_.temp }) | Measure-Object -Maximum).Maximum
if ($r.mhs -and [double] $r.mhs -gt 0) { $uj = [math]::Round($m.Average / [double] $r.mhs, 3) }
}
if ($r.match -eq 'yes') { $g1Match++ }
$ladderRows++
"RESULT LADDER pack=$($r.pack) ops=$($r.ops) shadow_instrs=$($r.instrs) mhs=$($r.mhs) watts=$watts uj=$uj watts_min=$wmin watts_max=$wmax gclk_mhz=$gclk mclk_mhz=$mclk temp_max=$tmax samples=$($in.Count) window_s=$([int]($r.t1 - $r.t0).TotalSeconds) build_ms=$($r.build_ms) dataset_ms=$($r.dataset_ms) fingerprint=$($r.fp) match=$($r.match) card_alone=$cardAlone tag=$($r.tag)"
}
foreach ($win in $windows) { $in = @(Window $win.t0 $win.t1); if ($in.Count -gt 0) { $m = $in | Measure-Object -Property w -Average; "RESULT power $($win.tag) samples=$($in.Count) watts_mean=$([math]::Round($m.Average, 1))" } }
if ($rows.Count -gt 0) {
$idle = @($rows | Where-Object { $results.Count -gt 0 -and $_.ts -lt $results[0].t0 -and $null -ne $_.w })
if ($idle.Count -gt 0) { "RESULT idle_power samples=$($idle.Count) watts_mean=$([math]::Round(($idle | Measure-Object -Property w -Average).Average, 1)) (the card switched off in the app, before the first bench)" }
foreach ($r in $rows) { "RESULT sample $($r.ts.ToString('HH:mm:ss')) $($r.w) $($r.temp) $($r.fan) $($r.mclk) $($r.gclk) $($r.util) $($r.src)" }
}
$status = if (-not $samplerOk) { 'failed' } elseif ($results.Count -eq $order.Count) { 'done' } elseif ($results.Count -gt 0) { 'partial' } else { 'failed' }
"RESULT end $(Stamp)"
Summary $status @{ packs_run = $results.Count; packs_wanted = $order.Count; g1_match = $g1Match; ladder_rows = $ladderRows; watts = $wattsSource; sampler_proven = $samplerOk; card_alone = $cardAlone; device = $dev }
if ($status -eq 'failed') { exit 1 }
exit 0

View file

@ -22,6 +22,14 @@ check_file() {
if grep -vE '^\s*#' "$f" | grep -qE "api/cards"; then if grep -vE '^\s*#' "$f" | grep -qE "api/cards"; then
echo "playbook-quit: $f sends a request to the installed app's api/cards (a script never switches cards; ask the runner: publish-jobs.sh add --kind run --cards-off <key,key>)"; bad=1 echo "playbook-quit: $f sends a request to the installed app's api/cards (a script never switches cards; ask the runner: publish-jobs.sh add --kind run --cards-off <key,key>)"; bad=1
fi fi
# rule 3 (STANDING RULE, the project lead through main, 7 October 2026, 18:5x UTC): no PC job raises a UAC prompt or needs a click, ever.
# A script that launches anything with -Verb RunAs (or runas.exe) raises one; the rights path is the installed app's Igneum
# Power Helper task (Start-ScheduledTask by the owning user, commands through its cmd.txt: app/igneum-app/src/powertask.rs).
# publish-jobs.sh refuses --elevated for the same reason (the 18:27Z job run-ca3-pc1-v4-eff-5090-20261007: exit 251 after
# two minutes waiting for a click).
if grep -vE '^\s*#' "$f" | grep -qiE -e "(-Verb +['\"]?RunAs)|(\brunas(\.exe)? +/user)"; then
echo "playbook-quit: $f raises an administrator prompt (-Verb RunAs or runas): no PC job prompts; use the Igneum Power Helper task (app/igneum-app/src/powertask.rs)"; bad=1
fi
grep -qE "api/(quit|pause|resume)" "$f" || return $bad grep -qE "api/(quit|pause|resume)" "$f" || return $bad
if grep -vE '^\s*#' "$f" | grep -qE "igneum\\\\app\\\\app\.url|igneum/app/app\.url|Application Support/Igneum/app/app\.url|IGNEUM_APP_DIR[^\n]*app\.url|\\\$appDir[^\n]*'app\.url'"; then if grep -vE '^\s*#' "$f" | grep -qE "igneum\\\\app\\\\app\.url|igneum/app/app\.url|Application Support/Igneum/app/app\.url|IGNEUM_APP_DIR[^\n]*app\.url|\\\$appDir[^\n]*'app\.url'"; then
echo "playbook-quit: $f reads the installed app's URL file and sends quit, pause or resume to it (a job may only quit an engine it started: its own scratch URL file)"; bad=1 echo "playbook-quit: $f reads the installed app's URL file and sends quit, pause or resume to it (a job may only quit an engine it started: its own scratch URL file)"; bad=1
@ -38,7 +46,12 @@ if [ "${1:-}" = "--self-test" ]; then
if ! check_file "$t/good.ps1"; then echo "self-test FAILED: the good playbook failed"; exit 1; fi if ! check_file "$t/good.ps1"; then echo "self-test FAILED: the good playbook failed"; exit 1; fi
if check_file "$t/cards.ps1" >/dev/null; then echo "self-test FAILED: the api/cards switch passed"; exit 1; fi if check_file "$t/cards.ps1" >/dev/null; then echo "self-test FAILED: the api/cards switch passed"; exit 1; fi
if ! check_file "$t/state.ps1"; then echo "self-test FAILED: a read of api/state (api/cards only in a comment) failed"; exit 1; fi if ! check_file "$t/state.ps1"; then echo "self-test FAILED: a read of api/state (api/cards only in a comment) failed"; exit 1; fi
rm -rf "$t"; echo "self-test passed: the installed app's URL file with a quit fails, a scratch URL file passes, a request to api/cards fails, a read of api/state passes"; exit 0 printf '%s\n' '$p = Start-Process powershell.exe -Verb RunAs -ArgumentList @("-File", $s) -Wait -PassThru' > "$t/runas.ps1"
printf '%s\n' '# the old shape ran Start-Process -Verb RunAs; now the Power Helper task carries the rights' 'Start-ScheduledTask -TaskName "Igneum Power Helper"' > "$t/helper.ps1"
if check_file "$t/runas.ps1" >/dev/null; then echo "self-test FAILED: the RunAs launch passed"; exit 1; fi
if ! check_file "$t/helper.ps1"; then echo "self-test FAILED: the Power Helper task start (RunAs only in a comment) failed"; exit 1; fi
if ! bash packaging/ota/publish-jobs.sh add --kind run --target ae432dc7 --id self-test-elevated --elevated --script "$t/helper.ps1" --title "self-test" --dest "$t/dest" >/dev/null 2>&1; then :; else echo "self-test FAILED: publish-jobs.sh accepted --elevated (the 18:27Z job as published)"; exit 1; fi
rm -rf "$t"; echo "self-test passed: the installed app's URL file with a quit fails, a scratch URL file passes, a request to api/cards fails, a read of api/state passes, a -Verb RunAs launch fails, a Power Helper task start passes, publish-jobs.sh refuses --elevated"; exit 0
fi fi
# allowed senders: the installer's own stop step (the update-now path the rule names), and, pending the rule owner's # allowed senders: the installer's own stop step (the update-now path the rule names), and, pending the rule owner's
# word (6 October 2026, 15:10 UTC): tools/proving-v1/pc2-agg-cost.ps1 and its restore step pc2-agg-cost-restore.ps1, which switch the 5090 off through /api/cards # word (6 October 2026, 15:10 UTC): tools/proving-v1/pc2-agg-cost.ps1 and its restore step pc2-agg-cost-restore.ps1, which switch the 5090 off through /api/cards