A desktop productivity organism. A fire burns on your screen. Your work is its only fuel. It begins dying the moment you stop. If it dies, you are punished.
The fire is always decaying. Work does not "add" progress — work offsets decay. There is no coasting, no banked credit, no pause button. This is the entire design.
Consequence: every visible frame of a healthy fire is evidence you are working right now.
| Field | Type | Meaning |
|---|---|---|
L |
float, 0 – 2600 | Ascension. The single continuous progress scalar. |
mode |
enum | BURNING | DORMANT | EXTINCT |
starvedFor |
float (s) | Seconds since last positive fuel. Drives starvation ramp. |
peakL |
float | Highest L ever reached. Permanent. Never resets. |
lastTickWall |
epoch ms | For sleep/crash gap detection. |
L is continuous. There are 2600 stages (26 epochs × 100 substages). The renderer
consumes L directly as a float, so the fire morphs smoothly — substages are not
discrete art, they are points along a continuous parameter space.
| # | Epoch | # | Epoch |
|---|---|---|---|
| 0 | Spark in dry leaves | 13 | Main-sequence star |
| 1 | Ember / smolder | 14 | Blue giant |
| 2 | Kindling flame | 15 | Supernova |
| 3 | Campfire | 16 | Pulsar |
| 4 | Bonfire | 17 | Magnetar |
| 5 | Pyre | 18 | Quasar |
| 6 | Wildfire | 19 | Black hole accretion |
| 7 | Firestorm | 20 | Galactic core |
| 8 | Fire tornado | 21 | Galaxy cluster |
| 9 | Living flame (becomes an entity) | 22 | Cosmic web |
| 10 | Forge-heart | 23 | Universe-flame |
| 11 | Solar flare | 24 | Multiversal conflagration |
| 12 | Protostar | 25 | Omniversal flame |
Tick rate: 4 Hz (dt = 0.25s).
Derived each tick from two sensors:
- Focus class of the foreground window →
FUEL/NEUTRAL/POISON - Input intensity
i ∈ [0,1]=clamp(eventsPerMin / TARGET_EPM, 0, 1), and idle seconds since last input.
if idleSec > IDLE_DEAD (120s): w = -0.30 # you left. the fire notices.
elif idleSec > IDLE_SOFT (45s): w = 0.0 # staring, not working. no fuel.
elif class == POISON: w = -1.0 # actively hostile
elif class == FUEL: w = 0.35 + 0.65*i
else /* NEUTRAL */: w = 0.15 * (0.35 + 0.65*i)
Reading docs (FUEL, low input) still fuels at 0.35. Typing hard fuels at 1.0.
This is deliberate — thinking is work.
gain = max(w, 0) * CLIMB_RATE
starve = 1 + STARVE_RAMP * clamp(starvedFor / STARVE_FULL, 0, 1) # 1 → 6
decay = DECAY_BASE * (1 + L / DECAY_SCALE) * starve
poison = max(-w, 0) * POISON_RATE
dL/dt = gain - decay - poison
Constants (tunable in config.json):
| Const | Value | Unit |
|---|---|---|
CLIMB_RATE |
0.35 | stages/s at full work |
DECAY_BASE |
0.02 | stages/s at L=0 |
DECAY_SCALE |
157 | — |
STARVE_RAMP |
5.0 | multiplier ceiling − 1 |
STARVE_FULL |
600 | s to reach full starvation |
POISON_RATE |
0.50 | stages/s |
TARGET_EPM |
240 | input events/min for full intensity |
Equilibrium ascension for sustained work quality w:
L_eq = DECAY_SCALE * (CLIMB_RATE * w / DECAY_BASE - 1)
w = 1.00(flawless) →L_eq ≈ 2591— the Omniversal Flame is asymptotically reachable and only by near-perfect sustained work. It is meant to be nearly unreachable.w = 0.60→L_eq ≈ 1492— solid work day caps you around Supernova.w = 0.35→L_eq ≈ 805— reading-only caps you at Firestorm.
Decay felt at the top: at L = 2500, idle decay is 0.34 stages/s = 20 stages/min,
ramping to 120 stages/min once fully starved. You lose an epoch every ~5 minutes of
neglect, accelerating. Falling from Omniversal to dead is ~1 hour of total abandonment;
falling one visible epoch is ~4 minutes. The loss is felt immediately, the death is earned.
Poison: 10 minutes of YouTube Shorts = -300 stages = three epochs erased.
L ≤ 0 while mode == BURNING → mode = EXTINCT → punishment ladder fires.
The app must not judge you for time the machine was not usable.
mode = DORMANT freezes L entirely: no decay, no starvation accumulation, no punishment.
Triggers into DORMANT:
powerMonitor:suspend,lock-screen,shutdown- Wall-clock gap guard: if
now - lastTickWall > 90s, the process was frozen (sleep, hibernate, crash, laptop lid). Retroactively discard that interval — never apply decay for unobserved time. - Explicit
Sabbathcommand (user-declared away). Logged, not punished.
Triggers back to BURNING: resume, unlock-screen, or app relaunch after clean exit.
On unclean exit (crash/power loss), state is restored from the last 5-second checkpoint and the gap is treated as dormant. A crash is never a death.
Platform-abstracted behind sensors/index.js → { sample(): {idleSec, eventsPerMin, proc, title} }.
| Platform | Foreground window | Idle / input |
|---|---|---|
win32 |
GetForegroundWindow + GetWindowText via PowerShell/C# |
GetLastInputInfo |
wsl |
same PowerShell, invoked via /mnt/c/.../powershell.exe |
same |
linux |
xdotool getactivewindow getwindowname |
xprintidle |
Privacy: keystrokes are never read, logged, or transmitted. Only an event count and an idle timestamp are sampled. No network access, ever.
Ordered rules over {proc, title}; first match wins. Defaults:
POISON : youtube.*shorts | tiktok | instagram/reels | twitter|x.com | reddit
| netflix | twitch | (youtube.com AND NOT title matches allowlist)
FUEL : code|nvim|vim|jetbrains|terminal|wsl|figma|notion|obsidian|docs
| localhost | github.com | stackoverflow | *.md | pdf reader
NEUTRAL: everything else
YouTube is special-cased: a video whose title matches learnRegex (docs, talks,
lectures) is NEUTRAL, not POISON. Shorts is always POISON.
Rungs escalate as L falls, and the top rung fires on extinction.
| Rung | Trigger | Effect |
|---|---|---|
0 GUTTER |
w ≤ 0 for 20s |
Fire visibly gutters, desaturates, smoke. Low wind audio. |
1 WARN |
L fell 1 epoch in 5 min |
Toast: what you're losing, and your peakL. |
2 SHAME |
L < 40 |
Fullscreen always-on-top overlay. Undismissable while w ≤ 0. Shows the corpse of your fire and every stage you burned to reach it. |
3 BLOCK |
L < 15 |
Kill/blur POISON windows on sight. Hosts-file block of poison domains. |
4 LOCK |
L ≤ 0 (extinction) |
Force OS screen lock. You must physically return. |
5 NUCLEAR |
extinction and armed | OS shutdown, 60s abort countdown. |
Shutdown can destroy unsaved work. It is therefore:
- Disabled by default.
config.nuclear.enabled = false. - Armed only by setting the flag and typing the exact phrase
I ACCEPT THE LOSS OF UNSAVED WORKin settings. - Always preceded by a 60-second fullscreen countdown with a working abort key.
- Never fires within 120s of app start, or in
DORMANT, or if the wall-clock gap guard tripped this session (i.e. never punishes for a sleep or a crash). - Emits a desktop notification 10 minutes before it could become possible.
Rungs 3–5 are individually toggleable. Rung 0–2 are always on.
~/.fire/state.json — checkpointed every 5s (atomic write via tmp+rename).
~/.fire/ledger.jsonl — append-only, never truncated.
Every death writes one immutable line:
{"t":"2026-07-20T18:04:11Z","event":"EXTINCTION","peakL":1840,"epoch":"Quasar",
"livedFor":19442,"killedBy":"POISON:youtube.com/shorts","punishment":"LOCK"}The ledger is surfaced in the shame overlay. You are meant to read your failures.
Tracked: current streak (days without extinction), longest streak, peakL all-time,
total burn hours, cause-of-death histogram.
src/main/ electron main process — owns truth
engine.js the heat equation. pure, synchronous, unit-testable.
tiers.js epoch table + L → {epoch, substage, name} lookup
sensors/ index.js + win32.js | wsl.js | linux.js (+ probe.ps1)
classify.js {proc,title} → FUEL|NEUTRAL|POISON
session.js dormancy: powerMonitor + wall-clock gap guard
punish.js the ladder
store.js state checkpoint + append-only ledger
config.js defaults + ~/.fire/config.json merge + validation
src/renderer/ the fire itself
gl.js WebGL2 boilerplate
shaders/ fire.vert, fire.frag (continuous morph, no branching)
hud.js epoch name, substage, intensity, streak, peak
engine.js is pure — step(state, {w, dt}) → newState. No I/O, no Electron.
That is what makes the whole thing testable without a display or a desktop.
-
engine: equilibrium math matches §3.3 within 1% -
engine:Lstrictly decreases whenw ≤ 0, for allL > 0 -
engine: starvation ramp is monotonic and clamps at ×6 -
engine:Lnever goes negative; extinction fires exactly once -
session: a 10-minute wall-clock gap applies zero decay -
session: suspend→resume across an epoch boundary loses nothing -
classify: shorts=POISON, lecture=NEUTRAL, editor=FUEL — table-driven -
punish: NUCLEAR cannot fire unarmed, in dormancy, or within 120s of boot -
punish: abort countdown actually cancels -
store: ledger append survives kill -9 mid-write (atomic) - renderer: 60fps @1080p,
Lsweep 0→2600 shows no visual discontinuity