Skip to content

Latest commit

 

History

History
258 lines (187 loc) · 9.8 KB

File metadata and controls

258 lines (187 loc) · 9.8 KB

FIRE — Specification

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.


1. Core principle

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.


2. State

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 ladder

# 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

3. The simulation

Tick rate: 4 Hz (dt = 0.25s).

3.1 Work intensity w ∈ [-1, +1]

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.

3.2 Heat equation

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

3.3 Why these numbers

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.60L_eq ≈ 1492 — solid work day caps you around Supernova.
  • w = 0.35L_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.

3.4 Extinction

L ≤ 0 while mode == BURNINGmode = EXTINCT → punishment ladder fires.


4. Dormancy (the "I cannot be working" rule)

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 Sabbath command (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.


5. Sensors

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.

Classification (classify.js)

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.


6. Punishment ladder

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.

NUCLEAR safety contract (non-negotiable)

Shutdown can destroy unsaved work. It is therefore:

  1. Disabled by default. config.nuclear.enabled = false.
  2. Armed only by setting the flag and typing the exact phrase I ACCEPT THE LOSS OF UNSAVED WORK in settings.
  3. Always preceded by a 60-second fullscreen countdown with a working abort key.
  4. 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).
  5. Emits a desktop notification 10 minutes before it could become possible.

Rungs 3–5 are individually toggleable. Rung 0–2 are always on.


7. Persistence & the Ledger

~/.fire/state.json — checkpointed every 5s (atomic write via tmp+rename). ~/.fire/ledger.jsonlappend-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.


8. Architecture

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 purestep(state, {w, dt}) → newState. No I/O, no Electron. That is what makes the whole thing testable without a display or a desktop.


9. Test plan (gates before ship)

  • engine: equilibrium math matches §3.3 within 1%
  • engine: L strictly decreases when w ≤ 0, for all L > 0
  • engine: starvation ramp is monotonic and clamps at ×6
  • engine: L never 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, L sweep 0→2600 shows no visual discontinuity