# Counter ASIC 3.0, PC 1 AMD job 1b (6 October 2026, run b): the CANDIDATE row's watts (sh256x27, class v4, the 9070 XT # ALONE through the kit worker) for the item 8 table, the cell job 1 left owed. The control row (class v3, the card ON # in the app) is the separate read-only job pc1-amd-watts-app.ps1, because the runner switches the card off BEFORE a # script starts, so one job cannot hold the card ON for that row and OFF for this one. # Published as a signed `run` job with `--cards-off amd:gfx1201` (NOT --stop-miners): the RUNNER switches the 9070 XT # off through the app's own card path before this script and puts it back, with its own enabled flag, identities and # cap, on ANY exit (done, failed, timeout, aborted, the app quitting). This script never touches /api/cards (rule of # 6 October 2026: the first run of this job, run-ca3-pc1-amd-watts-20261006, switched the card itself, the 0.3.14 app # update relaunched the app at 17:52:54Z and killed the script mid-bench, its finally never ran and the card stayed off # until a hand POST; tools/ci/playbook-quit-check.sh rule 2). It confirms, read-only, by the process list that the # card's igneum-worker-opencl process (the one with its --device index) is gone; the sampler (a second copy of the # installed app's igneum-gpu-telemetry.exe at -l 1 through an environment-fed wrapper, ended by its own pid in # finally) proves itself on 12 idle seconds (first three raw lines, a 9070 XT line with watts) or the job fails loudly # with no bench; then 100 dispatches of 2^24 on sh256x27 (about 87 s at the 19.3 MH/s job 1 measured), the mean board # watts over the bench window after its first 12 s and before its last 2 s, microjoules per hash = watts / MH/s. The # helper's watts are read with the calibration the control job measured beside the app (`RESULT sampler_vs_app ratio=` # of pc1-amd-watts-app): the publisher substitutes it for __CALIBRATION_RATIO__ below (sed at publish time; the README # has the line); unsubstituted, the row prints `calibration_ratio=owed` and the watts stand as the helper's raw reading. # A ratio outside 0.95 to 1.05 prints the watts as owed with the reason. The 5090 and the 4070 keep mining. Never quits, # pauses, resumes or updates the app; never writes settings.json. Lines: `RESULT G1 ...`, `RESULT LADDER pack=sh256x27 # ops=102100 mhs= watts= uj= ... watts_source=adlx calibration_ratio=`, `RESULT finally_begin` as the finally's first # line, a `SUMMARY {json}` line. Read back with `node tools/jobs.mjs `. $ErrorActionPreference = 'Continue' $jobName = 'pc1-amd-watts' $kitId = 'fetch-ca3-pc1-amd-20261006' $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) } $expectedFp = '3d2e8245cc084d07' # sh256x27 at base nonce 0, 2^24 (the Mac, the 5090, PC 1 job 1) $candBatches = 100 $calibrationText = '__CALIBRATION_RATIO__' $ratio = $null if ($calibrationText -match '^\d+(\.\d+)?$') { $ratio = [double] $calibrationText } "RESULT start $(Stamp) job=$jobName machine=$env:COMPUTERNAME app_version=$env:IGNEUM_APP_VERSION calibration_ratio=$(if ($null -ne $ratio) { $ratio } else { 'owed (not substituted at publish)' })" $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' $packDir = Join-Path $kit 'packs\sh256x27' if (-not (Test-Path $exe)) { "RESULT error worker missing at $exe"; Summary 'failed' @{ error = 'worker missing' }; exit 2 } if (-not (Test-Path $packDir)) { "RESULT error pack missing at $packDir"; Summary 'failed' @{ error = 'pack missing' }; exit 2 } "RESULT worker kit $exe sha256 $((Get-FileHash -Algorithm SHA256 $exe).Hash.ToLower()) bytes $((Get-Item $exe).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 $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' } "RESULT app install=$inst app_dir=$appDir" $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 | Where-Object { $_ -match 'gfx1201|gfx1036' } | 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: the row stays OWED)"; Summary 'failed' @{ error = 'no gfx1201' }; exit 2 } "RESULT device $dev gfx1201 (list from $listExe)" # READ ONLY: the card's settings.json entries, for the record (the runner holds and restores them, not this script) try { $sj = Get-Content -LiteralPath (Join-Path $appDir 'settings.json') -Raw | ConvertFrom-Json foreach ($prop in $sj.cards.PSObject.Properties) { if ($prop.Name -like 'amd:*' -and $prop.Name -match 'gfx1201') { "RESULT card settings_key=$($prop.Name) enabled=$([bool] $prop.Value.enabled) identities=$($prop.Value.identities)" } } } catch { "RESULT settings_read_failed $($_.Exception.Message)" } 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+', ' ')]" }) } 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: one in-process sample first ---- $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 } } if (-not $tele) { "RESULT watts error=no igneum-gpu-telemetry.exe in the install folder or under an amd-kit job folder"; Summary 'failed' @{ error = 'no helper' }; exit 2 } $one = @(& $tele 2>&1 | ForEach-Object { "$_" }) $one | Where-Object { $_ -match '9070|^end' } | ForEach-Object { "RESULT tele_once $_" } $line9070 = $one | Where-Object { $_ -match '^amd \d+ .*name "[^"]*9070[^"]*" watts (\S+) ' } | Select-Object -First 1 if (-not $line9070 -or $line9070 -notmatch ' watts ([\d.]+) ') { "RESULT watts error=the helper's one-shot sample has no 9070 XT line with a watts number"; Summary 'failed' @{ error = 'no 9070 watts in the one-shot sample' }; exit 2 } "RESULT tele helper=$tele sha256=$((Get-FileHash -Algorithm SHA256 $tele).Hash.ToLower()) one_shot_watts=$($Matches[1])" $samples = Join-Path $job 'amd-samples.log' $sampler = $null $samplerOk = $false $cardAlone = $false $cand = $null try { # ---- the card quiet? read-only: the runner switched it off before this script started ---- $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)"; $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 row below is a LOADED-card figure" } Start-Sleep -Seconds 5 } "RESULT workers_during $(Stamp) $((Workers) -join ' ')" # ---- the sampler through the environment-fed wrapper; proven on 12 idle seconds ---- $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 bench is NOT run blind" "RESULT LADDER pack=sh256x27 error=$why" } else { $samplerOk = $true "RESULT idle $(Stamp) sampler proven on the idle window" $instrs = 0 try { $pj = Get-Content -LiteralPath (Join-Path $packDir 'program.json') -Raw | ConvertFrom-Json; if ($pj.shadow) { $instrs = [int] $pj.shadow.instrs_per_hash } } catch { } $t0 = (Get-Date).ToUniversalTime() "RESULT bench sh256x27 start $(Stamp) batches=$candBatches shadow_instrs_per_hash=$instrs" $lines = @(& $exe --bench-pack --pack $packDir --batches $candBatches --batch-log2 24 --device $dev 2>&1 | ForEach-Object { "$_" }) $rc = $LASTEXITCODE $t1 = (Get-Date).ToUniversalTime() $lines | Where-Object { $_ -match '^RESULT|^pack |^warm-up|^build |^kernel:|FAIL|error|MISMATCH' } | ForEach-Object { "RESULT bench sh256x27 $_" } "RESULT bench sh256x27 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] } 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=sh256x27 error=$($err -replace '\s+', ' ')"; "RESULT LADDER pack=sh256x27 error=$($err -replace '\s+', ' ')" } else { "RESULT G1 pack=sh256x27 fingerprint=$fp match=$(if ($fp -eq $expectedFp) { 'yes' } else { 'no' }) expected=$expectedFp check=$check card=gfx1201 harness=opencl-kit" $cand = [pscustomobject]@{ instrs = $instrs; mhs = $mhs; fp = $fp; t0 = $t0; t1 = $t1 } } } } finally { "RESULT finally_begin $(Stamp) (the card comes back through the RUNNER's restore on any exit, not here)" 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)" } } "RESULT workers_after $(Stamp) $((Workers) -join ' ')" } # ---- the helper samples inside the bench window (after its first 12 s, before its last 2 s) ---- $rows = @() if (Test-Path -LiteralPath $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' -or $Matches[6] -eq '-') { continue } try { $ts = [datetime]::ParseExact($Matches[1], 'yyyy-MM-ddTHH:mm:ss.fff', [Globalization.CultureInfo]::InvariantCulture) } catch { continue } $rows += [pscustomobject]@{ ts = $ts; w = [double] $Matches[6]; temp = $Matches[7]; mclk = $Matches[10]; gclk = $Matches[11]; util = $Matches[12] } } } } "RESULT samples total $($rows.Count) file $samples (9070 XT lines with watts)" $taken = $false if ($cand) { $in = @($rows | Where-Object { $_.ts -gt $cand.t0.AddSeconds(12) -and $_.ts -lt $cand.t1.AddSeconds(-2) }) $watts = 'owed'; $uj = 'owed'; $gclk = 'owed'; $mclk = 'owed'; $tmax = 'owed'; $wmin = ''; $wmax = ''; $why = ''; $rawMean = '' if ($in.Count -gt 0) { $m = $in | Measure-Object -Property w -Average -Minimum -Maximum $rawMean = [math]::Round($m.Average, 1) $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 ($null -ne $ratio -and ($ratio -lt 0.95 -or $ratio -gt 1.05)) { $why = "calibration ratio $ratio outside 0.95..1.05: the helper disagreed with the app in the control job; raw helper mean $rawMean W kept for the record only" } else { $watts = $rawMean; $wmin = $m.Minimum; $wmax = $m.Maximum if ($cand.mhs -and [double] $cand.mhs -gt 0) { $uj = [math]::Round($m.Average / [double] $cand.mhs, 3) } $taken = $true } } else { $why = 'no helper samples inside the bench window' } "RESULT LADDER pack=sh256x27 ops=102100 shadow_instrs=$($cand.instrs) mhs=$($cand.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]($cand.t1 - $cand.t0).TotalSeconds) watts_source=adlx calibration_ratio=$(if ($null -ne $ratio) { $ratio } else { 'owed' }) raw_helper_mean=$rawMean fingerprint=$($cand.fp) card_alone=$cardAlone$(if ($why) { " owed_reason=$why" })" } foreach ($r in $rows) { "RESULT sample $($r.ts.ToString('HH:mm:ss')) $($r.w) $($r.temp) $($r.mclk) $($r.gclk) $($r.util)" } $status = if (-not $samplerOk) { 'failed' } elseif ($taken) { 'done' } elseif ($cand) { 'partial' } else { 'failed' } "RESULT end $(Stamp)" Summary $status @{ candidate_row = [bool]$cand; watts_taken = $taken; calibration_ratio = "$ratio"; sampler_proven = $samplerOk; card_alone = $cardAlone; device = $dev } if ($status -eq 'failed') { exit 1 } exit 0