class v5: the design-to-code gap as section 13 (every item without code, test or measurement, with owner and hours: about 40 agent hours, 17 this lane's); the Metal pack bench takes a class v5 pack's leaves.bin (buffers 2 and 3 on igneum_build, the pack's FNV checked first)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-07 17:58:17 +00:00
parent 83395dd124
commit 3204af92dc
2 changed files with 56 additions and 6 deletions

View file

@ -247,3 +247,32 @@ Owed, named here so nobody looks for them: an acceptance-rule bound on a program
Litepaper, Mining, after "The work that waits can grow": "**The dataset is the chain.** Class v5 builds each day's dataset from the chain's own execution state at a block one hour before the day: every account, every storage slot and every byte of contract code, hashed under the day's state root and folded into every item. A card that does not hold the state cannot build the dataset, and a card that builds it wrong is wrong on every hash. The hash itself does not change, and neither does its speed: measured on an RTX 4090 on 6 October 2026, 63.08 against 63.09 million hashes a second at 207 W, the daily build 1.4 ms longer, a node's check 0.1 to 0.2 ms longer per block. What it buys is a floor under what mining means: a chip that recomputes items instead of storing them must now hold the state too, and a pool miner who today needs nothing but the date needs the state. What it does not buy is stated as plainly: a pool can ship the state to its miners once a day, so the claim is that every mining operation holds the chain, not every card; and against a chip that stores the whole dataset it changes nothing, which is why it sits beside the latency-shadow work, not in its place. It ships switched off, behind the 95 percent class signal with a floor height."
Ledger row M35 (in `docs/fud-ledger.md`): the scheme, the measured cost, the pool caveat, the stateless known-failed case, the owed items.
## 13. The design-to-code gap (for the coordinator, 7 October 2026, 19:0x UK)
Every item of this page that has no code, no test or no measurement yet, with the owner and the hours (agent hours; the project lead's rule). "Done" rows are listed first so the gap reads against them.
| Item | State | Owner | Hours |
|---|---|---|---|
| The generator and verifier (igneum-pow): `V5_CLASS`, generator 5, blake2b, the state leaves and the leaf XOR, the emitters' leaf buffer, `--state`, the pinned packs, the known-failed tests | done; on the frozen sub-version 3 base (ca3-v4-amend 017e7037 merged 18:5x UK, the source rule keyed with the state flag aside, 67 unit and 39 integration tests on build-2 before the merge, the suite re-running on it now) | this lane | 0 |
| The +0.2 ms per warp cost against the 10 ms gate | measured on igneum-build-1's reference core (section 7: +0.28 cold, +0.20 average, both classes under 10 ms loaded); NOT yet on one M5 Max core as the order asks (the Mac rule: one Metal or macOS run at a time under the lock) | this lane | 1 |
| The node side: the state commitment into the derivation, the per-epoch capture, the lock-free stream cache, the provider, the refusal, `igneum_getPowStateLeaves`, the signal rule and the floor, the RPC fields, the miner's fetch | done on the fork; rebased onto release-0.3.23-node at 19:0x UK (v5 pinned to object byte 6, counted exactly; byte 7 is v4 sub-version 3); the suites on build-2 owed on the rebased fork | this lane | 1 |
| The stateless and stale-chip rule as a test, known-failed first | done: `a_stateless_hasher_is_wrong_on_every_item` (igneum-pow), `class_v5_refuses_without_state_and_refreshes_the_leaves_per_epoch` (kaspa-pow), the harness's stale miner (66 of 66 rejected) and stateless node | this lane | 0 |
| The digest-compat rule for the new field | done: `program_class_v5_activation_daa` enters the digest only once set (the 0.3.15 rule; the params test covers the override file); the 60x file carries it at never | this lane | 0 |
| The hot-set cache rule (AP-F8-1, 1.067x bound) | inherited by merge from sub-version 3 (the dataflow freshness rule, the shared-operand rule, the 256 cap and the last resort); the bound itself is the attack-pass lane's F8 gate on the v5 stream | attack-pass lane (a3832b1c3b274b310) for the gate; this lane for the v5 pack export it reads | 2 |
| The weak-day FPGA rule (AP-F4-1, NAF sum at least 163, at least 4 distinct rotations) | NO code, NO test: recorded in section 11 with the rule text; lands in `memhard.rs` `MixParams::with_shape` behind the v5 class (the mixer draw redrawn from the next stream values), with the known-failed case (the worst calendar day 29,337 at 1.121x redrawn) and the per-day rejection count (6.1e-4) | this lane | 3 |
| The shadow-redundancy rule (AP-F1-1, 3.0 percent) | NO code, NO test: recorded in section 11 with the fraction; lands in `generator.rs` beside the shadow draw behind the v5 class (a block whose peephole-removable share exceeds 3.0 percent redrawn), with the known-failed case (the census's 5.078 percent block redrawn) | this lane | 3 |
| The attack-pass families on v5 (F1, F4, F8's 64-seed gate at 2^24 with the 1.2x line, F9's exhaustion count) | NOT run on v5; the harnesses exist on branch attack-pass for v4 | attack-pass lane; this lane hands it the v5 stream (`--program-class v5 --state`) and the pinned pack | 4 (its) |
| The kit for every platform: Metal | the kernel text is emitted (the leaf buffer on `igneum_build`); the Metal pack bench takes `leaves.bin` as of 19:0x UK (this commit); the one-click worker's Metal host (`proto-metal/main.swift`) does NOT yet upload the leaves | this lane (packbench), the worker lane for main.swift | 2 |
| The kit: CUDA | the kernel text emitted and measured on the 4090 through `proto-newpow/class-v5/bench.cu` (section 7); the one-click NVRTC host (`proto-cuda/nvrtc`) does NOT yet upload the leaves | worker lane | 2 |
| The kit: OpenCL (AMD), Intel | the kernel text emitted; no host, no run | worker lane (host), fleet or PC 1 (the 9070 XT run) | 3 |
| Fingerprints equal across platforms, G1 on the 5090 and the Mac, AMD and Intel after | the 4090 pack fingerprints are in (section 7); the Mac's Metal fingerprint is the clock reading below; the 5090 (PC 2) and AMD rows owed | this lane (Mac), the fleet and PC lanes (5090, 9070 XT) | 2 |
| The crossing on Devnet 3 by height after its gate | NOT started: the floor in the Devnet 3 override, the Devnet 2 style gate (zero rejected across the flip, exec roots agreeing, every node holding the epoch's stream); the harness's flip case is the rehearsal (PASS twice) | node lane (a283f5f0d364ceef0) owns the fork, the shipper (ae892a8b0f78fe31c) the cut; this lane the gate's v5 checks | 3 |
| Miner cost rows per card (5090, M5 Max, 9070 XT, 4070) | the 4090 row only (rate equal within 0.01 percent, build +0.24 ms); the four named cards owed, each labelled measured with the date | fleet (5090, 4070), this lane (M5 Max under the lock), PC 1 (9070 XT) | 3 |
| The exec snapshot wire carrying the day streams (a proof-synced node's trusted-data path) | NO code: the executor persists nothing of the captures across a restart and the p2p snapshot carries none | node lane | 3 |
| The pool protocol's per-epoch state fetch | NO code | pool lane | 2 |
| The day-state witness in the pruning-proof format | NO code (the class-signal witness is owed in the same shape) | node lane | 4 |
| The spec text (01 1.8.5 the leaf line, 1.12 the cut, 10 the witness) | NO text | this lane | 2 |
| The litepaper paragraph and ledger M35 | done (the litepaper's measured numbers are the 4090's) | this lane | 0 |
Sum of the gap: about 40 agent hours, 17 of them this lane's (the two draw rules, the M5 Max rows, the spec text, the gate's v5 checks), the rest the node, worker, pool, fleet and attack-pass lanes'.

View file

@ -51,6 +51,12 @@ func defineStr(_ name: String) -> String? {
guard let re = try? NSRegularExpression(pattern: pat), let m = re.firstMatch(in: programH, range: NSRange(programH.startIndex..., in: programH)) else { return nil }
return String(programH[Range(m.range(at: 1), in: programH)!])
}
/// An unquoted 64-bit define (`0x...ull`), the pack's FNV lines.
func defineU64(_ name: String) -> UInt64? {
let pat = "#define \(name) 0x([0-9a-fA-F]+)ull"
guard let re = try? NSRegularExpression(pattern: pat), let m = re.firstMatch(in: programH, range: NSRange(programH.startIndex..., in: programH)) else { return nil }
return UInt64(programH[Range(m.range(at: 1), in: programH)!], radix: 16)
}
let datasetLog2 = Int(defineU32("IGNEUM_DATASET_LOG2") ?? 28)
let datasetMode = defineU32("IGNEUM_DATASET_MODE") ?? 1
if datasetMode != 1 { fail("packbench runs memory-hard packs only") }
@ -126,9 +132,30 @@ let (cacheWall, cacheGpu) = run { enc in
enc.setComputePipelineState(fillPipe); enc.setBuffer(cache, offset: 0, index: 0)
enc.dispatchThreadgroups(MTLSize(width: cacheSegments / 256, height: 1, depth: 1), threadsPerThreadgroup: MTLSize(width: 256, height: 1, depth: 1))
}
func fnv1a64(_ p: UnsafeRawPointer, _ n: Int) -> UInt64 {
var h: UInt64 = 0xcbf29ce484222325
let b = p.bindMemory(to: UInt8.self, capacity: n)
for i in 0..<n { h ^= UInt64(b[i]); h = h &* 0x100000001b3 }
return h
}
let items = words / 16
// class v5 (docs/design/class-v5-stored-state.md): a pack with IGNEUM_STATE_LEAVES carries leaves.bin (16 words per leaf), bound
// as buffer 2 of igneum_build with the count in buffer 3; the pack's FNV of the leaves is checked first
var leavesBuf: MTLBuffer? = nil
var nLeaves: UInt32 = 0
if let n = defineU32("IGNEUM_STATE_LEAVES") {
let path = opts.pack + "/" + (defineStr("IGNEUM_STATE_LEAVES_FILE") ?? "leaves.bin")
guard let data = FileManager.default.contents(atPath: path) else { fail("class v5 pack without its leaves file \(path)") }
if data.count != Int(n) * 64 { fail("leaves.bin is \(data.count) bytes, the pack says \(n) leaves of 64") }
let fnv = data.withUnsafeBytes { fnv1a64($0.baseAddress!, data.count) }
guard let want = defineU64("IGNEUM_STATE_LEAVES_FNV64") else { fail("class v5 pack without IGNEUM_STATE_LEAVES_FNV64") }
if want != fnv { fail("leaves.bin FNV \(String(fnv, radix: 16)) is not the pack's \(String(want, radix: 16))") }
guard let b = device.makeBuffer(bytes: (data as NSData).bytes, length: data.count, options: .storageModeShared) else { fail("leaves alloc") }
leavesBuf = b; nLeaves = n
}
let (buildWall, buildGpu) = run { enc in
enc.setComputePipelineState(buildPipe); enc.setBuffer(cache, offset: 0, index: 0); enc.setBuffer(dataset, offset: 0, index: 1)
if let lb = leavesBuf { enc.setBuffer(lb, offset: 0, index: 2); var n = nLeaves; enc.setBytes(&n, length: 4, index: 3) }
enc.dispatchThreadgroups(MTLSize(width: items / 256, height: 1, depth: 1), threadsPerThreadgroup: MTLSize(width: 256, height: 1, depth: 1))
}
// cache fingerprint and dataset head/last through a blit to shared memory
@ -138,12 +165,6 @@ func blit(_ src: MTLBuffer, _ offset: Int, _ n: Int) -> MTLBuffer {
b.copy(from: src, sourceOffset: offset, to: dst, destinationOffset: 0, size: n); b.endEncoding(); cb.commit(); cb.waitUntilCompleted()
return dst
}
func fnv1a64(_ p: UnsafeRawPointer, _ n: Int) -> UInt64 {
var h: UInt64 = 0xcbf29ce484222325
let b = p.bindMemory(to: UInt8.self, capacity: n)
for i in 0..<n { h ^= UInt64(b[i]); h = h &* 0x100000001b3 }
return h
}
let cacheCopy = blit(cache, 0, cacheWords * 4)
let cacheFnv = fnv1a64(cacheCopy.contents(), cacheWords * 4)
let cacheOk = cacheFnv == cacheFnvWant