@adam0white/human-framework 2.1.4 → 2.2.1
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 +31 -3
- package/dist/cognition/cognition.js +5 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/methods/index.d.ts +1 -0
- package/dist/methods/index.js +1 -0
- package/dist/methods/methods.d.ts +50 -0
- package/dist/methods/methods.js +234 -0
- package/dist/migrate.js +12 -1
- package/dist/restore.js +3 -0
- package/dist/types.d.ts +40 -4
- package/dist/types.js +8 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ Each [GitHub release](https://github.com/adam0white/human-framework/releases) al
|
|
|
52
52
|
installs by URL:
|
|
53
53
|
|
|
54
54
|
```sh
|
|
55
|
-
npm install https://github.com/adam0white/human-framework/releases/download/v2.
|
|
55
|
+
npm install https://github.com/adam0white/human-framework/releases/download/v2.2.0/adam0white-human-framework-2.2.0.tgz
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
or build one from a checkout:
|
|
@@ -177,13 +177,13 @@ reference host: `createVillage`.
|
|
|
177
177
|
|
|
178
178
|
There are two version numbers, and they move independently.
|
|
179
179
|
|
|
180
|
-
- **The release version** is the package version (`package.json`, now `2.
|
|
180
|
+
- **The release version** is the package version (`package.json`, now `2.2.1`) and `FRAMEWORK_VERSION`, the same
|
|
181
181
|
string compiled into the build; a test keeps them equal. It versions the public API under
|
|
182
182
|
[semver](https://semver.org/): a breaking change to an exported name or signature bumps the major, a new
|
|
183
183
|
faculty or export the minor, a fix the patch. Each release is a tag `vX.Y.Z` and a GitHub release whose notes
|
|
184
184
|
are that version's section of the root [CHANGELOG.md](../../CHANGELOG.md), with the package tarball attached
|
|
185
185
|
(`npm run release`, run on a clean, pushed `main`).
|
|
186
|
-
- **`ENGINE_VERSION`** (exported; `2.
|
|
186
|
+
- **`ENGINE_VERSION`** (exported; `2.2.0` at release 2.2.1) versions simulation behaviour and the save format. It
|
|
187
187
|
changes when the same seed and inputs would give different decisions, or when person state changes shape.
|
|
188
188
|
`restore` upgrades saves from engine 1.4.0 and later through `migrate` and refuses older ones. Every release's notes name the
|
|
189
189
|
`ENGINE_VERSION` it ships, so a host can tell whether its saves still restore.
|
|
@@ -274,3 +274,31 @@ that place lose appeal until the fear fades. Groups and factions themselves stay
|
|
|
274
274
|
|
|
275
275
|
MIT; see [LICENSE](LICENSE). The repository's `docs/` and `research/`, which this README links to, are
|
|
276
276
|
CC BY 4.0.
|
|
277
|
+
|
|
278
|
+
## Learning a demonstrated conditional step
|
|
279
|
+
|
|
280
|
+
Use observations the person actually received; the host owns what those observations mean. Matching is on categorical cue strings, not hidden world state or a per-object correct-action table.
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
receiveMethodCues(learner, {
|
|
284
|
+
contextId: 'observed-example',
|
|
285
|
+
cues: [{ cue: 'route', value: 'north' }, { cue: 'signal', value: 'clear' }],
|
|
286
|
+
});
|
|
287
|
+
demonstrateMethod(learner, {
|
|
288
|
+
id: 'routing-north', demonstrationId: 'example-1',
|
|
289
|
+
contextId: 'observed-example', sourceId: 'instructor',
|
|
290
|
+
conditions: [{ cue: 'route', value: 'north' }, { cue: 'signal', value: 'clear' }],
|
|
291
|
+
action: 'send-north', outcome: 'success',
|
|
292
|
+
});
|
|
293
|
+
// A new parcel can share the learned observation structure.
|
|
294
|
+
receiveMethodCues(learner, {
|
|
295
|
+
contextId: 'new-parcel',
|
|
296
|
+
cues: [{ cue: 'route', value: 'north' }, { cue: 'signal', value: 'clear' }],
|
|
297
|
+
});
|
|
298
|
+
const evidence = methodEvidence(learner, 'send-north', 'new-parcel');
|
|
299
|
+
// Offers for that parcel carry methodContext: 'new-parcel'.
|
|
300
|
+
// Ordinary decide() includes the acquired method term alongside other motives.
|
|
301
|
+
clearMethodContext(learner); // when leaving or switching the task
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Import these functions from the package. Rules/evidence live in optional `Person.methods` and survive snapshots. Current cues expire after 120 minutes. A stable demonstration id avoids counting the same example twice within the retained 256-example history, even after its rule or evidence is evicted. Once an id has left both that history and retained rule evidence, it can be admitted again. Missing observations cannot satisfy a condition. Failed demonstrations reduce support for that action. Successful alternatives retain their own support; ordinary cognition weighs the supported offers alongside other motives. A successful lecture does not certify reliable execution. [The routing control](examples/method-control.ts) demonstrates changed teaching, withheld teaching, a scalar-skill control and acquired-state ablation with two methods and an exception. [The alternatives control](examples/method-alternatives-control.ts) demonstrates two independently successful procedures under the same observations, failed practice, hunger and exact restore.
|
|
@@ -31,6 +31,7 @@ import { normTerms } from '../conscience/index.js';
|
|
|
31
31
|
import { clamp01, cmpStr, dexp, minuteOfDay, round } from '../core/index.js';
|
|
32
32
|
import { habitEase, habitPull } from '../habits/index.js';
|
|
33
33
|
import { expectedEffect } from '../memory/index.js';
|
|
34
|
+
import { methodEvidence } from '../methods/index.js';
|
|
34
35
|
import { urgencyOf } from '../needs/index.js';
|
|
35
36
|
import { partneringTerms } from '../partnering/index.js';
|
|
36
37
|
import { companionSteadiness, socialTerms } from '../social/index.js';
|
|
@@ -238,6 +239,10 @@ export function consider(p, aff, ctx) {
|
|
|
238
239
|
if (expected.gist) {
|
|
239
240
|
push('memory', K.expectationScale * expected.gist.valence * expected.gist.weight * (1 - expected.confidence));
|
|
240
241
|
}
|
|
242
|
+
// Acquired cue-conditioned methods remain one pull among ordinary motives.
|
|
243
|
+
if (aff.methodContext !== undefined)
|
|
244
|
+
for (const t of methodEvidence(p, aff.action, aff.methodContext).terms)
|
|
245
|
+
push(t.source, t.value);
|
|
241
246
|
// Norms, commitments and goals. Planning terms scale with developmental maturity.
|
|
242
247
|
let refractoryCache;
|
|
243
248
|
const refractory = () => {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Human Framework v1 public surface. */
|
|
2
|
-
export declare const FRAMEWORK_VERSION = "2.1
|
|
2
|
+
export declare const FRAMEWORK_VERSION = "2.2.1";
|
|
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';
|
|
@@ -20,6 +20,8 @@ export * from './impression.ts';
|
|
|
20
20
|
export * from './lifecourse/index.ts';
|
|
21
21
|
export * from './longrun.ts';
|
|
22
22
|
export * from './memory/index.ts';
|
|
23
|
+
export type { MethodDemonstration, MethodEvidence } from './methods/index.ts';
|
|
24
|
+
export { clearMethodContext, demonstrateMethod, METHOD_DEFAULTS, methodEvidence, receiveMethodCues, } from './methods/index.ts';
|
|
23
25
|
export type { MigrationStep } from './migrate.ts';
|
|
24
26
|
export { MIGRATIONS, migratableVersions, migrate } from './migrate.ts';
|
|
25
27
|
export * from './narrate/index.ts';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Human Framework v1 public surface. */
|
|
2
|
-
export const FRAMEWORK_VERSION = '2.1
|
|
2
|
+
export const FRAMEWORK_VERSION = '2.2.1';
|
|
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';
|
|
@@ -19,6 +19,7 @@ export * from './impression.js';
|
|
|
19
19
|
export * from './lifecourse/index.js';
|
|
20
20
|
export * from './longrun.js';
|
|
21
21
|
export * from './memory/index.js';
|
|
22
|
+
export { clearMethodContext, demonstrateMethod, METHOD_DEFAULTS, methodEvidence, receiveMethodCues, } from './methods/index.js';
|
|
22
23
|
export { MIGRATIONS, migratableVersions, migrate } from './migrate.js';
|
|
23
24
|
export * from './narrate/index.js';
|
|
24
25
|
export * from './needs/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './methods.ts';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './methods.js';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { MethodCue, Person, Term } from '../types.ts';
|
|
2
|
+
export declare const METHOD_DEFAULTS: {
|
|
3
|
+
maxRules: number;
|
|
4
|
+
maxConditions: number;
|
|
5
|
+
maxCues: number;
|
|
6
|
+
maxEvidence: number;
|
|
7
|
+
maxRecentDemonstrations: number;
|
|
8
|
+
maxIdentifierLength: number;
|
|
9
|
+
maxValueLength: number;
|
|
10
|
+
contextMinutes: number;
|
|
11
|
+
evidenceHalfLife: number;
|
|
12
|
+
/** Two fully attended ordinary examples raise support to 0.5 before source trust. */
|
|
13
|
+
evidencePrior: number;
|
|
14
|
+
utilityScale: number;
|
|
15
|
+
};
|
|
16
|
+
export interface MethodDemonstration {
|
|
17
|
+
/** Name of the explained method; action/conditions distinguish its variants. */
|
|
18
|
+
id: string;
|
|
19
|
+
/** Stable event id: repeats within the retained 256-example horizon cannot strengthen it. */
|
|
20
|
+
demonstrationId: string;
|
|
21
|
+
contextId: string;
|
|
22
|
+
conditions: readonly MethodCue[];
|
|
23
|
+
action: string;
|
|
24
|
+
sourceId: string;
|
|
25
|
+
outcome: 'success' | 'failure';
|
|
26
|
+
/** Attended fraction of this example, 0..1; zero acquires nothing. */
|
|
27
|
+
attention?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface MethodEvidence {
|
|
30
|
+
/** Net matching evidence for this action, -1..1; its failures reduce it. */
|
|
31
|
+
support: number;
|
|
32
|
+
terms: Term[];
|
|
33
|
+
matched: {
|
|
34
|
+
id: string;
|
|
35
|
+
action: string;
|
|
36
|
+
sourceId: string;
|
|
37
|
+
support: number;
|
|
38
|
+
}[];
|
|
39
|
+
}
|
|
40
|
+
/** Replace current received observations. This is an explicit host reception event, not host truth. */
|
|
41
|
+
export declare function receiveMethodCues(p: Person, received: {
|
|
42
|
+
contextId: string;
|
|
43
|
+
cues: readonly MethodCue[];
|
|
44
|
+
}): boolean;
|
|
45
|
+
/** Drop current observations while retaining learned methods. Call when leaving/switching a task. */
|
|
46
|
+
export declare function clearMethodContext(p: Person): void;
|
|
47
|
+
/** Acquire one explained step only when its learner actually received the matching observations. */
|
|
48
|
+
export declare function demonstrateMethod(p: Person, example: MethodDemonstration): boolean;
|
|
49
|
+
/** Read acquired evidence only. No draw, state mutation, hidden fault access or fixed action policy. */
|
|
50
|
+
export declare function methodEvidence(p: Person, action: string, contextId: string): MethodEvidence;
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SCOPE: bounded acquisition of conditional action methods from attended examples.
|
|
3
|
+
* A learner must have received every categorical cue in an explained conjunction.
|
|
4
|
+
* Successful and unsuccessful demonstrations become person-owned evidence; the
|
|
5
|
+
* same reader supplies host queries and ordinary cognition terms. Unknown cues
|
|
6
|
+
* do not mean false. Context identity prevents one task's observations being used
|
|
7
|
+
* for another task. Failed examples weaken that action without choosing a hidden
|
|
8
|
+
* correct answer. Successful alternatives retain their own support. Attention,
|
|
9
|
+
* source trust and recency grade evidence.
|
|
10
|
+
* These are explicit engineering choices, not fitted human learning parameters.
|
|
11
|
+
* This module does not infer cues, invent actions, plan sequences, certify skill,
|
|
12
|
+
* access host truth, compute religious worth, or guarantee that a person obeys.
|
|
13
|
+
*/
|
|
14
|
+
import { trustOf } from '../beliefs/index.js';
|
|
15
|
+
import { clamp01, cmpStr, decay, isNum, isObj } from '../core/index.js';
|
|
16
|
+
import { MAX_MINUTE, MINUTES_PER_DAY } from '../types.js';
|
|
17
|
+
export const METHOD_DEFAULTS = {
|
|
18
|
+
maxRules: 24,
|
|
19
|
+
maxConditions: 8,
|
|
20
|
+
maxCues: 24,
|
|
21
|
+
maxEvidence: 16,
|
|
22
|
+
maxRecentDemonstrations: 256,
|
|
23
|
+
maxIdentifierLength: 96,
|
|
24
|
+
maxValueLength: 128,
|
|
25
|
+
contextMinutes: 120,
|
|
26
|
+
evidenceHalfLife: 180 * MINUTES_PER_DAY,
|
|
27
|
+
/** Two fully attended ordinary examples raise support to 0.5 before source trust. */
|
|
28
|
+
evidencePrior: 2,
|
|
29
|
+
utilityScale: 1.2,
|
|
30
|
+
};
|
|
31
|
+
const validId = (x) => typeof x === 'string' &&
|
|
32
|
+
x.length > 0 &&
|
|
33
|
+
x.length <= METHOD_DEFAULTS.maxIdentifierLength &&
|
|
34
|
+
x !== '__proto__' &&
|
|
35
|
+
x !== 'constructor' &&
|
|
36
|
+
x !== 'prototype';
|
|
37
|
+
const validValue = (x) => typeof x === 'string' && x.length > 0 && x.length <= METHOD_DEFAULTS.maxValueLength;
|
|
38
|
+
const validMinute = (x) => isNum(x) && Math.abs(x) <= MAX_MINUTE;
|
|
39
|
+
function cleanCues(input, maximum) {
|
|
40
|
+
if (!Array.isArray(input) || input.length === 0 || input.length > maximum)
|
|
41
|
+
return undefined;
|
|
42
|
+
const out = [];
|
|
43
|
+
for (const c of input) {
|
|
44
|
+
if (!isObj(c) || !validId(c.cue) || !validValue(c.value))
|
|
45
|
+
return undefined;
|
|
46
|
+
// Ambiguous duplicate observations do not silently choose the last value.
|
|
47
|
+
if (out.some((v) => v.cue === c.cue))
|
|
48
|
+
return undefined;
|
|
49
|
+
out.push({ cue: c.cue, value: c.value });
|
|
50
|
+
}
|
|
51
|
+
return out.sort((a, b) => cmpStr(a.cue, b.cue));
|
|
52
|
+
}
|
|
53
|
+
function available(p) {
|
|
54
|
+
return p.body.alive && !p.body.asleep && !p.body.downed;
|
|
55
|
+
}
|
|
56
|
+
/** Replace current received observations. This is an explicit host reception event, not host truth. */
|
|
57
|
+
export function receiveMethodCues(p, received) {
|
|
58
|
+
const cues = cleanCues(received.cues, METHOD_DEFAULTS.maxCues);
|
|
59
|
+
// Bad/new unreadable input must not leave stale cues available.
|
|
60
|
+
clearMethodContext(p);
|
|
61
|
+
if (!available(p) || !validId(received.contextId) || !cues)
|
|
62
|
+
return false;
|
|
63
|
+
p.methods ??= { rules: [], recentDemonstrations: [] };
|
|
64
|
+
p.methods.context = { id: received.contextId, at: p.now, cues };
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
/** Drop current observations while retaining learned methods. Call when leaving/switching a task. */
|
|
68
|
+
export function clearMethodContext(p) {
|
|
69
|
+
if (p.methods)
|
|
70
|
+
delete p.methods.context;
|
|
71
|
+
}
|
|
72
|
+
function matches(conditions, cues) {
|
|
73
|
+
return conditions.every((c) => cues.some((v) => v.cue === c.cue && v.value === c.value));
|
|
74
|
+
}
|
|
75
|
+
function currentContext(p, id) {
|
|
76
|
+
const context = p.methods?.context;
|
|
77
|
+
if (!available(p) ||
|
|
78
|
+
!context ||
|
|
79
|
+
context.id !== id ||
|
|
80
|
+
p.now < context.at ||
|
|
81
|
+
p.now - context.at > METHOD_DEFAULTS.contextMinutes)
|
|
82
|
+
return undefined;
|
|
83
|
+
return context;
|
|
84
|
+
}
|
|
85
|
+
/** Acquire one explained step only when its learner actually received the matching observations. */
|
|
86
|
+
export function demonstrateMethod(p, example) {
|
|
87
|
+
const conditions = cleanCues(example.conditions, METHOD_DEFAULTS.maxConditions);
|
|
88
|
+
const context = currentContext(p, example.contextId);
|
|
89
|
+
const attention = example.attention === undefined ? 1 : example.attention;
|
|
90
|
+
if (!context ||
|
|
91
|
+
!conditions ||
|
|
92
|
+
!validId(example.id) ||
|
|
93
|
+
!validId(example.action) ||
|
|
94
|
+
!validId(example.sourceId) ||
|
|
95
|
+
!validId(example.demonstrationId) ||
|
|
96
|
+
(example.outcome !== 'success' && example.outcome !== 'failure') ||
|
|
97
|
+
!isNum(attention) ||
|
|
98
|
+
attention <= 0 ||
|
|
99
|
+
attention > 1 ||
|
|
100
|
+
!matches(conditions, context.cues))
|
|
101
|
+
return false;
|
|
102
|
+
const state = p.methods;
|
|
103
|
+
if (!state)
|
|
104
|
+
return false;
|
|
105
|
+
if (state.recentDemonstrations.includes(example.demonstrationId) ||
|
|
106
|
+
state.rules.some((r) => r.evidence.some((e) => e.id === example.demonstrationId)))
|
|
107
|
+
return false;
|
|
108
|
+
state.recentDemonstrations.push(example.demonstrationId);
|
|
109
|
+
if (state.recentDemonstrations.length > METHOD_DEFAULTS.maxRecentDemonstrations)
|
|
110
|
+
state.recentDemonstrations.shift();
|
|
111
|
+
let rule = state.rules.find((r) => r.action === example.action && JSON.stringify(r.conditions) === JSON.stringify(conditions));
|
|
112
|
+
if (!rule) {
|
|
113
|
+
rule = { id: example.id, action: example.action, conditions, evidence: [] };
|
|
114
|
+
state.rules.push(rule);
|
|
115
|
+
if (state.rules.length > METHOD_DEFAULTS.maxRules)
|
|
116
|
+
state.rules.shift();
|
|
117
|
+
}
|
|
118
|
+
rule.evidence.push({
|
|
119
|
+
id: example.demonstrationId,
|
|
120
|
+
sourceId: example.sourceId,
|
|
121
|
+
at: p.now,
|
|
122
|
+
attention: clamp01(attention),
|
|
123
|
+
outcome: example.outcome,
|
|
124
|
+
});
|
|
125
|
+
if (rule.evidence.length > METHOD_DEFAULTS.maxEvidence)
|
|
126
|
+
rule.evidence.shift();
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
/** Read acquired evidence only. No draw, state mutation, hidden fault access or fixed action policy. */
|
|
130
|
+
export function methodEvidence(p, action, contextId) {
|
|
131
|
+
const result = { support: 0, terms: [], matched: [] };
|
|
132
|
+
const context = currentContext(p, contextId);
|
|
133
|
+
if (!context)
|
|
134
|
+
return result;
|
|
135
|
+
const rules = p.methods?.rules ?? [];
|
|
136
|
+
for (const rule of rules) {
|
|
137
|
+
if (!matches(rule.conditions, context.cues))
|
|
138
|
+
continue;
|
|
139
|
+
const sources = [...new Set(rule.evidence.map((e) => e.sourceId))].sort(cmpStr);
|
|
140
|
+
for (const sourceId of sources) {
|
|
141
|
+
let positive = 0;
|
|
142
|
+
let negative = 0;
|
|
143
|
+
for (const e of rule.evidence) {
|
|
144
|
+
if (e.sourceId !== sourceId || e.at > p.now)
|
|
145
|
+
continue;
|
|
146
|
+
const weight = decay(e.attention, p.now - e.at, METHOD_DEFAULTS.evidenceHalfLife);
|
|
147
|
+
if (e.outcome === 'success')
|
|
148
|
+
positive += weight;
|
|
149
|
+
else
|
|
150
|
+
negative += weight;
|
|
151
|
+
}
|
|
152
|
+
const voice = p.will.voices.find((v) => v.voiceId === sourceId);
|
|
153
|
+
const receivedTrust = voice?.trust ?? trustOf(p, sourceId);
|
|
154
|
+
const trust = isNum(receivedTrust) ? clamp01(receivedTrust) : 0.5;
|
|
155
|
+
const support = (trust * (positive - negative)) / (positive + negative + METHOD_DEFAULTS.evidencePrior);
|
|
156
|
+
result.matched.push({ id: rule.id, action: rule.action, sourceId, support });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// A successful alternative is not evidence that this action failed. Each action
|
|
160
|
+
// retains its own signed support; ordinary cognition weighs all legal offers.
|
|
161
|
+
const strengths = new Map();
|
|
162
|
+
for (const match of result.matched)
|
|
163
|
+
strengths.set(match.action, (strengths.get(match.action) ?? 0) + match.support);
|
|
164
|
+
const own = strengths.get(action) ?? 0;
|
|
165
|
+
result.support = own / Math.max(1, Math.abs(own));
|
|
166
|
+
const scale = own === 0 ? 0 : result.support / own;
|
|
167
|
+
for (const match of result.matched) {
|
|
168
|
+
if (match.action !== action || match.support === 0)
|
|
169
|
+
continue;
|
|
170
|
+
result.terms.push({
|
|
171
|
+
source: `method:${match.id}:${match.sourceId}`,
|
|
172
|
+
value: METHOD_DEFAULTS.utilityScale * match.support * scale,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
/** @internal Strict bounded restore; no unrecognized fields survive. */
|
|
178
|
+
export function sanitizeMethods(input) {
|
|
179
|
+
if (!isObj(input) || !Array.isArray(input.rules))
|
|
180
|
+
return undefined;
|
|
181
|
+
const rules = [];
|
|
182
|
+
const seen = new Set();
|
|
183
|
+
for (const row of input.rules.slice(-METHOD_DEFAULTS.maxRules)) {
|
|
184
|
+
if (!isObj(row) || !validId(row.id) || !validId(row.action) || !Array.isArray(row.evidence))
|
|
185
|
+
continue;
|
|
186
|
+
const conditions = cleanCues(row.conditions, METHOD_DEFAULTS.maxConditions);
|
|
187
|
+
if (!conditions)
|
|
188
|
+
continue;
|
|
189
|
+
const evidence = [];
|
|
190
|
+
for (const e of row.evidence.slice(-METHOD_DEFAULTS.maxEvidence)) {
|
|
191
|
+
if (!isObj(e) ||
|
|
192
|
+
!validId(e.id) ||
|
|
193
|
+
!validId(e.sourceId) ||
|
|
194
|
+
!validMinute(e.at) ||
|
|
195
|
+
!isNum(e.attention) ||
|
|
196
|
+
e.attention <= 0 ||
|
|
197
|
+
e.attention > 1 ||
|
|
198
|
+
(e.outcome !== 'success' && e.outcome !== 'failure') ||
|
|
199
|
+
seen.has(e.id))
|
|
200
|
+
continue;
|
|
201
|
+
seen.add(e.id);
|
|
202
|
+
evidence.push({ id: e.id, sourceId: e.sourceId, at: e.at, attention: e.attention, outcome: e.outcome });
|
|
203
|
+
}
|
|
204
|
+
if (evidence.length) {
|
|
205
|
+
const same = rules.find((r) => r.action === row.action && JSON.stringify(r.conditions) === JSON.stringify(conditions));
|
|
206
|
+
if (same)
|
|
207
|
+
same.evidence = [...same.evidence, ...evidence].slice(-METHOD_DEFAULTS.maxEvidence);
|
|
208
|
+
else
|
|
209
|
+
rules.push({ id: row.id, action: row.action, conditions, evidence });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const recentDemonstrations = [];
|
|
213
|
+
if (Array.isArray(input.recentDemonstrations))
|
|
214
|
+
for (const id of input.recentDemonstrations.slice(-METHOD_DEFAULTS.maxRecentDemonstrations))
|
|
215
|
+
if (validId(id) && !recentDemonstrations.includes(id))
|
|
216
|
+
recentDemonstrations.push(id);
|
|
217
|
+
// Only a missing history is reconstructed. Appending older evidence to a full
|
|
218
|
+
// valid horizon would evict genuinely newer event ids and alter a live save.
|
|
219
|
+
if (input.recentDemonstrations === undefined)
|
|
220
|
+
for (const rule of rules)
|
|
221
|
+
for (const e of rule.evidence)
|
|
222
|
+
if (!recentDemonstrations.includes(e.id))
|
|
223
|
+
recentDemonstrations.push(e.id);
|
|
224
|
+
const out = {
|
|
225
|
+
rules,
|
|
226
|
+
recentDemonstrations: recentDemonstrations.slice(-METHOD_DEFAULTS.maxRecentDemonstrations),
|
|
227
|
+
};
|
|
228
|
+
if (isObj(input.context) && validId(input.context.id) && validMinute(input.context.at)) {
|
|
229
|
+
const cues = cleanCues(input.context.cues, METHOD_DEFAULTS.maxCues);
|
|
230
|
+
if (cues)
|
|
231
|
+
out.context = { id: input.context.id, at: input.context.at, cues };
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
}
|
package/dist/migrate.js
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* optional fields that are absent by default), so those steps only stamp the version; 1.9.0 to 2.0.0 renames one
|
|
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
|
-
* the slices as for any save.
|
|
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. A migrated save continues under the current engine's rules: it restores and runs,
|
|
11
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
|
|
12
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.
|
|
13
14
|
*
|
|
@@ -57,6 +58,16 @@ export const MIGRATIONS = {
|
|
|
57
58
|
note: 'the surroundings field `ambient.now` is renamed `ambient.percept`; per-person retention is optional and starts absent; behaviour unchanged',
|
|
58
59
|
apply: renameAmbient,
|
|
59
60
|
},
|
|
61
|
+
'2.1.0': {
|
|
62
|
+
to: '2.2.0',
|
|
63
|
+
note: 'person shape unchanged; method evidence now grades each action independently, preserving successful alternatives',
|
|
64
|
+
apply: stamp,
|
|
65
|
+
},
|
|
66
|
+
'2.0.0': {
|
|
67
|
+
to: '2.1.0',
|
|
68
|
+
note: 'conditional method state and received cues are optional and start absent; existing host decisions unchanged',
|
|
69
|
+
apply: stamp,
|
|
70
|
+
},
|
|
60
71
|
};
|
|
61
72
|
/** The step upgrading from `v`, looked up as an own property only (a version of "__proto__" matches nothing). */
|
|
62
73
|
const stepFrom = (v) => Object.hasOwn(MIGRATIONS, v) ? MIGRATIONS[v] : undefined;
|
package/dist/restore.js
CHANGED
|
@@ -16,6 +16,7 @@ import { sanitizeAmbient } from './environment/index.js';
|
|
|
16
16
|
import { sanitizeFamily } from './family/index.js';
|
|
17
17
|
import { HABIT_DEFAULTS } from './habits/index.js';
|
|
18
18
|
import { GIST_DEFAULTS, MEMORY_DEFAULTS, sanitizeGists } from './memory/index.js';
|
|
19
|
+
import { sanitizeMethods } from './methods/index.js';
|
|
19
20
|
import { migrate } from './migrate.js';
|
|
20
21
|
import { sanitizeBonds } from './partnering/index.js';
|
|
21
22
|
import { cleanRetention, createPerson, traceLimit } from './person.js';
|
|
@@ -57,6 +58,7 @@ const OPTIONAL_KEYS = new Set([
|
|
|
57
58
|
'bonds',
|
|
58
59
|
'ambient',
|
|
59
60
|
'retention',
|
|
61
|
+
'methods',
|
|
60
62
|
]);
|
|
61
63
|
/** Kind of a JSON value for the per-field type check in `restore`. */
|
|
62
64
|
const kindOf = (x) => (Array.isArray(x) ? 'array' : x === null ? 'null' : typeof x);
|
|
@@ -168,6 +170,7 @@ export function restore(input) {
|
|
|
168
170
|
keepValid(out, 'bonds', sanitizeBonds);
|
|
169
171
|
keepValid(out, 'ambient', sanitizeAmbient);
|
|
170
172
|
keepValid(out, 'retention', cleanRetention);
|
|
173
|
+
keepValid(out, 'methods', sanitizeMethods);
|
|
171
174
|
boundLists(out);
|
|
172
175
|
return out;
|
|
173
176
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -787,9 +787,13 @@ export interface Activity {
|
|
|
787
787
|
}
|
|
788
788
|
export declare const PERSON_SCHEMA = "human/person@1";
|
|
789
789
|
/**
|
|
790
|
-
* Current: 2.
|
|
791
|
-
*
|
|
792
|
-
*
|
|
790
|
+
* Current: 2.2.0 (2026-10-14): method evidence grades each action independently.
|
|
791
|
+
* Successful alternatives no longer count as failures of another action. Person state shape is unchanged.
|
|
792
|
+
* 2.1.0 saves migrate by stamping the version.
|
|
793
|
+
*
|
|
794
|
+
* 2.1.0 (2026-10-10): optional acquired conditional methods and explicit received cue contexts.
|
|
795
|
+
* Normal cognition reads learned method evidence only on offers carrying the matching context.
|
|
796
|
+
* Existing hosts with no method state keep their previous decisions; 2.0.0 saves migrate by stamping the version.
|
|
793
797
|
*
|
|
794
798
|
* Earlier engines (oldest first):
|
|
795
799
|
* 1.1.0 (2026-10-03): joint activities, omission/distrust rules, reactance, voice history.
|
|
@@ -818,7 +822,7 @@ export declare const PERSON_SCHEMA = "human/person@1";
|
|
|
818
822
|
* and an activity that would cover a closing stretch is reviewed when the stretch begins (`isUnderWay`,
|
|
819
823
|
* `dutyReviewAt`). Person shape unchanged.
|
|
820
824
|
*/
|
|
821
|
-
export declare const ENGINE_VERSION = "2.
|
|
825
|
+
export declare const ENGINE_VERSION = "2.2.0";
|
|
822
826
|
/** A pregnancy carried by this person (host-driven; see `family/`). */
|
|
823
827
|
export interface Pregnancy {
|
|
824
828
|
fatherId: PersonId;
|
|
@@ -891,6 +895,34 @@ export interface AmbientState {
|
|
|
891
895
|
/** Minute these surroundings began. */
|
|
892
896
|
since: Minute;
|
|
893
897
|
}
|
|
898
|
+
/** A received categorical observation; missing values never stand for a negative observation. */
|
|
899
|
+
export interface MethodCue {
|
|
900
|
+
cue: string;
|
|
901
|
+
value: string;
|
|
902
|
+
}
|
|
903
|
+
export interface MethodRule {
|
|
904
|
+
id: string;
|
|
905
|
+
action: string;
|
|
906
|
+
conditions: MethodCue[];
|
|
907
|
+
evidence: {
|
|
908
|
+
id: string;
|
|
909
|
+
sourceId: string;
|
|
910
|
+
at: Minute;
|
|
911
|
+
attention: Unit;
|
|
912
|
+
outcome: 'success' | 'failure';
|
|
913
|
+
}[];
|
|
914
|
+
}
|
|
915
|
+
/** Optional conditional action learning. Owned by methods/, not a host answer table. */
|
|
916
|
+
export interface MethodsState {
|
|
917
|
+
rules: MethodRule[];
|
|
918
|
+
/** Last 256 acquired example ids, independent of rule/evidence eviction. */
|
|
919
|
+
recentDemonstrations: string[];
|
|
920
|
+
context?: {
|
|
921
|
+
id: string;
|
|
922
|
+
at: Minute;
|
|
923
|
+
cues: MethodCue[];
|
|
924
|
+
};
|
|
925
|
+
}
|
|
894
926
|
export interface Person {
|
|
895
927
|
schema: typeof PERSON_SCHEMA;
|
|
896
928
|
/** Engine version that last wrote this state. */
|
|
@@ -908,6 +940,8 @@ export interface Person {
|
|
|
908
940
|
conscience: ConscienceState;
|
|
909
941
|
affect: AffectState;
|
|
910
942
|
skills: Record<string, Skill>;
|
|
943
|
+
/** Acquired conditional method evidence and current received cues; absent until used. */
|
|
944
|
+
methods?: MethodsState;
|
|
911
945
|
/** Practice consolidation (1.8.0, opt-in via `enableSkillRetention`). Owned by `skills/`. */
|
|
912
946
|
skillRetention?: {
|
|
913
947
|
consolidationHours: number;
|
|
@@ -968,6 +1002,8 @@ export interface Affordance {
|
|
|
968
1002
|
/** Verb/action class shared across offers: 'eat', 'sleep', 'farm', 'talk', 'pray', 'steal', ... */
|
|
969
1003
|
action: string;
|
|
970
1004
|
label: string;
|
|
1005
|
+
/** Received method context this offer belongs to; absent = no method term. */
|
|
1006
|
+
methodContext?: string;
|
|
971
1007
|
targetId?: EntityId;
|
|
972
1008
|
placeId?: EntityId;
|
|
973
1009
|
/** Other people taking part. */
|
package/dist/types.js
CHANGED
|
@@ -84,9 +84,13 @@ export const EMOTIONS = [
|
|
|
84
84
|
// ---------------------------------------------------------------------------------------------
|
|
85
85
|
export const PERSON_SCHEMA = 'human/person@1';
|
|
86
86
|
/**
|
|
87
|
-
* Current: 2.
|
|
88
|
-
*
|
|
89
|
-
*
|
|
87
|
+
* Current: 2.2.0 (2026-10-14): method evidence grades each action independently.
|
|
88
|
+
* Successful alternatives no longer count as failures of another action. Person state shape is unchanged.
|
|
89
|
+
* 2.1.0 saves migrate by stamping the version.
|
|
90
|
+
*
|
|
91
|
+
* 2.1.0 (2026-10-10): optional acquired conditional methods and explicit received cue contexts.
|
|
92
|
+
* Normal cognition reads learned method evidence only on offers carrying the matching context.
|
|
93
|
+
* Existing hosts with no method state keep their previous decisions; 2.0.0 saves migrate by stamping the version.
|
|
90
94
|
*
|
|
91
95
|
* Earlier engines (oldest first):
|
|
92
96
|
* 1.1.0 (2026-10-03): joint activities, omission/distrust rules, reactance, voice history.
|
|
@@ -115,4 +119,4 @@ export const PERSON_SCHEMA = 'human/person@1';
|
|
|
115
119
|
* and an activity that would cover a closing stretch is reviewed when the stretch begins (`isUnderWay`,
|
|
116
120
|
* `dutyReviewAt`). Person shape unchanged.
|
|
117
121
|
*/
|
|
118
|
-
export const ENGINE_VERSION = '2.
|
|
122
|
+
export const ENGINE_VERSION = '2.2.0';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adam0white/human-framework",
|
|
3
|
-
"version": "2.1
|
|
3
|
+
"version": "2.2.1",
|
|
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",
|