Files
roam/trmnlife.org
2026-06-17 07:36:46 +03:00

82 lines
4.2 KiB
Org Mode
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
:PROPERTIES:
:ID: 9465af82-4383-466c-bf09-5be19c328f0b
:END:
#+title: trmn/life
#+filetags: :project: :knowledge: :life:
** Architecture
~life.js~ is a standalone ES module (no dependencies) that exports:
- ~simulateLife(masterSeed)~~{ traits, buf, eventLog, people }~
- ~getDay(buf, day)~ → plain object (unpack one day from typed array)
- ~makeRng(masterSeed, day)~ → mulberry32 closure (day-local PRNG)
- ~DAYS~ (36501), ~FIELDS~ (22), ~F~ (field index map)
State is packed into ~Float32Array(DAYS * FIELDS)~ (~3.2MB). ~eventLog~ is ~Map<day, Array<{id, personId?, attended?}>>~ (entries are objects, NOT plain strings — changed when NPC system was added). ~people~ is ~Map<personId, PersonRecord>~ for all tracked NPCs. ~window.__life~ exposes ~{ traits, buf, eventLog, people, today, currentDay, masterSeed }~.
** PRNG
mulberry32 seeded per-day via splitmix32 hash:
#+begin_src js
makeRng(masterSeed, day) // derives seed from hash(masterSeed ^ day*0x9e3779b9)
#+end_src
- ~Math.imul~ throughout — bitwise-identical on all IEEE-754 JS engines
- Any day independently computable; no need to simulate prior days
- masterSeed = ~(Date.now() / 1000) | 0~ (wall-clock seconds)
- Reserved day slots: ~-1~ = protagonist traits; ~-2~ = family layout offsets (NPC system)
- NPC streams are fully isolated — see [[id:423cac95-a80d-4db6-8bef-14297fb38437][trmn/people]]
** State Fields (22 packed floats per day)
| Index | Field | Notes |
|-------|-------|-------|
| 0 | alive | 0/1 |
| 1 | health | 0100 |
| 24 | sick, disabled, chronicDisease | 0/1 flags |
| 57 | eduStage (06), inCollege, collegeDropout | |
| 811 | employed, jobTier (04), incomeLevel, retired | |
| 1216 | inRelationship, married, everDivorced, divorceCount, widowed | |
| 1721 | numChildren, friendCount, loneliness, wealthLevel, debtLevel | |
Fixed-at-birth traits (not packed): ~sex, introversion, ambition, resilience, baseHealth~
Internal-only (not packed): ~friendList, friendLifetimeIndex, childrenList, childLifetimeIndex~
** Scheduled Events (deterministic)
birth(0), kindergarten(1826), primary(3652), middle(5113), hs_grad(6575), college_grad(8036), retirement(23725), death_cap(36500)
Plus dynamically injected ~sibling_born~ entries (built inside ~simulateLife()~, merged + sorted into ~mergedSchedule~).
** Random Events (31 total, evaluated in priority order, cap 2/day)
Categories: death, disaster, health, financial, relationship, family, career, social
Death uses Gompertz curve: ~0.000008 * exp((age-40)/60 * 5) * healthMod * resilienceMod~
NPC-triggered events (not in EVENTS array): ~family_member_death~ (day loop step 4), ~friend_death~ (post-apply of lose_friend)
** Gotchas
- NEVER spread ~buf~ inside a loop — ~[...buf]~ on 800k-element Float32Array per iteration hangs the browser. Use ~buf[d * FIELDS + fieldIdx]~ directly.
- ~life.js~ must be listed in ~flake.nix~ installPhase (~cp $src/life.js $out/~) or Caddy serves a 404.
- eventLog entries are now ~{id, personId?, attended?}~ objects — any consumer using ~evs.includes('event_id')~ or ~counts[ev]~ will silently break. Use ~e.id~.
- ~breakup~ and ~marry~ both have weight 0.0008/day → equal competition → many characters die "in relationship, not married". May need tuning.
- "Unexpected end of input" on ~import('./life.js')~ usually means 404 (not rebuilt yet), not a real syntax error.
** Console Snippets
#+begin_src js
// Import and run
const { simulateLife, getDay, DAYS, FIELDS } = await import('/life.js');
const sim = simulateLife(42);
// Readable timeline (eventLog entries are objects now)
const timeline = [...sim.eventLog.entries()].map(([d, evs]) => ({
age: (d / 365.25 | 0), day: d, events: evs.map(e => e.id).join(', ')
}));
console.table(timeline);
// Inspect people (family + friends + children)
[...sim.people.entries()].forEach(([id, p]) =>
console.log(id, 'sex', p.sex, 'born', p.birthDay, 'dies', p.deathDay)
);
// Find death day (safe — no buf spreading)
const death = Array.from({length: DAYS}, (_, d) => d).find(d => d > 0 && sim.buf[d * FIELDS] === 0);
console.log('died age', death && (death / 365.25 | 0));
#+end_src
** Subnodes
- [[id:423cac95-a80d-4db6-8bef-14297fb38437][trmn/people]] — NPC system: PersonRecord schema, PRNG isolation, family generation, post-apply hooks, gotchas