backup: 2026-06-17 07:36
This commit is contained in:
@@ -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 | 0–100 |
|
||||
| 2–4 | sick, disabled, chronicDisease | 0/1 flags |
|
||||
| 5–7 | eduStage (0–6), inCollege, collegeDropout | |
|
||||
| 8–11 | employed, jobTier (0–4), incomeLevel, retired | |
|
||||
| 12–16 | inRelationship, married, everDivorced, divorceCount, widowed | |
|
||||
| 17–21 | 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
|
||||
Reference in New Issue
Block a user