Chain scene: back to four states with a bold locked ring

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-josh 2026-10-03 17:05:19 +01:00
parent 7375cb5b7d
commit 57745fc056
6 changed files with 29 additions and 17 deletions

View file

@ -36,6 +36,7 @@
static const uint32_t SEEDW[8] = IGNEUM_SEEDW_INIT;
#if IGNEUM_DATASET_MODE == 0
// Same closed form as ds_elem in kernel.cu and datasetElem in proto-metal/main.swift.
static uint32_t host_ds_elem(uint32_t i, uint32_t d0, uint32_t d1) {
uint32_t x = i ^ d0;
@ -45,9 +46,11 @@ static uint32_t host_ds_elem(uint32_t i, uint32_t d0, uint32_t d1) {
x *= 0xC2B2AE3Du; x ^= x >> 16;
return x;
}
#endif
static double wallMs();
#if IGNEUM_DATASET_MODE == 1
static uint64_t fnv1a64(const void* p, size_t n) {
const uint8_t* b = (const uint8_t*)p;
uint64_t h = 0xcbf29ce484222325ull;
@ -55,7 +58,6 @@ static uint64_t fnv1a64(const void* p, size_t n) {
return h;
}
#if IGNEUM_DATASET_MODE == 1
// Memory-hard mode: the 256 MiB cache on the device (filled by the pack's kernel) and on the host (filled by the
// same mh_cache_segment text on one thread). Both are built once per process in setupCache(), compared word for
// word, and checked against the head, last line and FNV-1a 64 the Mac recorded in vectors.h.

View file

@ -40,6 +40,7 @@ __global__ void igneum_build(uint32_t* ds, const uint32_t* cache, uint32_t nItem
for (uint32_t i = 0u; i < 16u; ++i) d[i] = s[i];
}
}
// One hash per thread. blockDim.x is a multiple of 32; lane = threadIdx.x & 31 and every
// __shfl_xor_sync stays inside the lane's own warp, exactly like simd_shuffle_xor inside a
// 32-wide Metal SIMD group. Control flow is uniform, so the full 0xffffffff member mask is valid.
@ -144,6 +145,7 @@ cudaError_t igneum_launch_build(uint32_t* ds, const uint32_t* cache, uint32_t nI
igneum_build<<<grid, block>>>(ds, cache, nItems);
return cudaGetLastError();
}
cudaError_t igneum_launch_hash(const uint32_t* ds, uint64_t* out, uint32_t baseNonce, uint32_t mask,
uint32_t nonces, uint32_t blockWarps) {
if (blockWarps == 0u || blockWarps > 32u) return cudaErrorInvalidValue;

View file

@ -20,6 +20,7 @@ inline uint ds_elem(uint i, uint d0, uint d1) {
x *= 0xC2B2AE3Du; x ^= x >> 16;
return x;
}
kernel void igneum_hash(device const uint* dataset [[buffer(0)]],
device ulong* out [[buffer(1)]],
constant uint& baseNonce [[buffer(2)]],

View file

@ -558,8 +558,9 @@ func generateMSL(_ p: Program, datasetLog2: Int, source: LoadSource = .stored) -
return x;
}
"""
if p.hasWide { s += " #define WMASK (MASK & ~31u)\n\n" }
if p.hasWide { s += "#define WMASK (MASK & ~31u)\n\n" }
var buffer0 = "device const uint* dataset [[buffer(0)]]"
if case .inlineMemhard(let mp) = source {
s += emitMemhardCore(mp, cuda: false) + "\n"
@ -755,8 +756,7 @@ func generateCUDA(_ p: Program, memhard: MixParams?) -> String {
#include <cuda_runtime.h>
#include <cstdint>
#include "program.h"
\(memhard != nil ? "#include \"memhard.h\"" : "")
\(memhard != nil ? "#include \"memhard.h\"\n" : "")
__device__ __forceinline__ uint32_t splitmix32(uint32_t x) {
x ^= x >> 16; x *= 0x7feb352du;
x ^= x >> 15; x *= 0x846ca68bu;
@ -786,6 +786,7 @@ func generateCUDA(_ p: Program, memhard: MixParams?) -> String {
if (i < n) ds[i] = ds_elem(i, d0, d1);
}
"""
} else {
s += """
@ -805,6 +806,7 @@ func generateCUDA(_ p: Program, memhard: MixParams?) -> String {
}
}
"""
}
s += """
@ -863,6 +865,7 @@ func generateCUDA(_ p: Program, memhard: MixParams?) -> String {
return cudaGetLastError();
}
"""
} else {
s += """
@ -882,6 +885,7 @@ func generateCUDA(_ p: Program, memhard: MixParams?) -> String {
return cudaGetLastError();
}
"""
}
s += """
@ -2141,7 +2145,7 @@ func runDeterminism(_ opts: Options, ctx: DatasetContext) -> Bool {
}
let sameFill = fillFps[0] == fillFps[1]
if !sameFill || sampleBad != 0 { ok = false }
print("dataset fill: two fills fingerprint \(h64(fillFps[0])) and \(h64(fillFps[1])): \(sameFill ? "identical" : "DIFFERENT"); 4096 sampled words (incl. 0, 1, MASK-1, MASK) vs CPU datasetElem: \(sampleBad == 0 ? "all match" : "\(sampleBad) MISMATCH")")
print("dataset fill: two fills fingerprint \(h64(fillFps[0])) and \(h64(fillFps[1])): \(sameFill ? "identical" : "DIFFERENT"); 4096 sampled words (incl. 0, 1, MASK-1, MASK) vs CPU dataset word (\(ds.modeName)): \(sampleBad == 0 ? "all match" : "\(sampleBad) MISMATCH")")
} else {
print("dataset fill check skipped: could not allocate a shared copy")
}

View file

@ -655,7 +655,8 @@ def scenario_c(args):
fracs = (0.34, 0.40, 0.45, 0.50, 0.55)
configs = [("active", "cert", args.hours_c, 0.0, 240), ("active", "seen", args.hours_c, 0.0, 240),
("active", "frozen", args.hours_c, 0.0, 240), ("active", "cert", args.hours_c_total, 0.0, 2880),
("active", "cert", args.hours_c_total, 0.8, 240), ("total", "cert", args.hours_c_total, 0.0, 240)]
("active", "cert", args.hours_c_total, 0.8, 240), ("active", "cert", args.hours_c_total, 0.85, 240),
("total", "cert", args.hours_c_total, 0.0, 240)]
labels = []
for denom, pmode, hours, floor, presence in configs:
lab = P(denom=denom, pmode=pmode, floor=floor, presence=presence).label()
@ -715,7 +716,8 @@ def scenario_d(args):
fracs = (0.35, 0.50)
rows = []
dconfigs = [P(denom="active", delay=args.delay), P(denom="active", presence=2880, delay=args.delay),
P(denom="active", floor=0.8, delay=args.delay), P(denom="total", delay=args.delay)]
P(denom="active", floor=0.8, delay=args.delay), P(denom="active", floor=0.85, delay=args.delay),
P(denom="total", delay=args.delay)]
for frac in fracs:
for p in dconfigs:
days = args.days_d35 if frac < 0.4 else args.days_d50
@ -823,7 +825,8 @@ def scenario_e(args):
# supplementary: more splits, 0% attacker, plus the DAA-retarget and floor variants
configs2 = configs + [P(denom="active", pmode="cert", daa="full", delay=args.delay),
P(denom="active", pmode="seen", daa="full", delay=args.delay),
P(denom="active", pmode="cert", floor=0.8, daa="full", delay=args.delay)]
P(denom="active", pmode="cert", floor=0.8, daa="full", delay=args.delay),
P(denom="active", pmode="cert", floor=0.85, daa="full", delay=args.delay)]
labels2 = [p.label() for p in configs2]
rows = []
for name, fr in (("50/50", [0.5, 0.5]), ("60/40", [0.6, 0.4]), ("67/33", [0.67, 0.33]), ("80/20", [0.8, 0.2]),
@ -836,7 +839,8 @@ def scenario_e(args):
rows.append(rc)
out.append("Supplementary, 0% attacker, 150 and 360 min. Cell = conflicting locks; minutes to each side's first lock. "
"'+daa' = each side retargets to 1 block/s at once (worst case for the presence clock); "
"'+floor0.80' = active denominator never below 80% of total weight (a lock needs at least 53.3% of total).")
"'+floor0.80' = active denominator never below 80% of total weight (a lock needs at least 53.3% of total), "
"'+floor0.85' needs 56.7%.")
out.append("")
out.append(md_table(["honest split", "partition min"] + labels2, rows))
out.append("")
@ -927,8 +931,10 @@ def scenario_f(args):
"back to 1, min after end", "conflicting locks", "stalls during", "stalls after"], rows))
out.append("")
rows = []
configs2 = configs + [P(denom="active", pmode="cert", floor=0.8, delay=args.delay),
P(denom="active", pmode="cert", floor=0.85, delay=args.delay)]
for dur in (1, 2, 4):
for p in configs:
for p in configs2:
r = run_eclipse(args.seed, dur, True, p)
rows.append([dur, p.label(), "%.3f" % r["min_part"], fm(r["recover_min"]), r["conflicts"], fm(r["first_conflict_min"]),
r["ecl_locks"], r["honest_stalls"], r["post_stalls"]])

View file

@ -257,8 +257,7 @@ footer .wrap{padding-block:48px 32px}
<span><i class="sw" style="border:2px solid var(--line-2)"></i>mined</span>
<span><i class="sw" style="background:var(--molten)"></i>shards being proven</span>
<span><i class="sw" style="background:var(--ember)"></i>proven</span>
<span><i class="sw" style="border:2px dashed var(--molten);border-radius:50%"></i>checkpoint, every 30 s on chain</span>
<span><i class="sw" style="border:3px solid var(--ember);border-radius:50%"></i>locked by sustained miners, final</span>
<span><i class="sw" style="border:3px solid var(--ember);border-radius:50%;background:transparent"></i>checkpoint locked by sustained miners</span>
</div>
</div>
<div class="grid" style="margin-top:24px">
@ -490,8 +489,7 @@ footer .wrap{padding-block:48px 32px}
blocks.forEach(function(b){b.x-=speed*dt;var age=t-b.born;
if(b.state==='mined'&&age>1200)b.state='proving';
if(b.state==='proving'){for(var i=0;i<4;i++){if(!b.claimed[i]&&Math.random()<0.012){b.claimed[i]=true;var fromTop=Math.random()<0.5;sparks.push({x:Math.random()*W,y:fromTop?-6:H+6,tx:b.x,ty:b.y,b:b,i:i,p:0});}}
if(b.shards.every(function(v){return v>=1;})){b.state='proven';b.glow=1;proven++;cp.textContent=proven;if(b.cp)setTimeout(function(){b.locked=true;b.flash=1;locked++;cl.textContent=locked;ripples.push({x:b.x,y:b.y,r:S,a:1});ripples.push({x:b.x,y:b.y,r:S*0.5,a:1});},1800);}}
if(b.flash>0)b.flash-=0.012;
if(b.shards.every(function(v){return v>=1;})){b.state='proven';b.glow=1;proven++;cp.textContent=proven;if(b.cp)setTimeout(function(){b.locked=true;locked++;cl.textContent=locked;ripples.push({x:b.x,y:b.y,r:S,a:1});ripples.push({x:b.x,y:b.y,r:S*0.5,a:1});},1800);}}
if(b.glow>0)b.glow-=0.02;});
sparks.forEach(function(sp){sp.p+=0.035;sp.tx=sp.b.x;sp.ty=sp.b.y;if(sp.p>=1){sp.done=true;sp.b.shards[sp.i]=1;}});
sparks=sparks.filter(function(sp){return !sp.done;});
@ -503,15 +501,14 @@ footer .wrap{padding-block:48px 32px}
blocks.forEach(function(b){b.parents.forEach(function(q){if(blocks.indexOf(q)<0)return;var both=b.state==='proven'&&q.state==='proven';x.strokeStyle=both?'rgba(242,84,27,0.45)':'rgba(154,154,158,0.22)';x.lineWidth=both?1.5:1;x.beginPath();x.moveTo(b.x,b.y);var mx=(b.x+q.x)/2;x.bezierCurveTo(mx,b.y,mx,q.y,q.x,q.y);x.stroke();});});
// lock line: everything left of the newest locked block is final
var lk=null;blocks.forEach(function(b){if(b.locked&&(!lk||b.x>lk.x))lk=b;});
if(lk){if(lk.flash>0){x.fillStyle='rgba(242,84,27,'+(0.16*lk.flash)+')';x.fillRect(0,0,lk.x,H);}x.strokeStyle='rgba(242,84,27,0.7)';x.lineWidth=1.5;x.setLineDash([5,6]);x.beginPath();x.moveTo(lk.x,6);x.lineTo(lk.x,H-6);x.stroke();x.setLineDash([]);x.fillStyle='rgba(242,84,27,0.95)';x.font='600 '+Math.round(S*0.42)+'px IBM Plex Mono, monospace';x.textAlign='right';x.fillText('FINAL \u2190',lk.x-6,14);}
if(lk){x.strokeStyle='rgba(242,84,27,0.55)';x.lineWidth=1.5;x.setLineDash([5,6]);x.beginPath();x.moveTo(lk.x,6);x.lineTo(lk.x,H-6);x.stroke();x.setLineDash([]);}
// blocks
blocks.forEach(function(b){var h=S/2;
if(b.glow>0){var g=x.createRadialGradient(b.x,b.y,0,b.x,b.y,S*1.6);g.addColorStop(0,'rgba(242,84,27,'+(0.45*b.glow)+')');g.addColorStop(1,'rgba(242,84,27,0)');x.fillStyle=g;x.beginPath();x.arc(b.x,b.y,S*1.6,0,Math.PI*2);x.fill();}
if(b.state==='proven'){x.fillStyle='#F2541B';rr(b.x-h,b.y-h,S,S,S*0.24);x.fill();}
else{x.fillStyle='#0C0C0E';rr(b.x-h,b.y-h,S,S,S*0.24);x.fill();x.strokeStyle=b.state==='proving'?'rgba(255,179,92,0.9)':'rgba(58,58,66,1)';x.lineWidth=2;rr(b.x-h,b.y-h,S,S,S*0.24);x.stroke();
if(b.state==='proving'){var q=S/2-3;for(var i=0;i<4;i++){if(b.shards[i]>=1){x.fillStyle='#FFB35C';var qx=b.x-h+3+(i%2)*q,qy=b.y-h+3+Math.floor(i/2)*q;rr(qx,qy,q-1,q-1,2);x.fill();}}}}
if(b.cp&&!b.locked){x.strokeStyle='rgba(255,179,92,0.7)';x.lineWidth=1.5;x.setLineDash([3,3]);x.beginPath();x.arc(b.x,b.y,S*1.05,0,Math.PI*2);x.stroke();x.setLineDash([]);x.fillStyle='rgba(255,179,92,0.9)';x.font='600 '+Math.round(S*0.38)+'px IBM Plex Mono, monospace';x.textAlign='center';x.fillText('CHECKPOINT',b.x,b.y-S*1.35);}
if(b.locked){var pulse=0.85+0.15*Math.sin(t/300);x.strokeStyle='rgba(242,84,27,'+pulse+')';x.lineWidth=3;x.beginPath();x.arc(b.x,b.y,S*1.05,0,Math.PI*2);x.stroke();x.fillStyle='#F2541B';rr(b.x-S*0.9,b.y-S*1.75,S*1.8,S*0.6,4);x.fill();x.fillStyle='#0C0C0E';x.font='700 '+Math.round(S*0.4)+'px IBM Plex Mono, monospace';x.textAlign='center';x.fillText('LOCKED',b.x,b.y-S*1.32);}
if(b.locked){var pulse=0.8+0.2*Math.sin(t/350);x.strokeStyle='rgba(242,84,27,'+pulse+')';x.lineWidth=3;x.beginPath();x.arc(b.x,b.y,S*1.15,0,Math.PI*2);x.stroke();x.fillStyle='#F4F1EC';x.beginPath();x.arc(b.x,b.y,S*0.16,0,Math.PI*2);x.fill();}
});
// sparks (miners claiming shards)
sparks.forEach(function(sp){var e=1-Math.pow(1-sp.p,3);var sx=sp.x+(sp.tx-sp.x)*e,sy=sp.y+(sp.ty-sp.y)*e;x.strokeStyle='rgba(255,179,92,'+(0.5*(1-sp.p))+')';x.lineWidth=1;x.beginPath();x.moveTo(sp.x+(sp.tx-sp.x)*Math.max(0,e-0.15),sp.y+(sp.ty-sp.y)*Math.max(0,e-0.15));x.lineTo(sx,sy);x.stroke();x.fillStyle='#FFB35C';x.beginPath();x.arc(sx,sy,2.2,0,Math.PI*2);x.fill();});