@adam0white/human-framework 2.9.0 → 2.10.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/dist/agenda/agenda.js +1 -1
- package/dist/beliefs/beliefs.d.ts +45 -1
- package/dist/beliefs/beliefs.js +208 -4
- package/dist/cognition/cognition.js +2 -2
- package/dist/conscience/conscience.d.ts +7 -3
- package/dist/conscience/conscience.js +5 -1
- package/dist/conversation/conversation.d.ts +4 -0
- package/dist/conversation/conversation.js +25 -0
- package/dist/core/math.d.ts +1 -0
- package/dist/core/math.js +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/memory/memory.js +4 -1
- package/dist/migrate.js +18 -1
- package/dist/person.js +150 -100
- package/dist/prospection/prospection.js +8 -2
- package/dist/restore.d.ts +9 -0
- package/dist/restore.js +22 -0
- package/dist/sim/sim.d.ts +7 -2
- package/dist/sim/sim.js +118 -93
- package/dist/social/impressions.d.ts +31 -2
- package/dist/social/impressions.js +38 -1
- package/dist/social/social.d.ts +2 -0
- package/dist/social/social.js +18 -2
- package/dist/types.d.ts +55 -6
- package/dist/types.js +1 -1
- package/dist/will/will.js +94 -65
- package/package.json +1 -1
package/dist/sim/sim.js
CHANGED
|
@@ -125,6 +125,7 @@ export function communityState(c) {
|
|
|
125
125
|
/**
|
|
126
126
|
* A community over `people`. With `prior` (from `communityState`, possibly through JSON), the host-side state is
|
|
127
127
|
* resumed from it, so a run restored from people + community + world state continues exactly as it would have.
|
|
128
|
+
* `prior` is copied: after stepping, write back `communityState(c)`; the old `prior` stays stale.
|
|
128
129
|
*/
|
|
129
130
|
export function createCommunity(people, prior) {
|
|
130
131
|
const sorted = [...people].sort((a, b) => cmpStr(a.id, b.id));
|
|
@@ -181,9 +182,19 @@ export function birth(c, a, b, spec) {
|
|
|
181
182
|
/**
|
|
182
183
|
* Tell `dead`'s death at `at` to everyone alive in the community who has a tie to them (1.8.0): a told `death`
|
|
183
184
|
* percept through `perceive`, so they grieve, the tie is kept and marked, and a spouse is widowed. Each listener's
|
|
184
|
-
*
|
|
185
|
+
* receipt time is at least their own `now`. `late: 'now'` explicitly permits listeners past `at`, as does the
|
|
186
|
+
* existing default. `late: 'throw'` requires all eligible listeners to be at or before `at` and throws before
|
|
187
|
+
* delivering any news if one is ahead. This changes no listener's clock.
|
|
185
188
|
*/
|
|
186
|
-
export function tellDeath(c, dead, at) {
|
|
189
|
+
export function tellDeath(c, dead, at, opts = {}) {
|
|
190
|
+
if (opts.late === 'throw') {
|
|
191
|
+
for (const q of c.people) {
|
|
192
|
+
if (q === dead || !q.body.alive || q.now <= at)
|
|
193
|
+
continue;
|
|
194
|
+
if (q.social.relationships.some((r) => r.otherId === dead.id))
|
|
195
|
+
throw new Error(`tellDeath: listener ${q.id} is past the death minute`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
187
198
|
for (const q of c.people) {
|
|
188
199
|
if (q === dead || !q.body.alive)
|
|
189
200
|
continue;
|
|
@@ -572,24 +583,8 @@ function contagionBetween(p, q, act, t, log) {
|
|
|
572
583
|
}
|
|
573
584
|
}
|
|
574
585
|
}
|
|
575
|
-
/**
|
|
576
|
-
|
|
577
|
-
*/
|
|
578
|
-
export function stepCommunity(c, world, until, opts = {}) {
|
|
579
|
-
const events = [];
|
|
580
|
-
const quantum = Math.max(1, opts.idleQuantum ?? SIM_DEFAULTS.idleQuantum);
|
|
581
|
-
const maxEvents = opts.maxEvents ?? SIM_DEFAULTS.maxEvents;
|
|
582
|
-
const salience = opts.interruptSalience ?? SIM_DEFAULTS.interruptSalience;
|
|
583
|
-
// Communities created before joint support (plain JSON from an older host) get the new bookkeeping.
|
|
584
|
-
c.interrupts ??= {};
|
|
585
|
-
c.joint ??= { nextId: 0, proposals: [], excluded: {} };
|
|
586
|
-
const log = (e) => {
|
|
587
|
-
if (events.length < maxEvents)
|
|
588
|
-
events.push(e);
|
|
589
|
-
};
|
|
590
|
-
const contagion = opts.contagion ?? true;
|
|
591
|
-
c.queued ??= {};
|
|
592
|
-
c.dayDone ??= {};
|
|
586
|
+
/** Synchronize the host's commands before selecting the next event. */
|
|
587
|
+
function synchronizeCommands(c, opts, log) {
|
|
593
588
|
if (opts.controlled) {
|
|
594
589
|
for (const p of c.people) {
|
|
595
590
|
const cmd = opts.controlled[p.id];
|
|
@@ -614,36 +609,108 @@ export function stepCommunity(c, world, until, opts = {}) {
|
|
|
614
609
|
}
|
|
615
610
|
}
|
|
616
611
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
612
|
+
}
|
|
613
|
+
/** Run life-course bookkeeping and the host's day hook at the first event of a new day. */
|
|
614
|
+
function bookkeepDay(p, world, t, dayDone, opts) {
|
|
615
|
+
// First event of a new day: life-course rolls for the days crossed, then the host's day hook.
|
|
616
|
+
const day = dayOf(t);
|
|
617
|
+
const lastDay = dayDone[p.id];
|
|
618
|
+
if (lastDay === undefined || day > lastDay) {
|
|
619
|
+
dayDone[p.id] = day;
|
|
620
|
+
const lc = opts.lifecourse;
|
|
621
|
+
if (lc && lastDay !== undefined && p.body.alive) {
|
|
622
|
+
const dt = (day - lastDay) * MINUTES_PER_DAY;
|
|
623
|
+
if (lc.chronicOnsets) {
|
|
624
|
+
const onsetOpts = {};
|
|
625
|
+
if (lc.conditions)
|
|
626
|
+
onsetOpts.conditions = lc.conditions;
|
|
627
|
+
const ex = lc.exposures?.(p);
|
|
628
|
+
if (ex)
|
|
629
|
+
onsetOpts.exposures = ex;
|
|
630
|
+
for (const ill of chronicOnsets(p, dt, p.rng, onsetOpts))
|
|
631
|
+
sicken(p, ill);
|
|
632
|
+
}
|
|
633
|
+
if (lc.mortality) {
|
|
634
|
+
const roll = mortalityEvent(p, dt, p.rng, lc.multiplier !== undefined ? { multiplier: lc.multiplier } : {});
|
|
635
|
+
if (roll.died)
|
|
636
|
+
die(p);
|
|
637
|
+
}
|
|
631
638
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
639
|
+
if (p.body.alive)
|
|
640
|
+
world.onDay?.(p, day);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
/** Merge heard advice and the host's decision options in their existing precedence order. */
|
|
644
|
+
function decisionOptions(c, world, p, opts, reason, affordances) {
|
|
645
|
+
const o = {};
|
|
646
|
+
// Voices: queued conversation advice first (fresher), then the host's standing suggestions that are heard now
|
|
647
|
+
// (standing-advice SCOPE); one per voice. At reviews they still weigh in, but `decide` resolves them quietly
|
|
648
|
+
// (no counters, pressure or autonomy cost), so one request is not re-counted as many verdicts.
|
|
649
|
+
const standing = opts.suggestions?.[p.id];
|
|
650
|
+
const merged = [];
|
|
651
|
+
const seen = new Set();
|
|
652
|
+
const heard = (standing ? (Array.isArray(standing) ? standing : [standing]) : []).filter((s) => standingHeard(c, p, s, affordances, p.now));
|
|
653
|
+
for (const s of [...(c.queued?.[p.id] ?? []), ...heard]) {
|
|
654
|
+
if (seen.has(s.voiceId))
|
|
655
|
+
continue;
|
|
656
|
+
seen.add(s.voiceId);
|
|
657
|
+
merged.push(s);
|
|
658
|
+
}
|
|
659
|
+
const [first, ...rest] = merged;
|
|
660
|
+
if (first)
|
|
661
|
+
o.suggestion = first;
|
|
662
|
+
if (rest.length > 0)
|
|
663
|
+
o.suggestions = rest;
|
|
664
|
+
const scarcity = world.scarcityFor?.(p);
|
|
665
|
+
if (scarcity !== undefined && scarcity > 0)
|
|
666
|
+
o.scarcity = scarcity;
|
|
667
|
+
if (opts.necessity !== undefined)
|
|
668
|
+
o.necessity = opts.necessity;
|
|
669
|
+
const forced = reason ?? c.interrupts[p.id]?.reason;
|
|
670
|
+
if (forced !== undefined && !p.activity)
|
|
671
|
+
o.interrupt = forced;
|
|
672
|
+
return o;
|
|
673
|
+
}
|
|
674
|
+
/** Add pending joint mirrors to world offers and apply this minute's declined-offer exclusions. */
|
|
675
|
+
function offersForDecision(c, world, p, t) {
|
|
676
|
+
// Offers: the world's, plus mirrors of joint proposals awaiting this person's answer, minus offers a partner
|
|
677
|
+
// just declined (this minute only).
|
|
678
|
+
const asked = pendingProposals(c).filter((x) => x.partnerIds.includes(p.id) && !x.accepted.includes(p.id));
|
|
679
|
+
const mirrors = new Map();
|
|
680
|
+
let affordances = world.affordancesFor(p);
|
|
681
|
+
for (const x of asked) {
|
|
682
|
+
const m = mirrorFor(world, p, x);
|
|
683
|
+
mirrors.set(m.id, x);
|
|
684
|
+
affordances = [...affordances.filter((a) => a.id !== m.id), m];
|
|
685
|
+
}
|
|
686
|
+
const ex = c.joint.excluded[p.id];
|
|
687
|
+
if (ex) {
|
|
688
|
+
if (ex.at === t)
|
|
689
|
+
affordances = affordances.filter((a) => !ex.ids.includes(a.id));
|
|
690
|
+
else
|
|
691
|
+
delete c.joint.excluded[p.id];
|
|
692
|
+
}
|
|
693
|
+
return { asked, mirrors, affordances };
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Advance the community to `until`, processing person events in (time, id) order. Returns the events.
|
|
697
|
+
*/
|
|
698
|
+
export function stepCommunity(c, world, until, opts = {}) {
|
|
699
|
+
const events = [];
|
|
700
|
+
const quantum = Math.max(1, opts.idleQuantum ?? SIM_DEFAULTS.idleQuantum);
|
|
701
|
+
const maxEvents = opts.maxEvents ?? SIM_DEFAULTS.maxEvents;
|
|
702
|
+
const salience = opts.interruptSalience ?? SIM_DEFAULTS.interruptSalience;
|
|
703
|
+
// Communities created before joint support (plain JSON from an older host) get the new bookkeeping.
|
|
704
|
+
c.interrupts ??= {};
|
|
705
|
+
c.joint ??= { nextId: 0, proposals: [], excluded: {} };
|
|
706
|
+
const log = (e) => {
|
|
707
|
+
if (events.length < maxEvents)
|
|
708
|
+
events.push(e);
|
|
646
709
|
};
|
|
710
|
+
const contagion = opts.contagion ?? true;
|
|
711
|
+
c.queued ??= {};
|
|
712
|
+
c.dayDone ??= {};
|
|
713
|
+
synchronizeCommands(c, opts, log);
|
|
647
714
|
// Pairs whose shared activity already ran its conversation and contagion at `pairMinute` (both partners
|
|
648
715
|
// finishing the same joint activity at the same minute would otherwise run them twice).
|
|
649
716
|
let pairMinute = Number.NEGATIVE_INFINITY;
|
|
@@ -662,33 +729,7 @@ export function stepCommunity(c, world, until, opts = {}) {
|
|
|
662
729
|
break;
|
|
663
730
|
const p = next;
|
|
664
731
|
tick(p, t);
|
|
665
|
-
|
|
666
|
-
const day = dayOf(t);
|
|
667
|
-
const lastDay = c.dayDone[p.id];
|
|
668
|
-
if (lastDay === undefined || day > lastDay) {
|
|
669
|
-
c.dayDone[p.id] = day;
|
|
670
|
-
const lc = opts.lifecourse;
|
|
671
|
-
if (lc && lastDay !== undefined && p.body.alive) {
|
|
672
|
-
const dt = (day - lastDay) * MINUTES_PER_DAY;
|
|
673
|
-
if (lc.chronicOnsets) {
|
|
674
|
-
const onsetOpts = {};
|
|
675
|
-
if (lc.conditions)
|
|
676
|
-
onsetOpts.conditions = lc.conditions;
|
|
677
|
-
const ex = lc.exposures?.(p);
|
|
678
|
-
if (ex)
|
|
679
|
-
onsetOpts.exposures = ex;
|
|
680
|
-
for (const ill of chronicOnsets(p, dt, p.rng, onsetOpts))
|
|
681
|
-
sicken(p, ill);
|
|
682
|
-
}
|
|
683
|
-
if (lc.mortality) {
|
|
684
|
-
const roll = mortalityEvent(p, dt, p.rng, lc.multiplier !== undefined ? { multiplier: lc.multiplier } : {});
|
|
685
|
-
if (roll.died)
|
|
686
|
-
die(p);
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
if (p.body.alive)
|
|
690
|
-
world.onDay?.(p, day);
|
|
691
|
-
}
|
|
732
|
+
bookkeepDay(p, world, t, c.dayDone, opts);
|
|
692
733
|
if (!p.body.alive) {
|
|
693
734
|
log({ at: t, personId: p.id, kind: 'died', detail: `${p.name} died` });
|
|
694
735
|
if (opts.tellDeaths ?? true)
|
|
@@ -758,24 +799,8 @@ export function stepCommunity(c, world, until, opts = {}) {
|
|
|
758
799
|
}
|
|
759
800
|
act = p.activity;
|
|
760
801
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
const asked = pendingProposals(c).filter((x) => x.partnerIds.includes(p.id) && !x.accepted.includes(p.id));
|
|
764
|
-
const mirrors = new Map();
|
|
765
|
-
let affordances = world.affordancesFor(p);
|
|
766
|
-
for (const x of asked) {
|
|
767
|
-
const m = mirrorFor(world, p, x);
|
|
768
|
-
mirrors.set(m.id, x);
|
|
769
|
-
affordances = [...affordances.filter((a) => a.id !== m.id), m];
|
|
770
|
-
}
|
|
771
|
-
const ex = c.joint.excluded[p.id];
|
|
772
|
-
if (ex) {
|
|
773
|
-
if (ex.at === t)
|
|
774
|
-
affordances = affordances.filter((a) => !ex.ids.includes(a.id));
|
|
775
|
-
else
|
|
776
|
-
delete c.joint.excluded[p.id];
|
|
777
|
-
}
|
|
778
|
-
const record = decide(p, affordances, decideOpts(p, perceptReason, affordances));
|
|
802
|
+
const { asked, mirrors, affordances } = offersForDecision(c, world, p, t);
|
|
803
|
+
const record = decide(p, affordances, decisionOptions(c, world, p, opts, perceptReason, affordances));
|
|
779
804
|
delete c.interrupts[p.id];
|
|
780
805
|
// Conversation advice is weighed until the next fresh decision, then it has been heard.
|
|
781
806
|
if (!act)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { EntityId, Minute, Person, PersonId, Signed, StateCue, Traits, Unit } from '../types.ts';
|
|
2
|
-
export type CueKind = 'state' | 'place' | 'trait' | 'tie' | 'trust' | 'skill';
|
|
1
|
+
import type { EntityId, Minute, NormTag, Person, PersonId, Signed, StateCue, Traits, Unit } from '../types.ts';
|
|
2
|
+
export type CueKind = 'state' | 'place' | 'trait' | 'tie' | 'trust' | 'skill' | 'regard';
|
|
3
3
|
export declare const STATE_CUES: readonly StateCue[];
|
|
4
4
|
/** @deprecated since 2.1: use `TRAIT_KEYS` (types.ts), the same list. Removed in 3.0. */
|
|
5
5
|
export declare const TRAIT_NAMES: readonly (keyof Traits)[];
|
|
@@ -21,6 +21,7 @@ export declare const IMPRESSION_DEFAULTS: {
|
|
|
21
21
|
tie: number;
|
|
22
22
|
trust: number;
|
|
23
23
|
skill: number;
|
|
24
|
+
regard: number;
|
|
24
25
|
};
|
|
25
26
|
/** Default reserve: base + emotionalityCoef × (1 − emotionality) for pain and fear; fatigue and mood a little. */
|
|
26
27
|
reserve: {
|
|
@@ -129,3 +130,31 @@ export declare function impressionOf(observer: Person, targetId: PersonId, now:
|
|
|
129
130
|
* and unhurt reads positive, frightened or hurt negative. Cognition weighs it on risky offers shared with them.
|
|
130
131
|
*/
|
|
131
132
|
export declare function companionSteadiness(p: Person, otherId: PersonId, now: Minute): Signed;
|
|
133
|
+
/**
|
|
134
|
+
* SCOPE (believed regard): the person's estimate of how another regards them, updated only by their own evidence
|
|
135
|
+
* that the other knows of an act. Seen witnesses carry full weight; a learned relay carries information-source
|
|
136
|
+
* trust. The person projects the act's valence and their own held-norm judgement, without reading the other's
|
|
137
|
+
* mind. Negative samples carry the social negativity bias. Evidence halves over 60 days. This does not model
|
|
138
|
+
* hidden witnesses, unknown relays, recursive beliefs or the other's actual feelings. Relationships and
|
|
139
|
+
* impressions remain separate stores. The projection weights and half-life are engineering defaults.
|
|
140
|
+
*/
|
|
141
|
+
/** An act of mine that someone else knows of (2.10.0). */
|
|
142
|
+
export interface KnownAct {
|
|
143
|
+
kind: string;
|
|
144
|
+
targetId?: PersonId;
|
|
145
|
+
valence?: Signed;
|
|
146
|
+
norms?: NormTag[];
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The person has their own evidence that `otherId` knows of their act: they saw them see it (`via: 'seen'`) or were
|
|
150
|
+
* told they heard (`via: 'told'`, weighted by trust in `sourceId`). Moves the believed regard cue. See the SCOPE.
|
|
151
|
+
*/
|
|
152
|
+
export declare function noteKnownBy(p: Person, ev: {
|
|
153
|
+
otherId: PersonId;
|
|
154
|
+
act: KnownAct;
|
|
155
|
+
at?: Minute;
|
|
156
|
+
via: 'seen' | 'told';
|
|
157
|
+
sourceId?: EntityId;
|
|
158
|
+
}): Estimate | undefined;
|
|
159
|
+
/** How `p` believes `otherId` regards them; the neutral prior has zero confidence. Pure and RNG-free. */
|
|
160
|
+
export declare function regardOf(p: Person, otherId: PersonId, now: Minute): Estimate;
|
|
@@ -25,8 +25,10 @@
|
|
|
25
25
|
* of one's own state, deliberate deception beyond reserve, the target noticing being watched, or gossip about
|
|
26
26
|
* impressions (conversation carries reputation beliefs separately, `social.ts`).
|
|
27
27
|
*/
|
|
28
|
+
import { testimonyWeight, trustOf } from '../beliefs/beliefs.js';
|
|
28
29
|
import { clamp, clamp01, cmpStr, decay, latestPerId, newestN, round } from '../core/index.js';
|
|
29
30
|
import { MINUTES_PER_DAY, MINUTES_PER_YEAR, TRAIT_KEYS } from '../types.js';
|
|
31
|
+
import { judge, SOCIAL_DEFAULTS } from './social.js';
|
|
30
32
|
export const STATE_CUES = ['fatigue', 'pain', 'fear', 'mood'];
|
|
31
33
|
/** @deprecated since 2.1: use `TRAIT_KEYS` (types.ts), the same list. Removed in 3.0. */
|
|
32
34
|
export const TRAIT_NAMES = TRAIT_KEYS;
|
|
@@ -40,6 +42,7 @@ export const IMPRESSION_DEFAULTS = {
|
|
|
40
42
|
trust: 30 * MINUTES_PER_DAY,
|
|
41
43
|
/** `skill:<id>`: how good someone is at a craft changes over seasons, and what was seen of it lasts. */
|
|
42
44
|
skill: MINUTES_PER_YEAR,
|
|
45
|
+
regard: 60 * MINUTES_PER_DAY,
|
|
43
46
|
},
|
|
44
47
|
/** confidence = weight / (weight + priorWeight). */
|
|
45
48
|
priorWeight: 1.5,
|
|
@@ -56,6 +59,7 @@ export const IMPRESSION_DEFAULTS = {
|
|
|
56
59
|
tie: 0,
|
|
57
60
|
trust: 0.5,
|
|
58
61
|
skill: 0.3,
|
|
62
|
+
regard: 0,
|
|
59
63
|
},
|
|
60
64
|
/** Default reserve: base + emotionalityCoef × (1 − emotionality) for pain and fear; fatigue and mood a little. */
|
|
61
65
|
reserve: { base: 0.1, emotionalityCoef: 0.4, fatigue: 0.1, mood: 0.2 },
|
|
@@ -83,6 +87,8 @@ export const IMPRESSION_DEFAULTS = {
|
|
|
83
87
|
maxCues: 48,
|
|
84
88
|
};
|
|
85
89
|
function cueKind(key) {
|
|
90
|
+
if (key === 'regard')
|
|
91
|
+
return 'regard';
|
|
86
92
|
if (key.startsWith('trait:'))
|
|
87
93
|
return 'trait';
|
|
88
94
|
if (key.startsWith('tie:'))
|
|
@@ -103,7 +109,7 @@ function priorOf(key) {
|
|
|
103
109
|
return P[kind];
|
|
104
110
|
}
|
|
105
111
|
function rangeOf(key) {
|
|
106
|
-
return key === 'mood' || cueKind(key) === 'tie' ? [-1, 1] : [0, 1];
|
|
112
|
+
return key === 'mood' || key === 'regard' || cueKind(key) === 'tie' ? [-1, 1] : [0, 1];
|
|
107
113
|
}
|
|
108
114
|
/** FNV-1a over a string, as a 0..1 fraction: deterministic observation error with no RNG drawn. */
|
|
109
115
|
function hash01(s) {
|
|
@@ -379,3 +385,34 @@ export function sanitizeImpressions(social) {
|
|
|
379
385
|
else
|
|
380
386
|
social.impressions = kept;
|
|
381
387
|
}
|
|
388
|
+
/**
|
|
389
|
+
* The person has their own evidence that `otherId` knows of their act: they saw them see it (`via: 'seen'`) or were
|
|
390
|
+
* told they heard (`via: 'told'`, weighted by trust in `sourceId`). Moves the believed regard cue. See the SCOPE.
|
|
391
|
+
*/
|
|
392
|
+
export function noteKnownBy(p, ev) {
|
|
393
|
+
if (ev.otherId === p.id)
|
|
394
|
+
return undefined;
|
|
395
|
+
const weight = ev.via === 'seen' ? 1 : ev.sourceId === undefined ? 0 : testimonyWeight(trustOf(p, ev.sourceId));
|
|
396
|
+
if (!(weight > 0))
|
|
397
|
+
return undefined;
|
|
398
|
+
const at = ev.at ?? p.now;
|
|
399
|
+
const defaults = {
|
|
400
|
+
'promise-broken': -0.6,
|
|
401
|
+
'promise-kept': 0.3,
|
|
402
|
+
harm: -0.6,
|
|
403
|
+
help: 0.4,
|
|
404
|
+
apology: 0.2,
|
|
405
|
+
};
|
|
406
|
+
const valence = ev.act.valence ?? (Object.hasOwn(defaults, ev.act.kind) ? (defaults[ev.act.kind] ?? 0) : 0);
|
|
407
|
+
const judgement = judge(p, { actorId: p.id, norms: ev.act.norms ?? [] }, p.conscience.norms, at).praiseworthiness;
|
|
408
|
+
const value = clamp(valence * (ev.act.targetId === ev.otherId ? 1 : 0.5) + 0.3 * judgement, -1, 1);
|
|
409
|
+
if (!Number.isFinite(value) || !Number.isFinite(at))
|
|
410
|
+
return undefined;
|
|
411
|
+
const imp = impressionFor(p, ev.otherId, true, at);
|
|
412
|
+
sample(imp, 'regard', value, weight * (value < 0 ? SOCIAL_DEFAULTS.negativityBias : 1), at);
|
|
413
|
+
return regardOf(p, ev.otherId, at);
|
|
414
|
+
}
|
|
415
|
+
/** How `p` believes `otherId` regards them; the neutral prior has zero confidence. Pure and RNG-free. */
|
|
416
|
+
export function regardOf(p, otherId, now) {
|
|
417
|
+
return estimate(p, otherId, 'regard', now);
|
|
418
|
+
}
|
package/dist/social/social.d.ts
CHANGED
package/dist/social/social.js
CHANGED
|
@@ -6,14 +6,16 @@
|
|
|
6
6
|
* helps), reciprocity as a favour ledger (Gouldner 1960), the mere-exposure / familiarity growth with
|
|
7
7
|
* diminishing returns, HEXACO agreeableness as tolerance of provocation and honesty-humility as sensitivity
|
|
8
8
|
* to deceit (Ashton & Lee), Schwartz benevolence as care for close others. Observers judge only the outward
|
|
9
|
-
* act and never see intentions.
|
|
10
|
-
*
|
|
9
|
+
* act and never see intentions. An opt-in `regard` impression estimates the other's view of me and adds a bounded
|
|
10
|
+
* repair term; it never reads the other's actual view. It does NOT model attachment styles
|
|
11
|
+
* (gossip, reputation and bereavement are in the SCOPE paragraphs
|
|
11
12
|
* further down; insiders, outsiders and threats in `groups.ts`); the
|
|
12
13
|
* coefficients are engineering defaults, not calibrated estimates.
|
|
13
14
|
*/
|
|
14
15
|
import { clamp, clamp01, clampSigned, cmpStr, decay, dlog, expit, round } from '../core/index.js';
|
|
15
16
|
import { MINUTES_PER_DAY, MINUTES_PER_YEAR } from '../types.js';
|
|
16
17
|
import { careFor } from './groups.js';
|
|
18
|
+
import { regardOf } from './impressions.js';
|
|
17
19
|
const WEEK = 7 * MINUTES_PER_DAY;
|
|
18
20
|
export const SOCIAL_DEFAULTS = {
|
|
19
21
|
maxRelationships: 80,
|
|
@@ -50,6 +52,8 @@ export const SOCIAL_DEFAULTS = {
|
|
|
50
52
|
group: 0.3,
|
|
51
53
|
/** Group size (others) from which the extraversion term applies. */
|
|
52
54
|
groupFrom: 3,
|
|
55
|
+
regardRepair: 0.5,
|
|
56
|
+
regardRepairMax: 0.3,
|
|
53
57
|
},
|
|
54
58
|
judge: {
|
|
55
59
|
respect: 0.12,
|
|
@@ -328,6 +332,18 @@ export function socialTerms(p, aff, ctx) {
|
|
|
328
332
|
const size = clamp01((others - W.groupFrom + 1) / 6);
|
|
329
333
|
terms.push({ source: 'social:group', value: round(W.group * (p.traits.extraversion - 0.5) * 2 * size) });
|
|
330
334
|
}
|
|
335
|
+
if (aff.targetId !== undefined &&
|
|
336
|
+
aff.targetId !== p.id &&
|
|
337
|
+
(tags.includes('repair') || aff.action === 'apology' || aff.action === 'amends') &&
|
|
338
|
+
!isDeceased(p, aff.targetId)) {
|
|
339
|
+
const regard = regardOf(p, aff.targetId, p.now);
|
|
340
|
+
if (regard.value < 0 && regard.confidence > 0) {
|
|
341
|
+
terms.push({
|
|
342
|
+
source: `regard:${aff.targetId}`,
|
|
343
|
+
value: round(Math.min(W.regardRepairMax, W.regardRepair * -regard.value * regard.confidence * (0.5 + p.traits.agreeableness))),
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
331
347
|
return terms;
|
|
332
348
|
}
|
|
333
349
|
/** Moral weight of an observed act against one held standing, -1..1 before conviction. */
|
package/dist/types.d.ts
CHANGED
|
@@ -393,6 +393,49 @@ export interface Belief {
|
|
|
393
393
|
id: EntityId;
|
|
394
394
|
value: boolean;
|
|
395
395
|
}[];
|
|
396
|
+
/**
|
|
397
|
+
* Where this person first got the belief (2.10.0, `beliefs/`). Absent unless an incoming claim carried provenance
|
|
398
|
+
* (`PerceptClaim.origin` or `lineage`). First acquisition wins; never read by decisions.
|
|
399
|
+
*/
|
|
400
|
+
provenance?: BeliefProvenance;
|
|
401
|
+
}
|
|
402
|
+
/** Where a belief first came from, as far as the person knows (2.10.0). Owned by `beliefs/`. */
|
|
403
|
+
export interface BeliefProvenance {
|
|
404
|
+
/** Who first said it: the first teller of the chain as told, or this person when they saw it themselves. */
|
|
405
|
+
origin: EntityId;
|
|
406
|
+
/** Who this person first heard it from (equal to `origin` when told first-hand or observed). */
|
|
407
|
+
firstFrom: EntityId;
|
|
408
|
+
/** Minute the provenance was acquired. */
|
|
409
|
+
at: Minute;
|
|
410
|
+
/** Opaque host token (at most 64 characters), carried unchanged through relays; HF never parses it. */
|
|
411
|
+
lineage?: string;
|
|
412
|
+
}
|
|
413
|
+
/** One proposition claim carried by a percept. */
|
|
414
|
+
export interface PerceptClaim {
|
|
415
|
+
prop: string;
|
|
416
|
+
value: boolean;
|
|
417
|
+
confidence: Unit;
|
|
418
|
+
/** 2.10.0: who first said it, as the teller passes it on (provenance; never read by decisions). */
|
|
419
|
+
origin?: EntityId;
|
|
420
|
+
/** 2.10.0: opaque host lineage token (at most 64 characters), carried unchanged by `converse`. */
|
|
421
|
+
lineage?: string;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* A report that `sourceId` told the reporter (the percept's `actorId`) that `prop` is `value` (2.10.0). On an attended
|
|
425
|
+
* `told` percept it is checked against what `sourceId` told this person (`beliefs.contradicted`).
|
|
426
|
+
*/
|
|
427
|
+
export interface ReportedAccount {
|
|
428
|
+
sourceId: EntityId;
|
|
429
|
+
prop: string;
|
|
430
|
+
value: boolean;
|
|
431
|
+
}
|
|
432
|
+
/** A noted contradiction by one source on one proposition (2.10.0, `beliefs/`); kept so it counts once. */
|
|
433
|
+
export interface NotedContradiction {
|
|
434
|
+
sourceId: EntityId;
|
|
435
|
+
prop: string;
|
|
436
|
+
at: Minute;
|
|
437
|
+
/** Testimony weight already applied, 0..1; absent means 1 (heard both accounts directly). */
|
|
438
|
+
weight?: Unit;
|
|
396
439
|
}
|
|
397
440
|
/** Learned expectation of an action's actual result for this person (prediction-error learning). */
|
|
398
441
|
export interface ActionExpectation {
|
|
@@ -431,6 +474,8 @@ export interface MemoryState {
|
|
|
431
474
|
/** Lasting gists (1.8.0, opt-in via `enableGists`; absent = episodes are simply forgotten). Bounded. */
|
|
432
475
|
gists?: Gist[];
|
|
433
476
|
nextGist?: number;
|
|
477
|
+
/** Contradictions already noted (2.10.0, `beliefs.contradicted`). Absent until one is noted. Bounded. */
|
|
478
|
+
contradictions?: NotedContradiction[];
|
|
434
479
|
}
|
|
435
480
|
export interface Relationship {
|
|
436
481
|
otherId: PersonId;
|
|
@@ -840,7 +885,7 @@ export declare const PERSON_SCHEMA = "human/person@1";
|
|
|
840
885
|
* and an activity that would cover a closing stretch is reviewed when the stretch begins (`isUnderWay`,
|
|
841
886
|
* `dutyReviewAt`). Person shape unchanged.
|
|
842
887
|
*/
|
|
843
|
-
export declare const ENGINE_VERSION = "2.
|
|
888
|
+
export declare const ENGINE_VERSION = "2.10.0";
|
|
844
889
|
/** A pregnancy carried by this person (host-driven; see `family/`). */
|
|
845
890
|
export interface Pregnancy {
|
|
846
891
|
fatherId: PersonId;
|
|
@@ -1157,11 +1202,7 @@ export interface Percept {
|
|
|
1157
1202
|
/** Base salience before attention, 0..1. */
|
|
1158
1203
|
salience: Unit;
|
|
1159
1204
|
/** Proposition claims carried by the percept (e.g. a rumour). */
|
|
1160
|
-
claims?:
|
|
1161
|
-
prop: string;
|
|
1162
|
-
value: boolean;
|
|
1163
|
-
confidence: Unit;
|
|
1164
|
-
}[];
|
|
1205
|
+
claims?: PerceptClaim[];
|
|
1165
1206
|
/** Norms the observed act honoured or violated (for judging others). */
|
|
1166
1207
|
norms?: NormTag[];
|
|
1167
1208
|
/**
|
|
@@ -1206,6 +1247,14 @@ export interface Percept {
|
|
|
1206
1247
|
id: string;
|
|
1207
1248
|
revision: number;
|
|
1208
1249
|
}[];
|
|
1250
|
+
/**
|
|
1251
|
+
* 2.10.0: on a percept of my own act (`actorId` is the perceiver), the people I saw see it. Each one's believed
|
|
1252
|
+
* regard of me moves (`social/impressions.ts`, `noteKnownBy`). Someone present but not listed changes nothing.
|
|
1253
|
+
* The first 8 are read.
|
|
1254
|
+
*/
|
|
1255
|
+
witnesses?: readonly PersonId[];
|
|
1256
|
+
/** 2.10.0: on a `told` percept, accounts the teller says another source gave them (at most four). */
|
|
1257
|
+
reports?: readonly ReportedAccount[];
|
|
1209
1258
|
}
|
|
1210
1259
|
/**
|
|
1211
1260
|
* Domains with distinct age curves for learning rate (`lifecourse.learningMultiplier`). 'general' is
|
package/dist/types.js
CHANGED
|
@@ -120,4 +120,4 @@ export const PERSON_SCHEMA = 'human/person@1';
|
|
|
120
120
|
* and an activity that would cover a closing stretch is reviewed when the stretch begins (`isUnderWay`,
|
|
121
121
|
* `dutyReviewAt`). Person shape unchanged.
|
|
122
122
|
*/
|
|
123
|
-
export const ENGINE_VERSION = '2.
|
|
123
|
+
export const ENGINE_VERSION = '2.10.0';
|