backup: 2026-06-17 07:36

This commit is contained in:
2026-06-17 07:36:46 +03:00
parent 78bcf45137
commit 4806acf57d
13 changed files with 544 additions and 66 deletions
+81
View File
@@ -0,0 +1,81 @@
: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