@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/person.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SCOPE: the composite. `createPerson` wires every faculty's constructor; `tick` advances all faculties in
|
|
3
3
|
* closed form from `p.now` to a later minute under the current activity's body load (idle awake otherwise),
|
|
4
|
-
* splitting at the activity's end; `perceive` runs attention, then beliefs,
|
|
5
|
-
* others' acts, memory and appraisal; `decide` scores offers and resolves the
|
|
6
|
-
* suggestion immediately); `begin` and `finish` bracket an activity and route the
|
|
7
|
-
* skills, habits, memory, conscience, agenda, relationships and trust in advising
|
|
8
|
-
* `command`, `releaseCommand` and `previewCommand`; `knockDown` and `standUp`
|
|
9
|
-
* downed state; `observeSkill` is observational learning; `setRetention` sets the per-person retention
|
|
4
|
+
* splitting at the activity's end and the absolute hourly grid; `perceive` runs attention, then beliefs,
|
|
5
|
+
* relationships, judgement of others' acts, memory and appraisal; `decide` scores offers and resolves the
|
|
6
|
+
* will (`answerNow` answers a suggestion immediately); `begin` and `finish` bracket an activity and route the
|
|
7
|
+
* outcome into body, needs, skills, habits, memory, conscience, agenda, relationships and trust in advising
|
|
8
|
+
* voices. Direct control is `command`, `releaseCommand` and `previewCommand`; `knockDown` and `standUp`
|
|
9
|
+
* move a person in and out of the downed state; `observeSkill` is observational learning; `setRetention` sets the per-person retention
|
|
10
10
|
* bounds; `readPerson` is the read-only summary. Snapshot and restore live in `restore.ts`. This file owns
|
|
11
11
|
* `now`, `activity`, `trace`, `nextDecision` and `retention`; every other slice is written only through its
|
|
12
12
|
* owning module. It sequences the modules and makes no behavioural claims of its own beyond the engineering
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { actionTendencies, advanceAffect, appraise, CRISIS_DEFAULTS, checkCrisis, createAffect, easeBreak, emotionLevel, feel, readAffect, regulate, release, skipAffect, skipCrisis, strain, tendencyEmotions, } from './affect/index.js';
|
|
23
23
|
import { advanceAgenda, createAgenda, onFinished, promise, proposeGoals, revisePurposes, } from './agenda/index.js';
|
|
24
|
-
import { advanceBeliefs, attend, believe, confirm, credence } from './beliefs/index.js';
|
|
24
|
+
import { advanceBeliefs, applyContradiction, attend, believe, confirm, credence, noteProvenance, trustOf, } from './beliefs/index.js';
|
|
25
25
|
import { advanceBody, BODY_DEFAULTS, checkDowned, clearDowned, consume, createBody, downedAllows, enableFatigueStore, enableFullness, expose, injure, nextBodyThreshold, readBody, setDowned, sicken, skipBody, } from './body/index.js';
|
|
26
26
|
import { ageCharacter, enableCharacterChange, noteCharacterDay, noteCharacterSocial, } from './character/index.js';
|
|
27
27
|
import { closeDay, enableYearbook, endDay, noteAnswer, noteCommitments, noteDecision, noteMood, noteOutcome, trimChronicle, } from './chronicle/index.js';
|
|
@@ -40,7 +40,7 @@ import { enablePromptings, nafsPatterns, notePromptings } from './promptings/ind
|
|
|
40
40
|
import { bodyForecastFor, consumeConsequences, forecastLine, forecastThresholds, pruneConsequences, receiveConsequences, } from './prospection/index.js';
|
|
41
41
|
import { enableReview, reviewIfDue } from './review/index.js';
|
|
42
42
|
import { enableSkillRetention, learnByWatching, practise, seedSkills, } from './skills/index.js';
|
|
43
|
-
import { advanceSocial, applyReputationBelief, careFor, closeness, judge, markDeceased, relationshipWith, seedRelationships, socialEvent, threatAppraisal, } from './social/index.js';
|
|
43
|
+
import { advanceSocial, applyReputationBelief, careFor, closeness, judge, markDeceased, noteKnownBy, relationshipWith, seedRelationships, socialEvent, threatAppraisal, } from './social/index.js';
|
|
44
44
|
import { ENGINE_VERSION, MAX_MINUTE, PERSON_SCHEMA, PHYSIOLOGICAL_NEEDS, PSYCHOLOGICAL_NEEDS, TRAIT_KEYS, VALUE_KEYS, } from './types.js';
|
|
45
45
|
import { advanceWill, answerSuggestion, chargeCommand, commandTargets, createWill, creditedVoices, dischargeAdvice, dutyReviewAt, endCommand, learnFromVoice, noteCommandOutcome, predictResponse, rememberAdvice, resolveCommand, takeCommand, voicesIn, WILL_DEFAULTS, wakeReviewAt, } from './will/index.js';
|
|
46
46
|
export const PERSON_DEFAULTS = {
|
|
@@ -571,9 +571,36 @@ function perceiveOne(p, pc) {
|
|
|
571
571
|
confirm(p, claim.prop, claim.value, at);
|
|
572
572
|
else
|
|
573
573
|
believe(p, claim.prop, claim.value, claim.confidence, direct ? p.id : (actor ?? 'rumour'), at);
|
|
574
|
+
// Provenance (2.10.0): recorded only when the claim carries it; first acquisition wins.
|
|
575
|
+
if (claim.origin !== undefined || claim.lineage !== undefined)
|
|
576
|
+
noteProvenance(p, claim, direct ? p.id : (actor ?? 'rumour'), at);
|
|
574
577
|
// Hearsay about someone's character moves my relationship with them (reputation).
|
|
575
578
|
applyReputationBelief(p, claim.prop, before, credence(p, claim.prop), at);
|
|
576
579
|
}
|
|
580
|
+
// Another source's account, reported by the teller (2.10.0): one source telling two people opposite things.
|
|
581
|
+
if (pc.channel === 'told' && actor !== undefined && Array.isArray(pc.reports)) {
|
|
582
|
+
// One trust basis for the whole percept: a report that lowers the reporter's own trust must not reweigh the rest.
|
|
583
|
+
const reporterTrust = trustOf(p, actor);
|
|
584
|
+
for (const r of pc.reports.slice(0, 4))
|
|
585
|
+
applyContradiction(p, { ...r, reporterId: actor, at }, reporterTrust);
|
|
586
|
+
}
|
|
587
|
+
// My own act, seen by others (2.10.0): what I believe they now think of me.
|
|
588
|
+
if (byMe && Array.isArray(pc.witnesses)) {
|
|
589
|
+
for (const w of pc.witnesses.slice(0, 8)) {
|
|
590
|
+
if (typeof w !== 'string')
|
|
591
|
+
continue;
|
|
592
|
+
if (w === p.id)
|
|
593
|
+
continue;
|
|
594
|
+
const act = { kind: pc.kind };
|
|
595
|
+
if (target !== undefined)
|
|
596
|
+
act.targetId = target;
|
|
597
|
+
if (pc.valence !== undefined)
|
|
598
|
+
act.valence = pc.valence;
|
|
599
|
+
if (pc.norms)
|
|
600
|
+
act.norms = pc.norms;
|
|
601
|
+
noteKnownBy(p, { otherId: w, act, at, via: 'seen' });
|
|
602
|
+
}
|
|
603
|
+
}
|
|
577
604
|
// Advice carried by testimony keeps pulling after the speaker falls silent (standing advice, N9).
|
|
578
605
|
if (pc.channel === 'told')
|
|
579
606
|
rememberAdvice(p, pc);
|
|
@@ -1136,6 +1163,115 @@ export function learningFor(p, domain) {
|
|
|
1136
1163
|
export function observeSkill(p, skill, minutes, modelLevel, domain) {
|
|
1137
1164
|
return learnByWatching(p, skill, minutes, modelLevel, learningFor(p, domain), p.now);
|
|
1138
1165
|
}
|
|
1166
|
+
/** Realized need changes, using readings captured before and after the outcome's deltas. */
|
|
1167
|
+
function realizedNeedDeltas(act, given, before, after) {
|
|
1168
|
+
const aff = act.affordance;
|
|
1169
|
+
const realized = {};
|
|
1170
|
+
for (const id of [...PHYSIOLOGICAL_NEEDS, ...PSYCHOLOGICAL_NEEDS]) {
|
|
1171
|
+
if (given[id] === undefined)
|
|
1172
|
+
continue;
|
|
1173
|
+
realized[id] =
|
|
1174
|
+
id === 'food' || id === 'water' || id === 'relief'
|
|
1175
|
+
? levelOf(after, id) - levelOf(before, id)
|
|
1176
|
+
: given[id];
|
|
1177
|
+
}
|
|
1178
|
+
for (const id of ['sleep', 'rest']) {
|
|
1179
|
+
if (aff.advertises[id] !== undefined && given[id] === undefined) {
|
|
1180
|
+
realized[id] = levelOf(after, id) - (act.needsAtStart[id] ?? levelOf(before, id));
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
return realized;
|
|
1184
|
+
}
|
|
1185
|
+
/** Record the deed and apply its appraisal, repentance, repair and regulation. */
|
|
1186
|
+
function respondToConscience(p, act, now, completed, minutes) {
|
|
1187
|
+
const aff = act.affordance;
|
|
1188
|
+
const deed = recordDeed(p, aff, act.intention, now, completed, act.necessity ? { necessity: true } : {});
|
|
1189
|
+
for (const ev of deed.appraisal)
|
|
1190
|
+
appraise(p, ev);
|
|
1191
|
+
const tags = aff.tags ?? [];
|
|
1192
|
+
if (completed && (tags.includes('worship') || tags.includes('repent'))) {
|
|
1193
|
+
// Worship closes breaches against God alone; guilt toward a wronged person waits for repair.
|
|
1194
|
+
const repented = new Set();
|
|
1195
|
+
for (const b of p.conscience.breaches) {
|
|
1196
|
+
if (!b.repaired && b.victimId === undefined && repent(p, b.id, now))
|
|
1197
|
+
repented.add(b.normId);
|
|
1198
|
+
}
|
|
1199
|
+
for (const b of p.conscience.breaches)
|
|
1200
|
+
if (!b.repaired && b.victimId !== undefined)
|
|
1201
|
+
repented.delete(b.normId);
|
|
1202
|
+
for (const normId of [...repented].sort())
|
|
1203
|
+
release(p, { id: 'guilt', causePrefix: `breach:${normId}` }, 0.6);
|
|
1204
|
+
regulate(p, minutes, 'worship');
|
|
1205
|
+
}
|
|
1206
|
+
if (completed && (tags.includes('repair') || tags.includes('apologize')) && aff.targetId !== undefined) {
|
|
1207
|
+
recordRepair(p, aff.targetId, now);
|
|
1208
|
+
release(p, { id: 'guilt', targetId: aff.targetId });
|
|
1209
|
+
socialEvent(p, { at: now, kind: 'apology', otherId: aff.targetId, byMe: true, magnitude: 0.7 });
|
|
1210
|
+
}
|
|
1211
|
+
if (act.mode === 'sleep' || tags.includes('rest') || tags.includes('reflection')) {
|
|
1212
|
+
regulate(p, minutes, tags.includes('reflection') ? 'reflection' : 'rest');
|
|
1213
|
+
}
|
|
1214
|
+
return { deed, tags };
|
|
1215
|
+
}
|
|
1216
|
+
/** Record relationships and character experience from a completed activity. */
|
|
1217
|
+
function recordParticipation(p, aff, tags, now, minutes) {
|
|
1218
|
+
const D = PERSON_DEFAULTS;
|
|
1219
|
+
if (p.character && (aff.with ?? []).some((o) => o !== p.id))
|
|
1220
|
+
noteCharacterSocial(p, minutes);
|
|
1221
|
+
const kind = tags.includes('work') ? 'shared-work' : 'chat';
|
|
1222
|
+
for (const other of aff.with ?? []) {
|
|
1223
|
+
if (other === p.id)
|
|
1224
|
+
continue;
|
|
1225
|
+
socialEvent(p, { at: now, kind, otherId: other, byMe: true, magnitude: D.participationMagnitude });
|
|
1226
|
+
}
|
|
1227
|
+
if (aff.targetId !== undefined && aff.targetId !== p.id) {
|
|
1228
|
+
const care = tags.includes('help') ? 'help' : tags.includes('gift') ? 'gift' : undefined;
|
|
1229
|
+
if (care)
|
|
1230
|
+
socialEvent(p, { at: now, kind: care, otherId: aff.targetId, byMe: true, magnitude: 0.7 });
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
/** Learn from each advising voice after the activity's conscience and agenda results are known. */
|
|
1234
|
+
function learnActivityVoices(p, act, after, now, felt, broken, breached) {
|
|
1235
|
+
const aff = act.affordance;
|
|
1236
|
+
const D = PERSON_DEFAULTS;
|
|
1237
|
+
// Trust in every voice that pushed this activity: how it felt, minus bodily harm the host's deltas do not
|
|
1238
|
+
// show (needs that fell during the activity and are now urgent).
|
|
1239
|
+
const voices = act.suggestions ?? (act.suggestion ? [act.suggestion] : []);
|
|
1240
|
+
if (voices.length > 0) {
|
|
1241
|
+
let harm = 0;
|
|
1242
|
+
for (const id of ['food', 'water', 'sleep', 'rest']) {
|
|
1243
|
+
if (urgencyOf(after, id) < D.voiceHarmUrgency)
|
|
1244
|
+
continue;
|
|
1245
|
+
harm += Math.max(0, (act.needsAtStart[id] ?? levelOf(after, id)) - levelOf(after, id));
|
|
1246
|
+
}
|
|
1247
|
+
// Doing what a voice pushed broke a commitment or the person's own standards: that costs the voice as harm
|
|
1248
|
+
// does (review 2026-10-03: thirty broken fasts under insistence had moved trust only 0.50 -> 0.41). Omissions
|
|
1249
|
+
// the push caused later (a missed prayer during a pushed activity) are not attributed here.
|
|
1250
|
+
let breach = 0;
|
|
1251
|
+
for (const c of broken)
|
|
1252
|
+
breach += D.missedDistress * c.importance;
|
|
1253
|
+
breach += D.missedDistress * 0.5 * breached;
|
|
1254
|
+
for (const res of voices) {
|
|
1255
|
+
const protest = res.verdict === 'complied' || res.verdict === 'commanded';
|
|
1256
|
+
const reason = breach > 0
|
|
1257
|
+
? protest
|
|
1258
|
+
? 'breach-under-protest'
|
|
1259
|
+
: 'breach'
|
|
1260
|
+
: harm > 0
|
|
1261
|
+
? protest
|
|
1262
|
+
? 'harm-under-protest'
|
|
1263
|
+
: 'harm'
|
|
1264
|
+
: felt >= 0
|
|
1265
|
+
? 'went-well'
|
|
1266
|
+
: 'went-badly';
|
|
1267
|
+
learnFromVoice(p, res, clampSigned(felt - D.voiceHarmGain * harm - breach), {
|
|
1268
|
+
at: now,
|
|
1269
|
+
action: aff.action,
|
|
1270
|
+
reason,
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1139
1275
|
/**
|
|
1140
1276
|
* Apply what happened. Physiological deltas go to the body, psychological ones to the needs, then skills,
|
|
1141
1277
|
* habits, memory (expectation learning and an episode), conscience (deed, breaches, repair), agenda,
|
|
@@ -1179,20 +1315,7 @@ export function finish(p, outcome, opts = {}) {
|
|
|
1179
1315
|
// Realized deltas: what the body actually took in for food/water/relief (a drink at full hydration
|
|
1180
1316
|
// realizes nothing), host values for psychological needs, and measured since the start for sleep/rest.
|
|
1181
1317
|
const after = readNeeds(p, readBodyOf(p));
|
|
1182
|
-
const realized =
|
|
1183
|
-
for (const id of [...PHYSIOLOGICAL_NEEDS, ...PSYCHOLOGICAL_NEEDS]) {
|
|
1184
|
-
if (given[id] === undefined)
|
|
1185
|
-
continue;
|
|
1186
|
-
realized[id] =
|
|
1187
|
-
id === 'food' || id === 'water' || id === 'relief'
|
|
1188
|
-
? levelOf(after, id) - levelOf(before, id)
|
|
1189
|
-
: given[id];
|
|
1190
|
-
}
|
|
1191
|
-
for (const id of ['sleep', 'rest']) {
|
|
1192
|
-
if (aff.advertises[id] !== undefined && given[id] === undefined) {
|
|
1193
|
-
realized[id] = levelOf(after, id) - (act.needsAtStart[id] ?? levelOf(before, id));
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1318
|
+
const realized = realizedNeedDeltas(act, given, before, after);
|
|
1196
1319
|
// Felt valence.
|
|
1197
1320
|
let felt = 0;
|
|
1198
1321
|
for (const id of Object.keys(realized)) {
|
|
@@ -1255,32 +1378,7 @@ export function finish(p, outcome, opts = {}) {
|
|
|
1255
1378
|
episode.voiceId = act.suggestion.voiceId;
|
|
1256
1379
|
remember(p, episode);
|
|
1257
1380
|
// Conscience.
|
|
1258
|
-
const deed =
|
|
1259
|
-
for (const ev of deed.appraisal)
|
|
1260
|
-
appraise(p, ev);
|
|
1261
|
-
const tags = aff.tags ?? [];
|
|
1262
|
-
if (completed && (tags.includes('worship') || tags.includes('repent'))) {
|
|
1263
|
-
// Worship closes breaches against God alone; guilt toward a wronged person waits for repair.
|
|
1264
|
-
const repented = new Set();
|
|
1265
|
-
for (const b of p.conscience.breaches) {
|
|
1266
|
-
if (!b.repaired && b.victimId === undefined && repent(p, b.id, now))
|
|
1267
|
-
repented.add(b.normId);
|
|
1268
|
-
}
|
|
1269
|
-
for (const b of p.conscience.breaches)
|
|
1270
|
-
if (!b.repaired && b.victimId !== undefined)
|
|
1271
|
-
repented.delete(b.normId);
|
|
1272
|
-
for (const normId of [...repented].sort())
|
|
1273
|
-
release(p, { id: 'guilt', causePrefix: `breach:${normId}` }, 0.6);
|
|
1274
|
-
regulate(p, minutes, 'worship');
|
|
1275
|
-
}
|
|
1276
|
-
if (completed && (tags.includes('repair') || tags.includes('apologize')) && aff.targetId !== undefined) {
|
|
1277
|
-
recordRepair(p, aff.targetId, now);
|
|
1278
|
-
release(p, { id: 'guilt', targetId: aff.targetId });
|
|
1279
|
-
socialEvent(p, { at: now, kind: 'apology', otherId: aff.targetId, byMe: true, magnitude: 0.7 });
|
|
1280
|
-
}
|
|
1281
|
-
if (act.mode === 'sleep' || tags.includes('rest') || tags.includes('reflection')) {
|
|
1282
|
-
regulate(p, minutes, tags.includes('reflection') ? 'reflection' : 'rest');
|
|
1283
|
-
}
|
|
1381
|
+
const { deed, tags } = respondToConscience(p, act, now, completed, minutes);
|
|
1284
1382
|
// Agenda.
|
|
1285
1383
|
const routed = { ...outcome };
|
|
1286
1384
|
if (routed.fulfills === undefined && aff.fulfills !== undefined)
|
|
@@ -1303,58 +1401,10 @@ export function finish(p, outcome, opts = {}) {
|
|
|
1303
1401
|
}
|
|
1304
1402
|
}
|
|
1305
1403
|
// Relationships from taking part together.
|
|
1306
|
-
if (completed)
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
for (const other of aff.with ?? []) {
|
|
1311
|
-
if (other === p.id)
|
|
1312
|
-
continue;
|
|
1313
|
-
socialEvent(p, { at: now, kind, otherId: other, byMe: true, magnitude: D.participationMagnitude });
|
|
1314
|
-
}
|
|
1315
|
-
if (aff.targetId !== undefined && aff.targetId !== p.id) {
|
|
1316
|
-
const care = tags.includes('help') ? 'help' : tags.includes('gift') ? 'gift' : undefined;
|
|
1317
|
-
if (care)
|
|
1318
|
-
socialEvent(p, { at: now, kind: care, otherId: aff.targetId, byMe: true, magnitude: 0.7 });
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
// Trust in every voice that pushed this activity: how it felt, minus bodily harm the host's deltas do not
|
|
1322
|
-
// show (needs that fell during the activity and are now urgent).
|
|
1323
|
-
const voices = act.suggestions ?? (act.suggestion ? [act.suggestion] : []);
|
|
1324
|
-
if (voices.length > 0) {
|
|
1325
|
-
let harm = 0;
|
|
1326
|
-
for (const id of ['food', 'water', 'sleep', 'rest']) {
|
|
1327
|
-
if (urgencyOf(after, id) < D.voiceHarmUrgency)
|
|
1328
|
-
continue;
|
|
1329
|
-
harm += Math.max(0, (act.needsAtStart[id] ?? levelOf(after, id)) - levelOf(after, id));
|
|
1330
|
-
}
|
|
1331
|
-
// Doing what a voice pushed broke a commitment or the person's own standards: that costs the voice as harm
|
|
1332
|
-
// does (review 2026-10-03: thirty broken fasts under insistence had moved trust only 0.50 -> 0.41). Omissions
|
|
1333
|
-
// the push caused later (a missed prayer during a pushed activity) are not attributed here.
|
|
1334
|
-
let breach = 0;
|
|
1335
|
-
for (const c of broken)
|
|
1336
|
-
breach += D.missedDistress * c.importance;
|
|
1337
|
-
breach += D.missedDistress * 0.5 * deed.breached.length;
|
|
1338
|
-
for (const res of voices) {
|
|
1339
|
-
const protest = res.verdict === 'complied' || res.verdict === 'commanded';
|
|
1340
|
-
const reason = breach > 0
|
|
1341
|
-
? protest
|
|
1342
|
-
? 'breach-under-protest'
|
|
1343
|
-
: 'breach'
|
|
1344
|
-
: harm > 0
|
|
1345
|
-
? protest
|
|
1346
|
-
? 'harm-under-protest'
|
|
1347
|
-
: 'harm'
|
|
1348
|
-
: felt >= 0
|
|
1349
|
-
? 'went-well'
|
|
1350
|
-
: 'went-badly';
|
|
1351
|
-
learnFromVoice(p, res, clampSigned(felt - D.voiceHarmGain * harm - breach), {
|
|
1352
|
-
at: now,
|
|
1353
|
-
action: aff.action,
|
|
1354
|
-
reason,
|
|
1355
|
-
});
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1404
|
+
if (completed)
|
|
1405
|
+
recordParticipation(p, aff, tags, now, minutes);
|
|
1406
|
+
// Voice learning follows both conscience and agenda, so it includes their breaches.
|
|
1407
|
+
learnActivityVoices(p, act, after, now, felt, broken, deed.breached.length);
|
|
1358
1408
|
// Chronicle, while the activity is still current.
|
|
1359
1409
|
const noted = { kept: kept.map((c) => c.id) };
|
|
1360
1410
|
if (act.suggestion)
|
|
@@ -260,9 +260,15 @@ export function sanitizeProspection(input, now, personId) {
|
|
|
260
260
|
!minute(r.receivedAt) ||
|
|
261
261
|
r.receivedAt > now)
|
|
262
262
|
continue;
|
|
263
|
-
|
|
263
|
+
const validated = {
|
|
264
|
+
sourceId: r.sourceId,
|
|
265
|
+
id: r.id,
|
|
266
|
+
revision: r.revision,
|
|
267
|
+
receivedAt: r.receivedAt,
|
|
268
|
+
};
|
|
269
|
+
if (revisions.some((old) => identity(old, validated)))
|
|
264
270
|
return undefined;
|
|
265
|
-
revisions.push(
|
|
271
|
+
revisions.push(validated);
|
|
266
272
|
}
|
|
267
273
|
const valid = [];
|
|
268
274
|
for (const e of entries) {
|
package/dist/restore.d.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import type { Person } from './types.ts';
|
|
2
2
|
/** A deep copy of the person as plain JSON (what a host saves). */
|
|
3
3
|
export declare function snapshot(p: Person): Person;
|
|
4
|
+
/**
|
|
5
|
+
* A deterministic 16-hex-digit digest of the canonical saved person. Canonicalization runs the saved form
|
|
6
|
+
* through `restore` (including migrations, defaults, bounds and optional-slice sanitizers), then sorts every
|
|
7
|
+
* object's keys recursively; array order stays significant. Thus key insertion order and empty optional slices
|
|
8
|
+
* that `restore` drops do not change the digest. Includes the engine stamp. Reads only; never mutates `p`.
|
|
9
|
+
* Uses 64-bit FNV-1a over JSON's UTF-16 code units, with no platform or crypto dependency. This is a change
|
|
10
|
+
* detector, not a cryptographic integrity check; collisions are possible. Invalid saves throw as in `restore`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function fingerprint(p: Person): string;
|
|
4
13
|
/**
|
|
5
14
|
* Validate a saved person and fill defaults for missing or mistyped fields, slice by slice and field by
|
|
6
15
|
* field. A save from an earlier supported engine is upgraded by `migrate` first. Throws on a wrong schema, a
|
package/dist/restore.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { sanitizeCrisis } from './affect/index.js';
|
|
10
10
|
import { sanitizeAgenda } from './agenda/index.js';
|
|
11
|
+
import { sanitizeBeliefMeta } from './beliefs/index.js';
|
|
11
12
|
import { sanitizeBody, sanitizeInjuries } from './body/index.js';
|
|
12
13
|
import { sanitizeCharacter } from './character/index.js';
|
|
13
14
|
import { CHRONICLE_DEFAULTS, sanitizeYears } from './chronicle/index.js';
|
|
@@ -31,6 +32,26 @@ import { sanitizeWill } from './will/index.js';
|
|
|
31
32
|
export function snapshot(p) {
|
|
32
33
|
return structuredClone(p);
|
|
33
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* A deterministic 16-hex-digit digest of the canonical saved person. Canonicalization runs the saved form
|
|
37
|
+
* through `restore` (including migrations, defaults, bounds and optional-slice sanitizers), then sorts every
|
|
38
|
+
* object's keys recursively; array order stays significant. Thus key insertion order and empty optional slices
|
|
39
|
+
* that `restore` drops do not change the digest. Includes the engine stamp. Reads only; never mutates `p`.
|
|
40
|
+
* Uses 64-bit FNV-1a over JSON's UTF-16 code units, with no platform or crypto dependency. This is a change
|
|
41
|
+
* detector, not a cryptographic integrity check; collisions are possible. Invalid saves throw as in `restore`.
|
|
42
|
+
*/
|
|
43
|
+
export function fingerprint(p) {
|
|
44
|
+
const json = JSON.stringify(restore(snapshot(p)), (_key, value) => {
|
|
45
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
46
|
+
return value;
|
|
47
|
+
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)));
|
|
48
|
+
});
|
|
49
|
+
let hash = 0xcbf29ce484222325n;
|
|
50
|
+
for (let i = 0; i < json.length; i++) {
|
|
51
|
+
hash = BigInt.asUintN(64, (hash ^ BigInt(json.charCodeAt(i))) * 0x100000001b3n);
|
|
52
|
+
}
|
|
53
|
+
return hash.toString(16).padStart(16, '0');
|
|
54
|
+
}
|
|
34
55
|
// Unlike core `isObj`, arrays pass: a required slice saved as an array is filled from defaults, not refused.
|
|
35
56
|
const isObject = (x) => typeof x === 'object' && x !== null;
|
|
36
57
|
const isMinute = (x) => isNum(x) && Math.abs(x) <= MAX_MINUTE;
|
|
@@ -168,6 +189,7 @@ export function restore(input) {
|
|
|
168
189
|
sanitizeCrisis(out.affect);
|
|
169
190
|
sanitizeAgenda(out.agenda);
|
|
170
191
|
sanitizeGists(out.memory);
|
|
192
|
+
sanitizeBeliefMeta(out.memory);
|
|
171
193
|
// Optional top-level slices (1.8.0 onward), each checked by its owner.
|
|
172
194
|
keepValid(out, 'chronicleYears', sanitizeYears);
|
|
173
195
|
keepValid(out, 'character', sanitizeCharacter);
|
package/dist/sim/sim.d.ts
CHANGED
|
@@ -225,6 +225,7 @@ export declare function communityState(c: Community): CommunityState;
|
|
|
225
225
|
/**
|
|
226
226
|
* A community over `people`. With `prior` (from `communityState`, possibly through JSON), the host-side state is
|
|
227
227
|
* resumed from it, so a run restored from people + community + world state continues exactly as it would have.
|
|
228
|
+
* `prior` is copied: after stepping, write back `communityState(c)`; the old `prior` stays stale.
|
|
228
229
|
*/
|
|
229
230
|
export declare function createCommunity(people: Person[], prior?: CommunityState): Community;
|
|
230
231
|
/** Add a person to a community (a newborn, an arrival); keeps the id order the driver relies on. */
|
|
@@ -237,9 +238,13 @@ export declare function birth(c: Community, a: Person, b: Person, spec: ChildSpe
|
|
|
237
238
|
/**
|
|
238
239
|
* Tell `dead`'s death at `at` to everyone alive in the community who has a tie to them (1.8.0): a told `death`
|
|
239
240
|
* percept through `perceive`, so they grieve, the tie is kept and marked, and a spouse is widowed. Each listener's
|
|
240
|
-
*
|
|
241
|
+
* receipt time is at least their own `now`. `late: 'now'` explicitly permits listeners past `at`, as does the
|
|
242
|
+
* 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.
|
|
241
244
|
*/
|
|
242
|
-
export declare function tellDeath(c: Community, dead: Person, at: Minute
|
|
245
|
+
export declare function tellDeath(c: Community, dead: Person, at: Minute, opts?: {
|
|
246
|
+
late?: 'throw' | 'now';
|
|
247
|
+
}): void;
|
|
243
248
|
/** Next minute at which this person needs attention. */
|
|
244
249
|
export declare function nextEventAt(c: Community, p: Person): Minute;
|
|
245
250
|
/**
|