@adam0white/human-framework 2.3.0 → 2.4.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 +1 -1
- package/dist/cognition/cognition.d.ts +3 -0
- package/dist/cognition/cognition.js +22 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/migrate.js +6 -1
- package/dist/person.d.ts +6 -0
- package/dist/person.js +22 -2
- package/dist/promptings/index.d.ts +1 -0
- package/dist/promptings/index.js +1 -0
- package/dist/promptings/promptings.d.ts +100 -0
- package/dist/promptings/promptings.js +348 -0
- package/dist/restore.js +3 -0
- package/dist/types.d.ts +60 -2
- package/dist/types.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -348,7 +348,7 @@ Import these functions from the package. Rules/evidence live in optional `Person
|
|
|
348
348
|
|
|
349
349
|
## Receiving a possible future route
|
|
350
350
|
|
|
351
|
-
|
|
351
|
+
HF 2.3.0 lets an attended explanation connect one current action to one possible successor. The host supplies the explanation the person received, not a private success bonus.
|
|
352
352
|
|
|
353
353
|
```ts
|
|
354
354
|
import {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type HabitContext } from '../habits/index.ts';
|
|
2
|
+
import { type PromptingSeen } from '../promptings/index.ts';
|
|
2
3
|
import type { Affordance, BodyReadout, Command, Considered, DecisionRecord, LifeModifiers, Minute, NeedReading, Person, Suggestion, Term, Unit } from '../types.ts';
|
|
3
4
|
import { type CommandOutcome, type WillContext } from '../will/index.ts';
|
|
4
5
|
export declare const COGNITION_DEFAULTS: {
|
|
@@ -132,6 +133,8 @@ export interface Decision {
|
|
|
132
133
|
needDeltas: Partial<Record<'autonomy', number>>;
|
|
133
134
|
/** How a command in force fared (absent when none). */
|
|
134
135
|
command?: CommandOutcome;
|
|
136
|
+
/** Prompts noticed at this decision point (only with `p.promptings`); the composite records them. */
|
|
137
|
+
prompts?: PromptingSeen[];
|
|
135
138
|
}
|
|
136
139
|
/** Highest physiological urgency: the input to the necessity exception. */
|
|
137
140
|
export declare function desperationOf(needs: NeedReading[]): number;
|
|
@@ -34,6 +34,7 @@ import { expectedEffect } from '../memory/index.js';
|
|
|
34
34
|
import { methodEvidence } from '../methods/index.js';
|
|
35
35
|
import { urgencyOf } from '../needs/index.js';
|
|
36
36
|
import { partneringTerms } from '../partnering/index.js';
|
|
37
|
+
import { promptIn, promptingTerms } from '../promptings/index.js';
|
|
37
38
|
import { receivedGoalRoute } from '../prospection/index.js';
|
|
38
39
|
import { companionSteadiness, socialTerms } from '../social/index.js';
|
|
39
40
|
import { PHYSIOLOGICAL_NEEDS, VALUE_KEYS } from '../types.js';
|
|
@@ -398,6 +399,10 @@ export function consider(p, aff, ctx) {
|
|
|
398
399
|
if (!inBreak(p))
|
|
399
400
|
for (const t of rememberedTerms(p, aff, ctx.now, voices))
|
|
400
401
|
push(t.source, t.value);
|
|
402
|
+
// Promptings (2.4.0, opt-in): a recurrence pull under `fault` and a practised turning-away from a noticed prompt.
|
|
403
|
+
if (p.promptings)
|
|
404
|
+
for (const t of promptingTerms(p, aff.action, terms, aff.tags, ctx.now, ctx.desperation))
|
|
405
|
+
push(t.source, t.value);
|
|
401
406
|
let utility = 0;
|
|
402
407
|
for (const t of terms)
|
|
403
408
|
utility += t.value;
|
|
@@ -471,5 +476,22 @@ export function decide(p, affordances, ctx) {
|
|
|
471
476
|
const out = { record, needDeltas };
|
|
472
477
|
if (res.command)
|
|
473
478
|
out.command = res.command;
|
|
479
|
+
if (p.promptings) {
|
|
480
|
+
const prompts = [];
|
|
481
|
+
const tagsOf = new Map(sorted.map((a) => [a.id, a.tags]));
|
|
482
|
+
for (const c of res.considered) {
|
|
483
|
+
const seen = promptIn(c.terms, tagsOf.get(c.affordanceId));
|
|
484
|
+
if (seen)
|
|
485
|
+
prompts.push({
|
|
486
|
+
affordanceId: c.affordanceId,
|
|
487
|
+
action: c.action,
|
|
488
|
+
...seen,
|
|
489
|
+
// A vetoed option was not turned from: the will blocked it, so the response earns no practice.
|
|
490
|
+
turnApplied: c.vetoed === undefined && c.terms.some((t) => t.source === 'promptings:turn'),
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
if (prompts.length > 0)
|
|
494
|
+
out.prompts = prompts;
|
|
495
|
+
}
|
|
474
496
|
return out;
|
|
475
497
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Human Framework v1 public surface. */
|
|
2
|
-
export declare const FRAMEWORK_VERSION = "2.
|
|
2
|
+
export declare const FRAMEWORK_VERSION = "2.4.0";
|
|
3
3
|
export type { CrisisEvent, PracticeKind } from './affect/index.ts';
|
|
4
4
|
export { AFFECT_DEFAULTS, actionTendencies, advanceAffect, appraise, breakAllows, breakBehaviour, breakHazard, CRISIS_DEFAULTS, checkCrisis, createAffect, easeBreak, emotionLevel, enableBreaks, feel, inBreak, readAffect, regulate, release as releaseEmotion, skipCrisis, strain, tendencyEmotions, } from './affect/index.ts';
|
|
5
5
|
export type { GoalTemplate, PrayerCalendar, PrayerSchedule, PrayerTimes, Retimer, } from './agenda/index.ts';
|
|
@@ -28,6 +28,7 @@ export * from './narrate/index.ts';
|
|
|
28
28
|
export * from './needs/index.ts';
|
|
29
29
|
export * from './partnering/index.ts';
|
|
30
30
|
export * from './person.ts';
|
|
31
|
+
export * from './promptings/index.ts';
|
|
31
32
|
export type { ConsequenceEvidence } from './prospection/index.ts';
|
|
32
33
|
export { consequenceEvidence, PROSPECTION_DEFAULTS } from './prospection/index.ts';
|
|
33
34
|
export * from './restore.ts';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Human Framework v1 public surface. */
|
|
2
|
-
export const FRAMEWORK_VERSION = '2.
|
|
2
|
+
export const FRAMEWORK_VERSION = '2.4.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, isUnderWay, MAKRUH_DEFAULTS, makruhWindows, missedExcuse, onFinished, owedMakeUps, PRAYER_IMPORTANCE, PURPOSE_DEFAULTS, prayerWindows, pressureReachedAt, promise, proposeGoals, RAMADAN_DEFAULTS, ramadanFast, ramadanMeals, release as releaseCommitment, retimeCommitments, revisePurposes, scheduleMakeUp, spanMeetsWindow, suhoorWindow, timesFor, violatesAbstention, } from './agenda/index.js';
|
|
@@ -25,6 +25,7 @@ export * from './narrate/index.js';
|
|
|
25
25
|
export * from './needs/index.js';
|
|
26
26
|
export * from './partnering/index.js';
|
|
27
27
|
export * from './person.js';
|
|
28
|
+
export * from './promptings/index.js';
|
|
28
29
|
export { consequenceEvidence, PROSPECTION_DEFAULTS } from './prospection/index.js';
|
|
29
30
|
export * from './restore.js';
|
|
30
31
|
export * from './scenarios/index.js';
|
package/dist/migrate.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* field of the optional surroundings slice (`ambient.now` → `ambient.percept`) and changes no behaviour (the optional `social.impressions` and `social.reserve`, like `retention`, are optional and start
|
|
9
9
|
* absent in a migrated save); `restore` then fills and sanitizes
|
|
10
10
|
* the slices as for any save. 2.0.0 to 2.1.0 adds optional conditional method state, absent
|
|
11
|
-
* until explicitly used, so existing hosts retain their previous behavior; that step stamps the version. 2.1.0 to 2.2.0 preserves optional method records and stamps the version; successful alternatives now retain independent support. 2.2.0 to 2.3.0 stamps the version; received consequences and temporal goals remain absent unless explicitly supplied. A migrated save continues under the current engine's rules: it restores and runs,
|
|
11
|
+
* until explicitly used, so existing hosts retain their previous behavior; that step stamps the version. 2.1.0 to 2.2.0 preserves optional method records and stamps the version; successful alternatives now retain independent support. 2.2.0 to 2.3.0 stamps the version; received consequences and temporal goals remain absent unless explicitly supplied. 2.3.0 to 2.4.0 stamps the version; promptings remain absent unless enabled. A migrated save continues under the current engine's rules: it restores and runs,
|
|
12
12
|
* but it does not reproduce what the old engine would have done next where the rules changed (1.4.0 to 1.5.0
|
|
13
13
|
* changed how standing advice is heard; 1.8.0 to 1.9.0 changed the omission rule). Saves older than 1.4.0 and unknown versions are refused with an error.
|
|
14
14
|
*
|
|
@@ -58,6 +58,11 @@ export const MIGRATIONS = {
|
|
|
58
58
|
note: 'the surroundings field `ambient.now` is renamed `ambient.percept`; per-person retention is optional and starts absent; behaviour unchanged',
|
|
59
59
|
apply: renameAmbient,
|
|
60
60
|
},
|
|
61
|
+
'2.3.0': {
|
|
62
|
+
to: '2.4.0',
|
|
63
|
+
note: 'promptings are optional and start absent; existing hosts retain exact behavior',
|
|
64
|
+
apply: stamp,
|
|
65
|
+
},
|
|
61
66
|
'2.2.0': {
|
|
62
67
|
to: '2.3.0',
|
|
63
68
|
note: 'received consequences and temporal goals are optional; existing hosts retain exact behavior',
|
package/dist/person.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type NafsPattern } from './promptings/index.ts';
|
|
1
2
|
import { type SkillTransfer } from './skills/index.ts';
|
|
2
3
|
import type { Activity, Affordance, BodyLoad, BodyReadout, Command, Commitment, DecisionRecord, LearningDomain, Minute, NeedId, NeedReading, NormDefinition, Outcome, Percept, Person, PersonSpec, Retention, Suggestion, SuggestionResolution, Unit } from './types.ts';
|
|
3
4
|
import { type CommandOutcome } from './will/index.ts';
|
|
@@ -230,3 +231,8 @@ export declare function finish(p: Person, outcome: Outcome, opts?: FinishOptions
|
|
|
230
231
|
* entries are ignored; an empty or wholly invalid retention restores the defaults. Changes no decision.
|
|
231
232
|
*/
|
|
232
233
|
export declare function setRetention(p: Person, retention: Retention): void;
|
|
234
|
+
/**
|
|
235
|
+
* The nafs lens (2.4.0, read-only; see `promptings/` SCOPE): the three Qur'anic descriptions of one self (12:53,
|
|
236
|
+
* 75:2, 89:27), each with the matching evidence in existing state. Descriptions, never a level or a rank.
|
|
237
|
+
*/
|
|
238
|
+
export declare function nafsReading(p: Person): NafsPattern[];
|
package/dist/person.js
CHANGED
|
@@ -26,7 +26,7 @@ import { advanceBody, BODY_DEFAULTS, checkDowned, clearDowned, consume, createBo
|
|
|
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';
|
|
28
28
|
import { decide as cognitionDecide, desperationOf, scoreAll } from './cognition/index.js';
|
|
29
|
-
import { createConscience, heldNorms, normVeto, recordDeed, recordRepair, repent, } from './conscience/index.js';
|
|
29
|
+
import { createConscience, guiltLoad, heldNorms, normVeto, recordDeed, recordRepair, repent, } from './conscience/index.js';
|
|
30
30
|
import { clamp01, clampSigned, createRng, dayOf, isObj, minuteOfDay } from './core/index.js';
|
|
31
31
|
import { ambientBodyParams, ambientModifiers, ambientMood, ambientNeeds } from './environment/index.js';
|
|
32
32
|
import { aptitudeOf, createFamily, pregnancyModifiers } from './family/index.js';
|
|
@@ -36,6 +36,7 @@ import { advanceMemory, createMemory, enableGists, foldGists, learnOutcome, reca
|
|
|
36
36
|
import { intentionFor, narrateDecision, voiceLine } from './narrate/index.js';
|
|
37
37
|
import { advanceNeeds, createNeeds, levelOf, meanSatisfaction, readNeeds, satisfy, urgencyOf, } from './needs/index.js';
|
|
38
38
|
import { widow } from './partnering/index.js';
|
|
39
|
+
import { enablePromptings, nafsPatterns, notePromptings } from './promptings/index.js';
|
|
39
40
|
import { consumeConsequences, pruneConsequences, receiveConsequences } from './prospection/index.js';
|
|
40
41
|
import { enableSkillRetention, learnByWatching, practise, seedSkills, } from './skills/index.js';
|
|
41
42
|
import { advanceSocial, applyReputationBelief, careFor, closeness, judge, markDeceased, relationshipWith, seedRelationships, socialEvent, threatAppraisal, } from './social/index.js';
|
|
@@ -153,6 +154,8 @@ export function createPerson(spec) {
|
|
|
153
154
|
enableCharacterChange(p);
|
|
154
155
|
if (on?.skillRetention)
|
|
155
156
|
enableSkillRetention(p);
|
|
157
|
+
if (on?.promptings)
|
|
158
|
+
enablePromptings(p, on.promptings);
|
|
156
159
|
return p;
|
|
157
160
|
}
|
|
158
161
|
/**
|
|
@@ -817,7 +820,7 @@ export function decide(p, affordances, opts = {}) {
|
|
|
817
820
|
const review = p.activity !== null;
|
|
818
821
|
if (review)
|
|
819
822
|
ctx.quiet = true;
|
|
820
|
-
const { record, needDeltas, command: co } = cognitionDecide(p, list, ctx);
|
|
823
|
+
const { record, needDeltas, command: co, prompts } = cognitionDecide(p, list, ctx);
|
|
821
824
|
if (review)
|
|
822
825
|
record.review = true;
|
|
823
826
|
const interruptReason = opts.interrupt ?? p.activity?.interrupt?.reason;
|
|
@@ -831,6 +834,12 @@ export function decide(p, affordances, opts = {}) {
|
|
|
831
834
|
record.narration = narrateDecision(p, record);
|
|
832
835
|
const resolutions = resolutionsOf(record);
|
|
833
836
|
const continuing = review && (record.chosenAffordanceId === null || record.chosenAffordanceId === p.activity?.affordanceId);
|
|
837
|
+
// Promptings (opt-in): a review that keeps the running activity is not a new decision point for a prompt.
|
|
838
|
+
if (prompts && !continuing) {
|
|
839
|
+
const noted = notePromptings(p, prompts, record.chosenAffordanceId, p.now);
|
|
840
|
+
if (noted.guilt > 0)
|
|
841
|
+
feel(p, 'guilt', noted.guilt, 'prompting', p.now);
|
|
842
|
+
}
|
|
834
843
|
for (const res of resolutions) {
|
|
835
844
|
res.says = voiceLine(p, res, record.id);
|
|
836
845
|
if (res.verdict === 'complied' && !continuing) {
|
|
@@ -1390,3 +1399,14 @@ export function setRetention(p, retention) {
|
|
|
1390
1399
|
p.trace.splice(0, p.trace.length - keep);
|
|
1391
1400
|
trimChronicle(p);
|
|
1392
1401
|
}
|
|
1402
|
+
/**
|
|
1403
|
+
* The nafs lens (2.4.0, read-only; see `promptings/` SCOPE): the three Qur'anic descriptions of one self (12:53,
|
|
1404
|
+
* 75:2, 89:27), each with the matching evidence in existing state. Descriptions, never a level or a rank.
|
|
1405
|
+
*/
|
|
1406
|
+
export function nafsReading(p) {
|
|
1407
|
+
let guilt = 0;
|
|
1408
|
+
for (const e of p.affect.emotions)
|
|
1409
|
+
if (e.id === 'guilt' && e.cause !== 'prompting')
|
|
1410
|
+
guilt = Math.max(guilt, e.intensity);
|
|
1411
|
+
return nafsPatterns(p, { guiltLoad: guiltLoad(p), guilt });
|
|
1412
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './promptings.ts';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './promptings.js';
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { Minute, Person, PromptingReading, PromptingRecord, PromptingResponse, PromptingResponseKind, PromptingsSpec, PromptingsState, Term, Unit } from '../types.ts';
|
|
2
|
+
export declare const PROMPTINGS_DEFAULTS: {
|
|
3
|
+
/** Pull (utility units) from which a pull against a held norm or goal is noticed. */
|
|
4
|
+
readonly noticeAt: 0.15;
|
|
5
|
+
/** Largest turning-away term at full practice (utility units). */
|
|
6
|
+
readonly turnMax: 0.8;
|
|
7
|
+
/** No turning-away term at or above this desperation (same scale as will's survival desperation). */
|
|
8
|
+
readonly survivalDesperation: 0.75;
|
|
9
|
+
/** Practice gained at a prompt where the response held (× (1 - practice)). */
|
|
10
|
+
readonly gainTurned: 0.12;
|
|
11
|
+
/** Practice gained at a prompt where the response was tried but the person assented. */
|
|
12
|
+
readonly gainAttempt: 0.04;
|
|
13
|
+
/** Practice halves over this many days without use. */
|
|
14
|
+
readonly practiceHalfLifeDays: 60;
|
|
15
|
+
/** Guilt intensity at a prompt under the `fault` reading. */
|
|
16
|
+
readonly faultGuilt: 0.15;
|
|
17
|
+
/** Recurrence pull at echo level 1 (utility units); only reached when a host sets `echoRate` > 0. */
|
|
18
|
+
readonly echoScale: 0.5;
|
|
19
|
+
readonly echoHalfLifeDays: 3;
|
|
20
|
+
readonly maxRecent: 64;
|
|
21
|
+
readonly maxResponses: 8;
|
|
22
|
+
readonly maxEcho: 32;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Turn the slice on, or reconfigure it: recent records and echo are kept, and a response whose id is already held
|
|
26
|
+
* keeps its practice and last use (the spec's `practice` only seeds new responses). Returns the state.
|
|
27
|
+
*/
|
|
28
|
+
export declare function enablePromptings(p: Person, spec?: PromptingsSpec): PromptingsState;
|
|
29
|
+
/** Practice of a response at `now`, after disuse decay. Pure. */
|
|
30
|
+
export declare function practiceAt(r: PromptingResponse, now: Minute): Unit;
|
|
31
|
+
/** Whether an option's terms carry a noticed prompt: its strongest pull and the opposing term. Pure. */
|
|
32
|
+
export declare function promptIn(terms: readonly Term[], tags: readonly string[] | undefined): {
|
|
33
|
+
source: string;
|
|
34
|
+
opposedBy: string;
|
|
35
|
+
} | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Extra terms for one option (cognition calls this before summing): the echo pull under `fault`, then the
|
|
38
|
+
* turning-away term when the option carries a prompt. Pure apart from reads; empty without the slice.
|
|
39
|
+
*/
|
|
40
|
+
export declare function promptingTerms(p: Person, action: string, terms: readonly Term[], tags: readonly string[] | undefined, now: Minute, desperation: number): Term[];
|
|
41
|
+
/** A prompt found at a decision point, handed from cognition to the composite. */
|
|
42
|
+
export interface PromptingSeen {
|
|
43
|
+
affordanceId: string;
|
|
44
|
+
action: string;
|
|
45
|
+
source: string;
|
|
46
|
+
opposedBy: string;
|
|
47
|
+
/** Whether a turning-away term was applied. */
|
|
48
|
+
turnApplied: boolean;
|
|
49
|
+
}
|
|
50
|
+
/** What `notePromptings` did, for the composite (the `fault` guilt goes through affect). */
|
|
51
|
+
export interface PromptingsNoted {
|
|
52
|
+
records: PromptingRecord[];
|
|
53
|
+
/** Guilt intensity to feel at the prompt (only under `fault`); 0 otherwise. */
|
|
54
|
+
guilt: Unit;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Record the prompts of one decision point and learn from them. `chosenId` is the chosen affordance (null when
|
|
58
|
+
* nothing was chosen). The only writer of `p.promptings` after `enablePromptings`.
|
|
59
|
+
*/
|
|
60
|
+
export declare function notePromptings(p: Person, seen: readonly PromptingSeen[], chosenId: string | null, now: Minute): PromptingsNoted;
|
|
61
|
+
/** Vocabulary for one prompt under a held reading (narration only; it changes nothing). */
|
|
62
|
+
export declare function promptingLine(reading: PromptingReading, outcome: PromptingRecord['outcome'], kind?: PromptingResponseKind): string;
|
|
63
|
+
/** Read-only summary of recent prompts (a lens; computes nothing new about the person). */
|
|
64
|
+
export interface PromptingsReadingView {
|
|
65
|
+
reading: PromptingReading;
|
|
66
|
+
readingSource?: string;
|
|
67
|
+
prompts: number;
|
|
68
|
+
turned: number;
|
|
69
|
+
passed: number;
|
|
70
|
+
assented: number;
|
|
71
|
+
responses: {
|
|
72
|
+
id: string;
|
|
73
|
+
kind: PromptingResponseKind;
|
|
74
|
+
practice: Unit;
|
|
75
|
+
}[];
|
|
76
|
+
/** Vocabulary for the last few prompts, newest last. */
|
|
77
|
+
lines: string[];
|
|
78
|
+
}
|
|
79
|
+
export declare function promptingsReading(p: Person, now?: Minute): PromptingsReadingView | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* The nafs lens (reconciliation.md §3): the three Qur'anic descriptions of one self, each with the evidence in
|
|
82
|
+
* existing state that matches it. Descriptions, not a level, a ladder or three agents; several can hold at once,
|
|
83
|
+
* and none says anything about rank before God. Inputs are passed in by the composite (open-breach weight and
|
|
84
|
+
* felt guilt from conscience and affect), so this module reads nothing it does not own. Guilt that the `fault` reading
|
|
85
|
+
* feels at a prompt is excluded, so the scrupulous reading is not mapped onto self-reproach (75:2). These mappings
|
|
86
|
+
* from state to description are engineering conventions (C), not interpretations of the verses.
|
|
87
|
+
*/
|
|
88
|
+
export interface NafsPattern {
|
|
89
|
+
name: 'urging' | 'reproaching' | 'at rest';
|
|
90
|
+
/** Qur'anic description it borrows (12:53 ammara, 75:2 lawwama, 89:27 mutmaʾinna). */
|
|
91
|
+
ref: string;
|
|
92
|
+
evident: boolean;
|
|
93
|
+
evidence: string;
|
|
94
|
+
}
|
|
95
|
+
export declare function nafsPatterns(p: Person, inputs: {
|
|
96
|
+
guiltLoad: Unit;
|
|
97
|
+
guilt: Unit;
|
|
98
|
+
}): NafsPattern[];
|
|
99
|
+
/** Validate a saved slice; a malformed slice is dropped (undefined), malformed entries are skipped. */
|
|
100
|
+
export declare function sanitizePromptings(input: unknown, now: Minute): PromptingsState | undefined;
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SCOPE (promptings and turning away, 2.4.0): a *prompt* is a pull toward an option that the person's own held
|
|
3
|
+
* norms or goals oppose, strong enough to be noticed. This module records prompts, lets the person turn from them
|
|
4
|
+
* by a practised response, and keeps a prompt apart from assent: only an assented act reaches `conscience/
|
|
5
|
+
* recordDeed`, so a prompt is never a breach (Bukhari 5269, research/reconciliation.md §3). It is opt-in
|
|
6
|
+
* (`enablePromptings`, or `PersonSpec.enable.promptings`); without it nothing here runs and existing runs replay
|
|
7
|
+
* byte for byte. It draws no randomness.
|
|
8
|
+
*
|
|
9
|
+
* Detection reads an option's traced terms and changes none of them: the pull is the sum of positive `habit`,
|
|
10
|
+
* `emotion:*`, `need:*` and `promptings:echo` terms (every positive term on an offer tagged `temptation`); it is a
|
|
11
|
+
* prompt when the pull reaches `noticeAt` while a `norm:*` or `goal:*` term on the same option is negative.
|
|
12
|
+
* Abstention windows are not detected (they veto, and carry no term). A person with a response gets one extra
|
|
13
|
+
* term, `promptings:turn` = -turnMax × practice, on that option; it is bounded and absent at survival desperation,
|
|
14
|
+
* so a desperate bodily need still wins (2:286). Each prompt is recorded once per decision point (a review that
|
|
15
|
+
* keeps the running activity records nothing) as `assented` (chosen), `turned` (not chosen, a response practised)
|
|
16
|
+
* or `passed` (not chosen, no response). Practice grows with diminishing gains once per decision point where the
|
|
17
|
+
* response was used (more when it held; a vetoed option earns none, since the will blocked it), and halves over `practiceHalfLifeDays` of disuse.
|
|
18
|
+
*
|
|
19
|
+
* Held readings (reconciliation.md §5): `whisper`, `inclination`, `intrusion` and `none` give identical decisions
|
|
20
|
+
* and differ only in the vocabulary of `promptingLine`. `fault` (the scrupulous reading, which both the tradition
|
|
21
|
+
* and clinicians reject) adds a small guilt emotion at the prompt itself, from the person's own understanding;
|
|
22
|
+
* that guilt is an emotion, never a breach, so it changes decisions only where an offer acts through guilt's
|
|
23
|
+
* tendencies. With a host-set `echoRate` > 0, `fault` also gains a recurrence pull on that action
|
|
24
|
+
* (`promptings:echo`), decaying over days: a labelled assumption from contested suppression-rebound evidence
|
|
25
|
+
* (P3), off by default. No reading earns a hidden bonus.
|
|
26
|
+
*
|
|
27
|
+
* Borrowed shapes: everyday desire, conflict and resistance (P1, P2); suppression rebound and redirection (P3,
|
|
28
|
+
* P13); practised if–then responses (P5). Every number is an engineering default, not fitted. Does not claim: the
|
|
29
|
+
* source of any thought, a measure of faith, spiritual rank, reward for resisting (H4 is acknowledged as a held
|
|
30
|
+
* belief and never computed), clinical OCD, or a cessation rate. The lenses (`promptingsReading`, `nafsReading`)
|
|
31
|
+
* only describe existing state; they store and compute nothing new about the person.
|
|
32
|
+
*/
|
|
33
|
+
import { clamp01, dexp, isObj, round } from '../core/index.js';
|
|
34
|
+
import { MINUTES_PER_DAY } from '../types.js';
|
|
35
|
+
export const PROMPTINGS_DEFAULTS = {
|
|
36
|
+
/** Pull (utility units) from which a pull against a held norm or goal is noticed. */
|
|
37
|
+
noticeAt: 0.15,
|
|
38
|
+
/** Largest turning-away term at full practice (utility units). */
|
|
39
|
+
turnMax: 0.8,
|
|
40
|
+
/** No turning-away term at or above this desperation (same scale as will's survival desperation). */
|
|
41
|
+
survivalDesperation: 0.75,
|
|
42
|
+
/** Practice gained at a prompt where the response held (× (1 - practice)). */
|
|
43
|
+
gainTurned: 0.12,
|
|
44
|
+
/** Practice gained at a prompt where the response was tried but the person assented. */
|
|
45
|
+
gainAttempt: 0.04,
|
|
46
|
+
/** Practice halves over this many days without use. */
|
|
47
|
+
practiceHalfLifeDays: 60,
|
|
48
|
+
/** Guilt intensity at a prompt under the `fault` reading. */
|
|
49
|
+
faultGuilt: 0.15,
|
|
50
|
+
/** Recurrence pull at echo level 1 (utility units); only reached when a host sets `echoRate` > 0. */
|
|
51
|
+
echoScale: 0.5,
|
|
52
|
+
echoHalfLifeDays: 3,
|
|
53
|
+
maxRecent: 64,
|
|
54
|
+
maxResponses: 8,
|
|
55
|
+
maxEcho: 32,
|
|
56
|
+
};
|
|
57
|
+
const READINGS = ['whisper', 'inclination', 'intrusion', 'fault', 'none'];
|
|
58
|
+
const KINDS = ['refuge', 'redirect', 'leave'];
|
|
59
|
+
const halve = (value, minutes, halfLifeDays) => minutes <= 0 ? value : value * dexp((-Math.LN2 * minutes) / (halfLifeDays * MINUTES_PER_DAY));
|
|
60
|
+
/**
|
|
61
|
+
* Turn the slice on, or reconfigure it: recent records and echo are kept, and a response whose id is already held
|
|
62
|
+
* keeps its practice and last use (the spec's `practice` only seeds new responses). Returns the state.
|
|
63
|
+
*/
|
|
64
|
+
export function enablePromptings(p, spec = {}) {
|
|
65
|
+
const prev = p.promptings;
|
|
66
|
+
const responses = [];
|
|
67
|
+
for (const r of spec.responses ?? []) {
|
|
68
|
+
if (!isObj(r) || typeof r.id !== 'string' || r.id === '' || !KINDS.includes(r.kind))
|
|
69
|
+
continue;
|
|
70
|
+
if (responses.some((x) => x.id === r.id) || responses.length >= PROMPTINGS_DEFAULTS.maxResponses)
|
|
71
|
+
continue;
|
|
72
|
+
const held = prev?.responses.find((x) => x.id === r.id);
|
|
73
|
+
const seed = Number.isFinite(r.practice) ? clamp01(r.practice) : 0.1;
|
|
74
|
+
responses.push(held ? { ...held, kind: r.kind } : { id: r.id, kind: r.kind, practice: seed, lastUsedAt: p.now });
|
|
75
|
+
}
|
|
76
|
+
const state = {
|
|
77
|
+
reading: spec.reading && READINGS.includes(spec.reading) ? spec.reading : 'none',
|
|
78
|
+
responses,
|
|
79
|
+
recent: prev?.recent ?? [],
|
|
80
|
+
echo: prev?.echo ?? {},
|
|
81
|
+
echoRate: Number.isFinite(spec.echoRate) ? clamp01(spec.echoRate) : 0,
|
|
82
|
+
};
|
|
83
|
+
if (spec.readingSource !== undefined)
|
|
84
|
+
state.readingSource = spec.readingSource;
|
|
85
|
+
p.promptings = state;
|
|
86
|
+
return state;
|
|
87
|
+
}
|
|
88
|
+
/** Practice of a response at `now`, after disuse decay. Pure. */
|
|
89
|
+
export function practiceAt(r, now) {
|
|
90
|
+
return clamp01(halve(r.practice, now - r.lastUsedAt, PROMPTINGS_DEFAULTS.practiceHalfLifeDays));
|
|
91
|
+
}
|
|
92
|
+
/** The response a person would use now: highest practice, ties by id. */
|
|
93
|
+
function bestResponse(s, now) {
|
|
94
|
+
let best;
|
|
95
|
+
for (const r of s.responses) {
|
|
96
|
+
const practice = practiceAt(r, now);
|
|
97
|
+
if (!best || practice > best.practice || (practice === best.practice && r.id < best.r.id))
|
|
98
|
+
best = { r, practice };
|
|
99
|
+
}
|
|
100
|
+
return best;
|
|
101
|
+
}
|
|
102
|
+
const isPull = (source, tagged) => tagged ||
|
|
103
|
+
source === 'habit' ||
|
|
104
|
+
source === 'promptings:echo' ||
|
|
105
|
+
source.startsWith('emotion:') ||
|
|
106
|
+
source.startsWith('need:');
|
|
107
|
+
/** Whether an option's terms carry a noticed prompt: its strongest pull and the opposing term. Pure. */
|
|
108
|
+
export function promptIn(terms, tags) {
|
|
109
|
+
const tagged = tags?.includes('temptation') ?? false;
|
|
110
|
+
let pull = 0;
|
|
111
|
+
let top;
|
|
112
|
+
let opposed;
|
|
113
|
+
for (const t of terms) {
|
|
114
|
+
if (t.source === 'promptings:turn')
|
|
115
|
+
continue;
|
|
116
|
+
if (t.value > 0 && isPull(t.source, tagged)) {
|
|
117
|
+
pull += t.value;
|
|
118
|
+
if (!top || t.value > top.value)
|
|
119
|
+
top = t;
|
|
120
|
+
}
|
|
121
|
+
else if (t.value < 0 &&
|
|
122
|
+
(t.source.startsWith('norm:') || t.source.startsWith('goal:')) &&
|
|
123
|
+
(!opposed || t.value < opposed.value))
|
|
124
|
+
opposed = t;
|
|
125
|
+
}
|
|
126
|
+
if (!top || !opposed || pull < PROMPTINGS_DEFAULTS.noticeAt)
|
|
127
|
+
return undefined;
|
|
128
|
+
return { source: tagged && !isPull(top.source, false) ? 'host' : top.source, opposedBy: opposed.source };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Extra terms for one option (cognition calls this before summing): the echo pull under `fault`, then the
|
|
132
|
+
* turning-away term when the option carries a prompt. Pure apart from reads; empty without the slice.
|
|
133
|
+
*/
|
|
134
|
+
export function promptingTerms(p, action, terms, tags, now, desperation) {
|
|
135
|
+
const s = p.promptings;
|
|
136
|
+
if (!s)
|
|
137
|
+
return [];
|
|
138
|
+
const out = [];
|
|
139
|
+
const e = Object.hasOwn(s.echo, action) ? s.echo[action] : undefined;
|
|
140
|
+
if (e && s.reading === 'fault') {
|
|
141
|
+
const level = halve(e.level, now - e.at, PROMPTINGS_DEFAULTS.echoHalfLifeDays);
|
|
142
|
+
const v = round(PROMPTINGS_DEFAULTS.echoScale * level);
|
|
143
|
+
if (v > 0)
|
|
144
|
+
out.push({ source: 'promptings:echo', value: v });
|
|
145
|
+
}
|
|
146
|
+
if (desperation >= PROMPTINGS_DEFAULTS.survivalDesperation)
|
|
147
|
+
return out;
|
|
148
|
+
const best = bestResponse(s, now);
|
|
149
|
+
if (!best || best.practice <= 0)
|
|
150
|
+
return out;
|
|
151
|
+
if (!promptIn([...terms, ...out], tags))
|
|
152
|
+
return out;
|
|
153
|
+
const v = round(-PROMPTINGS_DEFAULTS.turnMax * best.practice);
|
|
154
|
+
if (v < 0)
|
|
155
|
+
out.push({ source: 'promptings:turn', value: v });
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Record the prompts of one decision point and learn from them. `chosenId` is the chosen affordance (null when
|
|
160
|
+
* nothing was chosen). The only writer of `p.promptings` after `enablePromptings`.
|
|
161
|
+
*/
|
|
162
|
+
export function notePromptings(p, seen, chosenId, now) {
|
|
163
|
+
const s = p.promptings;
|
|
164
|
+
const noted = { records: [], guilt: 0 };
|
|
165
|
+
if (!s || seen.length === 0)
|
|
166
|
+
return noted;
|
|
167
|
+
const D = PROMPTINGS_DEFAULTS;
|
|
168
|
+
const best = bestResponse(s, now);
|
|
169
|
+
// One practice gain per decision point, however many options carried a prompt: the larger one if any held.
|
|
170
|
+
let gain = 0;
|
|
171
|
+
for (const x of seen) {
|
|
172
|
+
const assented = x.affordanceId === chosenId;
|
|
173
|
+
const used = x.turnApplied && best !== undefined;
|
|
174
|
+
const rec = {
|
|
175
|
+
at: now,
|
|
176
|
+
action: x.action,
|
|
177
|
+
affordanceId: x.affordanceId,
|
|
178
|
+
source: x.source,
|
|
179
|
+
opposedBy: x.opposedBy,
|
|
180
|
+
outcome: assented ? 'assented' : used ? 'turned' : 'passed',
|
|
181
|
+
};
|
|
182
|
+
if (used && best) {
|
|
183
|
+
rec.responseId = best.r.id;
|
|
184
|
+
gain = Math.max(gain, assented ? D.gainAttempt : D.gainTurned);
|
|
185
|
+
}
|
|
186
|
+
s.recent.push(rec);
|
|
187
|
+
noted.records.push(rec);
|
|
188
|
+
if (s.reading === 'fault') {
|
|
189
|
+
noted.guilt = Math.max(noted.guilt, D.faultGuilt);
|
|
190
|
+
if (s.echoRate > 0) {
|
|
191
|
+
const prev = Object.hasOwn(s.echo, x.action) ? s.echo[x.action] : undefined;
|
|
192
|
+
const level = prev ? halve(prev.level, now - prev.at, D.echoHalfLifeDays) : 0;
|
|
193
|
+
s.echo[x.action] = { level: round(clamp01(level + s.echoRate * (1 - level))), at: now };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (best && gain > 0) {
|
|
198
|
+
const practice = practiceAt(best.r, now);
|
|
199
|
+
best.r.practice = round(clamp01(practice + gain * (1 - practice)));
|
|
200
|
+
best.r.lastUsedAt = now;
|
|
201
|
+
}
|
|
202
|
+
if (s.recent.length > D.maxRecent)
|
|
203
|
+
s.recent.splice(0, s.recent.length - D.maxRecent);
|
|
204
|
+
const keys = Object.keys(s.echo);
|
|
205
|
+
if (keys.length > D.maxEcho) {
|
|
206
|
+
keys.sort((a, b) => (s.echo[a]?.at ?? 0) - (s.echo[b]?.at ?? 0) || (a < b ? -1 : 1));
|
|
207
|
+
for (const k of keys.slice(0, keys.length - D.maxEcho))
|
|
208
|
+
delete s.echo[k];
|
|
209
|
+
}
|
|
210
|
+
return noted;
|
|
211
|
+
}
|
|
212
|
+
/** Vocabulary for one prompt under a held reading (narration only; it changes nothing). */
|
|
213
|
+
export function promptingLine(reading, outcome, kind) {
|
|
214
|
+
if (outcome === 'assented')
|
|
215
|
+
return reading === 'whisper'
|
|
216
|
+
? 'a whisper came and was followed'
|
|
217
|
+
: reading === 'inclination'
|
|
218
|
+
? 'the pull of the self won'
|
|
219
|
+
: 'an urge came and was acted on';
|
|
220
|
+
const how = outcome === 'turned'
|
|
221
|
+
? kind === 'refuge'
|
|
222
|
+
? 'sought refuge and '
|
|
223
|
+
: kind === 'redirect'
|
|
224
|
+
? 'turned to something else and '
|
|
225
|
+
: ''
|
|
226
|
+
: '';
|
|
227
|
+
switch (reading) {
|
|
228
|
+
case 'whisper':
|
|
229
|
+
return `a whisper came; ${how}let it pass`;
|
|
230
|
+
case 'inclination':
|
|
231
|
+
return `the self pulled; ${how}held back`;
|
|
232
|
+
case 'intrusion':
|
|
233
|
+
return `a thought came, not who I am; ${how}let it go`;
|
|
234
|
+
case 'fault':
|
|
235
|
+
return `a thought came and felt like wrongdoing; ${how}let it pass`;
|
|
236
|
+
default:
|
|
237
|
+
return `an urge came; ${how}it passed`;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
export function promptingsReading(p, now = p.now) {
|
|
241
|
+
const s = p.promptings;
|
|
242
|
+
if (!s)
|
|
243
|
+
return undefined;
|
|
244
|
+
const count = (o) => s.recent.filter((r) => r.outcome === o).length;
|
|
245
|
+
const kindOf = (id) => s.responses.find((r) => r.id === id)?.kind;
|
|
246
|
+
const view = {
|
|
247
|
+
reading: s.reading,
|
|
248
|
+
prompts: s.recent.length,
|
|
249
|
+
turned: count('turned'),
|
|
250
|
+
passed: count('passed'),
|
|
251
|
+
assented: count('assented'),
|
|
252
|
+
responses: s.responses.map((r) => ({ id: r.id, kind: r.kind, practice: round(practiceAt(r, now)) })),
|
|
253
|
+
lines: s.recent.slice(-5).map((r) => promptingLine(s.reading, r.outcome, kindOf(r.responseId))),
|
|
254
|
+
};
|
|
255
|
+
if (s.readingSource !== undefined)
|
|
256
|
+
view.readingSource = s.readingSource;
|
|
257
|
+
return view;
|
|
258
|
+
}
|
|
259
|
+
export function nafsPatterns(p, inputs) {
|
|
260
|
+
const recent = p.promptings?.recent ?? [];
|
|
261
|
+
const assentedAgainstNorm = recent.filter((r) => r.outcome === 'assented' && r.opposedBy.startsWith('norm:'));
|
|
262
|
+
const letPass = recent.filter((r) => r.outcome !== 'assented').length;
|
|
263
|
+
const reproach = inputs.guiltLoad > 0 || inputs.guilt > 0.05;
|
|
264
|
+
return [
|
|
265
|
+
{
|
|
266
|
+
name: 'urging',
|
|
267
|
+
ref: 'Qur’an 12:53',
|
|
268
|
+
evident: assentedAgainstNorm.length > 0,
|
|
269
|
+
evidence: `${assentedAgainstNorm.length} recent prompts followed against a held norm`,
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: 'reproaching',
|
|
273
|
+
ref: 'Qur’an 75:2',
|
|
274
|
+
evident: reproach,
|
|
275
|
+
evidence: `open-breach weight ${round(inputs.guiltLoad, 2)}, felt guilt ${round(inputs.guilt, 2)}`,
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: 'at rest',
|
|
279
|
+
ref: 'Qur’an 89:27',
|
|
280
|
+
evident: letPass > 0 && assentedAgainstNorm.length === 0 && !reproach,
|
|
281
|
+
evidence: `${letPass} recent prompts let pass, none followed against a norm, no felt reproach`,
|
|
282
|
+
},
|
|
283
|
+
];
|
|
284
|
+
}
|
|
285
|
+
const unit = (x) => typeof x === 'number' && Number.isFinite(x) && x >= 0 && x <= 1;
|
|
286
|
+
const minute = (x) => typeof x === 'number' && Number.isFinite(x);
|
|
287
|
+
const str = (x) => typeof x === 'string' && x !== '' && x.length <= 200;
|
|
288
|
+
/** Validate a saved slice; a malformed slice is dropped (undefined), malformed entries are skipped. */
|
|
289
|
+
export function sanitizePromptings(input, now) {
|
|
290
|
+
if (!isObj(input) || !Array.isArray(input.responses) || !Array.isArray(input.recent) || !isObj(input.echo))
|
|
291
|
+
return undefined;
|
|
292
|
+
if (typeof input.reading !== 'string' || !READINGS.includes(input.reading))
|
|
293
|
+
return undefined;
|
|
294
|
+
const D = PROMPTINGS_DEFAULTS;
|
|
295
|
+
const responses = [];
|
|
296
|
+
for (const r of input.responses.slice(0, D.maxResponses)) {
|
|
297
|
+
if (!isObj(r) || !str(r.id) || !KINDS.includes(r.kind) || !unit(r.practice))
|
|
298
|
+
continue;
|
|
299
|
+
if (!minute(r.lastUsedAt) || r.lastUsedAt > now || responses.some((x) => x.id === r.id))
|
|
300
|
+
continue;
|
|
301
|
+
responses.push({
|
|
302
|
+
id: r.id,
|
|
303
|
+
kind: r.kind,
|
|
304
|
+
practice: r.practice,
|
|
305
|
+
lastUsedAt: r.lastUsedAt,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
const recent = [];
|
|
309
|
+
for (const r of input.recent.slice(-D.maxRecent)) {
|
|
310
|
+
if (!isObj(r) || !minute(r.at) || r.at > now || !str(r.action) || !str(r.affordanceId))
|
|
311
|
+
continue;
|
|
312
|
+
if (!str(r.source) || !str(r.opposedBy))
|
|
313
|
+
continue;
|
|
314
|
+
if (r.outcome !== 'passed' && r.outcome !== 'turned' && r.outcome !== 'assented')
|
|
315
|
+
continue;
|
|
316
|
+
const rec = {
|
|
317
|
+
at: r.at,
|
|
318
|
+
action: r.action,
|
|
319
|
+
affordanceId: r.affordanceId,
|
|
320
|
+
source: r.source,
|
|
321
|
+
opposedBy: r.opposedBy,
|
|
322
|
+
outcome: r.outcome,
|
|
323
|
+
};
|
|
324
|
+
if (str(r.responseId))
|
|
325
|
+
rec.responseId = r.responseId;
|
|
326
|
+
recent.push(rec);
|
|
327
|
+
}
|
|
328
|
+
const echo = {};
|
|
329
|
+
for (const k of Object.keys(input.echo).slice(0, D.maxEcho)) {
|
|
330
|
+
if (k === '__proto__' || !str(k))
|
|
331
|
+
continue;
|
|
332
|
+
const e = input.echo[k];
|
|
333
|
+
if (!isObj(e) || !unit(e.level) || !minute(e.at) || e.at > now)
|
|
334
|
+
continue;
|
|
335
|
+
echo[k] = { level: e.level, at: e.at };
|
|
336
|
+
}
|
|
337
|
+
const rate = input.echoRate;
|
|
338
|
+
const out = {
|
|
339
|
+
reading: input.reading,
|
|
340
|
+
responses,
|
|
341
|
+
recent,
|
|
342
|
+
echo,
|
|
343
|
+
echoRate: typeof rate === 'number' && Number.isFinite(rate) && rate >= 0 ? Math.min(1, rate) : 0,
|
|
344
|
+
};
|
|
345
|
+
if (str(input.readingSource))
|
|
346
|
+
out.readingSource = input.readingSource;
|
|
347
|
+
return out;
|
|
348
|
+
}
|
package/dist/restore.js
CHANGED
|
@@ -20,6 +20,7 @@ import { sanitizeMethods } from './methods/index.js';
|
|
|
20
20
|
import { migrate } from './migrate.js';
|
|
21
21
|
import { sanitizeBonds } from './partnering/index.js';
|
|
22
22
|
import { cleanRetention, createPerson, traceLimit } from './person.js';
|
|
23
|
+
import { sanitizePromptings } from './promptings/index.js';
|
|
23
24
|
import { sanitizeProspection } from './prospection/index.js';
|
|
24
25
|
import { sanitizeSkillRetention } from './skills/index.js';
|
|
25
26
|
import { sanitizeGroups, sanitizeImpressions } from './social/index.js';
|
|
@@ -61,6 +62,7 @@ const OPTIONAL_KEYS = new Set([
|
|
|
61
62
|
'retention',
|
|
62
63
|
'methods',
|
|
63
64
|
'prospection',
|
|
65
|
+
'promptings',
|
|
64
66
|
]);
|
|
65
67
|
/** Kind of a JSON value for the per-field type check in `restore`. */
|
|
66
68
|
const kindOf = (x) => (Array.isArray(x) ? 'array' : x === null ? 'null' : typeof x);
|
|
@@ -174,6 +176,7 @@ export function restore(input) {
|
|
|
174
176
|
keepValid(out, 'retention', cleanRetention);
|
|
175
177
|
keepValid(out, 'methods', sanitizeMethods);
|
|
176
178
|
keepValid(out, 'prospection', (x) => sanitizeProspection(x, out.now, out.id));
|
|
179
|
+
keepValid(out, 'promptings', (x) => sanitizePromptings(x, out.now));
|
|
177
180
|
boundLists(out);
|
|
178
181
|
return out;
|
|
179
182
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -827,7 +827,7 @@ export declare const PERSON_SCHEMA = "human/person@1";
|
|
|
827
827
|
* and an activity that would cover a closing stretch is reviewed when the stretch begins (`isUnderWay`,
|
|
828
828
|
* `dutyReviewAt`). Person shape unchanged.
|
|
829
829
|
*/
|
|
830
|
-
export declare const ENGINE_VERSION = "2.
|
|
830
|
+
export declare const ENGINE_VERSION = "2.4.0";
|
|
831
831
|
/** A pregnancy carried by this person (host-driven; see `family/`). */
|
|
832
832
|
export interface Pregnancy {
|
|
833
833
|
fatherId: PersonId;
|
|
@@ -992,6 +992,8 @@ export interface Person {
|
|
|
992
992
|
methods?: MethodsState;
|
|
993
993
|
/** Received future consequences, absent until attended testimony supplies them. */
|
|
994
994
|
prospection?: ProspectionState;
|
|
995
|
+
/** Noticed promptings and practised turnings-away (2.4.0, opt-in via `enablePromptings`). Owned by `promptings/`. */
|
|
996
|
+
promptings?: PromptingsState;
|
|
995
997
|
/** Practice consolidation (1.8.0, opt-in via `enableSkillRetention`). Owned by `skills/`. */
|
|
996
998
|
skillRetention?: {
|
|
997
999
|
consolidationHours: number;
|
|
@@ -1295,7 +1297,8 @@ export interface SuggestionResolution {
|
|
|
1295
1297
|
export interface Term {
|
|
1296
1298
|
/** 'need:food', 'norm:salah', 'commitment:c3', 'commitment' (promise inertia of the running activity),
|
|
1297
1299
|
* 'goal:g1', 'habit', 'emotion:fear:risky' (emotion id, then the tag it acts through), 'effort', 'risk',
|
|
1298
|
-
* 'suggestion:player', 'social:p2', 'expectation', 'autonomy' (reactance to a pushing voice)
|
|
1300
|
+
* 'suggestion:player', 'social:p2', 'expectation', 'autonomy' (reactance to a pushing voice),
|
|
1301
|
+
* 'promptings:turn' (a practised turning-away), 'promptings:echo' (recurrence under the `fault` reading) */
|
|
1299
1302
|
source: string;
|
|
1300
1303
|
value: number;
|
|
1301
1304
|
}
|
|
@@ -1383,8 +1386,63 @@ export interface PersonSpec {
|
|
|
1383
1386
|
yearbook?: boolean;
|
|
1384
1387
|
character?: boolean;
|
|
1385
1388
|
skillRetention?: boolean;
|
|
1389
|
+
/** Promptings and turning away (2.4.0): the same as `enablePromptings(p, spec)` after creation. */
|
|
1390
|
+
promptings?: PromptingsSpec;
|
|
1386
1391
|
};
|
|
1387
1392
|
}
|
|
1393
|
+
/**
|
|
1394
|
+
* How a person understands their own unwanted inner pulls (a held reading, set by the host, with provenance).
|
|
1395
|
+
* `whisper`, `inclination` and `intrusion` share mechanics and differ only in vocabulary; `fault` (the scrupulous
|
|
1396
|
+
* reading) adds guilt at the prompt itself; `none` is neutral vocabulary. See research/reconciliation.md §5.
|
|
1397
|
+
*/
|
|
1398
|
+
export type PromptingReading = 'whisper' | 'inclination' | 'intrusion' | 'fault' | 'none';
|
|
1399
|
+
/** A practised turning-away: seeking refuge, redirecting attention, or simply leaving the thought. */
|
|
1400
|
+
export type PromptingResponseKind = 'refuge' | 'redirect' | 'leave';
|
|
1401
|
+
export interface PromptingResponse {
|
|
1402
|
+
id: string;
|
|
1403
|
+
kind: PromptingResponseKind;
|
|
1404
|
+
/** Skill-like practice 0..1 as of `lastUsedAt`; it decays slowly with disuse. */
|
|
1405
|
+
practice: Unit;
|
|
1406
|
+
lastUsedAt: Minute;
|
|
1407
|
+
}
|
|
1408
|
+
export interface PromptingRecord {
|
|
1409
|
+
at: Minute;
|
|
1410
|
+
action: string;
|
|
1411
|
+
affordanceId: string;
|
|
1412
|
+
/** The strongest pull: 'habit', 'emotion:*', 'need:*', 'promptings:echo' or 'host' (a `temptation` tag). */
|
|
1413
|
+
source: string;
|
|
1414
|
+
/** The opposing term: 'norm:x' or 'goal:g'. */
|
|
1415
|
+
opposedBy: string;
|
|
1416
|
+
outcome: 'passed' | 'turned' | 'assented';
|
|
1417
|
+
/** The response that was practised at this prompt, if any. */
|
|
1418
|
+
responseId?: string;
|
|
1419
|
+
}
|
|
1420
|
+
export interface PromptingsState {
|
|
1421
|
+
reading: PromptingReading;
|
|
1422
|
+
/** Provenance of the reading (upbringing, a teacher, a book id), as for held norms. */
|
|
1423
|
+
readingSource?: string;
|
|
1424
|
+
responses: PromptingResponse[];
|
|
1425
|
+
/** Recent prompts, oldest first, bounded (`PROMPTINGS_DEFAULTS.maxRecent`). */
|
|
1426
|
+
recent: PromptingRecord[];
|
|
1427
|
+
/** Recurrence per action under the `fault` reading (an off-by-default assumption; see `echoRate`). */
|
|
1428
|
+
echo: Record<string, {
|
|
1429
|
+
level: Unit;
|
|
1430
|
+
at: Minute;
|
|
1431
|
+
}>;
|
|
1432
|
+
/** Echo gained per prompt under `fault`. Default 0: the recurrence term is a host-set assumption (P3 contested). */
|
|
1433
|
+
echoRate: number;
|
|
1434
|
+
}
|
|
1435
|
+
/** What a host hands `enablePromptings`. */
|
|
1436
|
+
export interface PromptingsSpec {
|
|
1437
|
+
reading?: PromptingReading;
|
|
1438
|
+
readingSource?: string;
|
|
1439
|
+
responses?: {
|
|
1440
|
+
id: string;
|
|
1441
|
+
kind: PromptingResponseKind;
|
|
1442
|
+
practice?: Unit;
|
|
1443
|
+
}[];
|
|
1444
|
+
echoRate?: number;
|
|
1445
|
+
}
|
|
1388
1446
|
/** What the body is doing over an interval. */
|
|
1389
1447
|
export interface BodyLoad {
|
|
1390
1448
|
effort: Unit;
|
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.4.0';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adam0white/human-framework",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.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",
|