@adam0white/human-framework 2.19.0 → 2.21.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 +22 -1
- package/dist/body/bladder.d.ts +21 -0
- package/dist/body/bladder.js +64 -0
- package/dist/body/body.d.ts +10 -0
- package/dist/body/body.js +56 -3
- package/dist/body/doze.d.ts +28 -0
- package/dist/body/doze.js +114 -0
- package/dist/body/index.d.ts +3 -0
- package/dist/body/index.js +3 -0
- package/dist/body/quantity.d.ts +8 -0
- package/dist/body/quantity.js +27 -0
- package/dist/cognition/cognition.d.ts +37 -0
- package/dist/cognition/cognition.js +6 -2
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/keys.d.ts +6 -0
- package/dist/core/keys.js +8 -0
- package/dist/family/family.js +4 -4
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/memory/memory.d.ts +26 -0
- package/dist/memory/memory.js +5 -10
- package/dist/migrate.js +5 -0
- package/dist/narrate/lexicon.js +2 -0
- package/dist/narrate/plain.js +2 -0
- package/dist/observed-reputation.d.ts +4 -0
- package/dist/observed-reputation.js +16 -0
- package/dist/person.d.ts +5 -0
- package/dist/person.js +118 -7
- package/dist/persona.js +6 -4
- package/dist/qalb.d.ts +28 -0
- package/dist/qalb.js +67 -0
- package/dist/sim/sim.d.ts +1 -1
- package/dist/sim/sim.js +71 -3
- package/dist/social/social.js +12 -2
- package/dist/types.d.ts +28 -1
- package/dist/types.js +1 -1
- package/dist/will/will.d.ts +12 -0
- package/dist/will/will.js +21 -0
- package/package.json +1 -1
package/dist/sim/sim.js
CHANGED
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
*/
|
|
51
51
|
import { appraise } from '../affect/index.js';
|
|
52
52
|
import { matchesCommitment, spanMeetsWindow } from '../agenda/index.js';
|
|
53
|
-
import { BODY_DEFAULTS, contagionRoll, die, readBody, sicken } from '../body/index.js';
|
|
53
|
+
import { BODY_DEFAULTS, contagionRoll, die, nextDozeCheck, readBody, sicken } from '../body/index.js';
|
|
54
54
|
import { chronicleBetween } from '../chronicle/index.js';
|
|
55
55
|
import { recordDeed } from '../conscience/index.js';
|
|
56
56
|
import { converse } from '../conversation/index.js';
|
|
@@ -274,7 +274,7 @@ function awaitingJoint(c, id) {
|
|
|
274
274
|
return pendingProposals(c).some((x) => x.proposerId === id || x.accepted.includes(id));
|
|
275
275
|
}
|
|
276
276
|
/** Next minute at which this person needs attention. */
|
|
277
|
-
|
|
277
|
+
function ordinaryEventAt(c, p) {
|
|
278
278
|
if (!p.body.alive)
|
|
279
279
|
return Number.POSITIVE_INFINITY;
|
|
280
280
|
if (awaitingJoint(c, p.id))
|
|
@@ -284,6 +284,12 @@ export function nextEventAt(c, p) {
|
|
|
284
284
|
return Math.max(p.now, Math.min(act.endsAt, act.reviewAt));
|
|
285
285
|
return Math.max(p.now, c.idleUntil[p.id] ?? p.now);
|
|
286
286
|
}
|
|
287
|
+
/** Pure scheduling query, including passive bodily checkpoints during joint waiting. */
|
|
288
|
+
export function nextEventAt(c, p) {
|
|
289
|
+
if (p.body.doze?.since !== undefined && awaitingJoint(c, p.id))
|
|
290
|
+
return p.now;
|
|
291
|
+
return Math.min(ordinaryEventAt(c, p), nextDozeCheck(p));
|
|
292
|
+
}
|
|
287
293
|
/**
|
|
288
294
|
* How this person would answer `suggestion` now, without changing state (UI telegraph; wraps `predict`). Pass the
|
|
289
295
|
* world's `scarcityFor(p)` as `scarcity`, as the step does, or the telegraph can promise what the step refuses.
|
|
@@ -415,7 +421,6 @@ export function acceptJoint(c, world, proposal, partner, partnerRecord, writeLog
|
|
|
415
421
|
proposal.partnerRecords[partner.id] = partnerRecord;
|
|
416
422
|
if (proposal.accepted.length < proposal.partnerIds.length)
|
|
417
423
|
return [];
|
|
418
|
-
settle(c, proposal, 'accepted');
|
|
419
424
|
const proposer = personById(c, proposal.proposerId);
|
|
420
425
|
const participants = [];
|
|
421
426
|
if (proposer)
|
|
@@ -427,6 +432,19 @@ export function acceptJoint(c, world, proposal, partner, partnerRecord, writeLog
|
|
|
427
432
|
participants.push({ person: q, offer: mirrorFor(world, q, proposal), record: rec });
|
|
428
433
|
}
|
|
429
434
|
const at = Math.max(...participants.map((x) => x.person.now));
|
|
435
|
+
// Opted-in catch-up can physically stop a participant. Validate everyone before any begin.
|
|
436
|
+
if (participants.some(({ person }) => person.body.doze !== undefined)) {
|
|
437
|
+
for (const { person } of participants)
|
|
438
|
+
if (person.now < at)
|
|
439
|
+
tick(person, at);
|
|
440
|
+
if (participants.some(({ person }) => person.body.doze?.since !== undefined)) {
|
|
441
|
+
declineJoint(c, proposal);
|
|
442
|
+
for (const { person } of participants)
|
|
443
|
+
interruptPerson(c, person, at, person.activity?.interrupt?.reason ?? `declined:${proposal.id}`);
|
|
444
|
+
return [];
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
settle(c, proposal, 'accepted');
|
|
430
448
|
const begun = [];
|
|
431
449
|
for (const { person, offer, record } of participants) {
|
|
432
450
|
if (person.now < at)
|
|
@@ -770,6 +788,26 @@ export function stepCommunity(c, world, until, opts = {}) {
|
|
|
770
788
|
c.queued ??= {};
|
|
771
789
|
c.dayDone ??= {};
|
|
772
790
|
synchronizeCommands(c, opts, log);
|
|
791
|
+
// Cancellation releases clocks AND review deadlines. Keep pending causes, including simultaneous doze.
|
|
792
|
+
const cancelDozeWaiting = (p, t) => {
|
|
793
|
+
for (const x of pendingProposals(c)) {
|
|
794
|
+
if (x.proposerId !== p.id && !x.partnerIds.includes(p.id))
|
|
795
|
+
continue;
|
|
796
|
+
const waiters = [x.proposerId, ...x.accepted]
|
|
797
|
+
.map((id) => personById(c, id))
|
|
798
|
+
.filter((q) => q !== undefined);
|
|
799
|
+
for (const q of waiters)
|
|
800
|
+
if (q.now < t)
|
|
801
|
+
tick(q, t);
|
|
802
|
+
const reasons = new Map(waiters.map((q) => [q.id, q.activity?.interrupt?.reason ?? c.interrupts[q.id]?.reason]));
|
|
803
|
+
if (x.proposerId === p.id)
|
|
804
|
+
settle(c, x, 'declined');
|
|
805
|
+
else
|
|
806
|
+
declineJoint(c, x);
|
|
807
|
+
for (const q of waiters)
|
|
808
|
+
interruptPerson(c, q, t, reasons.get(q.id) ?? `declined:${x.id}`);
|
|
809
|
+
}
|
|
810
|
+
};
|
|
773
811
|
// Pairs whose shared activity already ran its conversation and contagion at `pairMinute` (both partners
|
|
774
812
|
// finishing the same joint activity at the same minute would otherwise run them twice).
|
|
775
813
|
let pairMinute = Number.NEGATIVE_INFINITY;
|
|
@@ -787,7 +825,32 @@ export function stepCommunity(c, world, until, opts = {}) {
|
|
|
787
825
|
if (!next || t > until)
|
|
788
826
|
break;
|
|
789
827
|
const p = next;
|
|
828
|
+
const ordinaryDue = ordinaryEventAt(c, p) <= t;
|
|
790
829
|
tick(p, t);
|
|
830
|
+
const dozed = p.activity?.dozedAt !== undefined || p.body.doze?.since !== undefined;
|
|
831
|
+
if (dozed) {
|
|
832
|
+
cancelDozeWaiting(p, t);
|
|
833
|
+
const act = p.activity;
|
|
834
|
+
if (act?.dozedAt !== undefined) {
|
|
835
|
+
if (act.interrupt)
|
|
836
|
+
c.interrupts[p.id] ??= { ...act.interrupt };
|
|
837
|
+
const outcome = world.resolve(p, act, 'interrupted');
|
|
838
|
+
finish(p, outcome, finishOpts(world));
|
|
839
|
+
log({
|
|
840
|
+
at: t,
|
|
841
|
+
personId: p.id,
|
|
842
|
+
kind: 'finish',
|
|
843
|
+
detail: `${act.action} interrupted by doze`,
|
|
844
|
+
action: act.action,
|
|
845
|
+
status: outcome.status,
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
c.idleUntil[p.id] = t + quantum;
|
|
849
|
+
if (!ordinaryDue)
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
if (!ordinaryDue && p.body.alive)
|
|
853
|
+
continue;
|
|
791
854
|
bookkeepDay(p, world, t, c.dayDone, opts);
|
|
792
855
|
if (!p.body.alive) {
|
|
793
856
|
log({ at: t, personId: p.id, kind: 'died', detail: `${p.name} died` });
|
|
@@ -821,6 +884,11 @@ export function stepCommunity(c, world, until, opts = {}) {
|
|
|
821
884
|
}
|
|
822
885
|
}
|
|
823
886
|
}
|
|
887
|
+
if (p.body.doze?.since !== undefined) {
|
|
888
|
+
cancelDozeWaiting(p, t);
|
|
889
|
+
c.idleUntil[p.id] = t + quantum;
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
824
892
|
let act = p.activity;
|
|
825
893
|
// An interrupt on an activity that ends at this event still reaches the next decision.
|
|
826
894
|
perceptReason ??= act?.interrupt?.reason;
|
package/dist/social/social.js
CHANGED
|
@@ -471,6 +471,16 @@ export const REPUTATION_DEFAULTS = {
|
|
|
471
471
|
/** First-hand familiarity damps hearsay: scale = 1 - familiarityDamping × familiarity. */
|
|
472
472
|
familiarityDamping: 0.5,
|
|
473
473
|
};
|
|
474
|
+
/** Only authored, finite effects count; prototype properties are not character traits. */
|
|
475
|
+
function reputationEffect(trait, table) {
|
|
476
|
+
const effect = Object.hasOwn(table, trait) ? table[trait] : undefined;
|
|
477
|
+
return effect &&
|
|
478
|
+
Number.isFinite(effect.affection) &&
|
|
479
|
+
Number.isFinite(effect.trust) &&
|
|
480
|
+
Number.isFinite(effect.respect)
|
|
481
|
+
? effect
|
|
482
|
+
: undefined;
|
|
483
|
+
}
|
|
474
484
|
/** Splits a character proposition '<personId>:<trait>[:...]' into its subject and trait, if it is one. */
|
|
475
485
|
export function parseTraitProp(prop, table = REPUTATION_TRAITS) {
|
|
476
486
|
const parts = prop.split(':');
|
|
@@ -478,7 +488,7 @@ export function parseTraitProp(prop, table = REPUTATION_TRAITS) {
|
|
|
478
488
|
return undefined;
|
|
479
489
|
const personId = parts[0];
|
|
480
490
|
const trait = parts[1];
|
|
481
|
-
if (personId === '' ||
|
|
491
|
+
if (personId === '' || reputationEffect(trait, table) === undefined)
|
|
482
492
|
return undefined;
|
|
483
493
|
return { personId, trait };
|
|
484
494
|
}
|
|
@@ -509,7 +519,7 @@ export function applyReputationBelief(p, prop, before, after, now, table = REPUT
|
|
|
509
519
|
}
|
|
510
520
|
/** Whether a character claim about a person is unfavourable to them (for backbiting tags). */
|
|
511
521
|
export function isUnfavourableTrait(trait, value, table = REPUTATION_TRAITS) {
|
|
512
|
-
const e = table
|
|
522
|
+
const e = reputationEffect(trait, table);
|
|
513
523
|
if (!e)
|
|
514
524
|
return false;
|
|
515
525
|
const sum = e.affection + e.trust + e.respect;
|
package/dist/types.d.ts
CHANGED
|
@@ -65,7 +65,22 @@ export interface Illness {
|
|
|
65
65
|
/** Exposure kinds (keys of `BodyState.exposures`) whose recent load raises this chronic condition. */
|
|
66
66
|
aggravatedBy?: string[];
|
|
67
67
|
}
|
|
68
|
+
export interface DozeContext {
|
|
69
|
+
posture: 'upright' | 'seated' | 'reclined';
|
|
70
|
+
/** Host-declared alerting demand, not volume. */
|
|
71
|
+
stimulation: Unit;
|
|
72
|
+
}
|
|
73
|
+
export interface DozeState {
|
|
74
|
+
context: DozeContext;
|
|
75
|
+
since?: Minute;
|
|
76
|
+
}
|
|
77
|
+
export interface BladderState {
|
|
78
|
+
fill: Unit;
|
|
79
|
+
pending: number;
|
|
80
|
+
}
|
|
68
81
|
export interface BodyState {
|
|
82
|
+
doze?: DozeState;
|
|
83
|
+
bladder?: BladderState;
|
|
69
84
|
/** 1 = fully fed, 0 = starving. */
|
|
70
85
|
satiety: Unit;
|
|
71
86
|
/** 1 = fully hydrated. */
|
|
@@ -848,6 +863,8 @@ export interface Activity {
|
|
|
848
863
|
at: Minute;
|
|
849
864
|
reason: string;
|
|
850
865
|
};
|
|
866
|
+
/** Involuntary doze stopped this activity here. Work and practice stay stopped until host resolution. */
|
|
867
|
+
dozedAt?: Minute;
|
|
851
868
|
/**
|
|
852
869
|
* Set by `begin` when the chosen act was possible only because necessity / the capacity bound lifted a
|
|
853
870
|
* conscience veto (eating forbidden food, or drinking during the fast, in extremity). `finish` then records no
|
|
@@ -898,7 +915,7 @@ export declare const PERSON_SCHEMA = "human/person@1";
|
|
|
898
915
|
* and an activity that would cover a closing stretch is reviewed when the stretch begins (`isUnderWay`,
|
|
899
916
|
* `dutyReviewAt`). Person shape unchanged.
|
|
900
917
|
*/
|
|
901
|
-
export declare const ENGINE_VERSION = "2.
|
|
918
|
+
export declare const ENGINE_VERSION = "2.21.0";
|
|
902
919
|
/** A pregnancy carried by this person (host-driven; see `family/`). */
|
|
903
920
|
export interface Pregnancy {
|
|
904
921
|
fatherId: PersonId;
|
|
@@ -1181,6 +1198,8 @@ export interface Affordance {
|
|
|
1181
1198
|
};
|
|
1182
1199
|
/** Advertised need deltas over the whole activity (positive = satisfies). */
|
|
1183
1200
|
advertises: Partial<Record<NeedId, number>>;
|
|
1201
|
+
/** Intended urination: actual voiding, not completion alone, defines expectation success. */
|
|
1202
|
+
voidsBladder?: boolean;
|
|
1184
1203
|
/** Free tags: 'work', 'leisure', 'social', 'worship', 'risky', 'outdoors', ... */
|
|
1185
1204
|
tags?: string[];
|
|
1186
1205
|
norms?: NormTag[];
|
|
@@ -1335,6 +1354,8 @@ export interface Outcome {
|
|
|
1335
1354
|
at: Minute;
|
|
1336
1355
|
/** Realized need deltas from world effects (e.g. food eaten). Body exertion is computed by the framework. */
|
|
1337
1356
|
needs?: Partial<Record<NeedId, number>>;
|
|
1357
|
+
/** Host-confirmed urination; only a completed activity clears stored bladder fill. */
|
|
1358
|
+
voidedBladder?: boolean;
|
|
1338
1359
|
material?: number;
|
|
1339
1360
|
/** Harm suffered. */
|
|
1340
1361
|
injury?: Omit<Injury, 'id' | 'since'>;
|
|
@@ -1509,6 +1530,10 @@ export interface PersonSpec {
|
|
|
1509
1530
|
values?: Partial<Values>;
|
|
1510
1531
|
norms?: HeldNorm[];
|
|
1511
1532
|
skills?: Record<string, Unit>;
|
|
1533
|
+
/** Authored habits, copied in order. Invalid or over-bound setup throws RangeError. */
|
|
1534
|
+
habits?: Habit[];
|
|
1535
|
+
/** Authored expectations, copied in order. Invalid or over-bound setup throws RangeError. */
|
|
1536
|
+
expectations?: ActionExpectation[];
|
|
1512
1537
|
body?: Partial<Omit<BodyState, 'injuries' | 'illnesses' | 'nextId'>>;
|
|
1513
1538
|
needs?: Partial<NeedReservoirs>;
|
|
1514
1539
|
relationships?: (Partial<Relationship> & {
|
|
@@ -1534,6 +1559,8 @@ export interface PersonSpec {
|
|
|
1534
1559
|
* `enableCharacterChange` and `enableSkillRetention` right after `createPerson`. Each is off when absent.
|
|
1535
1560
|
*/
|
|
1536
1561
|
enable?: {
|
|
1562
|
+
doze?: DozeContext;
|
|
1563
|
+
bladder?: boolean;
|
|
1537
1564
|
/** Read-only suggestion score explanations, as with `enableSuggestionReasons(p)`. */
|
|
1538
1565
|
suggestionReasons?: boolean;
|
|
1539
1566
|
gists?: boolean;
|
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.21.0';
|
package/dist/will/will.d.ts
CHANGED
|
@@ -233,6 +233,18 @@ export declare function noteVoicePromise(p: Person, voiceId: EntityId, event: {
|
|
|
233
233
|
at?: Minute;
|
|
234
234
|
action?: string;
|
|
235
235
|
}): VoiceRelation;
|
|
236
|
+
/**
|
|
237
|
+
* SCOPE (host trust gesture): add a host-supplied signed amount to an existing advising voice's trust,
|
|
238
|
+
* clamped to 0..1, and record the realized change in the usual bounded history. Magnitudes and reason
|
|
239
|
+
* are host premises, not calibrated claims. Missing voices return undefined without creating a voice.
|
|
240
|
+
* Non-finite amounts are ignored. No pressure, credit, counters, memory, time or RNG changes occur.
|
|
241
|
+
* Empty/non-string reasons and non-string actions reject before mutation with RangeError.
|
|
242
|
+
*/
|
|
243
|
+
export declare function gestureVoiceTrust(p: Person, voiceId: EntityId, amount: number, event: {
|
|
244
|
+
reason: string;
|
|
245
|
+
at?: Minute;
|
|
246
|
+
action?: string;
|
|
247
|
+
}): VoiceRelation | undefined;
|
|
236
248
|
/**
|
|
237
249
|
* The next minute after `now` at which a pending commitment reaches `wakeCommitmentPressure`, the level at which a
|
|
238
250
|
* sleeper may wake to serve it (the asleep veto lifts). The composite caps a sleeper's review there so a duty whose
|
package/dist/will/will.js
CHANGED
|
@@ -293,6 +293,27 @@ export function noteVoicePromise(p, voiceId, event) {
|
|
|
293
293
|
noteTrust(v, v.trust - before, event.kept ? 'promise-kept' : 'promise-broken', at, event.action);
|
|
294
294
|
return v;
|
|
295
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* SCOPE (host trust gesture): add a host-supplied signed amount to an existing advising voice's trust,
|
|
298
|
+
* clamped to 0..1, and record the realized change in the usual bounded history. Magnitudes and reason
|
|
299
|
+
* are host premises, not calibrated claims. Missing voices return undefined without creating a voice.
|
|
300
|
+
* Non-finite amounts are ignored. No pressure, credit, counters, memory, time or RNG changes occur.
|
|
301
|
+
* Empty/non-string reasons and non-string actions reject before mutation with RangeError.
|
|
302
|
+
*/
|
|
303
|
+
export function gestureVoiceTrust(p, voiceId, amount, event) {
|
|
304
|
+
if (!event || typeof event.reason !== 'string' || event.reason.length === 0)
|
|
305
|
+
throw new RangeError('A trust gesture needs a reason');
|
|
306
|
+
if (event.action !== undefined && typeof event.action !== 'string')
|
|
307
|
+
throw new RangeError('Invalid trust gesture action');
|
|
308
|
+
const v = voiceOf(p, voiceId);
|
|
309
|
+
if (!v || !Number.isFinite(amount))
|
|
310
|
+
return v;
|
|
311
|
+
const before = v.trust;
|
|
312
|
+
v.trust = clamp01(before + amount);
|
|
313
|
+
const at = event.at !== undefined && Number.isFinite(event.at) ? event.at : p.now;
|
|
314
|
+
noteTrust(v, v.trust - before, event.reason, at, event.action);
|
|
315
|
+
return v;
|
|
316
|
+
}
|
|
296
317
|
const finite = (x) => x === undefined || Number.isFinite(x);
|
|
297
318
|
/** Host numbers the framework computes with must be finite; a NaN offer is vetoed, never chosen. */
|
|
298
319
|
function wellFormed(aff) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adam0white/human-framework",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.21.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",
|