@adam0white/human-framework 2.19.0 → 2.20.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 +11 -0
- package/dist/body/body.d.ts +10 -0
- package/dist/body/body.js +15 -0
- package/dist/cognition/cognition.d.ts +37 -0
- package/dist/cognition/cognition.js +3 -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 +3 -9
- 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 +85 -4
- package/dist/qalb.d.ts +28 -0
- package/dist/qalb.js +67 -0
- package/dist/social/social.js +12 -2
- package/dist/types.d.ts +4 -0
- package/dist/will/will.d.ts +12 -0
- package/dist/will/will.js +21 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -529,3 +529,14 @@ wording, the generic fact sentence still includes that id so the claim is not hi
|
|
|
529
529
|
Stock village outcomes, birth/marriage memories, routine fallbacks and generated
|
|
530
530
|
community log details use the same per-person option. World-supplied outcome
|
|
531
531
|
summaries and routine summaries remain exact.
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
### Authored setup and explicit host effects
|
|
535
|
+
|
|
536
|
+
Hosts may pass `habits: Habit[]` and `expectations: ActionExpectation[]` to `createPerson`. The framework copies records and their nested objects in order. Invalid or over-bound setup throws `RangeError`; omission preserves prior state. Duplicates remain authored records, with the existing matcher semantics.
|
|
537
|
+
|
|
538
|
+
Use `gestureVoiceTrust(p, voiceId, amount, { reason, at?, action? })` for an explicit signed gesture toward an existing advising voice. It records the realized delta in bounded history and returns `undefined` for a missing voice. Reasons must be nonempty strings; optional action must be a string. Use `addTiredness(p, { sleepPressure?, exertion? })` for positive host-caused costs. Neither advances time or draws randomness. Host magnitudes are engineering premises.
|
|
539
|
+
|
|
540
|
+
`confirmObservedReputation(p, prop, truth, now, table?)` replaces the manual `credence → confirm → applyReputationBelief` sequence. Supply only a fact the person could observe. It does not run attention or invent a moral judgement.
|
|
541
|
+
|
|
542
|
+
`qalbReading(p)` is a read-only, sourced narration lens over existing records. Its detached categorical rows are this person's recorded understanding and evidence. They are never a spiritual health score, a ruling, a rank, or a decision input. Do not present mood, low social trust or missing memories as evidence of poor faith.
|
package/dist/body/body.d.ts
CHANGED
|
@@ -154,6 +154,16 @@ export declare function fastingDamping(b: Pick<BodyState, 'satiety' | 'hydration
|
|
|
154
154
|
* fast: perceived hunger is damped (see SCOPE, N2b); true values, capacity and health are unchanged.
|
|
155
155
|
*/
|
|
156
156
|
export declare function readBody(p: Person, params?: BodyParams, ctx?: BodyPerceptionContext): BodyReadout;
|
|
157
|
+
/**
|
|
158
|
+
* SCOPE (host-caused tiredness): add nonnegative sleep-pressure and exertion costs, clamped to 0..1.
|
|
159
|
+
* The host supplies magnitudes; these are not calibrated claims. Absent, negative and non-finite costs
|
|
160
|
+
* do nothing. A dead body is unchanged, as with consume. Writes only these two body fields: no ticking,
|
|
161
|
+
* sleep transition, depletion, memory, RNG or other effects. Recovery remains with the existing body APIs.
|
|
162
|
+
*/
|
|
163
|
+
export declare function addTiredness(p: Person, costs: {
|
|
164
|
+
sleepPressure?: number;
|
|
165
|
+
exertion?: number;
|
|
166
|
+
}): void;
|
|
157
167
|
/** Eat, drink or take pain relief. `relief` lowers current pain; it then relaxes back toward its sources. */
|
|
158
168
|
export declare function consume(p: Person, deltas: {
|
|
159
169
|
food?: number;
|
package/dist/body/body.js
CHANGED
|
@@ -578,6 +578,21 @@ export function readBody(p, params = BODY_DEFAULTS, ctx) {
|
|
|
578
578
|
},
|
|
579
579
|
};
|
|
580
580
|
}
|
|
581
|
+
/**
|
|
582
|
+
* SCOPE (host-caused tiredness): add nonnegative sleep-pressure and exertion costs, clamped to 0..1.
|
|
583
|
+
* The host supplies magnitudes; these are not calibrated claims. Absent, negative and non-finite costs
|
|
584
|
+
* do nothing. A dead body is unchanged, as with consume. Writes only these two body fields: no ticking,
|
|
585
|
+
* sleep transition, depletion, memory, RNG or other effects. Recovery remains with the existing body APIs.
|
|
586
|
+
*/
|
|
587
|
+
export function addTiredness(p, costs) {
|
|
588
|
+
if (!p.body.alive)
|
|
589
|
+
return;
|
|
590
|
+
for (const key of ['sleepPressure', 'exertion']) {
|
|
591
|
+
const amount = costs[key];
|
|
592
|
+
if (amount !== undefined && Number.isFinite(amount) && amount > 0)
|
|
593
|
+
p.body[key] = clamp01(p.body[key] + amount);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
581
596
|
/** Eat, drink or take pain relief. `relief` lowers current pain; it then relaxes back toward its sources. */
|
|
582
597
|
export function consume(p, deltas) {
|
|
583
598
|
const b = p.body;
|
|
@@ -1,3 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SCOPE: scoring of offered options. `consider` sums the utility terms listed in docs/framework.md
|
|
3
|
+
* ("Utility of an option"): need terms use the person's need urgencies times the gain they *believe* an
|
|
4
|
+
* option gives (the advertisement blended with learned expectation, times believed success); norm,
|
|
5
|
+
* commitment/goal, habit, emotion-tendency, social, effort, risk, material, precommitment and suggestion
|
|
6
|
+
* terms come from the owning faculties or from simple coefficient forms. Traits and values only scale
|
|
7
|
+
* terms. `decide` processes affordances in stable id order, scores them all and hands the list to `will`
|
|
8
|
+
* for vetoes and selection. Borrowed shapes: additive multi-attribute utility with a saturating money
|
|
9
|
+
* term (diminishing marginal value of material gain), prediction-error-adjusted action values
|
|
10
|
+
* (model-free cached values alongside the host's model-based advertisement). It does NOT claim calibrated
|
|
11
|
+
* weights, rational-choice optimality, or that a weighted sum exhausts human deliberation; every term is
|
|
12
|
+
* kept in the trace so the UI can show why.
|
|
13
|
+
*
|
|
14
|
+
* SCOPE (lane will+cognition, 2026-10-03): several voices add one `suggestion:<voiceId>` term each, with
|
|
15
|
+
* reactance summed into one `autonomy` term; remembered advice adds `suggestion:remembered:<source>` terms
|
|
16
|
+
* (see `rememberedTerms` and `will.rememberAdvice`). Scarcity (N13): a host-supplied 0..1 shortfall raises the
|
|
17
|
+
* weight of the material term, shrinks its saturation scale so small sums still count, and raises the
|
|
18
|
+
* `need:safety` term. Borrowed shapes: diminishing marginal utility of wealth (the same sum weighs more to
|
|
19
|
+
* someone who has less) and the scarcity account of Mullainathan & Shafir, where a shortfall captures
|
|
20
|
+
* attention and raises the felt value of the scarce resource. Neither source is catalogued in
|
|
21
|
+
* research/empirical-models.md yet; the coefficients are engineering defaults. It does not model the claimed
|
|
22
|
+
* cognitive cost of scarcity ("bandwidth tax"), debt itself (a host ledger plus commitments), or present
|
|
23
|
+
* bias; and because losses are amplified too, a host must carry the benefit of paying a debt (a commitment,
|
|
24
|
+
* an advertised `safety` gain) or paying looks only like a loss. The spec's "security need" is read as the
|
|
25
|
+
* `safety` need: `security` is a value, not a need, and no need is added.
|
|
26
|
+
*
|
|
27
|
+
* SCOPE (2.8.0, both additive): an offer naming `alongside` partners (people already taking that course) gets one
|
|
28
|
+
* `together:<id>` term per known, living partner, `togetherScale` × closeness, without belonging satiation: the
|
|
29
|
+
* pull is not to be left behind, which stays when the pair is together and belonging is full (The Rope's resting
|
|
30
|
+
* partner, 2026-10-18 reproduction: belonging terms near 0.01 against inertia of 0.07-0.15). Shape only, after
|
|
31
|
+
* joint commitment and the sense of commitment in joint action (Gilbert 1990; Michael and Pacherie 2015; cited unaudited, research/empirical-models.md §6b); it does
|
|
32
|
+
* not model who leads, negotiation, or obligations between the partners. With opt-in `body.fullness`, an offer
|
|
33
|
+
* that advertises food, water or relief while that need is past its comfort threshold gets `full:<need>` =
|
|
34
|
+
* -fullnessScale × gain × over²: eating or drinking when full is mildly unpleasant (negative alliesthesia, unaudited citation, Cabanac
|
|
35
|
+
* 1971), so idle choice stops cycling short consumption at full hydration (The Rope, owner Kestrel export
|
|
36
|
+
* 2026-10-19). It models no bladder, sensory-specific satiety by food, or overeating for comfort.
|
|
37
|
+
*/
|
|
1
38
|
import type { BodyPerceptionContext } from '../body/index.ts';
|
|
2
39
|
import { type HabitContext } from '../habits/index.ts';
|
|
3
40
|
import { type PromptingSeen } from '../promptings/index.ts';
|
|
@@ -40,6 +40,7 @@ import { agendaTermsWithGoalEstimates } from '../agenda/index.js';
|
|
|
40
40
|
import { trustOf } from '../beliefs/index.js';
|
|
41
41
|
import { normTerms } from '../conscience/index.js';
|
|
42
42
|
import { clamp01, cmpStr, dexp, minuteOfDay, round } from '../core/index.js';
|
|
43
|
+
import { keysOf } from '../core/keys.js';
|
|
43
44
|
import { curiosityTerms } from '../curiosity/index.js';
|
|
44
45
|
import { habitEase, habitPull } from '../habits/index.js';
|
|
45
46
|
import { expectedEffect } from '../memory/index.js';
|
|
@@ -270,7 +271,7 @@ export function consider(p, aff, ctx) {
|
|
|
270
271
|
};
|
|
271
272
|
// Needs: urgency × believed gain × believed success.
|
|
272
273
|
const expected = expectedEffect(p, aff);
|
|
273
|
-
for (const id of
|
|
274
|
+
for (const id of keysOf(expected.needs)) {
|
|
274
275
|
const gain = expected.needs[id] ?? 0;
|
|
275
276
|
if (gain === 0)
|
|
276
277
|
continue;
|
|
@@ -296,7 +297,7 @@ export function consider(p, aff, ctx) {
|
|
|
296
297
|
let relevance = 1;
|
|
297
298
|
if (expected.valence > 0) {
|
|
298
299
|
let served = -1;
|
|
299
|
-
for (const id of
|
|
300
|
+
for (const id of keysOf(expected.needs))
|
|
300
301
|
if ((expected.needs[id] ?? 0) > 0)
|
|
301
302
|
served = Math.max(served, urgencyOf(ctx.needs, id));
|
|
302
303
|
if (served >= 0)
|
package/dist/core/index.d.ts
CHANGED
package/dist/core/index.js
CHANGED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Own enumerable string keys of a typed record, in Object.keys order.
|
|
3
|
+
* Callers must supply records whose runtime keys match their declared key type.
|
|
4
|
+
* This only types enumeration; it does not sort, validate keys or change state.
|
|
5
|
+
*/
|
|
6
|
+
export declare function keysOf<K extends string>(record: Partial<Record<K, unknown>>): K[];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Own enumerable string keys of a typed record, in Object.keys order.
|
|
3
|
+
* Callers must supply records whose runtime keys match their declared key type.
|
|
4
|
+
* This only types enumeration; it does not sort, validate keys or change state.
|
|
5
|
+
*/
|
|
6
|
+
export function keysOf(record) {
|
|
7
|
+
return Object.keys(record);
|
|
8
|
+
}
|
package/dist/family/family.js
CHANGED
|
@@ -88,7 +88,7 @@ function sanitizeAptitudes(x) {
|
|
|
88
88
|
out[k] = clamp(v, 0.25, 4);
|
|
89
89
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
90
90
|
}
|
|
91
|
-
/** Validate a saved family slice;
|
|
91
|
+
/** Validate a saved family slice; drop malformed pregnancies, keep usable fields. Undefined when none remain. @internal */
|
|
92
92
|
export function sanitizeFamily(x) {
|
|
93
93
|
if (!isObj(x))
|
|
94
94
|
return undefined;
|
|
@@ -101,9 +101,9 @@ export function sanitizeFamily(x) {
|
|
|
101
101
|
if (pg &&
|
|
102
102
|
typeof pg === 'object' &&
|
|
103
103
|
typeof pg.fatherId === 'string' &&
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
isNum(pg.conceivedAt) &&
|
|
105
|
+
isNum(pg.dueAt) &&
|
|
106
|
+
isNum(pg.seed))
|
|
107
107
|
out.pregnancy = { fatherId: pg.fatherId, conceivedAt: pg.conceivedAt, dueAt: pg.dueAt, seed: pg.seed };
|
|
108
108
|
if (isNum(f.attachment))
|
|
109
109
|
out.attachment = clamp01(f.attachment);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Human Framework public surface. */
|
|
2
|
-
export declare const FRAMEWORK_VERSION = "2.
|
|
2
|
+
export declare const FRAMEWORK_VERSION = "2.20.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, MakeUpReceipt, PrayerCalendar, PrayerSchedule, PrayerTimes, Retimer, } from './agenda/index.ts';
|
|
@@ -28,6 +28,7 @@ export type { MigrationStep } from './migrate.ts';
|
|
|
28
28
|
export { MIGRATIONS, migratableVersions, migrate } from './migrate.ts';
|
|
29
29
|
export * from './narrate/index.ts';
|
|
30
30
|
export * from './needs/index.ts';
|
|
31
|
+
export { confirmObservedReputation } from './observed-reputation.ts';
|
|
31
32
|
export * from './partnering/index.ts';
|
|
32
33
|
export { enablePersistence, PERSISTENCE_DEFAULTS, pursuitOf } from './persistence/index.ts';
|
|
33
34
|
export * from './person.ts';
|
|
@@ -38,6 +39,7 @@ export { adoptProjectTask, considerProjects, enableProjects, PROJECTS_DEFAULTS,
|
|
|
38
39
|
export * from './promptings/index.ts';
|
|
39
40
|
export type { ConsequenceEvidence, PlanRoute, PlanStep } from './prospection/index.ts';
|
|
40
41
|
export { bodyForecastFor, bodyForecastTerm, consequenceEvidence, enablePlanning, FORECAST_DEFAULTS, forecastLine, forecastThresholds, PROSPECTION_DEFAULTS, seedConsequences, } from './prospection/index.ts';
|
|
42
|
+
export { type QalbDescription, qalbReading } from './qalb.ts';
|
|
41
43
|
export { fingerprint, restore, snapshot } from './restore.ts';
|
|
42
44
|
export * from './review/index.ts';
|
|
43
45
|
export * from './scenarios/index.ts';
|
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.20.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);
|
|
@@ -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
|
@@ -27,13 +27,13 @@ import { ageCharacter, enableCharacterChange, noteCharacterDay, noteCharacterSoc
|
|
|
27
27
|
import { closeDay, enableYearbook, endDay, noteAnswer, noteCommitments, noteDecision, noteMood, noteOutcome, trimChronicle, } from './chronicle/index.js';
|
|
28
28
|
import { decide as cognitionDecide, desperationOf, prospectiveRoute, scoreAll } from './cognition/index.js';
|
|
29
29
|
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';
|
|
30
|
+
import { clamp01, clampSigned, createRng, dayOf, isObj, keysOf, minuteOfDay, round } from './core/index.js';
|
|
31
31
|
import { enableCuriosity, noteCuriosity } from './curiosity/index.js';
|
|
32
32
|
import { ambientBodyParams, ambientModifiers, ambientMood, ambientNeeds } from './environment/index.js';
|
|
33
33
|
import { aptitudeOf, createFamily, pregnancyModifiers } from './family/index.js';
|
|
34
|
-
import { advanceHabits, reinforce, withholdCued } from './habits/index.js';
|
|
34
|
+
import { advanceHabits, HABIT_DEFAULTS, reinforce, withholdCued } from './habits/index.js';
|
|
35
35
|
import { ageYears, learningMultiplier, lifeModifiers } from './lifecourse/index.js';
|
|
36
|
-
import { advanceMemory, createMemory, enableGists, foldGists, learnOutcome, recallByCue, remember, } from './memory/index.js';
|
|
36
|
+
import { advanceMemory, createMemory, enableGists, foldGists, learnOutcome, MEMORY_DEFAULTS, recallByCue, remember, } from './memory/index.js';
|
|
37
37
|
import { intentionFor, narrateDecision, voiceLine } from './narrate/index.js';
|
|
38
38
|
import { advanceNeeds, createNeeds, levelOf, meanSatisfaction, readNeeds, satisfy, urgencyOf, } from './needs/index.js';
|
|
39
39
|
import { widow } from './partnering/index.js';
|
|
@@ -114,7 +114,56 @@ function fill(keys, given, def) {
|
|
|
114
114
|
}
|
|
115
115
|
return out;
|
|
116
116
|
}
|
|
117
|
+
function validateHostSetup(spec) {
|
|
118
|
+
const finite = (n) => Number.isFinite(n);
|
|
119
|
+
const unit = (n) => finite(n) && n >= 0 && n <= 1;
|
|
120
|
+
const count = (n) => Number.isSafeInteger(n) && n >= 0;
|
|
121
|
+
const optional = (n, valid) => n === undefined || valid(n);
|
|
122
|
+
if (spec.habits !== undefined) {
|
|
123
|
+
if (!Array.isArray(spec.habits) || spec.habits.length > HABIT_DEFAULTS.maxHabits)
|
|
124
|
+
throw new RangeError('Invalid habits setup bound');
|
|
125
|
+
for (const h of spec.habits) {
|
|
126
|
+
if (!h ||
|
|
127
|
+
!isObj(h.cue) ||
|
|
128
|
+
typeof h.action !== 'string' ||
|
|
129
|
+
h.action.length === 0 ||
|
|
130
|
+
!unit(h.strength) ||
|
|
131
|
+
!count(h.repetitions) ||
|
|
132
|
+
!finite(h.lastAt) ||
|
|
133
|
+
!optional(h.withheld, count) ||
|
|
134
|
+
!optional(h.lastWithheldAt, finite) ||
|
|
135
|
+
!optional(h.craving, unit) ||
|
|
136
|
+
!optional(h.cue.hour, (n) => finite(n) && n >= 0 && n < 24) ||
|
|
137
|
+
(h.cue.placeId !== undefined && typeof h.cue.placeId !== 'string') ||
|
|
138
|
+
(h.cue.after !== undefined && typeof h.cue.after !== 'string'))
|
|
139
|
+
throw new RangeError('Invalid habit setup record');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (spec.expectations !== undefined) {
|
|
143
|
+
if (!Array.isArray(spec.expectations) || spec.expectations.length > MEMORY_DEFAULTS.maxExpectations)
|
|
144
|
+
throw new RangeError('Invalid expectations setup bound');
|
|
145
|
+
for (const e of spec.expectations) {
|
|
146
|
+
if (!e ||
|
|
147
|
+
typeof e.key !== 'string' ||
|
|
148
|
+
e.key.length === 0 ||
|
|
149
|
+
!isObj(e.needs) ||
|
|
150
|
+
!unit(e.successRate) ||
|
|
151
|
+
!count(e.samples) ||
|
|
152
|
+
!finite(e.valence) ||
|
|
153
|
+
Math.abs(e.valence) > 1 ||
|
|
154
|
+
Object.entries(e.needs).some(([key, n]) => ![...PHYSIOLOGICAL_NEEDS, ...PSYCHOLOGICAL_NEEDS].some((need) => need === key) ||
|
|
155
|
+
(n !== undefined && !finite(n))))
|
|
156
|
+
throw new RangeError('Invalid expectation setup record');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* SCOPE (host setup): optional habits and expectations copy authored records, including nested cues and
|
|
162
|
+
* need deltas, in source order. Invalid numbers, domains or collection bounds reject with RangeError;
|
|
163
|
+
* setup never clamps, evicts, learns, advances time or consumes RNG. Omitted options keep legacy state.
|
|
164
|
+
*/
|
|
117
165
|
export function createPerson(spec) {
|
|
166
|
+
validateHostSetup(spec);
|
|
118
167
|
const now = spec.now ?? 0;
|
|
119
168
|
const traits = fill(TRAIT_KEYS, spec.traits, 0.5);
|
|
120
169
|
const values = fill(VALUE_KEYS, spec.values, 0.5);
|
|
@@ -143,6 +192,38 @@ export function createPerson(spec) {
|
|
|
143
192
|
trace: [],
|
|
144
193
|
nextDecision: 0,
|
|
145
194
|
};
|
|
195
|
+
if (spec.habits)
|
|
196
|
+
p.habits = spec.habits.map((h) => {
|
|
197
|
+
const cue = {};
|
|
198
|
+
if (h.cue.hour !== undefined)
|
|
199
|
+
cue.hour = h.cue.hour;
|
|
200
|
+
if (h.cue.placeId !== undefined)
|
|
201
|
+
cue.placeId = h.cue.placeId;
|
|
202
|
+
if (h.cue.after !== undefined)
|
|
203
|
+
cue.after = h.cue.after;
|
|
204
|
+
const out = {
|
|
205
|
+
cue,
|
|
206
|
+
action: h.action,
|
|
207
|
+
strength: h.strength,
|
|
208
|
+
repetitions: h.repetitions,
|
|
209
|
+
lastAt: h.lastAt,
|
|
210
|
+
};
|
|
211
|
+
if (h.craving !== undefined)
|
|
212
|
+
out.craving = h.craving;
|
|
213
|
+
if (h.withheld !== undefined)
|
|
214
|
+
out.withheld = h.withheld;
|
|
215
|
+
if (h.lastWithheldAt !== undefined)
|
|
216
|
+
out.lastWithheldAt = h.lastWithheldAt;
|
|
217
|
+
return out;
|
|
218
|
+
});
|
|
219
|
+
if (spec.expectations)
|
|
220
|
+
p.memory.expectations = spec.expectations.map((e) => ({
|
|
221
|
+
key: e.key,
|
|
222
|
+
needs: Object.fromEntries(Object.entries(e.needs)),
|
|
223
|
+
successRate: e.successRate,
|
|
224
|
+
samples: e.samples,
|
|
225
|
+
valence: e.valence,
|
|
226
|
+
}));
|
|
146
227
|
if (spec.lexicon)
|
|
147
228
|
p.lexicon = structuredClone(spec.lexicon);
|
|
148
229
|
if (spec.retention)
|
|
@@ -1399,7 +1480,7 @@ export function finish(p, outcome, opts = {}) {
|
|
|
1399
1480
|
const realized = realizedNeedDeltas(act, given, before, after);
|
|
1400
1481
|
// Felt valence.
|
|
1401
1482
|
let felt = 0;
|
|
1402
|
-
for (const id of
|
|
1483
|
+
for (const id of keysOf(realized)) {
|
|
1403
1484
|
felt += D.feltGain * (D.feltBase + urgencyOf(before, id)) * (realized[id] ?? 0);
|
|
1404
1485
|
}
|
|
1405
1486
|
if (outcome.status === 'failed')
|
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/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
|
@@ -1509,6 +1509,10 @@ export interface PersonSpec {
|
|
|
1509
1509
|
values?: Partial<Values>;
|
|
1510
1510
|
norms?: HeldNorm[];
|
|
1511
1511
|
skills?: Record<string, Unit>;
|
|
1512
|
+
/** Authored habits, copied in order. Invalid or over-bound setup throws RangeError. */
|
|
1513
|
+
habits?: Habit[];
|
|
1514
|
+
/** Authored expectations, copied in order. Invalid or over-bound setup throws RangeError. */
|
|
1515
|
+
expectations?: ActionExpectation[];
|
|
1512
1516
|
body?: Partial<Omit<BodyState, 'injuries' | 'illnesses' | 'nextId'>>;
|
|
1513
1517
|
needs?: Partial<NeedReservoirs>;
|
|
1514
1518
|
relationships?: (Partial<Relationship> & {
|
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.20.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",
|