@adam0white/human-framework 2.23.0 → 3.0.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 +137 -54
- package/dist/agenda/agenda.js +1 -1
- package/dist/beliefs/beliefs.js +2 -2
- 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/math.d.ts +0 -2
- package/dist/core/math.js +3 -3
- package/dist/family/family.d.ts +0 -5
- package/dist/family/family.js +12 -6
- package/dist/impression.d.ts +2 -7
- package/dist/impression.js +5 -7
- 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/person.js +4 -3
- package/dist/sim/longrun.d.ts +3 -3
- package/dist/sim/longrun.js +1 -1
- package/dist/social/impressions.d.ts +0 -12
- package/dist/social/impressions.js +13 -8
- package/dist/social/social.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,10 +4,50 @@ HF is a TypeScript library for simulated people in games and headless simulation
|
|
|
4
4
|
possible actions. Each person weighs needs, habits, duties, goals and suggestions, then chooses.
|
|
5
5
|
A player can suggest an action; the person can defer or refuse it.
|
|
6
6
|
|
|
7
|
-
HF
|
|
7
|
+
HF 3.0.0 uses utility scoring and seeded randomness, with no LLM in the actor loop and no runtime
|
|
8
8
|
dependencies. Decision records expose scored terms and template narration. Person state is plain JSON.
|
|
9
9
|
This is an authored simulation model, not a validated predictor of real people.
|
|
10
10
|
|
|
11
|
+
## Migrating to v3
|
|
12
|
+
|
|
13
|
+
The API migration keeps engine `2.23.0`. Relative to HF 2.23.0, it does not change seeded draws, numeric rules or saved Person state. The package version is `3.0.0`; publication is recorded separately in the release history.
|
|
14
|
+
|
|
15
|
+
| Before | v3 |
|
|
16
|
+
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
|
|
17
|
+
| `glimpse(p, id, signs, { at, clarity })` | `glimpse(p, id, signs, at, clarity)` |
|
|
18
|
+
| `glimpseOf(p, target, at, clarity)` or `glimpseOf(p, target, { at, clarity })` | `glimpsePerson(p, target, at, clarity)` |
|
|
19
|
+
| `acquaintWith(p, target, familiarity, at)` | `acquaintPerson(p, target, familiarity, at)` |
|
|
20
|
+
| `hear(p, id, key, value, { at, weight })` | `hear(p, id, key, value, at, weight)` |
|
|
21
|
+
| `conceive(mother, father, { seed, at })` | `conceive(mother, father, seed, at)` |
|
|
22
|
+
| `retell(teller, listener, { at, trust, limit, placeIds })` | `retell(teller, listener, at, { trust, limit, placeIds })` |
|
|
23
|
+
| `TRAIT_NAMES` | `TRAIT_KEYS` (same order) |
|
|
24
|
+
| `expit(x)` | `sigmoid(x)` (same function body) |
|
|
25
|
+
| `keysOf(record)` | `Object.keys(record)` (same enumeration order; narrow host key types explicitly when needed) |
|
|
26
|
+
| `repent(p, breachId, unusedTime)` | `repent(p, breachId)` |
|
|
27
|
+
| `recordRepair(p, victimId, unusedTime)` | `recordRepair(p, victimId)` |
|
|
28
|
+
| `liveRoutine(p, to, fixedRoutine)` | `liveRoutine(p, to, () => fixedRoutine)` |
|
|
29
|
+
| `routineFor: (p, day, community) => choose(p, day, community)` | `routineFor: (p, day) => choose(p, day, community)` with `community` captured from the host scope |
|
|
30
|
+
|
|
31
|
+
Removed options-object calls from untyped JavaScript throw `TypeError` before changing Person state. Their time argument must be a finite number in the positional slot.
|
|
32
|
+
|
|
33
|
+
Both routine drivers use `RoutineSource = (p: Person, day: number) => Routine`. `day` is the absolute day being started. Existing two-argument sources continue unchanged. A community source closes over the community for that run; bind a new closure after restoring a different community. Sources still run immediately before each person's routine day. Community dawn hooks still run first, and earlier people's days are already applied when later sources read community state. Do not precompute all routines at dawn if that would change those reads.
|
|
34
|
+
|
|
35
|
+
The primitive names `glimpse` and `acquaint` stay. Their Person composites use the explicit `Person` suffix. The proposed `skip` → `skipTo` rename is dropped (Q15): it provides no player value and `skip` is clear in context.
|
|
36
|
+
|
|
37
|
+
### Tuning defaults (Q5)
|
|
38
|
+
|
|
39
|
+
Assignment to existing mutable defaults remains the tuning API in v3. These settings apply to every run in the same module instance. Set them before creating or restoring a run, then keep them fixed while it runs. Already-frozen defaults remain frozen.
|
|
40
|
+
|
|
41
|
+
Deterministic replay requires the same engine version, effective tuning values, initial state, seeded streams, inputs and call order. Person snapshots do not store these global settings. A host that tunes must store its complete effective configuration or its overrides relative to a known pristine baseline in its save or replay envelope. Before restoring or continuing, restore that baseline first, then apply the saved overrides. A fresh module instance is another way to start from the baseline. Applying overrides over a previous run's settings can leak tuning between runs.
|
|
42
|
+
|
|
43
|
+
Two worlds with different settings cannot safely share one module instance. Tests that tune must restore values in a `finally` block and must not mutate them concurrently. This decision preserves existing hosts and seeded behavior. It adds no global configuration function or per-person settings. A future isolated settings API must first demonstrate exact snapshot, restore and continuation under overrides.
|
|
44
|
+
|
|
45
|
+
## Ordering inputs for one logical tick (opt-in)
|
|
46
|
+
|
|
47
|
+
`canonicalTickInputs(tick, inputs, expected)` waits for the complete declared sender/sequence set and returns a new envelope array sorted by sender, then sequence. It returns `undefined` while an input is missing and throws for duplicate, unexpected or wrong-tick inputs. Payloads remain host-owned. Use the returned array only after the barrier completes; save pending input with host state.
|
|
48
|
+
|
|
49
|
+
This is a bounded host convention, not transport, consensus or fairness. It reads no Person or RNG. Existing actor calls never use it implicitly. The H3 controls compare reordered deliveries, withheld input, restore and a behaviorally different unsorted control. The copyable two-person starter in `examples/h3-starter` owns its own clock and replay envelope; it is not a complete game engine or part of the npm artifact.
|
|
50
|
+
|
|
11
51
|
## Try the games
|
|
12
52
|
|
|
13
53
|
- [Twice at the Well](https://human.adamwhite.work/colony/): compare a colony without and with HF.
|
|
@@ -20,21 +60,23 @@ This is an authored simulation model, not a validated predictor of real people.
|
|
|
20
60
|
Use Node 24 or newer. In an empty directory, install the version this README describes:
|
|
21
61
|
|
|
22
62
|
```sh
|
|
23
|
-
npm install @adam0white/human-framework@
|
|
63
|
+
npm install @adam0white/human-framework@3.0.0
|
|
24
64
|
```
|
|
25
65
|
|
|
26
66
|
Save these 20 lines as `quickstart.mjs`, then run `node quickstart.mjs`:
|
|
27
67
|
|
|
28
68
|
```js
|
|
29
69
|
import {
|
|
30
|
-
createCommunity,
|
|
31
|
-
|
|
70
|
+
createCommunity,
|
|
71
|
+
createPerson,
|
|
72
|
+
createVillage,
|
|
73
|
+
predict,
|
|
74
|
+
stepCommunity,
|
|
75
|
+
villagerSpec,
|
|
32
76
|
} from '@adam0white/human-framework';
|
|
33
77
|
const start = 6 * 60; // simulation minutes
|
|
34
78
|
const ids = ['ada', 'bora'];
|
|
35
|
-
const people = ids.map((id, i) => createPerson(
|
|
36
|
-
villagerSpec(id, id, i + 1, { now: start, others: ids }),
|
|
37
|
-
));
|
|
79
|
+
const people = ids.map((id, i) => createPerson(villagerSpec(id, id, i + 1, { now: start, others: ids })));
|
|
38
80
|
const world = createVillage(people, { seed: 7 });
|
|
39
81
|
const community = createCommunity(people);
|
|
40
82
|
const ada = community.people[0];
|
|
@@ -83,14 +125,14 @@ A suggestion is one urge among many. Its weight is strength × trust in the voic
|
|
|
83
125
|
fits the person's motives, and a suggestion alone never overrides a veto. Each one resolves to a typed
|
|
84
126
|
verdict (`SuggestionResolution`):
|
|
85
127
|
|
|
86
|
-
| verdict
|
|
87
|
-
|
|
88
|
-
| `assented`
|
|
89
|
-
| `deferred`
|
|
90
|
-
| `modified`
|
|
91
|
-
| `refused`
|
|
92
|
-
| `complied`
|
|
93
|
-
| `commanded` | the host took direct control (see [Direct control](#direct-control)); the person does the order whatever they would have chosen | none
|
|
128
|
+
| verdict | meaning | `kind` |
|
|
129
|
+
| ----------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
|
|
130
|
+
| `assented` | the suggested option won | none |
|
|
131
|
+
| `deferred` | the person's own preference won; carries a `counterOffer` ("after I eat lunch") | `notNow` |
|
|
132
|
+
| `modified` | a near alternative with the same action or aim won; the `counterOffer` names it | `notNow` |
|
|
133
|
+
| `refused` | cannot (asleep, exhausted, skill far too low, a pressing bodily need) or will not (a firmly held norm, broken trust) | `cannot` / `willNot` |
|
|
134
|
+
| `complied` | the voice insisted on a "not now"; done under protest (`activity.protest`), at a cost to autonomy and trust | `notNow` |
|
|
135
|
+
| `commanded` | the host took direct control (see [Direct control](#direct-control)); the person does the order whatever they would have chosen | none |
|
|
94
136
|
|
|
95
137
|
Insisting (`insist: true`) turns only a `notNow` into compliance, and not when a bodily need is pressing. It
|
|
96
138
|
never overrides `cannot` or `willNot`.
|
|
@@ -132,11 +174,12 @@ An offer describes expected effects. An outcome reports what happened. The host
|
|
|
132
174
|
contact, inventory and payment; HF uses the reported outcome to update the person. Do not copy
|
|
133
175
|
`Affordance.advertises` into `Outcome.needs` as a general completion rule.
|
|
134
176
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
|
138
|
-
| A
|
|
139
|
-
| A
|
|
177
|
+
<!-- biome-ignore format: Biome 2.6.3 adds column padding on each pass for this inline-code table; preserve stable source. -->
|
|
178
|
+
| Host situation | Offer | Completion report |
|
|
179
|
+
| ------------------------------------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
180
|
+
| A person sleeps for part of the planned night. | Advertise typical sleep/rest benefits and set `mode: 'sleep'`. | Leave `sleep` and `rest` absent from `Outcome.needs`.`finish` advances the body to the actual end time and measures their change since the activity began. Explicit values replace that measurement; copying the advertisement can overstate the realized recovery reported to learning. |
|
|
181
|
+
| A task promises four coins but actually pays two. | Set expected `Affordance.material` to four host units. | Update the host ledger by two and report `Outcome.material: 2`. A completed task does not imply the promised payment arrived. Report zero for no payment and a negative value for an actual cost. HF does not maintain the host's money ledger. |
|
|
182
|
+
| A person can talk with someone who is present. | Tag the offer `social` when it really involves social contact; put participants in `with`. | Resolve actual participation. An empty room, a sleeping neighbour or someone who arrives after the conversation ends is not a completed encounter. Do not award contact or belonging merely because the activity uses a shared place. |
|
|
140
183
|
|
|
141
184
|
For in-person activities, the host must establish co-presence and overlapping participation. HF has
|
|
142
185
|
no map and does not discover companions. `with` names people taking part, not everyone assigned to a
|
|
@@ -211,7 +254,7 @@ reference host: `createVillage`.
|
|
|
211
254
|
const village2 = createVillage(people, { seed: 0, state: s.world });
|
|
212
255
|
const c2 = createCommunity(people, s.community); // continues exactly as the unsaved run would
|
|
213
256
|
```
|
|
214
|
-
- **Time arguments.** A host event takes `at` as its last required argument, with optional knobs after it (`
|
|
257
|
+
- **Time arguments.** A host event takes `at` as its last required argument, with optional knobs after it (`glimpsePerson(observer, target, at, clarity?)`, `retell(teller, listener, at, opts?)`); a query evaluated at a time takes `now`. The former 2.0 options-object forms are removed in v3. See docs/framework.md, "Argument order".
|
|
215
258
|
- Hosts that live people sparsely over decades can shrink what a save carries per person with
|
|
216
259
|
`setRetention(p, { trace, chronicleDays })` (or `PersonSpec.retention`): fewer decision records, and day records
|
|
217
260
|
bounded by age as well as count. No decision changes. `restore` also refuses a non-finite or out-of-range `now`
|
|
@@ -222,7 +265,7 @@ reference host: `createVillage`.
|
|
|
222
265
|
|
|
223
266
|
There are two version numbers, and they move independently.
|
|
224
267
|
|
|
225
|
-
- **The release version** is the package version (`package.json`, currently `
|
|
268
|
+
- **The release version** is the package version (`package.json`, currently `3.0.0`) and `FRAMEWORK_VERSION`, the same
|
|
226
269
|
string compiled into the build; a test keeps them equal. It versions the public API under
|
|
227
270
|
[semver](https://semver.org/): a breaking change to an exported name or signature bumps the major, a new
|
|
228
271
|
faculty or export the minor, a fix the patch. Each release is a tag `vX.Y.Z` and a GitHub release whose notes
|
|
@@ -269,8 +312,8 @@ of it runs as before.
|
|
|
269
312
|
- **Surroundings.** `setAmbient` (cold, dark, crowding, beauty, weather, day length) shifts mood, body and needs.
|
|
270
313
|
- **Multi-year stepping.** `liveRoutine` and `liveCommunity` live people by routine a coarse day at a time, with
|
|
271
314
|
natural death, chronic onsets, deaths told and minors raised.
|
|
272
|
-
- **Impressions of others.** What one person believes about another (`
|
|
273
|
-
`
|
|
315
|
+
- **Impressions of others.** What one person believes about another (`glimpsePerson(observer, target, at, clarity?)`, `observeAct`, `hear(observer, targetId, key, value, at, weight?)`,
|
|
316
|
+
`acquaintPerson`), with confidence; `predictAs` runs a prediction on the person as the observer pictures them.
|
|
274
317
|
- **Switching on at creation.** `PersonSpec.enable: { gists, yearbook, character, skillRetention }` turns the long-run faculties above on when the person is created.
|
|
275
318
|
- **Answering between decisions.** `answerNow` books a refusal at once without touching the running activity.
|
|
276
319
|
|
|
@@ -314,7 +357,7 @@ that place lose appeal until the fear fades. Groups and factions themselves stay
|
|
|
314
357
|
the bundled `DEFAULT_NORMS` is Islam-guided; each reference's verification status is listed in
|
|
315
358
|
[research/norm-sources.md](https://github.com/adam0white/human-framework/blob/main/research/norm-sources.md)). A host with its own catalog passes it as
|
|
316
359
|
`World.catalog`, so necessity excuses and exemptions read that catalog; a norm not in it is not excused.
|
|
317
|
-
A person holds an
|
|
360
|
+
A person holds an _understanding_ of a norm, with a standing and a conviction. That understanding is
|
|
318
361
|
not a ruling. The library never computes divine acceptance. Where schools of thought differ, the
|
|
319
362
|
position the bundled catalog takes, and why, is recorded in
|
|
320
363
|
[research/decisions.md](https://github.com/adam0white/human-framework/blob/main/research/decisions.md).
|
|
@@ -341,18 +384,30 @@ Use observations the person actually received; the host owns what those observat
|
|
|
341
384
|
```ts
|
|
342
385
|
receiveMethodCues(learner, {
|
|
343
386
|
contextId: 'observed-example',
|
|
344
|
-
cues: [
|
|
387
|
+
cues: [
|
|
388
|
+
{ cue: 'route', value: 'north' },
|
|
389
|
+
{ cue: 'signal', value: 'clear' },
|
|
390
|
+
],
|
|
345
391
|
});
|
|
346
392
|
demonstrateMethod(learner, {
|
|
347
|
-
id: 'routing-north',
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
393
|
+
id: 'routing-north',
|
|
394
|
+
demonstrationId: 'example-1',
|
|
395
|
+
contextId: 'observed-example',
|
|
396
|
+
sourceId: 'instructor',
|
|
397
|
+
conditions: [
|
|
398
|
+
{ cue: 'route', value: 'north' },
|
|
399
|
+
{ cue: 'signal', value: 'clear' },
|
|
400
|
+
],
|
|
401
|
+
action: 'send-north',
|
|
402
|
+
outcome: 'success',
|
|
351
403
|
});
|
|
352
404
|
// A new parcel can share the learned observation structure.
|
|
353
405
|
receiveMethodCues(learner, {
|
|
354
406
|
contextId: 'new-parcel',
|
|
355
|
-
cues: [
|
|
407
|
+
cues: [
|
|
408
|
+
{ cue: 'route', value: 'north' },
|
|
409
|
+
{ cue: 'signal', value: 'clear' },
|
|
410
|
+
],
|
|
356
411
|
});
|
|
357
412
|
const evidence = methodEvidence(learner, 'send-north', 'new-parcel');
|
|
358
413
|
// Offers for that parcel carry methodContext: 'new-parcel'.
|
|
@@ -369,39 +424,67 @@ HF 2.3.0 lets an attended explanation connect one current action to one possible
|
|
|
369
424
|
|
|
370
425
|
```ts
|
|
371
426
|
import {
|
|
372
|
-
type Affordance,
|
|
373
|
-
|
|
427
|
+
type Affordance,
|
|
428
|
+
type ReceivedConsequence,
|
|
429
|
+
consequenceEvidence,
|
|
430
|
+
createPerson,
|
|
431
|
+
decide,
|
|
432
|
+
perceive,
|
|
374
433
|
} from '@adam0white/human-framework';
|
|
375
434
|
|
|
376
435
|
const person = createPerson({
|
|
377
|
-
id: 'courier',
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
436
|
+
id: 'courier',
|
|
437
|
+
name: 'Courier',
|
|
438
|
+
seed: 19,
|
|
439
|
+
now: 600,
|
|
440
|
+
bornAt: -30 * 365 * 1440,
|
|
441
|
+
sex: 'female',
|
|
442
|
+
goals: [
|
|
443
|
+
{
|
|
444
|
+
id: 'delivery',
|
|
445
|
+
label: 'Deliver the parcel',
|
|
446
|
+
serves: ['achievement'],
|
|
447
|
+
advancedBy: [{ action: 'deliver', amount: 1 }],
|
|
448
|
+
importance: 0.8,
|
|
449
|
+
temporal: { halfLifeMinutes: 30 },
|
|
450
|
+
},
|
|
451
|
+
],
|
|
384
452
|
});
|
|
385
453
|
const route: ReceivedConsequence = {
|
|
386
|
-
id: 'access-window',
|
|
454
|
+
id: 'access-window',
|
|
455
|
+
revision: 0,
|
|
387
456
|
trigger: { action: 'ask-access', targetId: 'keeper' },
|
|
388
457
|
successor: { action: 'deliver', duration: 20, effort: 0.1 },
|
|
389
|
-
availableFrom: 620,
|
|
390
|
-
|
|
458
|
+
availableFrom: 620,
|
|
459
|
+
availableUntil: 680,
|
|
460
|
+
expiresAt: 800,
|
|
461
|
+
chance: 0.8,
|
|
462
|
+
confidence: 0.9,
|
|
391
463
|
};
|
|
392
|
-
perceive(person, [
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
464
|
+
perceive(person, [
|
|
465
|
+
{
|
|
466
|
+
at: 600,
|
|
467
|
+
channel: 'told',
|
|
468
|
+
kind: 'explanation',
|
|
469
|
+
actorId: 'keeper',
|
|
470
|
+
targetId: person.id,
|
|
471
|
+
salience: 1,
|
|
472
|
+
summary: 'If your request succeeds, this route may become available.',
|
|
473
|
+
consequences: [route],
|
|
474
|
+
},
|
|
475
|
+
]);
|
|
398
476
|
const request: Affordance = {
|
|
399
|
-
id: 'ask',
|
|
400
|
-
|
|
477
|
+
id: 'ask',
|
|
478
|
+
action: 'ask-access',
|
|
479
|
+
label: 'Request access',
|
|
480
|
+
targetId: 'keeper',
|
|
481
|
+
duration: 10,
|
|
482
|
+
effort: 0.1,
|
|
483
|
+
advertises: {},
|
|
401
484
|
};
|
|
402
|
-
const offers: Affordance[] = [
|
|
403
|
-
|
|
404
|
-
|
|
485
|
+
const offers: Affordance[] = [
|
|
486
|
+
request,
|
|
487
|
+
{ id: 'direct', action: 'deliver', label: 'Deliver directly', duration: 180, effort: 0.1, advertises: {} },
|
|
405
488
|
];
|
|
406
489
|
const decision = decide(person, offers);
|
|
407
490
|
const evidence = consequenceEvidence(person, request); // pure; no RNG consumed
|
package/dist/agenda/agenda.js
CHANGED
|
@@ -897,7 +897,7 @@ function boundOwed(state) {
|
|
|
897
897
|
return;
|
|
898
898
|
while (owed.length > AGENDA_DEFAULTS.maxOwed) {
|
|
899
899
|
const i = owed.findIndex((o) => o.scheduledAs !== undefined);
|
|
900
|
-
owed.splice(i
|
|
900
|
+
owed.splice(Math.max(i, 0), 1);
|
|
901
901
|
}
|
|
902
902
|
}
|
|
903
903
|
/** A kept, closed instance is positive recorded evidence; scheduling or missing history is not. */
|
package/dist/beliefs/beliefs.js
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* not prove deceit, change the belief, or change relationship or voice trust. Loss and retention limits are
|
|
25
25
|
* engineering defaults, not calibrated effect sizes. Deduplication lasts only while its bounded record stays.
|
|
26
26
|
*/
|
|
27
|
-
import { clamp, clamp01, decay, dpow,
|
|
27
|
+
import { clamp, clamp01, decay, dpow, isNum, isObj, logit, sigmoid } from '../core/index.js';
|
|
28
28
|
import { MAX_MINUTE, MINUTES_PER_DAY } from '../types.js';
|
|
29
29
|
export const BELIEF_DEFAULTS = {
|
|
30
30
|
maxBeliefs: 300,
|
|
@@ -229,7 +229,7 @@ export function believe(p, prop, value, confidence, sourceId, now) {
|
|
|
229
229
|
}
|
|
230
230
|
export function credence(p, prop) {
|
|
231
231
|
const b = findBelief(p, prop);
|
|
232
|
-
return b ?
|
|
232
|
+
return b ? sigmoid(b.logOdds) : 0.5;
|
|
233
233
|
}
|
|
234
234
|
/**
|
|
235
235
|
* Direct observation of the truth: set the belief firmly and recalibrate the sources that spoke on it.
|
|
@@ -100,13 +100,11 @@ export declare function recordDeed(p: Person, aff: Affordance, intention: string
|
|
|
100
100
|
* The person repents a breach: regret, stopping and resolve. When another person was wronged,
|
|
101
101
|
* restitution is also required, so the breach closes only after `recordRepair` for that victim.
|
|
102
102
|
* Returns whether the breach is now closed. This records the person's acts, not acceptance.
|
|
103
|
-
* The unused `_now` parameter is optional since 2.9.1 and is removed in 3.0.
|
|
104
103
|
*/
|
|
105
|
-
export declare function repent(p: Person, id: string
|
|
104
|
+
export declare function repent(p: Person, id: string): boolean;
|
|
106
105
|
/**
|
|
107
106
|
* Restitution/apology delivered to a wronged person (host-attested): closes their open breaches.
|
|
108
|
-
* The unused `_now` parameter is optional since 2.9.1 and is removed in 3.0.
|
|
109
107
|
*/
|
|
110
|
-
export declare function recordRepair(p: Person, victimId: string
|
|
108
|
+
export declare function recordRepair(p: Person, victimId: string): number;
|
|
111
109
|
/** Felt weight of open breaches, saturating: 1 - Π(1 - weight). Input for guilt in affect. */
|
|
112
110
|
export declare function guiltLoad(p: Person): Unit;
|
|
@@ -278,7 +278,7 @@ function breachId(p, at, normId) {
|
|
|
278
278
|
function boundBreaches(c) {
|
|
279
279
|
while (c.breaches.length > CONSCIENCE_DEFAULTS.maxBreaches) {
|
|
280
280
|
const i = c.breaches.findIndex((b) => b.repaired);
|
|
281
|
-
c.breaches.splice(i
|
|
281
|
+
c.breaches.splice(Math.max(i, 0), 1);
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
/**
|
|
@@ -358,9 +358,8 @@ export function recordDeed(p, aff, intention, now, completed, opts = {}) {
|
|
|
358
358
|
* The person repents a breach: regret, stopping and resolve. When another person was wronged,
|
|
359
359
|
* restitution is also required, so the breach closes only after `recordRepair` for that victim.
|
|
360
360
|
* Returns whether the breach is now closed. This records the person's acts, not acceptance.
|
|
361
|
-
* The unused `_now` parameter is optional since 2.9.1 and is removed in 3.0.
|
|
362
361
|
*/
|
|
363
|
-
export function repent(p, id
|
|
362
|
+
export function repent(p, id) {
|
|
364
363
|
const breach = p.conscience.breaches.find((b) => b.id === id);
|
|
365
364
|
if (!breach)
|
|
366
365
|
return false;
|
|
@@ -371,9 +370,8 @@ export function repent(p, id, _now) {
|
|
|
371
370
|
}
|
|
372
371
|
/**
|
|
373
372
|
* Restitution/apology delivered to a wronged person (host-attested): closes their open breaches.
|
|
374
|
-
* The unused `_now` parameter is optional since 2.9.1 and is removed in 3.0.
|
|
375
373
|
*/
|
|
376
|
-
export function recordRepair(p, victimId
|
|
374
|
+
export function recordRepair(p, victimId) {
|
|
377
375
|
let count = 0;
|
|
378
376
|
for (const b of p.conscience.breaches) {
|
|
379
377
|
if (!b.repaired && b.victimId === victimId) {
|
|
@@ -1,44 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SCOPE: one conversation turn between two people: what the speaker tells the listener, drawn from the
|
|
3
|
-
* speaker's own confident beliefs, and what advice they give, drawn from the speaker's beliefs about what the
|
|
4
|
-
* listener should do. Output is plain data: `told` percepts (testimony with claims at the speaker's credence)
|
|
5
|
-
* and advice percepts that the host delivers through the listener's `perceive`, a `Suggestion` per piece of
|
|
6
|
-
* advice for the listener's next decision, and social events for both sides. Reputation is nothing more than
|
|
7
|
-
* these claims spreading belief by belief (`social.applyReputationBelief` turns a changed character belief
|
|
8
|
-
* into a changed relationship).
|
|
9
|
-
*
|
|
10
|
-
* Choice of what to say is deterministic and explainable: each candidate claim gets a relevance score
|
|
11
|
-
* (confidence, recency, host topics, the speaker's feeling about its subject); an unfavourable claim about an
|
|
12
|
-
* absent third party is tagged with the catalog norm 'backbiting' (Qur'an 49:12, as catalogued in
|
|
13
|
-
* `conscience/catalog.ts`) and costs the speaker in proportion to their OWN held understanding of that norm and
|
|
14
|
-
* their honesty-humility, so a speaker who holds it firmly withholds it and one who does not tells it. The
|
|
15
|
-
* definition used here (an unfavourable character claim about someone not present) is an engineering
|
|
16
|
-
* assumption for the tag, not a ruling on what counts as backbiting. Deceit happens only when the host offers a
|
|
17
|
-
* temptation (the speaker gains if the listener believes something the speaker does not): temptation scales
|
|
18
|
-
* with (1 − honesty-humility) and is opposed by the speaker's held 'lying' norm. The lie is a host-visible flag
|
|
19
|
-
* (`ToldClaim.deceit`); the listener cannot see it. Named shapes: honesty-humility as the HEXACO factor most
|
|
20
|
-
* predictive of lower dishonesty (Hilbig & Zettler 2009; Heck et al. 2018 meta-analysis), gossip as a large
|
|
21
|
-
* share of conversation about absent third parties that spreads reputational information (Dunbar 2004), and
|
|
22
|
-
* extraversion as talkativeness (how many claims are offered).
|
|
23
|
-
*
|
|
24
|
-
* SCOPE (host options): `loyalWarning` admits only unfavourable listener claims when affection is at least
|
|
25
|
-
* 0.5 (an engineering threshold). `confidentMin` sets a per-call relay threshold. `phrase` renders selected
|
|
26
|
-
* testimony only; the host owns authored facts, delivery and subsequent disclosure. No hidden relay occurs.
|
|
27
|
-
*
|
|
28
|
-
* It does NOT generate language, model turn-taking, persuasion beyond source trust, what the speaker believes
|
|
29
|
-
* the listener already knows, lies of omission, white lies told to spare feelings, or the speaker's fear of
|
|
30
|
-
* being found out. Coefficients are engineering defaults, not calibrated estimates. Nothing here judges the
|
|
31
|
-
* worth of either person; norms are the people's own understandings with catalog provenance.
|
|
32
|
-
* Repetition (integration, 2026-10-03): a claim the listener already holds at the told confidence produces no
|
|
33
|
-
* percept (it is in `claims`, not `told`). A claim this listener already heard from this speaker, in the same
|
|
34
|
-
* direction, is dropped before the speaker chooses, so it is not even a deed. The speaker keeps no memory of what was said to whom; the listener's
|
|
35
|
-
* belief stands in for it.
|
|
36
|
-
*
|
|
37
|
-
* SCOPE (Word and witness, 2.10.0): `provenance: true` carries each claim's first known origin and opaque
|
|
38
|
-
* lineage into testimony. Missing provenance names the speaker as origin. Metadata changes neither claim
|
|
39
|
-
* selection nor repetition rules; the host still owns the relay graph and disclosure. It does not establish
|
|
40
|
-
* that an origin is truthful, expose deceit to the listener, or infer who else heard a claim.
|
|
41
|
-
*/
|
|
42
1
|
import { type TraitEffect } from '../social/index.ts';
|
|
43
2
|
import type { EntityId, Minute, NormTag, Percept, Person, PersonId, SocialEvent, Suggestion, Unit } from '../types.ts';
|
|
44
3
|
export declare const CONVERSATION_DEFAULTS: {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
* selection nor repetition rules; the host still owns the relay graph and disclosure. It does not establish
|
|
40
40
|
* that an origin is truthful, expose deceit to the listener, or infer who else heard a claim.
|
|
41
41
|
*/
|
|
42
|
-
import { clamp01,
|
|
42
|
+
import { clamp01, round, sigmoid } from '../core/index.js';
|
|
43
43
|
import { nameOf as listenerNameOf } from '../narrate/lexicon.js';
|
|
44
44
|
import { isDeceased, isUnfavourableTrait, parseTraitProp, REPUTATION_TRAITS, relationshipWith, } from '../social/index.js';
|
|
45
45
|
import { MINUTES_PER_DAY } from '../types.js';
|
|
@@ -157,7 +157,7 @@ export function converse(speaker, listener, ctx) {
|
|
|
157
157
|
const subject = b.prop.split(':')[0];
|
|
158
158
|
if (subject === speaker.id)
|
|
159
159
|
continue;
|
|
160
|
-
const cred =
|
|
160
|
+
const cred = sigmoid(b.logOdds);
|
|
161
161
|
const threshold = Number.isFinite(ctx.confidentMin) &&
|
|
162
162
|
ctx.confidentMin >= 0 &&
|
|
163
163
|
ctx.confidentMin <= 0.5
|
|
@@ -181,7 +181,7 @@ export function converse(speaker, listener, ctx) {
|
|
|
181
181
|
// Temptations: claims the speaker would gain from the listener believing.
|
|
182
182
|
for (const t of [...(ctx.temptations ?? [])].sort((a, b) => (a.prop < b.prop ? -1 : 1))) {
|
|
183
183
|
const b = speaker.memory.beliefs.find((x) => x.prop === t.prop);
|
|
184
|
-
const cred = b ?
|
|
184
|
+
const cred = b ? sigmoid(b.logOdds) : 0.5;
|
|
185
185
|
const forValue = t.value ? cred : 1 - cred;
|
|
186
186
|
const gain = clamp01(t.gain);
|
|
187
187
|
if (forValue >= 0.5) {
|
|
@@ -234,7 +234,7 @@ export function converse(speaker, listener, ctx) {
|
|
|
234
234
|
// deed (counted above) but yields no percept, so the same gossip retold at every tea is not judged afresh.
|
|
235
235
|
const heard = listener.memory.beliefs.find((x) => x.prop === c.prop);
|
|
236
236
|
if (heard) {
|
|
237
|
-
const cred =
|
|
237
|
+
const cred = sigmoid(heard.logOdds);
|
|
238
238
|
if ((c.value ? cred : 1 - cred) >= c.confidence)
|
|
239
239
|
continue;
|
|
240
240
|
}
|
|
@@ -281,7 +281,7 @@ export function converse(speaker, listener, ctx) {
|
|
|
281
281
|
for (const b of beliefs) {
|
|
282
282
|
if (!b.prop.startsWith(prefix))
|
|
283
283
|
continue;
|
|
284
|
-
const cred =
|
|
284
|
+
const cred = sigmoid(b.logOdds);
|
|
285
285
|
if (cred < C.adviceMin)
|
|
286
286
|
continue;
|
|
287
287
|
const strength = clamp01((cred - 0.5) * 2 * (0.3 + 0.7 * care) * (0.5 + benevolence));
|
package/dist/core/index.d.ts
CHANGED
package/dist/core/index.js
CHANGED
package/dist/core/math.d.ts
CHANGED
|
@@ -5,8 +5,6 @@ export declare const clampSigned: (value: number) => number;
|
|
|
5
5
|
export declare const lerp: (a: number, b: number, t: number) => number;
|
|
6
6
|
export declare const sigmoid: (x: number) => number;
|
|
7
7
|
export declare const logit: (p: number) => number;
|
|
8
|
-
/** @deprecated since 2.9.1, removed in 3.0; use sigmoid */
|
|
9
|
-
export declare const expit: typeof sigmoid;
|
|
10
8
|
/** Exponential decay of `value` toward `target` over `dt` minutes with the given half-life. */
|
|
11
9
|
export declare function decay(value: number, dt: number, halfLife: number, target?: number): number;
|
|
12
10
|
export declare const minuteOfDay: (now: number) => number;
|
package/dist/core/math.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { MINUTES_PER_DAY } from '../types.js';
|
|
2
2
|
import { dexp, dlog, dpow } from './libm.js';
|
|
3
3
|
/** Clamp into [min, max]. NaN maps to `min`, so one bad host number cannot propagate through state. */
|
|
4
|
-
export const clamp = (value, min, max) =>
|
|
4
|
+
export const clamp = (value, min, max) =>
|
|
5
|
+
// biome-ignore lint/complexity/useMathMinMax: Comparisons preserve signed zero and NaN bounds; Math.min/max change them.
|
|
6
|
+
Number.isNaN(value) ? min : value < min ? min : value > max ? max : value;
|
|
5
7
|
export const clamp01 = (value) => clamp(value, 0, 1);
|
|
6
8
|
export const clampSigned = (value) => clamp(value, -1, 1);
|
|
7
9
|
/** A finite number. Restore sanitizers use it. @internal */
|
|
@@ -36,8 +38,6 @@ export const logit = (p) => {
|
|
|
36
38
|
const q = clamp(p, 1e-6, 1 - 1e-6);
|
|
37
39
|
return dlog(q / (1 - q));
|
|
38
40
|
};
|
|
39
|
-
/** @deprecated since 2.9.1, removed in 3.0; use sigmoid */
|
|
40
|
-
export const expit = sigmoid;
|
|
41
41
|
/** Exponential decay of `value` toward `target` over `dt` minutes with the given half-life. */
|
|
42
42
|
export function decay(value, dt, halfLife, target = 0) {
|
|
43
43
|
if (halfLife <= 0)
|
package/dist/family/family.d.ts
CHANGED
|
@@ -40,11 +40,6 @@ export declare function conceptionChance(mother: Person, father: Person, days: n
|
|
|
40
40
|
* median (an engineering bound against extreme draws). Returns the pregnancy, or undefined if one is already open.
|
|
41
41
|
*/
|
|
42
42
|
export declare function conceive(mother: Person, father: Person | PersonId, seed: number, at: Minute): Pregnancy | undefined;
|
|
43
|
-
/** @deprecated since 2.1: pass `at` (and the optional knobs) positionally. Removed in 3.0. */
|
|
44
|
-
export declare function conceive(mother: Person, father: Person | PersonId, opts: {
|
|
45
|
-
at: Minute;
|
|
46
|
-
seed: number;
|
|
47
|
-
}): Pregnancy | undefined;
|
|
48
43
|
/**
|
|
49
44
|
* Weeks since conception (0 when not pregnant). Counted from conception, not from the last menstrual period, which
|
|
50
45
|
* is where obstetric weeks and trimesters are conventionally counted from (about two weeks earlier). The second
|
package/dist/family/family.js
CHANGED
|
@@ -161,19 +161,25 @@ export function conceptionChance(mother, father, days) {
|
|
|
161
161
|
return 0;
|
|
162
162
|
return clamp01(1 - dpow(1 - c, days / FAMILY_DEFAULTS.cycleDays));
|
|
163
163
|
}
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Record that `mother` has conceived by `father` at `at`. The due date is drawn from a stream derived from `seed`;
|
|
166
|
+
* `seed` also becomes the child's seed at delivery. The drawn length is clamped to 4 sd below and 3 sd above the
|
|
167
|
+
* median (an engineering bound against extreme draws). Returns the pregnancy, or undefined if one is already open.
|
|
168
|
+
*/
|
|
169
|
+
export function conceive(mother, father, seed, at) {
|
|
170
|
+
if (!Number.isFinite(at))
|
|
171
|
+
throw new TypeError('conceive requires a finite positional time');
|
|
166
172
|
const fam = familyOf(mother);
|
|
167
173
|
if (fam.pregnancy)
|
|
168
174
|
return undefined;
|
|
169
|
-
const rng = createRng((
|
|
175
|
+
const rng = createRng((seed ^ 0x9e3779b9) >>> 0);
|
|
170
176
|
const F = FAMILY_DEFAULTS;
|
|
171
177
|
const days = clamp(normal(rng, F.gestationDays, F.gestationSdDays), F.gestationDays - 4 * F.gestationSdDays, F.gestationDays + 3 * F.gestationSdDays);
|
|
172
178
|
const pregnancy = {
|
|
173
179
|
fatherId: typeof father === 'string' ? father : father.id,
|
|
174
|
-
conceivedAt:
|
|
175
|
-
dueAt:
|
|
176
|
-
seed
|
|
180
|
+
conceivedAt: at,
|
|
181
|
+
dueAt: at + Math.round(days * MINUTES_PER_DAY),
|
|
182
|
+
seed,
|
|
177
183
|
};
|
|
178
184
|
fam.pregnancy = pregnancy;
|
|
179
185
|
return { ...pregnancy };
|
package/dist/impression.d.ts
CHANGED
|
@@ -6,14 +6,9 @@ export declare function outwardSigns(target: Person): OutwardSigns;
|
|
|
6
6
|
/** What `target` says of themself: words hide more than faces (`wordsReserve`), and a limp is not mentioned. */
|
|
7
7
|
export declare function selfReport(target: Person): OutwardSigns;
|
|
8
8
|
/** `observer` looks at `target` at `at` (clarity 0..1: a clear look near by, or a distant figure; default 1). */
|
|
9
|
-
export declare function
|
|
10
|
-
/** @deprecated since 2.1: pass `at` (and the optional knobs) positionally. Removed in 3.0. */
|
|
11
|
-
export declare function glimpseOf(observer: Person, target: Person, opts: {
|
|
12
|
-
at: Minute;
|
|
13
|
-
clarity?: Unit;
|
|
14
|
-
}): void;
|
|
9
|
+
export declare function glimpsePerson(observer: Person, target: Person, at: Minute, clarity?: Unit): void;
|
|
15
10
|
/** `observer` has known `target` for a long time (familiarity 0..1): traits and family ties, condensed. */
|
|
16
|
-
export declare function
|
|
11
|
+
export declare function acquaintPerson(observer: Person, target: Person, familiarity: Unit, at: Minute): void;
|
|
17
12
|
/** How sure the observer is of the things that drive an answer: traits and the state cues, 0..1. */
|
|
18
13
|
export declare function impressionConfidence(observer: Person, targetId: PersonId, now: Minute): Unit;
|
|
19
14
|
/**
|
package/dist/impression.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* - `outwardSigns(target)`: perceived fatigue, pain and fear and the felt mood, each reduced by the target's reserve;
|
|
5
5
|
* a limp (lost moving capacity) shows through any reserve. `selfReport(target)` is the same in words, which hide
|
|
6
6
|
* more (`IMPRESSION_DEFAULTS.wordsReserve`): "I'm fine".
|
|
7
|
-
* - `
|
|
7
|
+
* - `glimpsePerson(observer, target, at, clarity?)` = `glimpse` of the target's outward signs; `acquaintPerson` hands the target's
|
|
8
8
|
* traits and family ties (public roles) to `acquaint`.
|
|
9
9
|
* - `imagine(observer, target, now)`: the target as the observer pictures them — a copy whose traits, fear, pain,
|
|
10
10
|
* fatigue, mood, ties and trust in voices are the observer's beliefs (each pulled to the prior by its uncertainty)
|
|
@@ -61,14 +61,12 @@ export function selfReport(target) {
|
|
|
61
61
|
s.pain = round(body.perceived.pain * (1 - clamp01(reserveOf(target, 'pain') * IMPRESSION_DEFAULTS.wordsReserve)));
|
|
62
62
|
return s;
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
else
|
|
68
|
-
glimpse(observer, target.id, outwardSigns(target), when.at, when.clarity);
|
|
64
|
+
/** `observer` looks at `target` at `at` (clarity 0..1: a clear look near by, or a distant figure; default 1). */
|
|
65
|
+
export function glimpsePerson(observer, target, at, clarity) {
|
|
66
|
+
glimpse(observer, target.id, outwardSigns(target), at, clarity);
|
|
69
67
|
}
|
|
70
68
|
/** `observer` has known `target` for a long time (familiarity 0..1): traits and family ties, condensed. */
|
|
71
|
-
export function
|
|
69
|
+
export function acquaintPerson(observer, target, familiarity, at) {
|
|
72
70
|
const ties = {};
|
|
73
71
|
for (const r of target.social.relationships)
|
|
74
72
|
if (r.roles.some((x) => PUBLIC_ROLES.includes(x)))
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Human Framework public surface. */
|
|
2
|
-
export declare const FRAMEWORK_VERSION = "
|
|
2
|
+
export declare const FRAMEWORK_VERSION = "3.0.0";
|
|
3
3
|
export type { CrisisEvent, PracticeKind } from './affect/index.ts';
|
|
4
4
|
export { AFFECT_DEFAULTS, actionTendencies, advanceAffect, appraise, breakAllows, breakBehaviour, breakHazard, CRISIS_DEFAULTS, checkCrisis, createAffect, easeBreak, emotionLevel, enableBreaks, feel, inBreak, readAffect, regulate, release as releaseEmotion, skipCrisis, strain, tendencyEmotions, } from './affect/index.ts';
|
|
5
5
|
export type { GoalTemplate, MakeUpReceipt, PrayerCalendar, PrayerSchedule, PrayerTimes, Retimer, } from './agenda/index.ts';
|
|
@@ -46,5 +46,6 @@ export * from './scenarios/index.ts';
|
|
|
46
46
|
export * from './sim/index.ts';
|
|
47
47
|
export * from './skills/index.ts';
|
|
48
48
|
export * from './social/index.ts';
|
|
49
|
+
export { canonicalTickInputs, type TickInput, type TickInputKey } from './tick-input.ts';
|
|
49
50
|
export * from './types.ts';
|
|
50
51
|
export * from './will/index.ts';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Human Framework public surface. */
|
|
2
|
-
export const FRAMEWORK_VERSION = '
|
|
2
|
+
export const FRAMEWORK_VERSION = '3.0.0';
|
|
3
3
|
export { AFFECT_DEFAULTS, actionTendencies, advanceAffect, appraise, breakAllows, breakBehaviour, breakHazard, CRISIS_DEFAULTS, checkCrisis, createAffect, easeBreak, emotionLevel, enableBreaks, feel, inBreak, readAffect, regulate, release as releaseEmotion, skipCrisis, strain, tendencyEmotions, } from './affect/index.js';
|
|
4
4
|
// `agenda.release` collides with `affect.release`; the agenda one is re-exported under a clearer name.
|
|
5
5
|
export { AGENDA_DEFAULTS, abandonGoal, adoptGoal, advanceAgenda, agendaTerms, applyExemptions, CARE_DEFAULTS, calendarRetimer, careDuty, commitmentPressure, createAgenda, DEFAULT_PRAYER_TIMES, EID_PRAYER_IMPORTANCE, EXEMPTION_DEFAULTS, eidPrayer, eidWindow, fastWindow, iftarWindow, inMakruhTime, isContinuingCommitment, isUnderWay, MAKRUH_DEFAULTS, makruhWindows, missedExcuse, onFinished, owedMakeUps, PRAYER_IMPORTANCE, PURPOSE_DEFAULTS, prayerWindows, pressureReachedAt, promise, proposeGoals, RAMADAN_DEFAULTS, ramadanFast, ramadanMeals, release as releaseCommitment, retimeCommitments, retirePaidMakeUps, revisePurposes, scheduleMakeUp, spanMeetsWindow, suhoorWindow, timesFor, violatesAbstention, } from './agenda/index.js';
|
|
@@ -42,5 +42,6 @@ export * from './scenarios/index.js';
|
|
|
42
42
|
export * from './sim/index.js';
|
|
43
43
|
export * from './skills/index.js';
|
|
44
44
|
export * from './social/index.js';
|
|
45
|
+
export { canonicalTickInputs } from './tick-input.js';
|
|
45
46
|
export * from './types.js';
|
|
46
47
|
export * from './will/index.js';
|
package/dist/longrun.d.ts
CHANGED
|
@@ -87,8 +87,8 @@ export interface RoutineDay {
|
|
|
87
87
|
* Live one routine day from `p.now` (see SCOPE). A dead person only moves the clock. Returns what happened.
|
|
88
88
|
*/
|
|
89
89
|
export declare function routineDay(p: Person, routine: Routine, opts?: RoutineOptions): RoutineDay;
|
|
90
|
-
/**
|
|
91
|
-
export type RoutineSource =
|
|
90
|
+
/** Choose a routine for a person at the start of an absolute day. Capture host context in a closure. */
|
|
91
|
+
export type RoutineSource = (p: Person, day: number) => Routine;
|
|
92
92
|
/**
|
|
93
93
|
* Live from `p.now` to `to` by routine (see SCOPE): the running activity is dropped and the open chronicle day closed
|
|
94
94
|
* first, whole days are lived with `routineDay`, a remainder shorter than a day is skipped (`skip`), and past
|
package/dist/longrun.js
CHANGED
|
@@ -181,7 +181,6 @@ export function routineDay(p, routine, opts = {}) {
|
|
|
181
181
|
report.stage = stageAfter;
|
|
182
182
|
return report;
|
|
183
183
|
}
|
|
184
|
-
const routineOf = (src, p) => typeof src === 'function' ? src(p, dayOf(p.now)) : src;
|
|
185
184
|
/**
|
|
186
185
|
* Live from `p.now` to `to` by routine (see SCOPE): the running activity is dropped and the open chronicle day closed
|
|
187
186
|
* first, whole days are lived with `routineDay`, a remainder shorter than a day is skipped (`skip`), and past
|
|
@@ -199,7 +198,7 @@ export function liveRoutine(p, to, routine, opts = {}) {
|
|
|
199
198
|
endDay(p);
|
|
200
199
|
p.activity = null;
|
|
201
200
|
while (p.now + MINUTES_PER_DAY <= to && p.body.alive) {
|
|
202
|
-
const r = routineDay(p,
|
|
201
|
+
const r = routineDay(p, routine(p, dayOf(p.now)), opts);
|
|
203
202
|
if (r.episodes.length > 0 || r.onsets.length > 0 || r.stage || r.died)
|
|
204
203
|
out.push(r);
|
|
205
204
|
}
|
package/dist/memory/memory.d.ts
CHANGED
|
@@ -139,10 +139,6 @@ export interface RetellOptions {
|
|
|
139
139
|
* from the listener to others (a host may call it again with the listener as teller).
|
|
140
140
|
*/
|
|
141
141
|
export declare function retell(teller: Person, listener: Person, at: Minute, opts?: RetellOptions): Gist[];
|
|
142
|
-
/** @deprecated since 2.1: pass `at` (and the optional knobs) positionally. Removed in 3.0. */
|
|
143
|
-
export declare function retell(teller: Person, listener: Person, opts: RetellOptions & {
|
|
144
|
-
at: Minute;
|
|
145
|
-
}): Gist[];
|
|
146
142
|
/** Encode an episode. Evicts the weakest (salience × recency) episode beyond the bound. */
|
|
147
143
|
export declare function remember(p: Person, e: Omit<Episode, 'id' | 'salience'> & {
|
|
148
144
|
salience?: Unit;
|
package/dist/memory/memory.js
CHANGED
|
@@ -287,8 +287,22 @@ export const RETELL_DEFAULTS = {
|
|
|
287
287
|
/** Gists weaker than this (teller's salience) are not worth telling. */
|
|
288
288
|
minSalience: 0.1,
|
|
289
289
|
};
|
|
290
|
-
|
|
291
|
-
|
|
290
|
+
/**
|
|
291
|
+
* `teller` tells `listener` at `at` the gist of what mattered to them (2.0, L2: stories passed in a household or around a
|
|
292
|
+
* fire). The listener must have gists on (`enableGists`); otherwise nothing happens. The teller's strongest gists
|
|
293
|
+
* (up to `limit`, optionally only those about `placeIds`) become the listener's own gists with the tag `told`, the
|
|
294
|
+
* teller's valence and summary, and a salience of the teller's × `trust` × `RETELL_DEFAULTS.share`. A listener who
|
|
295
|
+
* already holds a gist about the same thing (kind, action, target and place, whoever was there) from their own
|
|
296
|
+
* experience keeps it unchanged; one told before keeps the
|
|
297
|
+
* stronger telling (retelling the same story does not keep raising it). So a child of parents who fear a place
|
|
298
|
+
* weighs that fear (cognition's `memory` term, by place at half weight) before ever standing there, and their own
|
|
299
|
+
* later visits outweigh it as they would any gist. No randomness; returns the gists written or strengthened.
|
|
300
|
+
* It does not model distortion in the retelling, the listener doubting the teller beyond `trust`, or retelling on
|
|
301
|
+
* from the listener to others (a host may call it again with the listener as teller).
|
|
302
|
+
*/
|
|
303
|
+
export function retell(teller, listener, at, opts = {}) {
|
|
304
|
+
if (!Number.isFinite(at))
|
|
305
|
+
throw new TypeError('retell requires a finite positional time');
|
|
292
306
|
const told = teller.memory.gists;
|
|
293
307
|
const list = listener.memory.gists;
|
|
294
308
|
if (!told || !list || teller.id === listener.id)
|
|
@@ -312,7 +326,7 @@ export function retell(teller, listener, when, knobs = {}) {
|
|
|
312
326
|
if (salience > mine.salience) {
|
|
313
327
|
mine.salience = salience;
|
|
314
328
|
mine.weight = salience;
|
|
315
|
-
mine.lastAt =
|
|
329
|
+
mine.lastAt = at;
|
|
316
330
|
out.push(mine);
|
|
317
331
|
}
|
|
318
332
|
continue;
|
|
@@ -321,15 +335,15 @@ export function retell(teller, listener, when, knobs = {}) {
|
|
|
321
335
|
listener.memory.nextGist = (listener.memory.nextGist ?? 0) + 1;
|
|
322
336
|
const g = {
|
|
323
337
|
id,
|
|
324
|
-
at
|
|
338
|
+
at,
|
|
325
339
|
kind: src.kind,
|
|
326
340
|
valence: src.valence,
|
|
327
341
|
salience,
|
|
328
342
|
summary: src.summary,
|
|
329
343
|
tags: ['told', ...src.tags.filter((t) => t !== 'told')].slice(0, GIST_DEFAULTS.maxTags),
|
|
330
344
|
count: 0,
|
|
331
|
-
firstAt:
|
|
332
|
-
lastAt:
|
|
345
|
+
firstAt: at,
|
|
346
|
+
lastAt: at,
|
|
333
347
|
weight: salience,
|
|
334
348
|
peak: salience,
|
|
335
349
|
};
|
package/dist/person.js
CHANGED
|
@@ -29,7 +29,8 @@ import { ageCharacter, enableCharacterChange, noteCharacterDay, noteCharacterSoc
|
|
|
29
29
|
import { closeDay, enableYearbook, endDay, noteAnswer, noteCommitments, noteDecision, noteMood, noteOutcome, trimChronicle, } from './chronicle/index.js';
|
|
30
30
|
import { decide as cognitionDecide, desperationOf, prospectiveRoute, scoreAll } from './cognition/index.js';
|
|
31
31
|
import { createConscience, guiltLoad, heldNorms, normVeto, recordDeed, recordRepair, repent, } from './conscience/index.js';
|
|
32
|
-
import { clamp01, clampSigned, createRng, dayOf, isObj,
|
|
32
|
+
import { clamp01, clampSigned, createRng, dayOf, isObj, minuteOfDay, round } from './core/index.js';
|
|
33
|
+
import { keysOf } from './core/keys.js';
|
|
33
34
|
import { enableCuriosity, noteCuriosity } from './curiosity/index.js';
|
|
34
35
|
import { ambientBodyParams, ambientModifiers, ambientMood, ambientNeeds } from './environment/index.js';
|
|
35
36
|
import { aptitudeOf, createFamily, pregnancyModifiers } from './family/index.js';
|
|
@@ -1390,7 +1391,7 @@ function respondToConscience(p, act, now, completed, minutes) {
|
|
|
1390
1391
|
// Worship closes breaches against God alone; guilt toward a wronged person waits for repair.
|
|
1391
1392
|
const repented = new Set();
|
|
1392
1393
|
for (const b of p.conscience.breaches) {
|
|
1393
|
-
if (!b.repaired && b.victimId === undefined && repent(p, b.id
|
|
1394
|
+
if (!b.repaired && b.victimId === undefined && repent(p, b.id))
|
|
1394
1395
|
repented.add(b.normId);
|
|
1395
1396
|
}
|
|
1396
1397
|
for (const b of p.conscience.breaches)
|
|
@@ -1401,7 +1402,7 @@ function respondToConscience(p, act, now, completed, minutes) {
|
|
|
1401
1402
|
regulate(p, minutes, 'worship');
|
|
1402
1403
|
}
|
|
1403
1404
|
if (completed && (tags.includes('repair') || tags.includes('apologize')) && aff.targetId !== undefined) {
|
|
1404
|
-
recordRepair(p, aff.targetId
|
|
1405
|
+
recordRepair(p, aff.targetId);
|
|
1405
1406
|
release(p, { id: 'guilt', targetId: aff.targetId });
|
|
1406
1407
|
socialEvent(p, { at: now, kind: 'apology', otherId: aff.targetId, byMe: true, magnitude: 0.7 });
|
|
1407
1408
|
}
|
package/dist/sim/longrun.d.ts
CHANGED
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
* since a routine day writes only its own person. Births and arrivals are the host's (`birth`, `addPerson` in
|
|
16
16
|
* `onDay`); a person added during the stretch starts on the next day.
|
|
17
17
|
*/
|
|
18
|
-
import { type
|
|
18
|
+
import { type RoutineDay, type RoutineSource } from '../longrun.ts';
|
|
19
19
|
import type { SkillTransfer } from '../skills/index.ts';
|
|
20
|
-
import type { Minute,
|
|
20
|
+
import type { Minute, PersonId } from '../types.ts';
|
|
21
21
|
import { type Community, type LifecourseOptions, type SimEvent } from './sim.ts';
|
|
22
22
|
export declare const LIVE_COMMUNITY_DEFAULTS: {
|
|
23
23
|
/** Upbringing time a day with the parents (engineering assumption: a few waking hours together). */
|
|
@@ -25,7 +25,7 @@ export declare const LIVE_COMMUNITY_DEFAULTS: {
|
|
|
25
25
|
};
|
|
26
26
|
export interface LiveCommunityOptions {
|
|
27
27
|
/** Each living person's routine for a day (`day` is the day number being lived). */
|
|
28
|
-
routineFor
|
|
28
|
+
routineFor: RoutineSource;
|
|
29
29
|
/** At each dawn, before anyone's day: births, arrivals, routine changes. */
|
|
30
30
|
onDay?(c: Community, day: number, at: Minute): void;
|
|
31
31
|
/** Tell each death to everyone with a tie (default true; see `tellDeath`). */
|
package/dist/sim/longrun.js
CHANGED
|
@@ -70,7 +70,7 @@ export function liveCommunity(c, until, opts) {
|
|
|
70
70
|
tick(p, now);
|
|
71
71
|
if (p.now !== now)
|
|
72
72
|
continue; // added mid-stretch with a later clock: joins when the day catches up
|
|
73
|
-
const r = routineDay(p, opts.routineFor(p, day
|
|
73
|
+
const r = routineDay(p, opts.routineFor(p, day), routineOpts);
|
|
74
74
|
if (r.episodes.length > 0 || r.onsets.length > 0 || r.stage || r.died) {
|
|
75
75
|
const list = days[p.id] ?? [];
|
|
76
76
|
list.push(r);
|
|
@@ -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);
|
package/dist/social/social.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* further down; insiders, outsiders and threats in `groups.ts`); the
|
|
13
13
|
* coefficients are engineering defaults, not calibrated estimates.
|
|
14
14
|
*/
|
|
15
|
-
import { clamp, clamp01, clampSigned, cmpStr, decay, dlog,
|
|
15
|
+
import { clamp, clamp01, clampSigned, cmpStr, decay, dlog, round, sigmoid } from '../core/index.js';
|
|
16
16
|
import { MINUTES_PER_DAY, MINUTES_PER_YEAR } from '../types.js';
|
|
17
17
|
import { careFor } from './groups.js';
|
|
18
18
|
import { regardOf } from './impressions.js';
|
|
@@ -554,7 +554,7 @@ export function reputation(people, targetId, table = REPUTATION_TRAITS) {
|
|
|
554
554
|
continue;
|
|
555
555
|
knows = true;
|
|
556
556
|
const slot = traitSum[parsed.trait] ?? { sum: 0, n: 0 };
|
|
557
|
-
slot.sum +=
|
|
557
|
+
slot.sum += sigmoid(b.logOdds);
|
|
558
558
|
slot.n += 1;
|
|
559
559
|
traitSum[parsed.trait] = slot;
|
|
560
560
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adam0white/human-framework",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Deterministic, explainable simulated people: body, needs, affect, memory, beliefs, skills, habits, relationships, values and a will that can refuse a suggestion",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"simulation",
|