tools/heat-gate.mjs reads the HEAT lines of an app log and passes a hold within 1 degree for 4 hours with the hash following the slice; its self-test fires on a known hold, a drift, a hash through the rest, a short log, an empty log and a log without readings, and sits on the one gate beside heat-region.test.mjs. docs/plans/ember-heat.md carries the words, the loop, the sources, the per-tier table and the runbook for the project lead's desk (this lane never touches PC 1). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit 0d5fc6d258dee4774ebe7ea760736c9f05026d06)
14 KiB
Ember Heat: heat mode, the region and price prompt, the cost-against-rent line
Mission item 7 (docs/analysis/mission/mission.md section 2.7; invent.md section 5; future.md sections 3, 4, 8 and 10).
the project lead ordered it built on 7 October 2026, 10:1x UK. Branch ember-heat, worktree ../igneum-wt-ember-heat, based on
the miner-ui-4 tip 5d9c55a2 in the 0.3.19 line. App only: no digest, no protocol, ships in any cut. The first-month
lane (miner-ui-5) works beside it; this lane keeps to Ember, the Settings page and the Earnings cost line.
1. What a miner sees
| Where | What | Words |
|---|---|---|
| First run, step 2 of 3 | The region list, the price per kWh prefilled from the public table and editable, the restricted line for a listed region | "Every £ figure in the app comes from the price you type here. Nothing is fetched." |
| A Russian region or China chosen | The restricted line, the standing sentence first | "Mining may be restricted where you are. You are responsible for checking. Mining is banned in the Moscow region from 15 August 2026 to 2032." |
| Settings > Electricity | The same list and price, the source of the typical price | "Typical: 26.32 p per kWh (Ofgem cap, 1 October to 31 December 2026). Type what you pay, in pence per kWh. Nothing is fetched." |
| Settings > Heat mode | The switch, the set point, the schedule, a typed room reading, the live line | "Holding 20.0 °C: room 19.6 °C from your reading 40 min ago. heating, 5 min to go. Heat 60% of the time." |
| Cards, the Ember strip | The heat line | "Heat mode: holding 20.0 °C · room 19.6 °C · heating 58% of the time · 448 W of heat" |
| Cards, each row's Ember line | The duty and the watts | "heat 58% · 448 W of heat", or "heat 58% · resting" |
| A resting card | The row says why it shows 0 MH/s; the pill says Resting · heat mode; the big button keeps Stop mining | "resting: the room is 20.1 °C, holding 20.0 °C (heat mode)" |
| Earnings | The cost-against-rent row | "0.064 p per MH/s-hour · your cost of hash at 26.32 p/kWh and 2.44 W per MH/s, about USD 0.0008. Rented hash: USD 0.0117 per MH/s-hour (RunPod community pods, 38 pods at list price, 1,748 MH/s for USD 20.44 an hour, 6 October 2026). Yours is 14x cheaper than rented hash." |
The honest sentence on the switch, from invent.md section 5: "In heat mode your card is an electric heater that also earns; whether it beats your boiler depends on the network and the price, shown live."
2. Heat mode: the loop (app/igneum-app/src/heat.rs, engine.rs tick_heat)
| Part | Rule |
|---|---|
| Period | 10 minutes: the heating slice first, the rest after it. The hash follows: the miners run in the slice and stop for the rest. |
| Duty | Decided once per period on the reading at its start: duty = 0.5 per degree under the set point plus the integral; the integral moves 0.05 per degree per period and carries the steady share the room needs (starts at a half). Clamped 0 to 1. |
| Temperature source, in order | A typed reading (the miner's own thermometer, Settings > Heat mode > Room now), fresh for two hours. Else the coolest card's sensor after 3 minutes of rest, minus the idle offset (8 degrees until learned; typing a reading while the cards rest teaches the real offset), with the cooling tail still in the sensor taken off by its slope (one 60 s time constant, approximate). Else nothing: the loop heats 70% of every period and says "no room reading yet". |
| Card-sensor periods | The duty is capped at 70% so every period keeps the 3 minutes of rest the reading needs. A typed reading lifts the cap. |
| Schedule | "06:00 20, 22:00 16": each entry holds until the next; before the first the last of the day holds; times in the window's clock (the offset is saved with the schedule). Blank = one set point all day. |
| Set point | 5 to 30 degrees. A typed room reading is -20 to 50. |
| Rest mechanics | heat_rest: the miners stop the way a remote job stops them (stop_miners, the rows say why, nothing restarts them); the release re-arms every slot with a fresh pack check. A tune never starts during a rest and a running tune aborts into it. Paused mining, a remote job holding the cards, or no enabled card: no rest is taken. |
| Log | `HEAT t= phase=heating |
| State | state.heat (phase, set point, room and its source and error, duty this period and over the last hour, watts now and when heating, seconds to the next change, the one line). |
| Chain | Nothing. A heat-mode miner is a miner with a schedule (invent.md section 5); a thermostat that ramps over an hour is not a 10 percent removal (CLAUDE.md, the vote-weight rule). |
Model result (the tests in heat.rs, a 400 kJ/K room losing 20 W/K to 10 degrees outside, a 300 W card): with a typed reading refreshed every half hour the room stays within 1 degree of 20 for the four hours after the first, the hash on exactly while the slice heats, the integral settling near the two-thirds share the room needs; with the card sensor alone, the same hold, the estimate inside its stated 3 degrees. A model is not the gate; section 6 is.
3. The region and price prompt (ui/app.js View.REGIONS, the first-run step, Settings > Electricity)
| Rule | Where it comes from |
|---|---|
| The typical prices: UK 26.32 p (Ofgem cap Q4 2026), Ireland 40.42, Germany 38.69, Belgium 34.99, EU average 28.96, Hungary 10.82, Malta 12.82, Bulgaria 13.55 (euro cents, Eurostat H2 2025), US 17.7 c (EIA H2 2025), Kazakhstan about 4 c, Paraguay 4.4 c (4.4 to 6), Iran about 1 c, Ethiopia 2 c (2 to 5.3), Nigeria 4.8 c, the last five secondary and labelled | future.md 4.1 |
| A typed price, never fetched: the table prefills, the miner edits, the engine stores the number; no network call for a price or a rate | mission.md 2.7 |
| The restricted entries, and only these: Russia, Moscow region (15 August 2026 to 2032); Russia, Irkutsk, Buryatia, Zabaikalsky (seasonal bans); Russia, other region (ten regions, 1 January 2025 to 15 March 2031; the app says it does not carry the list of ten); China (illegal, the joint Notice of 6 February 2026) | future.md 4.4 and 8.3 item 7 |
| The sentence, verbatim in substance: "Mining may be restricted where you are. You are responsible for checking." | future.md 8.3 item 7 |
| Regions the file calls "nothing" for a home miner (Norway, Sweden, Kazakhstan, Paraguay, Iran, US states) carry no line | future.md 4.4 |
| The currency follows the region: £ and pence, € and euro cents, $ and US cents (every £ figure on every page) | this plan |
| The dollar rates used only to compare with the rent: USD 1.32 per pound, 1.08 per euro, the rates future.md 3.4 implies; approximate, dated 7 October 2026 | future.md 3.4 |
The test ui/heat-region.test.mjs checks the restricted list against the research file when the file is in the tree
(it is internal, outside the public export list, so a public tree skips that one test).
4. The cost-against-rent line (Earnings; View.rentLine, View.RENT)
cost per MH/s-hour = watts per MH/s / 1000 × price per kWh, in the region's minor unit; the live draw and rate of the mining cards, else the last tune's point ("the last tune"), else nothing. The rent is the bench log's measured USD 0.0117 per MH/s-hour (RunPod community pods, 6 October 2026); the test reads docs/bench-log.md and fails when the app's number drifts from the log's latest "Rental cost of hash" entry. The verdict: "Yours is Nx cheaper than rented hash", "Rented hash undercuts your card by Nx", or "Level with rented hash".
Per tier, at 26.32 p: a 5090 at 2.4 W per MH/s is 0.064 p (USD 0.0008), 14x under the rent; a 3.27 uJ card is USD 0.00114, 10x (future.md 3.4's row); an 8 or 12 GB card at 1.3 to 1.7x the 5090's uJ is 6 to 8x; the line flips the day idle datacentre hash on 5 c power sets the rent at USD 0.00016 (future.md 3.2 and 3.4), and the Earnings page shows it the morning it happens. Apple silicon reports no draw, so its line says "no reading yet" until a tune measures it.
5. Per tier
| Tier | Heat mode | The prompt and the rent line |
|---|---|---|
| 8 GB card at 150 W | a 150 W heater for a small room; the card sensor path works (NVIDIA and AMD report a temperature) | the dearest hash of the home tiers; the rent line shows it first |
| 12 and 16 GB cards | 160 to 220 W; the same | the same |
| 24 and 32 GB cards (5090 at 290 to 575 W) | a large-room heater; the typed reading is the better source at full duty | 10 to 14x under the rent at UK prices |
| A rig | a space heater that should not be in a bedroom; the whole rig heats and rests together (one room) | the rig's cost is the card's cost |
| A pool user | the duty shows as a share-rate pattern the pool sees; nothing else changes | the same |
| Windows, Linux | the same | the same |
| macOS, Apple silicon | 21 to 38 W is not a heater; no power reading, so the heat line says "no draw reading"; the mode still works on time | no cost line until a tune measures the draw |
| NVIDIA, AMD | the sensor is nvidia-smi or igneum-gpu-telemetry, read while the cards rest | the same |
6. The gate: the PC 1 four-hour hold, a runbook for the project lead's desk
This lane never touches PC 1 (CLAUDE.md: a job never quits, pauses or restarts the installed app). The gate is run by hand at the desk, on the installed app once a cut carrying this branch is on PC 1. Expected time: 4 h 20 min of waiting, 5 minutes of doing.
- Put a room thermometer on the desk, out of the PC's exhaust. Note the room temperature.
- In Igneum Miner: Settings > Electricity: region United Kingdom, the price as billed. Settings > Heat mode: Hold at a set point 1 degree above the room now (so the loop has something to do), schedule blank, Save. Room now: type the thermometer's reading, Use it. Switch "Hold a room temperature" on. The line under it reads "Holding N °C: room M °C from your reading just now. heating, 10 min to go." or close.
- Every 30 minutes for 4 hours: read the thermometer and type it into Room now, Use it (a fresh reading keeps the typed source for the whole run). Note the Cards strip line once an hour: the share of the hour and the watts.
- After 4 hours: Settings > Logs > Copy the log, paste it into a file, and run from the main checkout:
node tools/heat-gate.mjs <that file> --hours 4PASS = the room stayed within 1 degree of the set point on every sample, the hash was on exactly while the slice heated (90 s forgiven after each change), and the measured heating share matched the planned duty within 15 points. The tool's self-test (--self-test, in the pre-push gate) fires on a known hold and on a known drift, a hash through the rest, a short log, an empty log and a log without readings. - Switch heat mode off. The cards go back to mining whenever the node is synced.
What a FAIL means and what to do: a drift over 1 degree with a typed source is a gain problem (KP, KI in heat.rs; the
model room is not the desk) and comes back here with the log; a hash on through the rest is a rest that did not
hold (the log's heat: release lines say why); a missing room reading is a typed reading older than two hours.
The card-sensor path (no typed reading) is the second run, same steps without step 3; its gate is the same tool with the stated 3 degrees as the honest expectation of the estimate, and the band stays 1 degree on the thermometer.
7. Files
| File | What |
|---|---|
app/igneum-app/src/heat.rs |
the loop, the room estimate, the schedule, the log line, the words; 8 tests with a model room |
app/igneum-app/src/engine.rs |
tick_heat, heat_rest, heat_release, Cmd::Heat, Cmd::Region, the holds in tick_miners, tick_sweep and sweep_drive |
app/igneum-app/src/config.rs, state.rs |
the settings (region, heat_*) and state.heat |
app/igneum-app/src/server.rs |
POST /api/region {region, price_pence}, POST /api/heat {on, set_c, schedule, tz_min, room_c} |
app/igneum-app/ui/index.html, app.js, app.css |
the step, the two Settings cards, the strip line, the row words, the rent row, `?theme=light |
app/igneum-app/ui/heat-region.test.mjs |
the view tests (the Russian entry, the bench-log rate, the words) |
tools/heat-gate.mjs |
the gate reader and its self-test |
tools/ui-mock/server.mjs |
the heat and heat-rest scenarios |
tools/ci/pre-push.sh |
the new test file and the gate reader's self-test on the one gate |
8. Screenshots (the mock on port 4319, scenarios heat and heat-rest, the Mac host's snapshot mode)
docs/plans/ember-heat-shots/: h01-cards-dark-1200.png, h02-cards-light-1200.png (the strip's heat line, the rows'
duty and watts), h03-cards-resting-dark-1200.png (the rest: the pill, the rows, the strip), h04-settings-dark-1200.png,
h05-settings-light-1200.png (Electricity and Heat mode), h06-earnings-dark-1200.png, h07-earnings-light-1200.png
(the cost-against-rent row under miner-ui-5's IGN-first rows; retaken after the rebase onto miner-ui-5 e9fe1106), h08-region-step-dark-1200.png, h09-region-step-light-1200.png (step 2 of 3 with the
Moscow region chosen and its restricted line).
9. Owed
| What | Who |
|---|---|
| The PC 1 four-hour hold (section 6), on the first cut carrying this branch | the project lead, at the desk |
| The card-sensor path's idle offset and cooling tail on a real 5090 and a 9070 XT (the model's 8 degrees and 60 s are approximate) | the first run's log |
| A curtailment input (a webhook, a schedule, a price threshold; future.md 4.3) | a later lane |
| The 2025/422 sustainability indicators (future.md 8.3 item 2) | the site, not the app |