@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/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Human Framework public surface. */
|
|
2
|
-
export const FRAMEWORK_VERSION = '2.
|
|
2
|
+
export const FRAMEWORK_VERSION = '2.21.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';
|
|
@@ -25,6 +25,7 @@ export { clearMethodContext, demonstrateMethod, METHOD_DEFAULTS, methodEvidence,
|
|
|
25
25
|
export { MIGRATIONS, migratableVersions, migrate } from './migrate.js';
|
|
26
26
|
export * from './narrate/index.js';
|
|
27
27
|
export * from './needs/index.js';
|
|
28
|
+
export { confirmObservedReputation } from './observed-reputation.js';
|
|
28
29
|
export * from './partnering/index.js';
|
|
29
30
|
export { enablePersistence, PERSISTENCE_DEFAULTS, pursuitOf } from './persistence/index.js';
|
|
30
31
|
export * from './person.js';
|
|
@@ -34,6 +35,7 @@ export * from './presentation.js';
|
|
|
34
35
|
export { adoptProjectTask, considerProjects, enableProjects, PROJECTS_DEFAULTS, projectAppeal, } from './projects/index.js';
|
|
35
36
|
export * from './promptings/index.js';
|
|
36
37
|
export { bodyForecastFor, bodyForecastTerm, consequenceEvidence, enablePlanning, FORECAST_DEFAULTS, forecastLine, forecastThresholds, PROSPECTION_DEFAULTS, seedConsequences, } from './prospection/index.js';
|
|
38
|
+
export { qalbReading } from './qalb.js';
|
|
37
39
|
export { fingerprint, restore, snapshot } from './restore.js';
|
|
38
40
|
export * from './review/index.js';
|
|
39
41
|
export * from './scenarios/index.js';
|
package/dist/memory/memory.d.ts
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SCOPE: episodic memory and learned action expectations. Episodes carry a salience that decays
|
|
3
|
+
* exponentially (Ebbinghaus-style forgetting curve, modelled as a single exponential per episode) with a
|
|
4
|
+
* half-life that lengthens for emotionally strong episodes (emotional-enhancement of memory), and recall
|
|
5
|
+
* slightly raises the salience of what is retrieved (testing effect). Expectations are cached action
|
|
6
|
+
* values learned by a delta rule (prediction-error learning, Rescorla-Wagner shape) with a minimum
|
|
7
|
+
* learning rate and a clamped step, blended with the host's advertisement by sample-based confidence.
|
|
8
|
+
* It does NOT model reconsolidation, false memory, interference between similar episodes, sleep-dependent
|
|
9
|
+
* consolidation, semantic abstraction, or model-based planning; parameters are engineering defaults for
|
|
10
|
+
* game time scales, not calibrated estimates.
|
|
11
|
+
*
|
|
12
|
+
* SCOPE (lasting gists, 1.8.0, opt-in per person with `enableGists`): episodic detail does not last a life, but the
|
|
13
|
+
* gist of what mattered does. An episode that leaves the episode list (evicted beyond the bound, or older than
|
|
14
|
+
* `GIST_DEFAULTS.horizon` at a `foldGists` call, which the composite makes at each day's close) is folded into the
|
|
15
|
+
* gist with the same kind, action, people and place when it was emotional enough (|valence| ≥ `minValence`, or a
|
|
16
|
+
* loss); weaker ones are simply forgotten. A gist keeps the encoding-weighted mean valence, a count and the summary of
|
|
17
|
+
* its strongest episode, and fades with half-lives of years (`halfLifeNeutral`..`halfLifeEmotional` by |valence|).
|
|
18
|
+
* Gists answer `recall` and `recallByCue` like episodes (so a remembered fear or loss still comes back at its place,
|
|
19
|
+
* person or anniversary), and `expectedEffect` reports the gists about an option's action or place, which cognition
|
|
20
|
+
* weighs as a `memory` term that fades as fresh experience of the option accumulates. So a fear learned at 20 still
|
|
21
|
+
* shapes choices at 40 after its episodes are gone, and enough good later visits outweigh it. Named shapes: the
|
|
22
|
+
* fading of episodic detail while gist persists (fuzzy-trace theory, Brainerd & Reyna) and the long retention of
|
|
23
|
+
* emotional autobiographical memories; the horizon, thresholds, half-lives and the bound of 64 are engineering
|
|
24
|
+
* choices. It does not model reconstruction errors in gists, deliberate rehearsal beyond retelling (`recall`
|
|
25
|
+
* boosts), or semantic knowledge abstracted from gists.
|
|
26
|
+
*/
|
|
1
27
|
import type { Affordance, AppraisalEvent, EntityId, Episode, Gist, MemoryState, Minute, NeedId, Outcome, Person, Signed, Unit } from '../types.ts';
|
|
2
28
|
export declare const MEMORY_DEFAULTS: {
|
|
3
29
|
maxEpisodes: number;
|
package/dist/memory/memory.js
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* boosts), or semantic knowledge abstracted from gists.
|
|
26
26
|
*/
|
|
27
27
|
import { clamp, clamp01, clampSigned, decay, hourOf, isNum, isObj, lerp, runningMean, } from '../core/index.js';
|
|
28
|
+
import { keysOf } from '../core/keys.js';
|
|
28
29
|
import { DAYS_PER_YEAR, MINUTES_PER_DAY, MINUTES_PER_YEAR } from '../types.js';
|
|
29
30
|
export const MEMORY_DEFAULTS = {
|
|
30
31
|
maxEpisodes: 200,
|
|
@@ -511,10 +512,7 @@ export function expectedEffect(p, aff) {
|
|
|
511
512
|
const samples = learned?.samples ?? 0;
|
|
512
513
|
const confidence = samples / (samples + MEMORY_DEFAULTS.confidenceK);
|
|
513
514
|
const needs = {};
|
|
514
|
-
const keys = new Set([
|
|
515
|
-
...Object.keys(aff.advertises),
|
|
516
|
-
...(learned ? Object.keys(learned.needs) : []),
|
|
517
|
-
]);
|
|
515
|
+
const keys = new Set([...keysOf(aff.advertises), ...(learned ? keysOf(learned.needs) : [])]);
|
|
518
516
|
for (const k of [...keys].sort()) {
|
|
519
517
|
const adv = aff.advertises[k] ?? 0;
|
|
520
518
|
const lv = learned?.needs[k];
|
|
@@ -563,11 +561,7 @@ function updateExpectation(p, key, aff, success, realized, felt) {
|
|
|
563
561
|
}
|
|
564
562
|
x.samples += 1;
|
|
565
563
|
const n = x.samples;
|
|
566
|
-
const keys = new Set([
|
|
567
|
-
...Object.keys(x.needs),
|
|
568
|
-
...Object.keys(aff.advertises),
|
|
569
|
-
...Object.keys(realized),
|
|
570
|
-
]);
|
|
564
|
+
const keys = new Set([...keysOf(x.needs), ...keysOf(aff.advertises), ...keysOf(realized)]);
|
|
571
565
|
for (const k of keys) {
|
|
572
566
|
const prior = x.needs[k] ?? aff.advertises[k] ?? 0;
|
|
573
567
|
x.needs[k] = step(prior, realized[k] ?? 0, n);
|
|
@@ -582,7 +576,8 @@ function updateExpectation(p, key, aff, success, realized, felt) {
|
|
|
582
576
|
export function learnOutcome(p, aff, outcome, realized, felt) {
|
|
583
577
|
if (outcome.status === 'interrupted')
|
|
584
578
|
return;
|
|
585
|
-
|
|
579
|
+
// A declared voiding purpose succeeds only when the actual outcome confirms it.
|
|
580
|
+
const success = outcome.status === 'completed' && (aff.voidsBladder !== true || outcome.voidedBladder === true);
|
|
586
581
|
const touched = new Set([expectationKey({ action: aff.action })]);
|
|
587
582
|
if (aff.targetId !== undefined)
|
|
588
583
|
touched.add(expectationKey(aff));
|
package/dist/migrate.js
CHANGED
|
@@ -41,6 +41,11 @@ function renameAmbient(json) {
|
|
|
41
41
|
}
|
|
42
42
|
/** The chain, keyed by the version each step upgrades from. */
|
|
43
43
|
export const MIGRATIONS = {
|
|
44
|
+
'2.17.0': {
|
|
45
|
+
to: '2.21.0',
|
|
46
|
+
note: 'doze and bladder state start absent; old callers retain their continuation',
|
|
47
|
+
apply: stamp,
|
|
48
|
+
},
|
|
44
49
|
'1.4.0': {
|
|
45
50
|
to: '1.5.0',
|
|
46
51
|
note: 'person shape unchanged; standing advice is heard for the running activity from now on',
|
package/dist/narrate/lexicon.js
CHANGED
|
@@ -23,6 +23,7 @@ export const EN_LINES = {
|
|
|
23
23
|
],
|
|
24
24
|
'need:rest': ['I need to sit down for a while.', 'A rest first, then the rest.'],
|
|
25
25
|
'need:relief': ['This pain needs seeing to.', "I can't think past the pain."],
|
|
26
|
+
'need:bladder': ['I need to use the toilet.'],
|
|
26
27
|
'need:belonging': ["I've been alone too long. I want company.", 'Let me find someone to talk to.'],
|
|
27
28
|
'need:leisure': ['I need a break from all this.', 'Time for something easier.'],
|
|
28
29
|
'need:meaning': ['I want to do something that matters.', 'Something worthwhile, for once.'],
|
|
@@ -102,6 +103,7 @@ export const EN_LINES = {
|
|
|
102
103
|
'intention:sleep': ['to sleep'],
|
|
103
104
|
'intention:rest': ['to rest'],
|
|
104
105
|
'intention:relief': ['to ease the pain'],
|
|
106
|
+
'intention:bladder': ['to use the toilet'],
|
|
105
107
|
'intention:belonging': ['to be with people'],
|
|
106
108
|
'intention:leisure': ['to enjoy myself'],
|
|
107
109
|
'intention:meaning': ['to do something that matters'],
|
package/dist/narrate/plain.js
CHANGED
|
@@ -22,6 +22,7 @@ export const PLAIN_EN_LINES = {
|
|
|
22
22
|
'I need to sleep before I do anything else.',
|
|
23
23
|
],
|
|
24
24
|
'need:rest': ['I need to sit down for a while.', 'I will rest before I do anything else.'],
|
|
25
|
+
'need:bladder': ['I need to use the toilet.'],
|
|
25
26
|
'need:relief': [
|
|
26
27
|
'I need to do something about this pain.',
|
|
27
28
|
'The pain makes it hard to think about anything else.',
|
|
@@ -116,6 +117,7 @@ export const PLAIN_EN_LINES = {
|
|
|
116
117
|
'intention:water': ['to drink'],
|
|
117
118
|
'intention:sleep': ['to sleep'],
|
|
118
119
|
'intention:rest': ['to rest'],
|
|
120
|
+
'intention:bladder': ['to use the toilet'],
|
|
119
121
|
'intention:relief': ['to ease the pain'],
|
|
120
122
|
'intention:belonging': ['to be with people'],
|
|
121
123
|
'intention:leisure': ['to enjoy myself'],
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type TraitEffect } from './social/social.ts';
|
|
2
|
+
import type { EntityId, Minute, Person } from './types.ts';
|
|
3
|
+
/** Returns the same detached, sorted misled-source ids as confirm; no relationship alias escapes. */
|
|
4
|
+
export declare function confirmObservedReputation(p: Person, prop: string, truth: boolean, now: Minute, table?: Record<string, TraitEffect>): EntityId[];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SCOPE: opt-in composition of beliefs.confirm and social.applyReputationBelief, in that order.
|
|
3
|
+
* Owns no state or formula. This is the Hearsay 2 wrapper: direct observation corrects the belief,
|
|
4
|
+
* recalibrates its sources and applies the credence change to the subject's relationship. It does
|
|
5
|
+
* not infer opposite traits, accuse a source of lying, run attention or record a percept. Hosts
|
|
6
|
+
* supply only observations their person has access to. Existing callers remain unchanged.
|
|
7
|
+
*/
|
|
8
|
+
import { confirm, credence } from './beliefs/beliefs.js';
|
|
9
|
+
import { applyReputationBelief } from './social/social.js';
|
|
10
|
+
/** Returns the same detached, sorted misled-source ids as confirm; no relationship alias escapes. */
|
|
11
|
+
export function confirmObservedReputation(p, prop, truth, now, table) {
|
|
12
|
+
const before = credence(p, prop);
|
|
13
|
+
const misled = confirm(p, prop, truth, now);
|
|
14
|
+
applyReputationBelief(p, prop, before, credence(p, prop), now, table);
|
|
15
|
+
return misled;
|
|
16
|
+
}
|
package/dist/person.d.ts
CHANGED
|
@@ -50,6 +50,11 @@ export declare const PERSON_DEFAULTS: {
|
|
|
50
50
|
nightFrom: number;
|
|
51
51
|
nightTo: number;
|
|
52
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* SCOPE (host setup): optional habits and expectations copy authored records, including nested cues and
|
|
55
|
+
* need deltas, in source order. Invalid numbers, domains or collection bounds reject with RangeError;
|
|
56
|
+
* setup never clamps, evicts, learns, advances time or consumes RNG. Omitted options keep legacy state.
|
|
57
|
+
*/
|
|
53
58
|
export declare function createPerson(spec: PersonSpec): Person;
|
|
54
59
|
export interface PersonReadout {
|
|
55
60
|
body: BodyReadout;
|
package/dist/person.js
CHANGED
|
@@ -22,18 +22,20 @@
|
|
|
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, agendaTermsWithGoalEstimates, createAgenda, onFinished, promise, proposeGoals, revisePurposes, } from './agenda/index.js';
|
|
24
24
|
import { advanceBeliefs, applyContradiction, attend, believe, confirm, credence, noteProvenance, trustOf, } from './beliefs/index.js';
|
|
25
|
+
import { enableBladder, voidBladder } from './body/bladder.js';
|
|
26
|
+
import { checkDoze, DOZE_DEFAULTS, enableDoze, enterVoluntarySleep, wakeDoze } from './body/doze.js';
|
|
25
27
|
import { advanceBody, BODY_DEFAULTS, checkDowned, clearDowned, consume, createBody, downedAllows, enableFatigueStore, enableFullness, expose, injure, nextBodyThreshold, readBody, setDowned, sicken, skipBody, } from './body/index.js';
|
|
26
28
|
import { ageCharacter, enableCharacterChange, noteCharacterDay, noteCharacterSocial, } from './character/index.js';
|
|
27
29
|
import { closeDay, enableYearbook, endDay, noteAnswer, noteCommitments, noteDecision, noteMood, noteOutcome, trimChronicle, } from './chronicle/index.js';
|
|
28
30
|
import { decide as cognitionDecide, desperationOf, prospectiveRoute, scoreAll } from './cognition/index.js';
|
|
29
31
|
import { createConscience, guiltLoad, heldNorms, normVeto, recordDeed, recordRepair, repent, } from './conscience/index.js';
|
|
30
|
-
import { clamp01, clampSigned, createRng, dayOf, isObj, minuteOfDay, round } from './core/index.js';
|
|
32
|
+
import { clamp01, clampSigned, createRng, dayOf, isObj, keysOf, minuteOfDay, round } from './core/index.js';
|
|
31
33
|
import { enableCuriosity, noteCuriosity } from './curiosity/index.js';
|
|
32
34
|
import { ambientBodyParams, ambientModifiers, ambientMood, ambientNeeds } from './environment/index.js';
|
|
33
35
|
import { aptitudeOf, createFamily, pregnancyModifiers } from './family/index.js';
|
|
34
|
-
import { advanceHabits, reinforce, withholdCued } from './habits/index.js';
|
|
36
|
+
import { advanceHabits, HABIT_DEFAULTS, reinforce, withholdCued } from './habits/index.js';
|
|
35
37
|
import { ageYears, learningMultiplier, lifeModifiers } from './lifecourse/index.js';
|
|
36
|
-
import { advanceMemory, createMemory, enableGists, foldGists, learnOutcome, recallByCue, remember, } from './memory/index.js';
|
|
38
|
+
import { advanceMemory, createMemory, enableGists, foldGists, learnOutcome, MEMORY_DEFAULTS, recallByCue, remember, } from './memory/index.js';
|
|
37
39
|
import { intentionFor, narrateDecision, voiceLine } from './narrate/index.js';
|
|
38
40
|
import { advanceNeeds, createNeeds, levelOf, meanSatisfaction, readNeeds, satisfy, urgencyOf, } from './needs/index.js';
|
|
39
41
|
import { widow } from './partnering/index.js';
|
|
@@ -114,7 +116,56 @@ function fill(keys, given, def) {
|
|
|
114
116
|
}
|
|
115
117
|
return out;
|
|
116
118
|
}
|
|
119
|
+
function validateHostSetup(spec) {
|
|
120
|
+
const finite = (n) => Number.isFinite(n);
|
|
121
|
+
const unit = (n) => finite(n) && n >= 0 && n <= 1;
|
|
122
|
+
const count = (n) => Number.isSafeInteger(n) && n >= 0;
|
|
123
|
+
const optional = (n, valid) => n === undefined || valid(n);
|
|
124
|
+
if (spec.habits !== undefined) {
|
|
125
|
+
if (!Array.isArray(spec.habits) || spec.habits.length > HABIT_DEFAULTS.maxHabits)
|
|
126
|
+
throw new RangeError('Invalid habits setup bound');
|
|
127
|
+
for (const h of spec.habits) {
|
|
128
|
+
if (!h ||
|
|
129
|
+
!isObj(h.cue) ||
|
|
130
|
+
typeof h.action !== 'string' ||
|
|
131
|
+
h.action.length === 0 ||
|
|
132
|
+
!unit(h.strength) ||
|
|
133
|
+
!count(h.repetitions) ||
|
|
134
|
+
!finite(h.lastAt) ||
|
|
135
|
+
!optional(h.withheld, count) ||
|
|
136
|
+
!optional(h.lastWithheldAt, finite) ||
|
|
137
|
+
!optional(h.craving, unit) ||
|
|
138
|
+
!optional(h.cue.hour, (n) => finite(n) && n >= 0 && n < 24) ||
|
|
139
|
+
(h.cue.placeId !== undefined && typeof h.cue.placeId !== 'string') ||
|
|
140
|
+
(h.cue.after !== undefined && typeof h.cue.after !== 'string'))
|
|
141
|
+
throw new RangeError('Invalid habit setup record');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (spec.expectations !== undefined) {
|
|
145
|
+
if (!Array.isArray(spec.expectations) || spec.expectations.length > MEMORY_DEFAULTS.maxExpectations)
|
|
146
|
+
throw new RangeError('Invalid expectations setup bound');
|
|
147
|
+
for (const e of spec.expectations) {
|
|
148
|
+
if (!e ||
|
|
149
|
+
typeof e.key !== 'string' ||
|
|
150
|
+
e.key.length === 0 ||
|
|
151
|
+
!isObj(e.needs) ||
|
|
152
|
+
!unit(e.successRate) ||
|
|
153
|
+
!count(e.samples) ||
|
|
154
|
+
!finite(e.valence) ||
|
|
155
|
+
Math.abs(e.valence) > 1 ||
|
|
156
|
+
Object.entries(e.needs).some(([key, n]) => ![...PHYSIOLOGICAL_NEEDS, ...PSYCHOLOGICAL_NEEDS].some((need) => need === key) ||
|
|
157
|
+
(n !== undefined && !finite(n))))
|
|
158
|
+
throw new RangeError('Invalid expectation setup record');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* SCOPE (host setup): optional habits and expectations copy authored records, including nested cues and
|
|
164
|
+
* need deltas, in source order. Invalid numbers, domains or collection bounds reject with RangeError;
|
|
165
|
+
* setup never clamps, evicts, learns, advances time or consumes RNG. Omitted options keep legacy state.
|
|
166
|
+
*/
|
|
117
167
|
export function createPerson(spec) {
|
|
168
|
+
validateHostSetup(spec);
|
|
118
169
|
const now = spec.now ?? 0;
|
|
119
170
|
const traits = fill(TRAIT_KEYS, spec.traits, 0.5);
|
|
120
171
|
const values = fill(VALUE_KEYS, spec.values, 0.5);
|
|
@@ -143,6 +194,38 @@ export function createPerson(spec) {
|
|
|
143
194
|
trace: [],
|
|
144
195
|
nextDecision: 0,
|
|
145
196
|
};
|
|
197
|
+
if (spec.habits)
|
|
198
|
+
p.habits = spec.habits.map((h) => {
|
|
199
|
+
const cue = {};
|
|
200
|
+
if (h.cue.hour !== undefined)
|
|
201
|
+
cue.hour = h.cue.hour;
|
|
202
|
+
if (h.cue.placeId !== undefined)
|
|
203
|
+
cue.placeId = h.cue.placeId;
|
|
204
|
+
if (h.cue.after !== undefined)
|
|
205
|
+
cue.after = h.cue.after;
|
|
206
|
+
const out = {
|
|
207
|
+
cue,
|
|
208
|
+
action: h.action,
|
|
209
|
+
strength: h.strength,
|
|
210
|
+
repetitions: h.repetitions,
|
|
211
|
+
lastAt: h.lastAt,
|
|
212
|
+
};
|
|
213
|
+
if (h.craving !== undefined)
|
|
214
|
+
out.craving = h.craving;
|
|
215
|
+
if (h.withheld !== undefined)
|
|
216
|
+
out.withheld = h.withheld;
|
|
217
|
+
if (h.lastWithheldAt !== undefined)
|
|
218
|
+
out.lastWithheldAt = h.lastWithheldAt;
|
|
219
|
+
return out;
|
|
220
|
+
});
|
|
221
|
+
if (spec.expectations)
|
|
222
|
+
p.memory.expectations = spec.expectations.map((e) => ({
|
|
223
|
+
key: e.key,
|
|
224
|
+
needs: Object.fromEntries(Object.entries(e.needs)),
|
|
225
|
+
successRate: e.successRate,
|
|
226
|
+
samples: e.samples,
|
|
227
|
+
valence: e.valence,
|
|
228
|
+
}));
|
|
146
229
|
if (spec.lexicon)
|
|
147
230
|
p.lexicon = structuredClone(spec.lexicon);
|
|
148
231
|
if (spec.retention)
|
|
@@ -177,6 +260,10 @@ export function createPerson(spec) {
|
|
|
177
260
|
enablePromptings(p, on.promptings);
|
|
178
261
|
if (on?.review)
|
|
179
262
|
enableReview(p, on.review);
|
|
263
|
+
if (on?.doze)
|
|
264
|
+
enableDoze(p, on.doze);
|
|
265
|
+
if (on?.bladder)
|
|
266
|
+
enableBladder(p);
|
|
180
267
|
return p;
|
|
181
268
|
}
|
|
182
269
|
/**
|
|
@@ -392,10 +479,10 @@ export function tick(p, now) {
|
|
|
392
479
|
const seg = PERSON_DEFAULTS.maxSegment;
|
|
393
480
|
while (p.now < now) {
|
|
394
481
|
const act = p.activity;
|
|
395
|
-
const active = act !== null && p.now < act.endsAt;
|
|
482
|
+
const active = act !== null && act.dozedAt === undefined && p.body.doze?.since === undefined && p.now < act.endsAt;
|
|
396
483
|
// Segment boundaries sit on an absolute grid, so how a host chunks its calls does not change the result.
|
|
397
484
|
const grid = (Math.floor(p.now / seg) + 1) * seg;
|
|
398
|
-
const end = Math.min(active ? Math.min(now, act.endsAt) : now, grid);
|
|
485
|
+
const end = Math.min(active ? Math.min(now, act.endsAt) : now, grid, p.body.doze ? Math.floor(p.now) + 1 : Number.POSITIVE_INFINITY);
|
|
399
486
|
const load = active && act ? { effort: act.effort, focus: act.focus, mode: act.mode } : PERSON_DEFAULTS.idleLoad;
|
|
400
487
|
// A minute so large that the grid step rounds away cannot advance (security review H2): stop, never spin.
|
|
401
488
|
if (!(end > p.now)) {
|
|
@@ -407,6 +494,12 @@ export function tick(p, now) {
|
|
|
407
494
|
}
|
|
408
495
|
advanceSegment(p, end - p.now, load, active && act ? act.affordance : undefined);
|
|
409
496
|
p.now = end;
|
|
497
|
+
if (Number.isInteger(end) && p.body.doze && !(act && act.endsAt === end) && checkDoze(p, end)) {
|
|
498
|
+
if (act)
|
|
499
|
+
act.dozedAt ??= end;
|
|
500
|
+
// Physical onset must not erase a host/percept reason awaiting resolution.
|
|
501
|
+
interrupt(p, end, act?.interrupt?.reason ?? 'doze');
|
|
502
|
+
}
|
|
410
503
|
pruneConsequences(p);
|
|
411
504
|
if (p.pursuit)
|
|
412
505
|
releasePursuit(p);
|
|
@@ -771,6 +864,18 @@ function judgeDeed(p, actor, pc, at) {
|
|
|
771
864
|
export function perceive(p, percepts, options = {}) {
|
|
772
865
|
if (percepts.length === 0 || !p.body.alive)
|
|
773
866
|
return [];
|
|
867
|
+
if (p.body.doze?.since !== undefined) {
|
|
868
|
+
const wakes = percepts.some((pc) => Number.isFinite(pc.at) &&
|
|
869
|
+
pc.at === p.now &&
|
|
870
|
+
Number.isFinite(pc.salience) &&
|
|
871
|
+
pc.salience >= DOZE_DEFAULTS.wakeSalience &&
|
|
872
|
+
pc.near !== false &&
|
|
873
|
+
(pc.targetId === p.id || pc.near === true) &&
|
|
874
|
+
(pc.channel === 'heard' || pc.channel === 'felt' || pc.channel === 'social'));
|
|
875
|
+
if (!wakes)
|
|
876
|
+
return [];
|
|
877
|
+
wakeDoze(p, p.now);
|
|
878
|
+
}
|
|
774
879
|
const body = readBodyOf(p);
|
|
775
880
|
const attended = attend(p, [...percepts], {
|
|
776
881
|
focus: p.activity?.focus ?? 0,
|
|
@@ -1068,6 +1173,8 @@ function thresholdFor(p, load, review) {
|
|
|
1068
1173
|
export function begin(p, aff, record, opts = {}) {
|
|
1069
1174
|
if (!p.body.alive)
|
|
1070
1175
|
return null;
|
|
1176
|
+
if (p.body.doze?.since !== undefined && aff.mode !== 'sleep')
|
|
1177
|
+
return null;
|
|
1071
1178
|
const now = p.now;
|
|
1072
1179
|
if (p.pursuit) {
|
|
1073
1180
|
const considered = record.considered.find((c) => c.affordanceId === aff.id);
|
|
@@ -1143,6 +1250,8 @@ export function begin(p, aff, record, opts = {}) {
|
|
|
1143
1250
|
activity.commitmentId = c.id;
|
|
1144
1251
|
}
|
|
1145
1252
|
p.activity = activity;
|
|
1253
|
+
if (load.mode === 'sleep')
|
|
1254
|
+
enterVoluntarySleep(p);
|
|
1146
1255
|
advanceBody(p, 0, load, mods); // apply the sleep/wake switch now
|
|
1147
1256
|
const review = load.mode === 'sleep' ? PERSON_DEFAULTS.sleepReviewInterval : PERSON_DEFAULTS.reviewInterval;
|
|
1148
1257
|
const thresholdAt = thresholdFor(p, load, review);
|
|
@@ -1367,7 +1476,7 @@ export function finish(p, outcome, opts = {}) {
|
|
|
1367
1476
|
const now = p.now;
|
|
1368
1477
|
const aff = act.affordance;
|
|
1369
1478
|
const completed = outcome.status === 'completed';
|
|
1370
|
-
const minutes = Math.max(0, now - act.startedAt);
|
|
1479
|
+
const minutes = Math.max(0, Math.min(now, act.dozedAt ?? now) - act.startedAt);
|
|
1371
1480
|
const mods = lifeModifiers(p);
|
|
1372
1481
|
const D = PERSON_DEFAULTS;
|
|
1373
1482
|
const before = readNeeds(p, readBodyOf(p));
|
|
@@ -1380,6 +1489,8 @@ export function finish(p, outcome, opts = {}) {
|
|
|
1380
1489
|
if (given.relief)
|
|
1381
1490
|
bodyDeltas.relief = given.relief;
|
|
1382
1491
|
consume(p, bodyDeltas);
|
|
1492
|
+
if (completed && outcome.voidedBladder === true)
|
|
1493
|
+
voidBladder(p);
|
|
1383
1494
|
const psych = {};
|
|
1384
1495
|
for (const id of PSYCHOLOGICAL_NEEDS)
|
|
1385
1496
|
if (given[id] !== undefined)
|
|
@@ -1399,7 +1510,7 @@ export function finish(p, outcome, opts = {}) {
|
|
|
1399
1510
|
const realized = realizedNeedDeltas(act, given, before, after);
|
|
1400
1511
|
// Felt valence.
|
|
1401
1512
|
let felt = 0;
|
|
1402
|
-
for (const id of
|
|
1513
|
+
for (const id of keysOf(realized)) {
|
|
1403
1514
|
felt += D.feltGain * (D.feltBase + urgencyOf(before, id)) * (realized[id] ?? 0);
|
|
1404
1515
|
}
|
|
1405
1516
|
if (outcome.status === 'failed')
|
package/dist/persona.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* identity, age, family and health compatibility before returning a new Person. Neither input is mutated.
|
|
5
5
|
* This is not a world migration, identity registry, transport, hash, authentication or general calendar mapper.
|
|
6
6
|
* v1 admits zero-duration travel and healthy compatible bodies only; family/bonds remain host-authored and must
|
|
7
|
-
* match source provenance. Portable v1 accepts its published 2.14.1
|
|
8
|
-
* the
|
|
7
|
+
* match source provenance. Portable v1 accepts its published 2.14.1 and 2.17.0 stamps and the current engine;
|
|
8
|
+
* the bodily opt-ins change no portable field semantics. Pregnancy and nonempty temporal bonds are unsupported in v1. All other world-dependent stores stay with the source and are disclosed as omitted.
|
|
9
9
|
*/
|
|
10
10
|
import { hasUnresolvedMakeUps, sameChain } from './agenda/agenda.js';
|
|
11
11
|
import { createPerson } from './person.js';
|
|
@@ -186,8 +186,10 @@ export function validatePersonaProjection(input) {
|
|
|
186
186
|
text(input.mappingVersion);
|
|
187
187
|
text(input.name);
|
|
188
188
|
keys(input.source, ['schema', 'engine', 'id', 'now']);
|
|
189
|
-
//
|
|
190
|
-
const compatibleEngine = input.source.engine === ENGINE_VERSION ||
|
|
189
|
+
// The 2.14.1 and 2.17.0 portable fields retain their semantics across the bodily opt-ins.
|
|
190
|
+
const compatibleEngine = input.source.engine === ENGINE_VERSION ||
|
|
191
|
+
input.source.engine === '2.17.0' ||
|
|
192
|
+
input.source.engine === '2.14.1';
|
|
191
193
|
if (input.source.schema !== PERSON_SCHEMA || !compatibleEngine)
|
|
192
194
|
fail('unsupported source schema/engine; migrate the source before projection');
|
|
193
195
|
text(input.source.id);
|
package/dist/qalb.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SCOPE: a read-only narration lens over retained attention evidence, held norms and conviction,
|
|
3
|
+
* mood and the three distinct trust stores. Owns no state and supplies no decision input. It does
|
|
4
|
+
* not infer spiritual health, rank people, judge norms, measure the ruh or compute divine acceptance.
|
|
5
|
+
* Retained memories are evidence available for narration, not a complete attention history.
|
|
6
|
+
*
|
|
7
|
+
* Provenance: research/reconciliation.md §2–4, §7 (Q7: Qur'an 22:46; H9: Bukhari 52 /
|
|
8
|
+
* Muslim 1599a); research/islamic-foundations.md §2 (qalb as an overlapping interpretive view);
|
|
9
|
+
* research/decisions.md, “Qalb, nafs, fitra, ʿaql as state”. These support the lens, not a
|
|
10
|
+
* diagnostic mapping. Sign/zero categories below are engineering descriptions of stored values,
|
|
11
|
+
* not religious categories. Mood and interpersonal trust imply nothing about faith.
|
|
12
|
+
*/
|
|
13
|
+
import type { Person } from './types.ts';
|
|
14
|
+
export interface QalbDescription {
|
|
15
|
+
facet: 'attention' | 'held-norm' | 'mood' | 'trust';
|
|
16
|
+
category: string;
|
|
17
|
+
/** Every description is attributed to this simulated person's records. */
|
|
18
|
+
personId: string;
|
|
19
|
+
text: string;
|
|
20
|
+
/** Detached identifiers and state paths, never mutable state objects or spiritual scores. */
|
|
21
|
+
evidence: string[];
|
|
22
|
+
sources: string[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Plain English descriptions; no scalar summary, comparison or hidden update.
|
|
26
|
+
* Hosts must not present these records as spiritual health or a religious assessment.
|
|
27
|
+
*/
|
|
28
|
+
export declare function qalbReading(p: Person): QalbDescription[];
|
package/dist/qalb.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plain English descriptions; no scalar summary, comparison or hidden update.
|
|
3
|
+
* Hosts must not present these records as spiritual health or a religious assessment.
|
|
4
|
+
*/
|
|
5
|
+
export function qalbReading(p) {
|
|
6
|
+
const sources = [
|
|
7
|
+
'research/reconciliation.md §2–4, §7; Q7 (22:46); H9 (Bukhari 52; Muslim 1599a)',
|
|
8
|
+
'research/islamic-foundations.md §2: Qalb',
|
|
9
|
+
'research/decisions.md: Qalb, nafs, fitra, ʿaql as state (2026-10-18)',
|
|
10
|
+
];
|
|
11
|
+
const rows = [];
|
|
12
|
+
const add = (facet, category, text, evidence) => {
|
|
13
|
+
rows.push({ facet, category, personId: p.id, text, evidence, sources: [...sources] });
|
|
14
|
+
};
|
|
15
|
+
const memories = [...p.memory.episodes, ...(p.memory.gists ?? [])];
|
|
16
|
+
add('attention', memories.length ? 'retained-evidence' : 'no-retained-evidence', memories.length
|
|
17
|
+
? 'Their memories retain some experiences. These do not show everything they noticed.'
|
|
18
|
+
: 'Their records contain no retained experiences. This does not show what they noticed.', memories.map((e) => `memory:${e.id}:${e.kind}`));
|
|
19
|
+
if (!p.conscience.norms.length)
|
|
20
|
+
add('held-norm', 'unrecorded', 'Their records contain no held rules.', ['conscience.norms']);
|
|
21
|
+
for (const n of p.conscience.norms) {
|
|
22
|
+
add('held-norm', n.conviction === 0 ? 'no-conviction' : 'held-understanding', n.conviction === 0
|
|
23
|
+
? 'They have a recorded rule with no stated conviction.'
|
|
24
|
+
: 'They hold an understanding of this rule. This is their view.', [`norm:${n.normId}`, `standing:${n.standing}`, 'conscience.norms.conviction']);
|
|
25
|
+
}
|
|
26
|
+
add('mood', p.affect.mood.valence < 0 ? 'unpleasant' : p.affect.mood.valence > 0 ? 'pleasant' : 'neutral', p.affect.mood.valence < 0
|
|
27
|
+
? 'Their recorded mood feels unpleasant.'
|
|
28
|
+
: p.affect.mood.valence > 0
|
|
29
|
+
? 'Their recorded mood feels pleasant.'
|
|
30
|
+
: 'Their recorded mood feels neutral.', ['affect.mood.valence']);
|
|
31
|
+
add('mood', p.affect.mood.arousal === 0 ? 'no-arousal' : 'some-arousal', p.affect.mood.arousal === 0
|
|
32
|
+
? 'Their recorded mood has no arousal.'
|
|
33
|
+
: 'Their recorded mood has some arousal.', ['affect.mood.arousal']);
|
|
34
|
+
// Keep stores distinct. Do not substitute relationship trust for source or advising-voice trust.
|
|
35
|
+
const ties = p.social.relationships.map((r) => ({
|
|
36
|
+
store: 'social.relationships',
|
|
37
|
+
id: r.otherId,
|
|
38
|
+
trust: r.trust,
|
|
39
|
+
}));
|
|
40
|
+
const voices = p.will.voices.map((v) => ({ store: 'will.voices', id: v.voiceId, trust: v.trust }));
|
|
41
|
+
const sourcesTrust = Object.entries(p.memory.sourceTrust).map(([id, trust]) => ({
|
|
42
|
+
store: 'memory.sourceTrust',
|
|
43
|
+
id,
|
|
44
|
+
trust,
|
|
45
|
+
}));
|
|
46
|
+
const trusts = [...ties, ...voices, ...sourcesTrust];
|
|
47
|
+
if (!trusts.length)
|
|
48
|
+
add('trust', 'unrecorded', 'Their records contain no learned trust.', [
|
|
49
|
+
'social.relationships',
|
|
50
|
+
'will.voices',
|
|
51
|
+
'memory.sourceTrust',
|
|
52
|
+
]);
|
|
53
|
+
for (const t of trusts) {
|
|
54
|
+
// 0.5 is the numeric range midpoint, not a faith threshold or personal baseline.
|
|
55
|
+
const subject = t.store === 'social.relationships'
|
|
56
|
+
? 'this person'
|
|
57
|
+
: t.store === 'will.voices'
|
|
58
|
+
? 'this advising voice'
|
|
59
|
+
: 'this information source';
|
|
60
|
+
add('trust', t.trust < 0.5 ? 'below-midpoint' : t.trust > 0.5 ? 'above-midpoint' : 'at-midpoint', t.trust < 0.5
|
|
61
|
+
? `Their trust in ${subject} is below the middle of its range.`
|
|
62
|
+
: t.trust > 0.5
|
|
63
|
+
? `Their trust in ${subject} is above the middle of its range.`
|
|
64
|
+
: `Their trust in ${subject} is at the middle of its range.`, [`${t.store}:${t.id}`]);
|
|
65
|
+
}
|
|
66
|
+
return rows;
|
|
67
|
+
}
|
package/dist/sim/sim.d.ts
CHANGED
|
@@ -245,7 +245,7 @@ export declare function birth(c: Community, a: Person, b: Person, spec: ChildSpe
|
|
|
245
245
|
export declare function tellDeath(c: Community, dead: Person, at: Minute, opts?: {
|
|
246
246
|
late?: 'throw' | 'now';
|
|
247
247
|
}): void;
|
|
248
|
-
/**
|
|
248
|
+
/** Pure scheduling query, including passive bodily checkpoints during joint waiting. */
|
|
249
249
|
export declare function nextEventAt(c: Community, p: Person): Minute;
|
|
250
250
|
/**
|
|
251
251
|
* How this person would answer `suggestion` now, without changing state (UI telegraph; wraps `predict`). Pass the
|