@adam0white/human-framework 2.23.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +169 -55
- package/dist/agenda/agenda.js +1 -1
- package/dist/beliefs/beliefs.js +2 -2
- package/dist/cognition/cognition.d.ts +6 -1
- package/dist/cognition/cognition.js +1 -1
- package/dist/conscience/conscience.d.ts +2 -4
- package/dist/conscience/conscience.js +3 -5
- package/dist/conversation/conversation.d.ts +0 -41
- package/dist/conversation/conversation.js +5 -5
- package/dist/core/index.d.ts +0 -1
- package/dist/core/index.js +0 -1
- package/dist/core/libm.js +20 -0
- package/dist/core/math.d.ts +0 -2
- package/dist/core/math.js +3 -3
- package/dist/family/family.d.ts +5 -6
- package/dist/family/family.js +34 -12
- package/dist/habits/habits.js +5 -5
- package/dist/impression.d.ts +6 -8
- package/dist/impression.js +7 -9
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/longrun.d.ts +2 -2
- package/dist/longrun.js +1 -2
- package/dist/memory/memory.d.ts +0 -4
- package/dist/memory/memory.js +20 -6
- package/dist/migrate.js +5 -0
- package/dist/person.d.ts +11 -4
- package/dist/person.js +24 -7
- package/dist/persona.js +2 -1
- package/dist/prospection/forecast.d.ts +10 -5
- package/dist/prospection/forecast.js +11 -6
- package/dist/sim/index.d.ts +1 -1
- package/dist/sim/index.js +1 -1
- package/dist/sim/longrun.d.ts +24 -6
- package/dist/sim/longrun.js +43 -13
- package/dist/sim/sim.d.ts +28 -2
- package/dist/sim/sim.js +146 -36
- package/dist/social/impressions.d.ts +0 -12
- package/dist/social/impressions.js +13 -8
- package/dist/social/social.js +2 -2
- package/dist/types.d.ts +5 -2
- package/dist/types.js +5 -2
- package/package.json +1 -1
package/dist/sim/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export * from './longrun.js';
|
|
2
|
-
export
|
|
2
|
+
export { acceptJoint, addPerson, birth, communityState, createCommunity, declineJoint, interruptPerson, interrupts, jointSuccessChance, mirrorAffordance, nextEventAt, preview, proposeJoint, runSilent, SIM_DEFAULTS, sharesPlace, standingHeard, stepCommunity, tellDeath, } from './sim.js';
|
package/dist/sim/longrun.d.ts
CHANGED
|
@@ -10,31 +10,49 @@
|
|
|
10
10
|
* back to `stepCommunity` at the stretch's end: a common pattern is routine for the years nobody watches, lived
|
|
11
11
|
* minutes for the days someone is watching. Switching fidelity is the host's decision and happens on a day boundary.
|
|
12
12
|
*
|
|
13
|
+
* With `deathHandling: 'community'`, entry and dawn catch-up deaths are discovered at the sync minute and
|
|
14
|
+
* skip routine work. Routine deaths retain their whole-day batch ordering; attended news and proposal cleanup
|
|
15
|
+
* run only after every person's routine. Suppressing news does not suppress cleanup. This adds no mortality,
|
|
16
|
+
* funeral or religious model. The host pins the option after restore; omission retains the old path.
|
|
17
|
+
*
|
|
13
18
|
* It does not run decisions, conversation, joint activities, contagion or the world's percepts and affordances: those
|
|
14
19
|
* belong to lived time. Companions are listed by the host on both sides (each person's routine names the other),
|
|
15
20
|
* since a routine day writes only its own person. Births and arrivals are the host's (`birth`, `addPerson` in
|
|
16
21
|
* `onDay`); a person added during the stretch starts on the next day.
|
|
22
|
+
*
|
|
23
|
+
* Opt-in `upbringing.model: 'warmth-through-25'` selects living parents or guardians from the young person's
|
|
24
|
+
* ties, counting each different person once, through young adulthood (under 25). Guardianship does not supply
|
|
25
|
+
* warmth: `raise` reads caregiver-to-child affection. Its continuous age taper and warmth-scaled rates are
|
|
26
|
+
* engineering assumptions; no abuse, schooling or adult culture diffusion. Hosts pin `upbringing` in saved-run
|
|
27
|
+
* configuration and supply it after restore. Marriage, parenthood and co-residence do not gate this model;
|
|
28
|
+
* a living carer in the community suffices. Omitted, the under-18, parents-only path stays exact.
|
|
17
29
|
*/
|
|
18
|
-
import { type
|
|
30
|
+
import { type RaiseOptions } from '../family/index.ts';
|
|
31
|
+
import { type RoutineDay, type RoutineSource } from '../longrun.ts';
|
|
19
32
|
import type { SkillTransfer } from '../skills/index.ts';
|
|
20
|
-
import type { Minute,
|
|
21
|
-
import { type Community, type LifecourseOptions, type SimEvent } from './sim.ts';
|
|
33
|
+
import type { Minute, PersonId } from '../types.ts';
|
|
34
|
+
import { type Community, type LifecourseOptions, type SimEvent, type StepOptions } from './sim.ts';
|
|
22
35
|
export declare const LIVE_COMMUNITY_DEFAULTS: {
|
|
23
36
|
/** Upbringing time a day with the parents (engineering assumption: a few waking hours together). */
|
|
24
37
|
upbringingMinutes: number;
|
|
25
38
|
};
|
|
26
39
|
export interface LiveCommunityOptions {
|
|
27
40
|
/** Each living person's routine for a day (`day` is the day number being lived). */
|
|
28
|
-
routineFor
|
|
41
|
+
routineFor: RoutineSource;
|
|
29
42
|
/** At each dawn, before anyone's day: births, arrivals, routine changes. */
|
|
30
43
|
onDay?(c: Community, day: number, at: Minute): void;
|
|
31
44
|
/** Tell each death to everyone with a tie (default true; see `tellDeath`). */
|
|
32
45
|
tellDeaths?: boolean;
|
|
46
|
+
/** Opt into attended death receipts and scheduler cleanup; pin on every resumed call. */
|
|
47
|
+
deathHandling?: StepOptions['deathHandling'];
|
|
33
48
|
/**
|
|
34
|
-
* Minutes of upbringing a day for
|
|
35
|
-
*
|
|
49
|
+
* Minutes of upbringing a day for eligible young people with a living carer in the community (`family.raise`).
|
|
50
|
+
* Default eligibility is under 18 with a parent; warmth-through-25 includes guardians and ages under 25.
|
|
51
|
+
* Defaults to `LIVE_COMMUNITY_DEFAULTS.upbringingMinutes`; 0 turns it off.
|
|
36
52
|
*/
|
|
37
53
|
upbringingMinutes?: number;
|
|
54
|
+
/** Opt-in household model and voice selection, passed to `raise`. Pin in host saved-run configuration. */
|
|
55
|
+
upbringing?: RaiseOptions;
|
|
38
56
|
lifecourse?: LifecourseOptions;
|
|
39
57
|
transfer?: SkillTransfer;
|
|
40
58
|
}
|
package/dist/sim/longrun.js
CHANGED
|
@@ -10,19 +10,30 @@
|
|
|
10
10
|
* back to `stepCommunity` at the stretch's end: a common pattern is routine for the years nobody watches, lived
|
|
11
11
|
* minutes for the days someone is watching. Switching fidelity is the host's decision and happens on a day boundary.
|
|
12
12
|
*
|
|
13
|
+
* With `deathHandling: 'community'`, entry and dawn catch-up deaths are discovered at the sync minute and
|
|
14
|
+
* skip routine work. Routine deaths retain their whole-day batch ordering; attended news and proposal cleanup
|
|
15
|
+
* run only after every person's routine. Suppressing news does not suppress cleanup. This adds no mortality,
|
|
16
|
+
* funeral or religious model. The host pins the option after restore; omission retains the old path.
|
|
17
|
+
*
|
|
13
18
|
* It does not run decisions, conversation, joint activities, contagion or the world's percepts and affordances: those
|
|
14
19
|
* belong to lived time. Companions are listed by the host on both sides (each person's routine names the other),
|
|
15
20
|
* since a routine day writes only its own person. Births and arrivals are the host's (`birth`, `addPerson` in
|
|
16
21
|
* `onDay`); a person added during the stretch starts on the next day.
|
|
22
|
+
*
|
|
23
|
+
* Opt-in `upbringing.model: 'warmth-through-25'` selects living parents or guardians from the young person's
|
|
24
|
+
* ties, counting each different person once, through young adulthood (under 25). Guardianship does not supply
|
|
25
|
+
* warmth: `raise` reads caregiver-to-child affection. Its continuous age taper and warmth-scaled rates are
|
|
26
|
+
* engineering assumptions; no abuse, schooling or adult culture diffusion. Hosts pin `upbringing` in saved-run
|
|
27
|
+
* configuration and supply it after restore. Marriage, parenthood and co-residence do not gate this model;
|
|
28
|
+
* a living carer in the community suffices. Omitted, the under-18, parents-only path stays exact.
|
|
17
29
|
*/
|
|
18
30
|
import { endDay } from '../chronicle/index.js';
|
|
19
31
|
import { dayOf } from '../core/index.js';
|
|
20
32
|
import { raise } from '../family/index.js';
|
|
21
33
|
import { ageYears } from '../lifecourse/index.js';
|
|
22
34
|
import { routineDay } from '../longrun.js';
|
|
23
|
-
import { tick } from '../person.js';
|
|
24
35
|
import { MINUTES_PER_DAY } from '../types.js';
|
|
25
|
-
import { tellDeath } from './sim.js';
|
|
36
|
+
import { advanceCommunityPerson, cleanupCommunityDeath, tellDeath, } from './sim.js';
|
|
26
37
|
export const LIVE_COMMUNITY_DEFAULTS = {
|
|
27
38
|
/** Upbringing time a day with the parents (engineering assumption: a few waking hours together). */
|
|
28
39
|
upbringingMinutes: 240,
|
|
@@ -36,6 +47,15 @@ const UPBRINGING_UNTIL = 18;
|
|
|
36
47
|
export function liveCommunity(c, until, opts) {
|
|
37
48
|
const events = [];
|
|
38
49
|
const days = {};
|
|
50
|
+
const log = (e) => {
|
|
51
|
+
const p = c.people.find((q) => q.id === e.personId);
|
|
52
|
+
events.push(p?.lexicon?.plainEnglish && e.kind === 'died' ? { ...e, detail: `${p.name} died.` } : e);
|
|
53
|
+
};
|
|
54
|
+
if (opts.deathHandling === 'community') {
|
|
55
|
+
// Older raw community JSON lacks these maps. Only the opt-in needs them here.
|
|
56
|
+
c.interrupts ??= {};
|
|
57
|
+
c.joint ??= { nextId: 0, proposals: [], excluded: {} };
|
|
58
|
+
}
|
|
39
59
|
// Start at the first midnight at or after the community's clock: whoever is behind lives up to it minute by
|
|
40
60
|
// minute, and open chronicle days are closed so none is carried through the stretch.
|
|
41
61
|
let now = 0;
|
|
@@ -47,7 +67,7 @@ export function liveCommunity(c, until, opts) {
|
|
|
47
67
|
return { events, days };
|
|
48
68
|
for (const p of c.people) {
|
|
49
69
|
if (p.body.alive && p.now < now)
|
|
50
|
-
|
|
70
|
+
advanceCommunityPerson(c, p, now, opts, log);
|
|
51
71
|
endDay(p);
|
|
52
72
|
if (!p.body.alive && p.now < now)
|
|
53
73
|
p.now = now;
|
|
@@ -67,10 +87,12 @@ export function liveCommunity(c, until, opts) {
|
|
|
67
87
|
if (!p.body.alive)
|
|
68
88
|
continue;
|
|
69
89
|
if (p.now < now)
|
|
70
|
-
|
|
90
|
+
advanceCommunityPerson(c, p, now, opts, log);
|
|
91
|
+
if (opts.deathHandling === 'community' && !p.body.alive)
|
|
92
|
+
continue;
|
|
71
93
|
if (p.now !== now)
|
|
72
94
|
continue; // added mid-stretch with a later clock: joins when the day catches up
|
|
73
|
-
const r = routineDay(p, opts.routineFor(p, day
|
|
95
|
+
const r = routineDay(p, opts.routineFor(p, day), routineOpts);
|
|
74
96
|
if (r.episodes.length > 0 || r.onsets.length > 0 || r.stage || r.died) {
|
|
75
97
|
const list = days[p.id] ?? [];
|
|
76
98
|
list.push(r);
|
|
@@ -106,23 +128,31 @@ export function liveCommunity(c, until, opts) {
|
|
|
106
128
|
for (const p of c.people)
|
|
107
129
|
if (!p.body.alive && p.now < now)
|
|
108
130
|
p.now = now;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
131
|
+
// Keep routine deaths batched: recipients must run their own routine before end-of-day news/cleanup.
|
|
132
|
+
for (const d of died) {
|
|
133
|
+
if (tell)
|
|
134
|
+
tellDeath(c, d, now, opts.deathHandling === 'community' ? { receipt: 'attended' } : {});
|
|
135
|
+
if (opts.deathHandling === 'community')
|
|
136
|
+
cleanupCommunityDeath(c, d, now, opts, log);
|
|
137
|
+
}
|
|
138
|
+
// Legacy: minors with parents. Opt-in: household influence through young adulthood with parents/guardians.
|
|
113
139
|
if (upbringing > 0) {
|
|
114
140
|
const byId = new Map(c.people.map((q) => [q.id, q]));
|
|
115
141
|
for (const child of c.people) {
|
|
116
|
-
|
|
142
|
+
const continuous = opts.upbringing?.model === 'warmth-through-25';
|
|
143
|
+
if (!child.body.alive || ageYears(child) >= (continuous ? 25 : UPBRINGING_UNTIL))
|
|
117
144
|
continue;
|
|
118
145
|
const caregivers = child.social.relationships
|
|
119
|
-
.filter((r) => r.roles.includes('parent'))
|
|
146
|
+
.filter((r) => r.roles.includes('parent') || (continuous && r.roles.includes('guardian')))
|
|
120
147
|
.flatMap((r) => {
|
|
121
148
|
const q = byId.get(r.otherId);
|
|
122
149
|
return q?.body.alive ? [q] : [];
|
|
123
150
|
});
|
|
124
|
-
|
|
125
|
-
|
|
151
|
+
const selected = continuous
|
|
152
|
+
? [...new Map(caregivers.filter((q) => q.id !== child.id).map((q) => [q.id, q])).values()]
|
|
153
|
+
: caregivers;
|
|
154
|
+
if (selected.length > 0)
|
|
155
|
+
raise(child, { caregivers: selected }, upbringing, opts.upbringing);
|
|
126
156
|
}
|
|
127
157
|
}
|
|
128
158
|
}
|
package/dist/sim/sim.d.ts
CHANGED
|
@@ -183,6 +183,8 @@ export interface LifecourseOptions {
|
|
|
183
183
|
exposures?(p: Person): HealthExposures | undefined;
|
|
184
184
|
}
|
|
185
185
|
export interface StepOptions {
|
|
186
|
+
/** Hold current pregnancy and ambient conditions through body forecasts; pin in host run config. */
|
|
187
|
+
forecastContext?: 'conditioned';
|
|
186
188
|
/** Standing suggestions, applied at every decision of that person (resolved quietly at reviews); one or several voices. */
|
|
187
189
|
suggestions?: Record<PersonId, Suggestion | readonly Suggestion[]>;
|
|
188
190
|
/** Contagion rolls between people who just shared an activity (default true). */
|
|
@@ -194,6 +196,15 @@ export interface StepOptions {
|
|
|
194
196
|
* noticed (1.8.0, default true): they grieve, keep the tie as a memory, and a spouse is widowed (`partnering/`).
|
|
195
197
|
*/
|
|
196
198
|
tellDeaths?: boolean;
|
|
199
|
+
/**
|
|
200
|
+
* Opt in to transition-based dispatch around driver-owned tick and finish advancement. Discovery is the advanced clock
|
|
201
|
+
* minute, not an inferred biological death time. Notices use attended receipts. Pin this option in
|
|
202
|
+
* the host run config on every call and restore; no community/save mark enables it implicitly.
|
|
203
|
+
* A future-dated host outcome advances discovery and tied recipients to that future clock, even past
|
|
204
|
+
* the step bound and their intervening events. Hosts needing bounded event order must resolve at
|
|
205
|
+
* the current event minute. Returned finish/death entries record scheduling and discovery clocks.
|
|
206
|
+
*/
|
|
207
|
+
deathHandling?: 'community';
|
|
197
208
|
/** Hosts may disable the necessity exception. */
|
|
198
209
|
necessity?: boolean;
|
|
199
210
|
/** Minutes an idle person waits when no option is chosen. */
|
|
@@ -240,11 +251,25 @@ export declare function birth(c: Community, a: Person, b: Person, spec: ChildSpe
|
|
|
240
251
|
* percept through `perceive`, so they grieve, the tie is kept and marked, and a spouse is widowed. Each listener's
|
|
241
252
|
* receipt time is at least their own `now`. `late: 'now'` explicitly permits listeners past `at`, as does the
|
|
242
253
|
* existing default. `late: 'throw'` requires all eligible listeners to be at or before `at` and throws before
|
|
243
|
-
* delivering any news if one is ahead. This changes no listener's clock.
|
|
254
|
+
* delivering any news if one is ahead. This changes no listener's clock. `receipt: 'attended'`
|
|
255
|
+
* opts into knowledge-consistent bereavement: only an attended percept marks the tie and widows;
|
|
256
|
+
* dropped news changes neither. Omitted receipt keeps the original unconditional widow call.
|
|
244
257
|
*/
|
|
245
258
|
export declare function tellDeath(c: Community, dead: Person, at: Minute, opts?: {
|
|
246
259
|
late?: 'throw' | 'now';
|
|
260
|
+
receipt?: 'attended';
|
|
247
261
|
}): void;
|
|
262
|
+
/**
|
|
263
|
+
* SCOPE: opt-in community death discovery and scheduler cleanup, not a mortality model. Wrap one
|
|
264
|
+
* driver-owned advancement (tick by default); dispatch its alive-to-dead transition at the resulting clock.
|
|
265
|
+
* The optional advancement callback supports life-course rolls without another tick. It must be
|
|
266
|
+
* synchronous and advance only `p`; hosts own discovery of external deaths. No RNG or state mark
|
|
267
|
+
* is added. Pin `deathHandling` in the host run config, including after JSON restore. Event caps
|
|
268
|
+
* may omit returned log entries but never suppress cleanup or notices. No funeral is modeled.
|
|
269
|
+
*/
|
|
270
|
+
export declare function advanceCommunityPerson(c: Community, p: Person, at: Minute, opts?: Pick<StepOptions, 'deathHandling' | 'tellDeaths'>, log?: (e: SimEvent) => void, advance?: (p: Person, at: Minute) => void): void;
|
|
271
|
+
/** Internal scheduler settlement. Routine callers wait until every person's day has run. */
|
|
272
|
+
export declare function cleanupCommunityDeath(c: Community, p: Person, at: Minute, opts: Pick<StepOptions, 'deathHandling' | 'tellDeaths'>, log: (e: SimEvent) => void): void;
|
|
248
273
|
/** Pure scheduling query, including passive bodily checkpoints during joint waiting. */
|
|
249
274
|
export declare function nextEventAt(c: Community, p: Person): Minute;
|
|
250
275
|
/**
|
|
@@ -255,6 +280,7 @@ export declare function preview(p: Person, affordances: readonly Affordance[], s
|
|
|
255
280
|
necessity?: boolean;
|
|
256
281
|
others?: readonly Suggestion[];
|
|
257
282
|
scarcity?: number;
|
|
283
|
+
forecastContext?: 'conditioned';
|
|
258
284
|
}): SuggestionResolution;
|
|
259
285
|
/**
|
|
260
286
|
* Force `p` to re-decide at `at` (not earlier than their own clock): a running activity's review is brought
|
|
@@ -280,7 +306,7 @@ export declare function declineJoint(c: Community, proposal: JointProposal, prop
|
|
|
280
306
|
* activities are resolved as interrupted first, then the proposer begins the offer and each partner their
|
|
281
307
|
* mirror. Returns the activities begun (empty while other partners have yet to answer).
|
|
282
308
|
*/
|
|
283
|
-
export declare function acceptJoint(c: Community, world: World, proposal: JointProposal, partner: Person, partnerRecord: DecisionRecord, writeLog?: (e: SimEvent) => void): Activity[];
|
|
309
|
+
export declare function acceptJoint(c: Community, world: World, proposal: JointProposal, partner: Person, partnerRecord: DecisionRecord, writeLog?: (e: SimEvent) => void, opts?: Pick<StepOptions, 'deathHandling' | 'tellDeaths'>): Activity[];
|
|
284
310
|
/**
|
|
285
311
|
* Chance that `lead` succeeds at a skilled task with `partners` helping: `successChance` with
|
|
286
312
|
* support = Σ partner skill × partner capacity / max(difficulty, 0.1), clamped to 0..1.
|
package/dist/sim/sim.js
CHANGED
|
@@ -232,7 +232,9 @@ export function birth(c, a, b, spec) {
|
|
|
232
232
|
* percept through `perceive`, so they grieve, the tie is kept and marked, and a spouse is widowed. Each listener's
|
|
233
233
|
* receipt time is at least their own `now`. `late: 'now'` explicitly permits listeners past `at`, as does the
|
|
234
234
|
* existing default. `late: 'throw'` requires all eligible listeners to be at or before `at` and throws before
|
|
235
|
-
* delivering any news if one is ahead. This changes no listener's clock.
|
|
235
|
+
* delivering any news if one is ahead. This changes no listener's clock. `receipt: 'attended'`
|
|
236
|
+
* opts into knowledge-consistent bereavement: only an attended percept marks the tie and widows;
|
|
237
|
+
* dropped news changes neither. Omitted receipt keeps the original unconditional widow call.
|
|
236
238
|
*/
|
|
237
239
|
export function tellDeath(c, dead, at, opts = {}) {
|
|
238
240
|
if (opts.late === 'throw') {
|
|
@@ -259,11 +261,74 @@ export function tellDeath(c, dead, at, opts = {}) {
|
|
|
259
261
|
summary: q.lexicon?.plainEnglish ? `${dead.name} died.` : `${dead.name} died`,
|
|
260
262
|
},
|
|
261
263
|
]);
|
|
262
|
-
//
|
|
263
|
-
if (q.bonds)
|
|
264
|
+
// Legacy calls widow even when unattended; attended mode leaves all receipt effects to perceive.
|
|
265
|
+
if (opts.receipt !== 'attended' && q.bonds)
|
|
264
266
|
widow(q, dead.id, Math.max(at, q.now));
|
|
265
267
|
}
|
|
266
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* SCOPE: opt-in community death discovery and scheduler cleanup, not a mortality model. Wrap one
|
|
271
|
+
* driver-owned advancement (tick by default); dispatch its alive-to-dead transition at the resulting clock.
|
|
272
|
+
* The optional advancement callback supports life-course rolls without another tick. It must be
|
|
273
|
+
* synchronous and advance only `p`; hosts own discovery of external deaths. No RNG or state mark
|
|
274
|
+
* is added. Pin `deathHandling` in the host run config, including after JSON restore. Event caps
|
|
275
|
+
* may omit returned log entries but never suppress cleanup or notices. No funeral is modeled.
|
|
276
|
+
*/
|
|
277
|
+
export function advanceCommunityPerson(c, p, at, opts = {}, log = () => { }, advance = tick) {
|
|
278
|
+
const alive = p.body.alive;
|
|
279
|
+
advance(p, at);
|
|
280
|
+
// Composite finish may advance beyond the scheduled event. Discovery uses that actual clock.
|
|
281
|
+
at = p.now;
|
|
282
|
+
if (opts.deathHandling !== 'community' || !alive || p.body.alive)
|
|
283
|
+
return;
|
|
284
|
+
log({ at, personId: p.id, kind: 'died', detail: `${p.name} died` });
|
|
285
|
+
if (opts.tellDeaths ?? true) {
|
|
286
|
+
// Perception clamps faculty time to the listener's clock. Synchronize eligible recipients first,
|
|
287
|
+
// discovering any deaths there too, so a notice is never encoded before its discovery minute.
|
|
288
|
+
for (const q of c.people) {
|
|
289
|
+
if (q === p || !q.body.alive || q.now >= at)
|
|
290
|
+
continue;
|
|
291
|
+
if (q.social.relationships.some((r) => r.otherId === p.id))
|
|
292
|
+
advanceCommunityPerson(c, q, at, opts, log);
|
|
293
|
+
}
|
|
294
|
+
tellDeath(c, p, at, { receipt: 'attended' });
|
|
295
|
+
}
|
|
296
|
+
cleanupCommunityDeath(c, p, at, opts, log);
|
|
297
|
+
}
|
|
298
|
+
/** Preserve finish's host-declared timing and return value; discover any internal advancement death. */
|
|
299
|
+
function finishCommunityPerson(c, world, p, outcome, opts, log) {
|
|
300
|
+
let report = null;
|
|
301
|
+
advanceCommunityPerson(c, p, p.now, opts, log, () => {
|
|
302
|
+
report = finish(p, outcome, finishOpts(world));
|
|
303
|
+
});
|
|
304
|
+
return report;
|
|
305
|
+
}
|
|
306
|
+
/** Internal scheduler settlement. Routine callers wait until every person's day has run. */
|
|
307
|
+
export function cleanupCommunityDeath(c, p, at, opts, log) {
|
|
308
|
+
for (const proposal of pendingProposals(c)) {
|
|
309
|
+
if (proposal.proposerId !== p.id && !proposal.partnerIds.includes(p.id))
|
|
310
|
+
continue;
|
|
311
|
+
// Settle before synchronization: a second discovered death cannot cancel this proposal twice.
|
|
312
|
+
settle(c, proposal, 'declined');
|
|
313
|
+
const proposer = personById(c, proposal.proposerId);
|
|
314
|
+
if (proposer?.body.alive) {
|
|
315
|
+
const when = Math.max(at, proposer.now);
|
|
316
|
+
const ex = c.joint.excluded[proposer.id];
|
|
317
|
+
if (ex?.at === when)
|
|
318
|
+
ex.ids.push(proposal.offer.id);
|
|
319
|
+
else
|
|
320
|
+
c.joint.excluded[proposer.id] = { at: when, ids: [proposal.offer.id] };
|
|
321
|
+
}
|
|
322
|
+
for (const id of [proposal.proposerId, ...proposal.partnerIds]) {
|
|
323
|
+
const q = personById(c, id);
|
|
324
|
+
if (!q?.body.alive)
|
|
325
|
+
continue;
|
|
326
|
+
if (q.now < at)
|
|
327
|
+
advanceCommunityPerson(c, q, at, opts, log);
|
|
328
|
+
interruptPerson(c, q, Math.max(at, q.now), `declined:${proposal.id}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
267
332
|
/** Settled proposals kept for inspection. */
|
|
268
333
|
const MAX_SETTLED_PROPOSALS = 16;
|
|
269
334
|
const pendingProposals = (c) => c.joint.proposals.filter((x) => x.status === 'pending');
|
|
@@ -409,7 +474,7 @@ export function declineJoint(c, proposal, proposer) {
|
|
|
409
474
|
* activities are resolved as interrupted first, then the proposer begins the offer and each partner their
|
|
410
475
|
* mirror. Returns the activities begun (empty while other partners have yet to answer).
|
|
411
476
|
*/
|
|
412
|
-
export function acceptJoint(c, world, proposal, partner, partnerRecord, writeLog = () => { }) {
|
|
477
|
+
export function acceptJoint(c, world, proposal, partner, partnerRecord, writeLog = () => { }, opts = {}) {
|
|
413
478
|
const log = (e) => {
|
|
414
479
|
const person = personById(c, e.personId);
|
|
415
480
|
writeLog(person?.lexicon?.plainEnglish ? { ...e, detail: plainEventDetail(person, e) } : e);
|
|
@@ -431,12 +496,41 @@ export function acceptJoint(c, world, proposal, partner, partnerRecord, writeLog
|
|
|
431
496
|
if (q && rec)
|
|
432
497
|
participants.push({ person: q, offer: mirrorFor(world, q, proposal), record: rec });
|
|
433
498
|
}
|
|
434
|
-
|
|
499
|
+
let at = Math.max(...participants.map((x) => x.person.now));
|
|
500
|
+
const finishExisting = (person) => {
|
|
501
|
+
const act = person.activity;
|
|
502
|
+
if (act) {
|
|
503
|
+
const finishAt = opts.deathHandling === 'community' ? Math.max(at, person.now) : at;
|
|
504
|
+
const outcome = world.resolve(person, act, finishAt >= act.endsAt ? 'ended' : 'interrupted');
|
|
505
|
+
finishCommunityPerson(c, world, person, outcome, opts, log);
|
|
506
|
+
log({
|
|
507
|
+
at: finishAt,
|
|
508
|
+
personId: person.id,
|
|
509
|
+
kind: 'finish',
|
|
510
|
+
detail: person.lexicon?.plainEnglish
|
|
511
|
+
? taskEndLine(person, act.affordance.label, outcome.status)
|
|
512
|
+
: `${act.action} ${outcome.status}`,
|
|
513
|
+
decisionId: act.decisionId,
|
|
514
|
+
affordanceId: act.affordanceId,
|
|
515
|
+
action: act.action,
|
|
516
|
+
status: outcome.status,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
if (opts.deathHandling === 'community') {
|
|
521
|
+
for (const { person } of participants)
|
|
522
|
+
if (person.now < at)
|
|
523
|
+
advanceCommunityPerson(c, person, at, opts, log);
|
|
524
|
+
if (proposal.status !== 'pending' || participants.some(({ person }) => !person.body.alive)) {
|
|
525
|
+
declineJoint(c, proposal);
|
|
526
|
+
return [];
|
|
527
|
+
}
|
|
528
|
+
}
|
|
435
529
|
// Opted-in catch-up can physically stop a participant. Validate everyone before any begin.
|
|
436
530
|
if (participants.some(({ person }) => person.body.doze !== undefined)) {
|
|
437
531
|
for (const { person } of participants)
|
|
438
532
|
if (person.now < at)
|
|
439
|
-
|
|
533
|
+
advanceCommunityPerson(c, person, at, opts, log);
|
|
440
534
|
if (participants.some(({ person }) => person.body.doze?.since !== undefined)) {
|
|
441
535
|
declineJoint(c, proposal);
|
|
442
536
|
for (const { person } of participants)
|
|
@@ -444,28 +538,30 @@ export function acceptJoint(c, world, proposal, partner, partnerRecord, writeLog
|
|
|
444
538
|
return [];
|
|
445
539
|
}
|
|
446
540
|
}
|
|
541
|
+
if (opts.deathHandling === 'community') {
|
|
542
|
+
// Host outcomes may advance beyond `at`. Resolve all old work before anyone starts shared work.
|
|
543
|
+
for (const { person } of participants)
|
|
544
|
+
finishExisting(person);
|
|
545
|
+
at = Math.max(...participants.map(({ person }) => person.now));
|
|
546
|
+
for (const { person } of participants)
|
|
547
|
+
if (person.now < at)
|
|
548
|
+
advanceCommunityPerson(c, person, at, opts, log);
|
|
549
|
+
if (proposal.status !== 'pending' ||
|
|
550
|
+
participants.some(({ person }) => !person.body.alive || person.body.doze?.since !== undefined)) {
|
|
551
|
+
declineJoint(c, proposal);
|
|
552
|
+
for (const { person } of participants)
|
|
553
|
+
if (person.body.alive)
|
|
554
|
+
interruptPerson(c, person, at, `declined:${proposal.id}`);
|
|
555
|
+
return [];
|
|
556
|
+
}
|
|
557
|
+
}
|
|
447
558
|
settle(c, proposal, 'accepted');
|
|
448
559
|
const begun = [];
|
|
449
560
|
for (const { person, offer, record } of participants) {
|
|
450
561
|
if (person.now < at)
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
const outcome = world.resolve(person, act, at >= act.endsAt ? 'ended' : 'interrupted');
|
|
455
|
-
finish(person, outcome, finishOpts(world));
|
|
456
|
-
log({
|
|
457
|
-
at,
|
|
458
|
-
personId: person.id,
|
|
459
|
-
kind: 'finish',
|
|
460
|
-
detail: person.lexicon?.plainEnglish
|
|
461
|
-
? taskEndLine(person, act.affordance.label, outcome.status)
|
|
462
|
-
: `${act.action} ${outcome.status}`,
|
|
463
|
-
decisionId: act.decisionId,
|
|
464
|
-
affordanceId: act.affordanceId,
|
|
465
|
-
action: act.action,
|
|
466
|
-
status: outcome.status,
|
|
467
|
-
});
|
|
468
|
-
}
|
|
562
|
+
advanceCommunityPerson(c, person, at, opts, log);
|
|
563
|
+
if (opts.deathHandling !== 'community')
|
|
564
|
+
finishExisting(person);
|
|
469
565
|
const started = begin(person, offer, record, {
|
|
470
566
|
...world.beginOptions?.(person, offer, record),
|
|
471
567
|
jointId: proposal.id,
|
|
@@ -614,9 +710,9 @@ function converseOneWay(c, speaker, listener, ctx, log) {
|
|
|
614
710
|
});
|
|
615
711
|
}
|
|
616
712
|
}
|
|
617
|
-
function converseBoth(c, world, p, q, act, t, log) {
|
|
713
|
+
function converseBoth(c, world, p, q, act, t, log, opts) {
|
|
618
714
|
if (q.now < t)
|
|
619
|
-
|
|
715
|
+
advanceCommunityPerson(c, q, t, opts, log);
|
|
620
716
|
// Non-joint conversation offers assume the partner is available; nobody talks with someone asleep.
|
|
621
717
|
if (!q.body.alive || q.body.asleep)
|
|
622
718
|
return;
|
|
@@ -717,6 +813,8 @@ function bookkeepDay(p, world, t, dayDone, opts) {
|
|
|
717
813
|
/** Merge heard advice and the host's decision options in their existing precedence order. */
|
|
718
814
|
function decisionOptions(c, world, p, opts, reason, affordances) {
|
|
719
815
|
const o = {};
|
|
816
|
+
if (opts.forecastContext !== undefined)
|
|
817
|
+
o.forecastContext = opts.forecastContext;
|
|
720
818
|
// Voices: queued conversation advice first (fresher), then the host's standing suggestions that are heard now
|
|
721
819
|
// (standing-advice SCOPE); one per voice. At reviews they still weigh in, but `decide` resolves them quietly
|
|
722
820
|
// (no counters, pressure or autonomy cost), so one request is not re-counted as many verdicts.
|
|
@@ -798,7 +896,9 @@ export function stepCommunity(c, world, until, opts = {}) {
|
|
|
798
896
|
.filter((q) => q !== undefined);
|
|
799
897
|
for (const q of waiters)
|
|
800
898
|
if (q.now < t)
|
|
801
|
-
|
|
899
|
+
advanceCommunityPerson(c, q, t, opts, log);
|
|
900
|
+
if (opts.deathHandling === 'community' && x.status !== 'pending')
|
|
901
|
+
continue;
|
|
802
902
|
const reasons = new Map(waiters.map((q) => [q.id, q.activity?.interrupt?.reason ?? c.interrupts[q.id]?.reason]));
|
|
803
903
|
if (x.proposerId === p.id)
|
|
804
904
|
settle(c, x, 'declined');
|
|
@@ -826,7 +926,9 @@ export function stepCommunity(c, world, until, opts = {}) {
|
|
|
826
926
|
break;
|
|
827
927
|
const p = next;
|
|
828
928
|
const ordinaryDue = ordinaryEventAt(c, p) <= t;
|
|
829
|
-
|
|
929
|
+
advanceCommunityPerson(c, p, t, opts, log);
|
|
930
|
+
if (opts.deathHandling === 'community' && !p.body.alive)
|
|
931
|
+
continue;
|
|
830
932
|
const dozed = p.activity?.dozedAt !== undefined || p.body.doze?.since !== undefined;
|
|
831
933
|
if (dozed) {
|
|
832
934
|
cancelDozeWaiting(p, t);
|
|
@@ -835,7 +937,7 @@ export function stepCommunity(c, world, until, opts = {}) {
|
|
|
835
937
|
if (act.interrupt)
|
|
836
938
|
c.interrupts[p.id] ??= { ...act.interrupt };
|
|
837
939
|
const outcome = world.resolve(p, act, 'interrupted');
|
|
838
|
-
|
|
940
|
+
finishCommunityPerson(c, world, p, outcome, opts, log);
|
|
839
941
|
log({
|
|
840
942
|
at: t,
|
|
841
943
|
personId: p.id,
|
|
@@ -850,13 +952,17 @@ export function stepCommunity(c, world, until, opts = {}) {
|
|
|
850
952
|
status: outcome.status,
|
|
851
953
|
}, outcome.summary !== undefined);
|
|
852
954
|
}
|
|
955
|
+
if (opts.deathHandling === 'community' && !p.body.alive)
|
|
956
|
+
continue;
|
|
853
957
|
c.idleUntil[p.id] = t + quantum;
|
|
854
958
|
if (!ordinaryDue)
|
|
855
959
|
continue;
|
|
856
960
|
}
|
|
857
961
|
if (!ordinaryDue && p.body.alive)
|
|
858
962
|
continue;
|
|
859
|
-
bookkeepDay(p, world, t, c.dayDone, opts);
|
|
963
|
+
advanceCommunityPerson(c, p, t, opts, log, () => bookkeepDay(p, world, t, c.dayDone ?? {}, opts));
|
|
964
|
+
if (opts.deathHandling === 'community' && !p.body.alive)
|
|
965
|
+
continue;
|
|
860
966
|
if (!p.body.alive) {
|
|
861
967
|
log({ at: t, personId: p.id, kind: 'died', detail: `${p.name} died` });
|
|
862
968
|
if (opts.tellDeaths ?? true)
|
|
@@ -899,7 +1005,7 @@ export function stepCommunity(c, world, until, opts = {}) {
|
|
|
899
1005
|
perceptReason ??= act?.interrupt?.reason;
|
|
900
1006
|
if (act && t >= act.endsAt) {
|
|
901
1007
|
const outcome = world.resolve(p, act, 'ended');
|
|
902
|
-
const report =
|
|
1008
|
+
const report = finishCommunityPerson(c, world, p, outcome, opts, log);
|
|
903
1009
|
const ev = {
|
|
904
1010
|
at: t,
|
|
905
1011
|
personId: p.id,
|
|
@@ -915,6 +1021,8 @@ export function stepCommunity(c, world, until, opts = {}) {
|
|
|
915
1021
|
if (report)
|
|
916
1022
|
ev.status = report.status;
|
|
917
1023
|
log(ev, outcome.summary !== undefined);
|
|
1024
|
+
if (opts.deathHandling === 'community' && !p.body.alive)
|
|
1025
|
+
continue;
|
|
918
1026
|
if (outcome.status === 'completed') {
|
|
919
1027
|
noteStandingDone(c, p, act, t);
|
|
920
1028
|
if (pairMinute !== t) {
|
|
@@ -927,8 +1035,8 @@ export function stepCommunity(c, world, until, opts = {}) {
|
|
|
927
1035
|
continue;
|
|
928
1036
|
pairsDone.add(key);
|
|
929
1037
|
if (isConversation(act))
|
|
930
|
-
converseBoth(c, world, p, q, act, t, log);
|
|
931
|
-
if (contagion && sharesPlace(q, act))
|
|
1038
|
+
converseBoth(c, world, p, q, act, t, log, opts);
|
|
1039
|
+
if (contagion && (opts.deathHandling !== 'community' || q.body.alive) && sharesPlace(q, act))
|
|
932
1040
|
contagionBetween(p, q, act, t, log);
|
|
933
1041
|
}
|
|
934
1042
|
}
|
|
@@ -972,7 +1080,7 @@ export function stepCommunity(c, world, until, opts = {}) {
|
|
|
972
1080
|
detail: `joins ${acceptedProposal.offer.label}`,
|
|
973
1081
|
jointId: acceptedProposal.id,
|
|
974
1082
|
});
|
|
975
|
-
acceptJoint(c, world, acceptedProposal, p, record, (event) => log(event, true));
|
|
1083
|
+
acceptJoint(c, world, acceptedProposal, p, record, (event) => log(event, true), opts);
|
|
976
1084
|
continue;
|
|
977
1085
|
}
|
|
978
1086
|
const chosen = affordances.find((a) => a.id === record.chosenAffordanceId);
|
|
@@ -1010,7 +1118,7 @@ export function stepCommunity(c, world, until, opts = {}) {
|
|
|
1010
1118
|
}
|
|
1011
1119
|
if (act) {
|
|
1012
1120
|
const outcome = world.resolve(p, act, 'interrupted');
|
|
1013
|
-
|
|
1121
|
+
finishCommunityPerson(c, world, p, outcome, opts, log);
|
|
1014
1122
|
log({
|
|
1015
1123
|
at: t,
|
|
1016
1124
|
personId: p.id,
|
|
@@ -1024,6 +1132,8 @@ export function stepCommunity(c, world, until, opts = {}) {
|
|
|
1024
1132
|
status: outcome.status,
|
|
1025
1133
|
});
|
|
1026
1134
|
}
|
|
1135
|
+
if (opts.deathHandling === 'community' && !p.body.alive)
|
|
1136
|
+
continue;
|
|
1027
1137
|
if (!chosen) {
|
|
1028
1138
|
c.idleUntil[p.id] = t + quantum;
|
|
1029
1139
|
log({ at: t, personId: p.id, kind: 'idle', detail: 'nothing chosen' });
|
|
@@ -1048,7 +1158,7 @@ export function stepCommunity(c, world, until, opts = {}) {
|
|
|
1048
1158
|
}
|
|
1049
1159
|
for (const p of c.people)
|
|
1050
1160
|
if (p.now < until)
|
|
1051
|
-
|
|
1161
|
+
advanceCommunityPerson(c, p, until, opts, log);
|
|
1052
1162
|
return events;
|
|
1053
1163
|
}
|
|
1054
1164
|
/**
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { EntityId, Minute, NormTag, Person, PersonId, Signed, StateCue, Traits, Unit } from '../types.ts';
|
|
2
2
|
export type CueKind = 'state' | 'place' | 'trait' | 'tie' | 'trust' | 'skill' | 'regard';
|
|
3
3
|
export declare const STATE_CUES: readonly StateCue[];
|
|
4
|
-
/** @deprecated since 2.1: use `TRAIT_KEYS` (types.ts), the same list. Removed in 3.0. */
|
|
5
|
-
export declare const TRAIT_NAMES: readonly (keyof Traits)[];
|
|
6
4
|
export declare const IMPRESSION_DEFAULTS: {
|
|
7
5
|
/** Weight half-life by kind, minutes (0 = no decay). */
|
|
8
6
|
halfLife: Record<CueKind, number>;
|
|
@@ -88,20 +86,10 @@ export declare function setReserve(p: Person, reserve: Partial<Record<StateCue,
|
|
|
88
86
|
* the evidence and how true it is (a figure in the dark tells little and tells it wrong).
|
|
89
87
|
*/
|
|
90
88
|
export declare function glimpse(observer: Person, targetId: PersonId, signs: OutwardSigns, at: Minute, clarity?: Unit): void;
|
|
91
|
-
/** @deprecated since 2.1: pass `at` (and the optional knobs) positionally. Removed in 3.0. */
|
|
92
|
-
export declare function glimpse(observer: Person, targetId: PersonId, signs: OutwardSigns, opts: {
|
|
93
|
-
at: Minute;
|
|
94
|
-
clarity?: Unit;
|
|
95
|
-
}): void;
|
|
96
89
|
/** What someone was seen to do: place fear, ties, trust in a voice and trait evidence (see `ObservedAct`). */
|
|
97
90
|
export declare function observeAct(observer: Person, targetId: PersonId, act: ObservedAct, table?: Record<string, Partial<Record<keyof Traits, Unit>>>): void;
|
|
98
91
|
/** Testimony about someone (their own words, or another's): `value` on `key`, weighted (e.g. by trust in the teller). */
|
|
99
92
|
export declare function hear(observer: Person, targetId: PersonId, key: string, value: number, at: Minute, weight?: Unit): void;
|
|
100
|
-
/** @deprecated since 2.1: pass `at` (and the optional knobs) positionally. Removed in 3.0. */
|
|
101
|
-
export declare function hear(observer: Person, targetId: PersonId, key: string, value: number, opts: {
|
|
102
|
-
at: Minute;
|
|
103
|
-
weight?: Unit;
|
|
104
|
-
}): void;
|
|
105
93
|
/**
|
|
106
94
|
* Years of history condensed (host call, e.g. at the start of a run for people who already know each other): one
|
|
107
95
|
* sample per trait from `traits` with an error that shrinks with familiarity, at weight
|
|
@@ -30,8 +30,6 @@ import { clamp, clamp01, cmpStr, decay, latestPerId, newestN, round } from '../c
|
|
|
30
30
|
import { MINUTES_PER_DAY, MINUTES_PER_YEAR, TRAIT_KEYS } from '../types.js';
|
|
31
31
|
import { judge, SOCIAL_DEFAULTS } from './social.js';
|
|
32
32
|
export const STATE_CUES = ['fatigue', 'pain', 'fear', 'mood'];
|
|
33
|
-
/** @deprecated since 2.1: use `TRAIT_KEYS` (types.ts), the same list. Removed in 3.0. */
|
|
34
|
-
export const TRAIT_NAMES = TRAIT_KEYS;
|
|
35
33
|
export const IMPRESSION_DEFAULTS = {
|
|
36
34
|
/** Weight half-life by kind, minutes (0 = no decay). */
|
|
37
35
|
halfLife: {
|
|
@@ -208,11 +206,16 @@ function erred(observer, targetId, key, at, value, clarity) {
|
|
|
208
206
|
const e = (hash01(`${observer.id}|${targetId}|${key}|${at}`) - 0.5) * IMPRESSION_DEFAULTS.noise;
|
|
209
207
|
return value + e * (1 - clamp01(clarity));
|
|
210
208
|
}
|
|
211
|
-
|
|
209
|
+
/**
|
|
210
|
+
* A look at someone's outward signs (from the composite's `outwardSigns`). `clarity` 0..1 scales both the weight of
|
|
211
|
+
* the evidence and how true it is (a figure in the dark tells little and tells it wrong).
|
|
212
|
+
*/
|
|
213
|
+
export function glimpse(observer, targetId, signs, at, clarity) {
|
|
214
|
+
if (!Number.isFinite(at))
|
|
215
|
+
throw new TypeError('glimpse requires a finite positional time');
|
|
212
216
|
if (targetId === observer.id)
|
|
213
217
|
return;
|
|
214
|
-
const
|
|
215
|
-
const clear = clamp01((typeof when === 'number' ? clarity : when.clarity) ?? 1);
|
|
218
|
+
const clear = clamp01(clarity ?? 1);
|
|
216
219
|
if (clear <= 0)
|
|
217
220
|
return;
|
|
218
221
|
const imp = impressionFor(observer, targetId, true, at);
|
|
@@ -252,11 +255,13 @@ export function observeAct(observer, targetId, act, table = IMPRESSION_DEFAULTS.
|
|
|
252
255
|
}
|
|
253
256
|
}
|
|
254
257
|
}
|
|
255
|
-
|
|
258
|
+
/** Testimony about someone (their own words, or another's): `value` on `key`, weighted (e.g. by trust in the teller). */
|
|
259
|
+
export function hear(observer, targetId, key, value, at, weight) {
|
|
260
|
+
if (!Number.isFinite(at))
|
|
261
|
+
throw new TypeError('hear requires a finite positional time');
|
|
256
262
|
if (targetId === observer.id)
|
|
257
263
|
return;
|
|
258
|
-
const
|
|
259
|
-
const w = clamp01((typeof when === 'number' ? weight : when.weight) ?? 0.5);
|
|
264
|
+
const w = clamp01(weight ?? 0.5);
|
|
260
265
|
if (w <= 0)
|
|
261
266
|
return;
|
|
262
267
|
const imp = impressionFor(observer, targetId, true, at);
|