@adam0white/human-framework 2.13.0 → 2.14.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/dist/agenda/agenda.js +49 -3
- package/dist/cognition/cognition.js +4 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/migrate.js +12 -1
- package/dist/persistence/index.d.ts +1 -0
- package/dist/persistence/index.js +1 -0
- package/dist/persistence/persistence.d.ts +19 -0
- package/dist/persistence/persistence.js +209 -0
- package/dist/person.js +46 -2
- package/dist/projects/index.d.ts +1 -0
- package/dist/projects/index.js +1 -0
- package/dist/projects/projects.d.ts +23 -0
- package/dist/projects/projects.js +325 -0
- package/dist/prospection/prospection.js +5 -0
- package/dist/restore.js +6 -0
- package/dist/types.d.ts +95 -1
- package/dist/types.js +1 -1
- package/package.json +1 -1
package/dist/agenda/agenda.js
CHANGED
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
*/
|
|
48
48
|
// The bundled catalog is only the default; a host with its own catalog passes it (`World.catalog`).
|
|
49
49
|
import { DEFAULT_NORMS } from '../conscience/catalog.js';
|
|
50
|
-
import { clamp01, dayOf, dpow, hasNumbers, isObj } from '../core/index.js';
|
|
50
|
+
import { clamp01, dayOf, dpow, hasNumbers, isNum, isObj } from '../core/index.js';
|
|
51
51
|
import { expectedEffect } from '../memory/index.js';
|
|
52
52
|
import { MINUTES_PER_DAY } from '../types.js';
|
|
53
53
|
export const AGENDA_DEFAULTS = {
|
|
@@ -144,6 +144,20 @@ function copyArrays(c) {
|
|
|
144
144
|
*/
|
|
145
145
|
/** Copy valid temporal settings and drop invalid ones at every agenda entry point. */
|
|
146
146
|
function sanitizeGoalTemporal(g) {
|
|
147
|
+
const project = g.project;
|
|
148
|
+
if (project !== undefined) {
|
|
149
|
+
if (isObj(project) &&
|
|
150
|
+
typeof project.prospectId === 'string' &&
|
|
151
|
+
project.prospectId.length > 0 &&
|
|
152
|
+
project.prospectId.length <= 512 &&
|
|
153
|
+
typeof project.perceivedProgress === 'boolean') {
|
|
154
|
+
g.project = { prospectId: project.prospectId, perceivedProgress: project.perceivedProgress };
|
|
155
|
+
if (isNum(project.peak) && project.peak >= 0 && project.peak <= 1)
|
|
156
|
+
g.project.peak = project.peak;
|
|
157
|
+
}
|
|
158
|
+
else
|
|
159
|
+
delete g.project;
|
|
160
|
+
}
|
|
147
161
|
const t = g.temporal;
|
|
148
162
|
if (t !== undefined &&
|
|
149
163
|
!(isObj(t) &&
|
|
@@ -479,11 +493,16 @@ function heldConviction(p, c) {
|
|
|
479
493
|
* keeps by the same rule, so a pull never leads to an activity that cannot keep the commitment.
|
|
480
494
|
*/
|
|
481
495
|
export const spanMeetsWindow = (c, start, end) => start <= c.until && end >= c.from;
|
|
496
|
+
/** Explicit host progress can name either the adopted goal or its perceived prospect. */
|
|
497
|
+
function namesGoal(g, advances) {
|
|
498
|
+
return ((advances?.includes(g.id) ?? false) ||
|
|
499
|
+
(g.project !== undefined && (advances?.includes(g.project.prospectId) ?? false)));
|
|
500
|
+
}
|
|
482
501
|
function goalAmount(g, aff) {
|
|
483
502
|
const own = g.advancedBy.find((a) => a.action === aff.action);
|
|
484
503
|
if (own)
|
|
485
504
|
return own.amount;
|
|
486
|
-
return aff.advances
|
|
505
|
+
return namesGoal(g, aff.advances) ? AGENDA_DEFAULTS.defaultAdvanceAmount : 0;
|
|
487
506
|
}
|
|
488
507
|
function deadlinePressure(g, now) {
|
|
489
508
|
if (g.deadline === undefined)
|
|
@@ -671,9 +690,17 @@ export function onFinished(p, outcome, startedAt = outcome.at, opts = {}) {
|
|
|
671
690
|
if (g.status !== 'active')
|
|
672
691
|
continue;
|
|
673
692
|
const own = g.advancedBy.find((a) => a.action === outcome.action)?.amount ?? 0;
|
|
674
|
-
const amount = own > 0 ? own : outcome.advances
|
|
693
|
+
const amount = own > 0 ? own : namesGoal(g, outcome.advances) ? AGENDA_DEFAULTS.defaultAdvanceAmount : 0;
|
|
675
694
|
if (amount <= 0)
|
|
676
695
|
continue;
|
|
696
|
+
// onFinished runs before the host shows the result: advanced counts a contribution made from
|
|
697
|
+
// at or above the high-water mark, not a confirmed new high. Only the next perception moves
|
|
698
|
+
// the progress clock, importance and pull; the composite does not consume advanced.
|
|
699
|
+
if (g.project?.perceivedProgress) {
|
|
700
|
+
if (g.project.peak === undefined || g.progress >= g.project.peak)
|
|
701
|
+
advanced.push(g);
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
677
704
|
g.progress = clamp01(g.progress + amount);
|
|
678
705
|
g.lastAdvancedAt = outcome.at;
|
|
679
706
|
if (g.baseImportance !== undefined)
|
|
@@ -704,6 +731,25 @@ export function adoptGoal(p, g, now) {
|
|
|
704
731
|
p.agenda.goals.push(goal);
|
|
705
732
|
return goal;
|
|
706
733
|
}
|
|
734
|
+
/** Attended host progress is authoritative for an active perceived-progress project. @internal */
|
|
735
|
+
export function perceiveProjectProgress(p, prospectId, progress, now) {
|
|
736
|
+
for (const g of p.agenda.goals) {
|
|
737
|
+
if (g.status !== 'active' || !g.project?.perceivedProgress || g.project.prospectId !== prospectId)
|
|
738
|
+
continue;
|
|
739
|
+
if (p.projects?.behaviour === 2 && g.project.peak === undefined)
|
|
740
|
+
g.project.peak = g.progress;
|
|
741
|
+
if (progress > (g.project.peak ?? g.progress)) {
|
|
742
|
+
if (g.project.peak !== undefined)
|
|
743
|
+
g.project.peak = clamp01(progress);
|
|
744
|
+
g.lastAdvancedAt = now;
|
|
745
|
+
if (g.baseImportance !== undefined)
|
|
746
|
+
g.importance = g.baseImportance;
|
|
747
|
+
}
|
|
748
|
+
g.progress = clamp01(progress);
|
|
749
|
+
if (g.progress >= 1)
|
|
750
|
+
g.status = 'achieved';
|
|
751
|
+
}
|
|
752
|
+
}
|
|
707
753
|
export function abandonGoal(p, id) {
|
|
708
754
|
const g = p.agenda.goals.find((x) => x.id === id);
|
|
709
755
|
if (g?.status !== 'active')
|
|
@@ -46,6 +46,7 @@ import { expectedEffect } from '../memory/index.js';
|
|
|
46
46
|
import { methodEvidence } from '../methods/index.js';
|
|
47
47
|
import { levelOf, needThreshold, urgencyOf } from '../needs/index.js';
|
|
48
48
|
import { partneringTerms } from '../partnering/index.js';
|
|
49
|
+
import { persistenceTerms } from '../persistence/index.js';
|
|
49
50
|
import { promptIn, promptingTerms } from '../promptings/index.js';
|
|
50
51
|
import { bodyForecastTerm, receivedGoalRoute } from '../prospection/index.js';
|
|
51
52
|
import { reviewTerms } from '../review/index.js';
|
|
@@ -461,6 +462,9 @@ export function consider(p, aff, ctx) {
|
|
|
461
462
|
if (p.curiosity)
|
|
462
463
|
for (const t of curiosityTerms(p, aff, ctx))
|
|
463
464
|
push(t.source, t.value);
|
|
465
|
+
if (p.pursuit)
|
|
466
|
+
for (const t of persistenceTerms(p, terms, ctx))
|
|
467
|
+
push(t.source, t.value);
|
|
464
468
|
// Promptings (2.4.0, opt-in): a recurrence pull under `fault` and a practised turning-away from a noticed prompt.
|
|
465
469
|
if (p.promptings)
|
|
466
470
|
for (const t of promptingTerms(p, aff.action, terms, aff.tags, ctx.now, ctx.desperation))
|
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.14.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';
|
|
@@ -28,7 +28,9 @@ export { MIGRATIONS, migratableVersions, migrate } from './migrate.ts';
|
|
|
28
28
|
export * from './narrate/index.ts';
|
|
29
29
|
export * from './needs/index.ts';
|
|
30
30
|
export * from './partnering/index.ts';
|
|
31
|
+
export { enablePersistence, PERSISTENCE_DEFAULTS, pursuitOf } from './persistence/index.ts';
|
|
31
32
|
export * from './person.ts';
|
|
33
|
+
export { considerProjects, enableProjects, PROJECTS_DEFAULTS, projectAppeal } from './projects/index.ts';
|
|
32
34
|
export * from './promptings/index.ts';
|
|
33
35
|
export type { ConsequenceEvidence, PlanRoute, PlanStep } from './prospection/index.ts';
|
|
34
36
|
export { bodyForecastFor, bodyForecastTerm, consequenceEvidence, enablePlanning, FORECAST_DEFAULTS, forecastLine, forecastThresholds, PROSPECTION_DEFAULTS, seedConsequences, } from './prospection/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.14.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';
|
|
@@ -25,7 +25,9 @@ export { MIGRATIONS, migratableVersions, migrate } from './migrate.js';
|
|
|
25
25
|
export * from './narrate/index.js';
|
|
26
26
|
export * from './needs/index.js';
|
|
27
27
|
export * from './partnering/index.js';
|
|
28
|
+
export { enablePersistence, PERSISTENCE_DEFAULTS, pursuitOf } from './persistence/index.js';
|
|
28
29
|
export * from './person.js';
|
|
30
|
+
export { considerProjects, enableProjects, PROJECTS_DEFAULTS, projectAppeal } from './projects/index.js';
|
|
29
31
|
export * from './promptings/index.js';
|
|
30
32
|
export { bodyForecastFor, bodyForecastTerm, consequenceEvidence, enablePlanning, FORECAST_DEFAULTS, forecastLine, forecastThresholds, PROSPECTION_DEFAULTS, seedConsequences, } from './prospection/index.js';
|
|
31
33
|
export { fingerprint, restore, snapshot } from './restore.js';
|
package/dist/migrate.js
CHANGED
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
* version; fullness stays absent unless enabled, and alongside partners and reconsideration are opt-in.
|
|
19
19
|
* 2.8.0 to 2.9.0 stamps the version; prompting cues and practice half-lives stay absent unless supplied,
|
|
20
20
|
* review terms may carry refs, and an outcome naming several commitments now keeps each rather than only
|
|
21
|
-
* the earliest-ending one. 2.
|
|
21
|
+
* the earliest-ending one. 2.14.0 to 2.14.1 only stamps the version: slices without behaviour markers
|
|
22
|
+
* retain 2.14.0 crediting and an undecaying pull until the host re-enables them. 2.13.0 to 2.14.0 stamps the version (projects and persistence start absent; goals may carry `project`, received successors may carry `advances`). 2.12.0 to 2.13.0 stamps the version (suggestion reasons and firm rules start absent). 2.11.0 to 2.12.0 stamps the version (planning starts absent). 2.10.0 to 2.11.0 stamps the version (curiosity starts absent). 2.9.0 to 2.10.0 stamps the version; belief provenance, noted contradictions and
|
|
22
23
|
* believed regard stay absent unless a host supplies them;
|
|
23
24
|
* `regard` becomes a reserved impression cue key. A migrated save continues under the current engine's rules: it restores and runs,
|
|
24
25
|
* but it does not reproduce what the old engine would have done next where the rules changed (1.4.0 to 1.5.0
|
|
@@ -70,6 +71,16 @@ export const MIGRATIONS = {
|
|
|
70
71
|
note: 'the surroundings field `ambient.now` is renamed `ambient.percept`; per-person retention is optional and starts absent; behaviour unchanged',
|
|
71
72
|
apply: renameAmbient,
|
|
72
73
|
},
|
|
74
|
+
'2.14.0': {
|
|
75
|
+
to: '2.14.1',
|
|
76
|
+
note: 'version stamp only; restored projects and persistence retain 2.14.0 behaviour until explicitly re-enabled',
|
|
77
|
+
apply: stamp,
|
|
78
|
+
},
|
|
79
|
+
'2.13.0': {
|
|
80
|
+
to: '2.14.0',
|
|
81
|
+
note: 'projects (Person.projects) and persistence (Person.pursuit) are optional and start absent; Goal.project, Percept.prospect and received successor advances are optional; existing hosts retain exact behavior',
|
|
82
|
+
apply: stamp,
|
|
83
|
+
},
|
|
73
84
|
'2.12.0': {
|
|
74
85
|
to: '2.13.0',
|
|
75
86
|
note: 'suggestion reasons (Person.will.suggestionReasons) and firm self-rules (Commitment.firm) are optional and start absent; existing hosts retain exact behavior',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './persistence.ts';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './persistence.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PersistenceSpec, Person, Pursuit, PursuitState } from '../types.ts';
|
|
2
|
+
/** Engineering defaults, relative to cognition's need scale 4, pleasure .6 and curiosity .8/.15.
|
|
3
|
+
* Continuation and switching together outweigh mild relaxed distraction; urgency and fear gate both.
|
|
4
|
+
* A pressing commitment has its full agenda term and pays no switching cost.
|
|
5
|
+
*/
|
|
6
|
+
export declare const PERSISTENCE_DEFAULTS: {
|
|
7
|
+
readonly continueScale: 0.8;
|
|
8
|
+
readonly switchScale: 0.8;
|
|
9
|
+
readonly cap: 0.8;
|
|
10
|
+
readonly switchMargin: 0.15;
|
|
11
|
+
readonly slackMinutes: 20;
|
|
12
|
+
readonly maxMinutes: 240;
|
|
13
|
+
readonly restMinutes: 20;
|
|
14
|
+
readonly stallHalfLifeMinutes: 30;
|
|
15
|
+
};
|
|
16
|
+
/** Enable or reconfigure, retaining a current pursuit. Invalid overrides use defaults. */
|
|
17
|
+
export declare function enablePersistence(p: Person, spec?: PersistenceSpec): PursuitState;
|
|
18
|
+
/** A copy of the current pursuit, without pruning, mutation or RNG. */
|
|
19
|
+
export declare function pursuitOf(p: Person): Pursuit | undefined;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SCOPE: opt-in persistence of a started goal, owned only in p.pursuit. Positive goal terms cue
|
|
3
|
+
* continued means (implementation intentions: Gollwitzer 1999; Gollwitzer & Sheeran 2006), while
|
|
4
|
+
* unrelated options pay a switching cost (goal shielding: Shah, Friedman & Kruglanski 2002;
|
|
5
|
+
* task switching: Monsell 2003). Unused activation expires (Altmann & Trafton 2002); short
|
|
6
|
+
* interruptions allow resumption (Ovsiankina 1928). See research/projects-and-persistence.md.
|
|
7
|
+
* Maximum-duration release keeps one refractory entry, for the most recent release. With defaults
|
|
8
|
+
* (maxMinutes 240, restMinutes 20), two releases cannot fall within one interval; with a shorter
|
|
9
|
+
* maximum, a later release can replace an earlier unexpired interval.
|
|
10
|
+
* These are shapes, not fitted values; all coefficients and time bounds are engineering defaults.
|
|
11
|
+
* This is a bounded bias plus a switching cost, not a planner. It chooses no goals, executes no
|
|
12
|
+
* plans, reorders no steps and changes no agenda valuation. It imports no projects, uses no RNG,
|
|
13
|
+
* LLM or faith lens. Needs and fear gate both terms; commitment options never pay the switch cost.
|
|
14
|
+
* Behaviour 2 halves both terms with time since the pursued goal last advanced (or adoption),
|
|
15
|
+
* including after release and re-formation. Absent behaviour retains the 2.14.0 undecaying pull.
|
|
16
|
+
* Without the slice, it adds no state or terms.
|
|
17
|
+
*/
|
|
18
|
+
import { emotionLevel } from '../affect/index.js';
|
|
19
|
+
import { clamp01, cmpStr, dpow, isNum, isObj, round } from '../core/index.js';
|
|
20
|
+
import { MAX_MINUTE, PHYSIOLOGICAL_NEEDS } from '../types.js';
|
|
21
|
+
/** Engineering defaults, relative to cognition's need scale 4, pleasure .6 and curiosity .8/.15.
|
|
22
|
+
* Continuation and switching together outweigh mild relaxed distraction; urgency and fear gate both.
|
|
23
|
+
* A pressing commitment has its full agenda term and pays no switching cost.
|
|
24
|
+
*/
|
|
25
|
+
export const PERSISTENCE_DEFAULTS = {
|
|
26
|
+
continueScale: 0.8,
|
|
27
|
+
switchScale: 0.8,
|
|
28
|
+
cap: 0.8,
|
|
29
|
+
switchMargin: 0.15,
|
|
30
|
+
slackMinutes: 20,
|
|
31
|
+
maxMinutes: 240,
|
|
32
|
+
restMinutes: 20,
|
|
33
|
+
stallHalfLifeMinutes: 30,
|
|
34
|
+
};
|
|
35
|
+
const KEYS = [
|
|
36
|
+
'continueScale',
|
|
37
|
+
'switchScale',
|
|
38
|
+
'cap',
|
|
39
|
+
'switchMargin',
|
|
40
|
+
'slackMinutes',
|
|
41
|
+
'maxMinutes',
|
|
42
|
+
'restMinutes',
|
|
43
|
+
'stallHalfLifeMinutes',
|
|
44
|
+
];
|
|
45
|
+
function cleanSpec(input) {
|
|
46
|
+
const out = {};
|
|
47
|
+
if (!isObj(input))
|
|
48
|
+
return out;
|
|
49
|
+
for (const key of KEYS) {
|
|
50
|
+
const x = input[key];
|
|
51
|
+
const duration = key === 'slackMinutes' ||
|
|
52
|
+
key === 'maxMinutes' ||
|
|
53
|
+
key === 'restMinutes' ||
|
|
54
|
+
key === 'stallHalfLifeMinutes';
|
|
55
|
+
const min = key === 'stallHalfLifeMinutes' ? 1 : 0;
|
|
56
|
+
if (isNum(x) && x >= min && x <= (duration ? 1440 : 1))
|
|
57
|
+
out[key] = x;
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
const setting = (s, key) => s.spec?.[key] ?? PERSISTENCE_DEFAULTS[key];
|
|
62
|
+
const identifier = (x) => typeof x === 'string' && x.length > 0 && x.length <= 512;
|
|
63
|
+
const active = (p, id) => p.agenda.goals.find((g) => g.id === id && g.status === 'active');
|
|
64
|
+
/** Enable or reconfigure, retaining a current pursuit. Invalid overrides use defaults. */
|
|
65
|
+
export function enablePersistence(p, spec = {}) {
|
|
66
|
+
const out = { behaviour: 2 };
|
|
67
|
+
const clean = cleanSpec(spec);
|
|
68
|
+
if (Object.keys(clean).length)
|
|
69
|
+
out.spec = clean;
|
|
70
|
+
if (p.pursuit?.current)
|
|
71
|
+
out.current = { ...p.pursuit.current };
|
|
72
|
+
if (p.pursuit?.rested)
|
|
73
|
+
out.rested = { ...p.pursuit.rested };
|
|
74
|
+
p.pursuit = out;
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
/** A copy of the current pursuit, without pruning, mutation or RNG. */
|
|
78
|
+
export function pursuitOf(p) {
|
|
79
|
+
return p.pursuit?.current ? { ...p.pursuit.current } : undefined;
|
|
80
|
+
}
|
|
81
|
+
/** Drop finished, absent, neglected or overlong pursuits. Interruptions alone do not release. @internal */
|
|
82
|
+
export function releasePursuit(p) {
|
|
83
|
+
const s = p.pursuit;
|
|
84
|
+
if (!s)
|
|
85
|
+
return;
|
|
86
|
+
if (s.rested && p.now >= s.rested.until)
|
|
87
|
+
delete s.rested;
|
|
88
|
+
const c = s.current;
|
|
89
|
+
if (!c)
|
|
90
|
+
return;
|
|
91
|
+
if (!active(p, c.goalId) || p.now - c.lastServedAt >= setting(s, 'slackMinutes')) {
|
|
92
|
+
delete s.current;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (p.now - c.since >= setting(s, 'maxMinutes')) {
|
|
96
|
+
delete s.current;
|
|
97
|
+
s.rested = { goalId: c.goalId, until: Math.min(MAX_MINUTE, p.now + setting(s, 'restMinutes')) };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** Form from the begun option's already-rounded goal terms, including route credit. @internal */
|
|
101
|
+
export function beginPursuit(p, terms) {
|
|
102
|
+
const s = p.pursuit;
|
|
103
|
+
if (!s)
|
|
104
|
+
return;
|
|
105
|
+
releasePursuit(p);
|
|
106
|
+
const candidates = p.agenda.goals
|
|
107
|
+
.filter((g) => g.status === 'active' && identifier(g.id) && g.id !== s.rested?.goalId)
|
|
108
|
+
.map((goal) => ({
|
|
109
|
+
goal,
|
|
110
|
+
value: terms
|
|
111
|
+
.filter((t) => t.source === `goal:${goal.id}` && t.value > 0)
|
|
112
|
+
.reduce((v, t) => v + t.value, 0),
|
|
113
|
+
}))
|
|
114
|
+
.filter((x) => x.value > 0)
|
|
115
|
+
.sort((a, b) => b.value - a.value || cmpStr(a.goal.id, b.goal.id));
|
|
116
|
+
const best = candidates[0];
|
|
117
|
+
if (!best)
|
|
118
|
+
return;
|
|
119
|
+
const current = s.current;
|
|
120
|
+
const currentValue = candidates.find((x) => x.goal.id === current?.goalId)?.value ?? 0;
|
|
121
|
+
if (current &&
|
|
122
|
+
best.goal.id !== current.goalId &&
|
|
123
|
+
round(best.value - currentValue) <= setting(s, 'switchMargin')) {
|
|
124
|
+
if (currentValue > 0)
|
|
125
|
+
current.lastServedAt = p.now;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
s.current = {
|
|
129
|
+
goalId: best.goal.id,
|
|
130
|
+
since: current?.goalId === best.goal.id ? current.since : p.now,
|
|
131
|
+
lastServedAt: p.now,
|
|
132
|
+
strength: clamp01(best.goal.importance * (0.4 + 0.6 * p.traits.conscientiousness)),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/** Read already-pushed goal/commitment terms under the actual perceived needs. @internal */
|
|
136
|
+
export function persistenceTerms(p, terms, ctx) {
|
|
137
|
+
const s = p.pursuit;
|
|
138
|
+
const c = s?.current;
|
|
139
|
+
if (!s || !c)
|
|
140
|
+
return [];
|
|
141
|
+
const goal = active(p, c.goalId);
|
|
142
|
+
if (!goal)
|
|
143
|
+
return [];
|
|
144
|
+
const stall = Math.max(0, p.now - (goal.lastAdvancedAt ?? goal.adoptedAt));
|
|
145
|
+
const decay = s.behaviour === 2 ? dpow(0.5, stall / setting(s, 'stallHalfLifeMinutes')) : 1;
|
|
146
|
+
let urgency = 0;
|
|
147
|
+
for (const n of ctx.needs)
|
|
148
|
+
if (n.id === 'safety' || PHYSIOLOGICAL_NEEDS.includes(n.id))
|
|
149
|
+
urgency = Math.max(urgency, n.urgency);
|
|
150
|
+
const gate = (1 - clamp01(urgency)) * (1 - emotionLevel(p, 'fear'));
|
|
151
|
+
if (terms.some((t) => t.source === `goal:${c.goalId}` && t.value > 0))
|
|
152
|
+
return [
|
|
153
|
+
{
|
|
154
|
+
source: `pursuit:${c.goalId}`,
|
|
155
|
+
value: round(Math.min(setting(s, 'cap'), setting(s, 'continueScale') * c.strength * gate) * decay),
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
if (terms.some((t) => t.source.startsWith('commitment:')))
|
|
159
|
+
return [];
|
|
160
|
+
return [
|
|
161
|
+
{
|
|
162
|
+
source: 'pursuit:switch',
|
|
163
|
+
value: -round(Math.min(setting(s, 'cap'), setting(s, 'switchScale') * c.strength * gate) * decay),
|
|
164
|
+
},
|
|
165
|
+
];
|
|
166
|
+
}
|
|
167
|
+
const minute = (x, now) => isNum(x) && Math.abs(x) <= MAX_MINUTE && x <= now;
|
|
168
|
+
/** Conservative restore: malformed settings or pursuit drop the optional slice. @internal */
|
|
169
|
+
export function sanitizePursuit(input, now) {
|
|
170
|
+
if (!isObj(input) || (input.spec !== undefined && !isObj(input.spec)))
|
|
171
|
+
return undefined;
|
|
172
|
+
if (input.behaviour !== undefined && input.behaviour !== 2)
|
|
173
|
+
return undefined;
|
|
174
|
+
const out = {};
|
|
175
|
+
if (input.behaviour === 2)
|
|
176
|
+
out.behaviour = 2;
|
|
177
|
+
if (input.spec !== undefined) {
|
|
178
|
+
const spec = cleanSpec(input.spec);
|
|
179
|
+
if (Object.keys(spec).length !== Object.keys(input.spec).length)
|
|
180
|
+
return undefined;
|
|
181
|
+
if (Object.keys(spec).length)
|
|
182
|
+
out.spec = spec;
|
|
183
|
+
}
|
|
184
|
+
const c = input.current;
|
|
185
|
+
if (c !== undefined) {
|
|
186
|
+
if (!isObj(c) ||
|
|
187
|
+
!identifier(c.goalId) ||
|
|
188
|
+
!minute(c.since, now) ||
|
|
189
|
+
!minute(c.lastServedAt, now) ||
|
|
190
|
+
c.lastServedAt < c.since ||
|
|
191
|
+
!isNum(c.strength) ||
|
|
192
|
+
c.strength < 0 ||
|
|
193
|
+
c.strength > 1)
|
|
194
|
+
return undefined;
|
|
195
|
+
out.current = { goalId: c.goalId, since: c.since, lastServedAt: c.lastServedAt, strength: c.strength };
|
|
196
|
+
}
|
|
197
|
+
const r = input.rested;
|
|
198
|
+
if (r !== undefined) {
|
|
199
|
+
if (!isObj(r) ||
|
|
200
|
+
!identifier(r.goalId) ||
|
|
201
|
+
!isNum(r.until) ||
|
|
202
|
+
Math.abs(r.until) > MAX_MINUTE ||
|
|
203
|
+
// A later reconfiguration may shorten restMinutes without shortening an existing interval.
|
|
204
|
+
r.until > now + 1440)
|
|
205
|
+
return undefined;
|
|
206
|
+
out.rested = { goalId: r.goalId, until: r.until };
|
|
207
|
+
}
|
|
208
|
+
return out;
|
|
209
|
+
}
|
package/dist/person.js
CHANGED
|
@@ -20,14 +20,14 @@
|
|
|
20
20
|
* long-horizon runs. Each hook calls the owning module; nothing here writes another module's slice.
|
|
21
21
|
*/
|
|
22
22
|
import { actionTendencies, advanceAffect, appraise, CRISIS_DEFAULTS, checkCrisis, createAffect, easeBreak, emotionLevel, feel, readAffect, regulate, release, skipAffect, skipCrisis, strain, tendencyEmotions, } from './affect/index.js';
|
|
23
|
-
import { advanceAgenda, createAgenda, onFinished, promise, proposeGoals, revisePurposes, } from './agenda/index.js';
|
|
23
|
+
import { advanceAgenda, agendaTermsWithGoalEstimates, createAgenda, onFinished, promise, proposeGoals, revisePurposes, } from './agenda/index.js';
|
|
24
24
|
import { advanceBeliefs, applyContradiction, attend, believe, confirm, credence, noteProvenance, trustOf, } from './beliefs/index.js';
|
|
25
25
|
import { advanceBody, BODY_DEFAULTS, checkDowned, clearDowned, consume, createBody, downedAllows, enableFatigueStore, enableFullness, expose, injure, nextBodyThreshold, readBody, setDowned, sicken, skipBody, } from './body/index.js';
|
|
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, 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 } from './core/index.js';
|
|
30
|
+
import { clamp01, clampSigned, createRng, dayOf, isObj, 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';
|
|
@@ -37,6 +37,8 @@ import { advanceMemory, createMemory, enableGists, foldGists, learnOutcome, reca
|
|
|
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';
|
|
40
|
+
import { beginPursuit, enablePersistence, releasePursuit } from './persistence/index.js';
|
|
41
|
+
import { considerProjects, enableProjects, noteProjectStatus, perceiveProspect } from './projects/index.js';
|
|
40
42
|
import { enablePromptings, nafsPatterns, notePromptings } from './promptings/index.js';
|
|
41
43
|
import { bodyForecastFor, consumeConsequences, enablePlanning, forecastLine, forecastThresholds, pruneConsequences, receiveConsequences, } from './prospection/index.js';
|
|
42
44
|
import { enableReview, reviewIfDue } from './review/index.js';
|
|
@@ -162,8 +164,12 @@ export function createPerson(spec) {
|
|
|
162
164
|
enableFatigueStore(p);
|
|
163
165
|
if (on?.fullness)
|
|
164
166
|
enableFullness(p);
|
|
167
|
+
if (on?.projects)
|
|
168
|
+
enableProjects(p, on.projects === true ? {} : on.projects);
|
|
165
169
|
if (on?.curiosity)
|
|
166
170
|
enableCuriosity(p, on.curiosity === true ? {} : on.curiosity);
|
|
171
|
+
if (on?.persistence)
|
|
172
|
+
enablePersistence(p, on.persistence === true ? {} : on.persistence);
|
|
167
173
|
if (on?.planning)
|
|
168
174
|
enablePlanning(p, on.planning);
|
|
169
175
|
if (on?.promptings)
|
|
@@ -367,11 +373,15 @@ function recordKeptAbstentions(p, kept, now) {
|
|
|
367
373
|
* rounding (~1e-9) unless calls also fall on the grid, in which case replay is byte-identical.
|
|
368
374
|
*/
|
|
369
375
|
export function tick(p, now) {
|
|
376
|
+
if (p.pursuit)
|
|
377
|
+
releasePursuit(p);
|
|
370
378
|
if (now <= p.now)
|
|
371
379
|
return;
|
|
372
380
|
if (!p.body.alive) {
|
|
373
381
|
p.now = now;
|
|
374
382
|
pruneConsequences(p);
|
|
383
|
+
if (p.pursuit)
|
|
384
|
+
releasePursuit(p);
|
|
375
385
|
return;
|
|
376
386
|
}
|
|
377
387
|
const seg = PERSON_DEFAULTS.maxSegment;
|
|
@@ -386,15 +396,21 @@ export function tick(p, now) {
|
|
|
386
396
|
if (!(end > p.now)) {
|
|
387
397
|
p.now = now;
|
|
388
398
|
pruneConsequences(p);
|
|
399
|
+
if (p.pursuit)
|
|
400
|
+
releasePursuit(p);
|
|
389
401
|
return;
|
|
390
402
|
}
|
|
391
403
|
advanceSegment(p, end - p.now, load, active && act ? act.affordance : undefined);
|
|
392
404
|
p.now = end;
|
|
393
405
|
pruneConsequences(p);
|
|
406
|
+
if (p.pursuit)
|
|
407
|
+
releasePursuit(p);
|
|
394
408
|
if (!p.body.alive) {
|
|
395
409
|
p.activity = null;
|
|
396
410
|
p.now = now;
|
|
397
411
|
pruneConsequences(p);
|
|
412
|
+
if (p.pursuit)
|
|
413
|
+
releasePursuit(p);
|
|
398
414
|
return;
|
|
399
415
|
}
|
|
400
416
|
// Discrete bookkeeping runs only on grid boundaries, so it happens at the same minutes whatever the
|
|
@@ -454,6 +470,10 @@ export function tick(p, now) {
|
|
|
454
470
|
}
|
|
455
471
|
}
|
|
456
472
|
}
|
|
473
|
+
if (p.projects)
|
|
474
|
+
considerProjects(p, end);
|
|
475
|
+
if (p.pursuit)
|
|
476
|
+
releasePursuit(p);
|
|
457
477
|
}
|
|
458
478
|
}
|
|
459
479
|
/** The composite's side of the hourly crisis check: interrupt and remember an onset, remember a recovery. */
|
|
@@ -546,6 +566,8 @@ export function skip(p, to) {
|
|
|
546
566
|
skipBody(p, to);
|
|
547
567
|
p.now = to;
|
|
548
568
|
pruneConsequences(p);
|
|
569
|
+
if (p.pursuit)
|
|
570
|
+
releasePursuit(p);
|
|
549
571
|
skipAffect(p, to);
|
|
550
572
|
advanceAgenda(p, to);
|
|
551
573
|
// Lasting gists (1.8.0, opt-in): episodes now past the horizon fold into gists.
|
|
@@ -565,6 +587,8 @@ function socialKindOf(kind) {
|
|
|
565
587
|
}
|
|
566
588
|
function perceiveOne(p, pc) {
|
|
567
589
|
receiveConsequences(p, pc);
|
|
590
|
+
if (p.projects && pc.prospect)
|
|
591
|
+
perceiveProspect(p, pc.prospect, pc.at);
|
|
568
592
|
const at = Math.min(Math.max(pc.at, p.affect.lastUpdated), p.now);
|
|
569
593
|
const actor = pc.actorId;
|
|
570
594
|
const target = pc.targetId;
|
|
@@ -1021,6 +1045,20 @@ export function begin(p, aff, record, opts = {}) {
|
|
|
1021
1045
|
if (!p.body.alive)
|
|
1022
1046
|
return null;
|
|
1023
1047
|
const now = p.now;
|
|
1048
|
+
if (p.pursuit) {
|
|
1049
|
+
const considered = record.considered.find((c) => c.affordanceId === aff.id);
|
|
1050
|
+
let terms = considered?.terms;
|
|
1051
|
+
if (!terms) {
|
|
1052
|
+
const { ctx } = decisionInputs(p, [aff], record.id, {});
|
|
1053
|
+
const route = prospectiveRoute(p, aff, ctx);
|
|
1054
|
+
const scale = 0.5 + 0.5 * clamp01(ctx.mods.maturity);
|
|
1055
|
+
terms = agendaTermsWithGoalEstimates(p, aff, now, route?.goals).map((t) => ({
|
|
1056
|
+
source: t.source,
|
|
1057
|
+
value: round(t.value * scale),
|
|
1058
|
+
}));
|
|
1059
|
+
}
|
|
1060
|
+
beginPursuit(p, terms);
|
|
1061
|
+
}
|
|
1024
1062
|
const duration = Number.isFinite(aff.duration) ? Math.max(1, Math.round(aff.duration)) : 1;
|
|
1025
1063
|
const mode = aff.mode ?? 'awake';
|
|
1026
1064
|
const load = { effort: clamp01(aff.effort), focus: clamp01(aff.focus ?? 0), mode };
|
|
@@ -1295,6 +1333,8 @@ function learnActivityVoices(p, act, after, now, felt, broken, breached) {
|
|
|
1295
1333
|
* relationships, trust in the advising voice, injuries and illness. Clears the activity.
|
|
1296
1334
|
*/
|
|
1297
1335
|
export function finish(p, outcome, opts = {}) {
|
|
1336
|
+
if (p.pursuit)
|
|
1337
|
+
releasePursuit(p);
|
|
1298
1338
|
const act = p.activity;
|
|
1299
1339
|
if (!act)
|
|
1300
1340
|
return null;
|
|
@@ -1409,6 +1449,10 @@ export function finish(p, outcome, opts = {}) {
|
|
|
1409
1449
|
...(act.necessity ? { necessity: true } : {}),
|
|
1410
1450
|
...(opts.catalog ? { catalog: opts.catalog } : {}),
|
|
1411
1451
|
});
|
|
1452
|
+
if (p.projects)
|
|
1453
|
+
noteProjectStatus(p, now);
|
|
1454
|
+
if (p.pursuit)
|
|
1455
|
+
releasePursuit(p);
|
|
1412
1456
|
// A completed violating action inside an open abstention's window broke it (the fast, e.g. "no work after the evening prayer").
|
|
1413
1457
|
if (broken.length > 0) {
|
|
1414
1458
|
recordMissed(p, broken, now, outcome.action);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './projects.ts';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './projects.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Minute, Person, ProjectsSpec, ProjectsState, Prospect } from '../types.ts';
|
|
2
|
+
export declare const PROJECTS_DEFAULTS: {
|
|
3
|
+
readonly maxProspects: 8;
|
|
4
|
+
readonly maxHistory: 8;
|
|
5
|
+
readonly forgetMinutes: 1440;
|
|
6
|
+
readonly calmUrgency: 0.35;
|
|
7
|
+
readonly fearBelow: 0.35;
|
|
8
|
+
readonly adoptEveryMinutes: 30;
|
|
9
|
+
readonly cooldownMinutes: 60;
|
|
10
|
+
readonly adoptAppeal: 0.3;
|
|
11
|
+
readonly halfLifeMinutes: 120;
|
|
12
|
+
readonly staleMinutes: 180;
|
|
13
|
+
};
|
|
14
|
+
/** Enable or reconfigure without discarding retained prospects and history. */
|
|
15
|
+
export declare function enableProjects(p: Person, spec?: ProjectsSpec): ProjectsState;
|
|
16
|
+
/** Read-only, deterministic appeal. Thresholds close the relaxed gate before the bounded bonus. */
|
|
17
|
+
export declare function projectAppeal(p: Person, prospect: Prospect, now?: Minute): number;
|
|
18
|
+
/** Only the composite calls this, after attention. Newest event minute wins; equal-minute input order wins. */
|
|
19
|
+
export declare function perceiveProspect(p: Person, input: Prospect, at: Minute): void;
|
|
20
|
+
/** Called on each tick segment, including one-minute begin/tick/finish host loops. No RNG. */
|
|
21
|
+
export declare function considerProjects(p: Person, now: Minute): void;
|
|
22
|
+
/** Conservative restore: bounded fields, finite past timestamps, and no ambiguous duplicate ids. */
|
|
23
|
+
export declare function sanitizeProjects(input: unknown, now: Minute): ProjectsState | undefined;
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SCOPE: self-set, bounded pursuits from host-shown, attended prospects. Fit to temperament and
|
|
3
|
+
* relaxed needs chooses one project; perceived progress or completed acts advances it; neglect drops
|
|
4
|
+
* it. Shapes, not fitted values: Little's personal projects (1983), Locke & Latham's goal setting
|
|
5
|
+
* (2002), autonomy/competence in Deci & Ryan and Ryan & Deci (2000), constructive play (Smilansky
|
|
6
|
+
* 1968; Burghardt 2005), and interrupted-task resumption (Ovsiankina 1928). The resumption bonus
|
|
7
|
+
* models a pull to continue, not a Zeigarnik memory advantage. See research/projects-and-persistence.md.
|
|
8
|
+
* All thresholds and coefficients are engineering defaults. This owns only p.projects; agenda owns
|
|
9
|
+
* goals. All kinds serve competence and leisure: making is chosen as a free-time pursuit, not duty.
|
|
10
|
+
* Behaviour 2 credits only new perceived highs; event-credited projects cannot observe reversals and stay unchanged.
|
|
11
|
+
* Restore recovers one active project, keeping the newest (id breaks ties) and abandoning extras.
|
|
12
|
+
* No invented prospects, host task interpretation, world search, persistence, shielding, faith lens,
|
|
13
|
+
* LLM or RNG. Without the slice, perception and minute bookkeeping write nothing.
|
|
14
|
+
*/
|
|
15
|
+
import { emotionLevel } from '../affect/index.js';
|
|
16
|
+
import { AGENDA_DEFAULTS, abandonGoal, adoptGoal, perceiveProjectProgress } from '../agenda/index.js';
|
|
17
|
+
import { clamp01, cmpStr, isNum, isObj, round } from '../core/index.js';
|
|
18
|
+
import { curiosityOf } from '../curiosity/index.js';
|
|
19
|
+
import { readPerson } from '../person.js';
|
|
20
|
+
import { MAX_MINUTE, PHYSIOLOGICAL_NEEDS } from '../types.js';
|
|
21
|
+
export const PROJECTS_DEFAULTS = {
|
|
22
|
+
maxProspects: 8,
|
|
23
|
+
maxHistory: 8,
|
|
24
|
+
forgetMinutes: 1440,
|
|
25
|
+
calmUrgency: 0.35,
|
|
26
|
+
fearBelow: 0.35,
|
|
27
|
+
adoptEveryMinutes: 30,
|
|
28
|
+
cooldownMinutes: 60,
|
|
29
|
+
adoptAppeal: 0.3,
|
|
30
|
+
halfLifeMinutes: 120,
|
|
31
|
+
staleMinutes: 180,
|
|
32
|
+
};
|
|
33
|
+
const KEYS = [
|
|
34
|
+
'forgetMinutes',
|
|
35
|
+
'calmUrgency',
|
|
36
|
+
'fearBelow',
|
|
37
|
+
'adoptEveryMinutes',
|
|
38
|
+
'cooldownMinutes',
|
|
39
|
+
'adoptAppeal',
|
|
40
|
+
'halfLifeMinutes',
|
|
41
|
+
'staleMinutes',
|
|
42
|
+
];
|
|
43
|
+
function cleanSpec(input) {
|
|
44
|
+
const out = {};
|
|
45
|
+
if (!isObj(input))
|
|
46
|
+
return out;
|
|
47
|
+
for (const key of KEYS) {
|
|
48
|
+
const x = input[key];
|
|
49
|
+
const unit = key === 'calmUrgency' || key === 'fearBelow' || key === 'adoptAppeal';
|
|
50
|
+
if (isNum(x) && x >= (unit ? 0 : 1) && x <= (unit ? 1 : 525600))
|
|
51
|
+
out[key] = x;
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
const setting = (s, key) => s.spec?.[key] ?? PROJECTS_DEFAULTS[key];
|
|
56
|
+
const str = (x) => typeof x === 'string' && x.length > 0 && x.length <= 512;
|
|
57
|
+
const unit = (x) => isNum(x) && x >= 0 && x <= 1;
|
|
58
|
+
const minute = (x, now) => isNum(x) && Math.abs(x) <= MAX_MINUTE && x <= now;
|
|
59
|
+
function cleanProspect(x) {
|
|
60
|
+
if (!isObj(x) ||
|
|
61
|
+
!str(x.id) ||
|
|
62
|
+
!str(x.label) ||
|
|
63
|
+
!['make', 'arrange', 'gather', 'finish'].includes(x.kind) ||
|
|
64
|
+
(x.placeId !== undefined && !str(x.placeId)) ||
|
|
65
|
+
(x.progress !== undefined && !unit(x.progress)) ||
|
|
66
|
+
(x.materialIds !== undefined &&
|
|
67
|
+
(!Array.isArray(x.materialIds) || x.materialIds.length > 256 || !x.materialIds.every(str))))
|
|
68
|
+
return undefined;
|
|
69
|
+
const p = { id: x.id, label: x.label, kind: x.kind };
|
|
70
|
+
if (x.placeId !== undefined)
|
|
71
|
+
p.placeId = x.placeId;
|
|
72
|
+
if (x.progress !== undefined)
|
|
73
|
+
p.progress = x.progress;
|
|
74
|
+
// A long list is kept, not rejected: the first eight distinct ids in the host's order (host rerun, 2026-10-20).
|
|
75
|
+
if (x.materialIds !== undefined)
|
|
76
|
+
p.materialIds = [...new Set(x.materialIds)].slice(0, 8);
|
|
77
|
+
return p;
|
|
78
|
+
}
|
|
79
|
+
/** Enable or reconfigure without discarding retained prospects and history. */
|
|
80
|
+
export function enableProjects(p, spec = {}) {
|
|
81
|
+
const state = p.projects ?? { prospects: [], history: [] };
|
|
82
|
+
for (const g of p.agenda.goals)
|
|
83
|
+
if (g.status === 'active' && g.project?.perceivedProgress && g.project.peak === undefined)
|
|
84
|
+
g.project.peak = g.progress;
|
|
85
|
+
state.behaviour = 2;
|
|
86
|
+
const clean = cleanSpec(spec);
|
|
87
|
+
delete state.spec;
|
|
88
|
+
if (Object.keys(clean).length)
|
|
89
|
+
state.spec = clean;
|
|
90
|
+
p.projects = state;
|
|
91
|
+
return state;
|
|
92
|
+
}
|
|
93
|
+
function relaxed(p) {
|
|
94
|
+
const needs = readPerson(p).needs;
|
|
95
|
+
return {
|
|
96
|
+
urgency: Math.max(0, ...needs
|
|
97
|
+
.filter((n) => n.id === 'safety' || PHYSIOLOGICAL_NEEDS.includes(n.id))
|
|
98
|
+
.map((n) => n.urgency)),
|
|
99
|
+
fear: emotionLevel(p, 'fear'),
|
|
100
|
+
interest: Math.max(0, ...needs.filter((n) => ['leisure', 'competence', 'autonomy'].includes(n.id)).map((n) => n.urgency)),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** Read-only, deterministic appeal. Thresholds close the relaxed gate before the bounded bonus. */
|
|
104
|
+
export function projectAppeal(p, prospect, now = p.now) {
|
|
105
|
+
const s = p.projects;
|
|
106
|
+
if (!s ||
|
|
107
|
+
!cleanProspect(prospect) ||
|
|
108
|
+
prospect.progress === 1 ||
|
|
109
|
+
p.body.asleep ||
|
|
110
|
+
p.activity?.mode === 'sleep' ||
|
|
111
|
+
!p.body.alive)
|
|
112
|
+
return 0;
|
|
113
|
+
const { urgency, fear, interest } = relaxed(p);
|
|
114
|
+
if (urgency >= setting(s, 'calmUrgency') || fear >= setting(s, 'fearBelow'))
|
|
115
|
+
return 0;
|
|
116
|
+
const gate = (1 - urgency) * (1 - fear);
|
|
117
|
+
const fit = 0.25 + 0.75 * (prospect.kind === 'finish' ? p.traits.conscientiousness : p.traits.openness);
|
|
118
|
+
const resume = prospect.progress !== undefined && prospect.progress > 0 && prospect.progress < 1 ? 0.1 : 0;
|
|
119
|
+
const bonus = p.curiosity
|
|
120
|
+
? 0.05 *
|
|
121
|
+
clamp01(curiosityOf(p, {
|
|
122
|
+
id: prospect.id,
|
|
123
|
+
action: prospect.kind,
|
|
124
|
+
label: prospect.label,
|
|
125
|
+
duration: 1,
|
|
126
|
+
effort: 0,
|
|
127
|
+
advertises: {},
|
|
128
|
+
}, now).drive)
|
|
129
|
+
: 0;
|
|
130
|
+
return round(clamp01(gate * (fit * (0.5 + 0.5 * interest) + resume + bonus)));
|
|
131
|
+
}
|
|
132
|
+
function recordProjectStatus(p, g, now) {
|
|
133
|
+
const s = p.projects;
|
|
134
|
+
if (!s || !g.project || g.status === 'active')
|
|
135
|
+
return;
|
|
136
|
+
if (!s.history.some((h) => h.goalId === g.id))
|
|
137
|
+
s.history.push({
|
|
138
|
+
prospectId: g.project.prospectId,
|
|
139
|
+
goalId: g.id,
|
|
140
|
+
adoptedAt: g.adoptedAt,
|
|
141
|
+
outcome: g.status,
|
|
142
|
+
at: now,
|
|
143
|
+
});
|
|
144
|
+
if (g.status === 'abandoned')
|
|
145
|
+
s.lastAbandonedAt = now;
|
|
146
|
+
else {
|
|
147
|
+
// Completed event-progress projects have no host progress reading: retain the known completion.
|
|
148
|
+
const prospect = s.prospects.find((x) => x.id === g.project?.prospectId);
|
|
149
|
+
if (prospect)
|
|
150
|
+
prospect.progress = 1;
|
|
151
|
+
}
|
|
152
|
+
if (s.history.length > PROJECTS_DEFAULTS.maxHistory)
|
|
153
|
+
s.history.shift();
|
|
154
|
+
if (s.currentGoalId === g.id)
|
|
155
|
+
delete s.currentGoalId;
|
|
156
|
+
}
|
|
157
|
+
/** Record the adopted project's terminal state once, even after history eviction. @internal */
|
|
158
|
+
export function noteProjectStatus(p, now) {
|
|
159
|
+
const s = p.projects;
|
|
160
|
+
if (!s?.currentGoalId)
|
|
161
|
+
return;
|
|
162
|
+
const g = p.agenda.goals.find((g) => g.id === s.currentGoalId);
|
|
163
|
+
if (!g?.project)
|
|
164
|
+
delete s.currentGoalId;
|
|
165
|
+
else
|
|
166
|
+
recordProjectStatus(p, g, now);
|
|
167
|
+
}
|
|
168
|
+
/** Only the composite calls this, after attention. Newest event minute wins; equal-minute input order wins. */
|
|
169
|
+
export function perceiveProspect(p, input, at) {
|
|
170
|
+
const s = p.projects;
|
|
171
|
+
if (!s || !minute(at, p.now))
|
|
172
|
+
return;
|
|
173
|
+
const prospect = cleanProspect(input);
|
|
174
|
+
if (!prospect)
|
|
175
|
+
return;
|
|
176
|
+
const old = s.prospects.find((x) => x.id === prospect.id);
|
|
177
|
+
if (old && old.lastSeenAt > at)
|
|
178
|
+
return;
|
|
179
|
+
if (p.now - at > setting(s, 'forgetMinutes'))
|
|
180
|
+
return;
|
|
181
|
+
const held = { ...prospect, lastSeenAt: at };
|
|
182
|
+
if (old)
|
|
183
|
+
s.prospects[s.prospects.indexOf(old)] = held;
|
|
184
|
+
else {
|
|
185
|
+
if (s.prospects.length >= PROJECTS_DEFAULTS.maxProspects) {
|
|
186
|
+
let oldest = 0;
|
|
187
|
+
for (const [i, entry] of s.prospects.entries())
|
|
188
|
+
if (entry.lastSeenAt < (s.prospects[oldest]?.lastSeenAt ?? Infinity))
|
|
189
|
+
oldest = i;
|
|
190
|
+
s.prospects.splice(oldest, 1);
|
|
191
|
+
}
|
|
192
|
+
s.prospects.push(held);
|
|
193
|
+
}
|
|
194
|
+
if (prospect.progress !== undefined)
|
|
195
|
+
perceiveProjectProgress(p, prospect.id, prospect.progress, p.now);
|
|
196
|
+
noteProjectStatus(p, p.now);
|
|
197
|
+
}
|
|
198
|
+
/** Called on each tick segment, including one-minute begin/tick/finish host loops. No RNG. */
|
|
199
|
+
export function considerProjects(p, now) {
|
|
200
|
+
const s = p.projects;
|
|
201
|
+
if (!s || !minute(now, p.now))
|
|
202
|
+
return;
|
|
203
|
+
s.prospects = s.prospects.filter((x) => now - x.lastSeenAt <= setting(s, 'forgetMinutes'));
|
|
204
|
+
for (const g of p.agenda.goals)
|
|
205
|
+
if (g.project &&
|
|
206
|
+
g.status === 'active' &&
|
|
207
|
+
now - (g.lastAdvancedAt ?? g.adoptedAt) >= setting(s, 'staleMinutes')) {
|
|
208
|
+
abandonGoal(p, g.id);
|
|
209
|
+
recordProjectStatus(p, g, now);
|
|
210
|
+
}
|
|
211
|
+
noteProjectStatus(p, now);
|
|
212
|
+
const active = p.agenda.goals.filter((g) => g.status === 'active');
|
|
213
|
+
if (active.some((g) => g.project) ||
|
|
214
|
+
active.length >= AGENDA_DEFAULTS.maxActiveGoals ||
|
|
215
|
+
p.agenda.commitments.some((c) => c.status === 'pending' && c.from <= now && c.until >= now) ||
|
|
216
|
+
(s.lastAdoptedAt !== undefined && now - s.lastAdoptedAt < setting(s, 'adoptEveryMinutes')) ||
|
|
217
|
+
(s.lastAbandonedAt !== undefined && now - s.lastAbandonedAt < setting(s, 'cooldownMinutes')))
|
|
218
|
+
return;
|
|
219
|
+
const best = s.prospects
|
|
220
|
+
.map((prospect) => ({ prospect, appeal: projectAppeal(p, prospect, now) }))
|
|
221
|
+
.filter((x) => x.appeal > setting(s, 'adoptAppeal'))
|
|
222
|
+
.sort((a, b) => b.appeal - a.appeal || cmpStr(a.prospect.id, b.prospect.id))[0];
|
|
223
|
+
if (!best)
|
|
224
|
+
return;
|
|
225
|
+
const { prospect, appeal } = best;
|
|
226
|
+
const g = adoptGoal(p, {
|
|
227
|
+
label: prospect.label,
|
|
228
|
+
serves: ['competence', 'leisure'],
|
|
229
|
+
advancedBy: [],
|
|
230
|
+
importance: Math.max(0.3, Math.min(0.7, appeal)),
|
|
231
|
+
temporal: { halfLifeMinutes: setting(s, 'halfLifeMinutes') },
|
|
232
|
+
project: { prospectId: prospect.id, perceivedProgress: prospect.progress !== undefined },
|
|
233
|
+
}, now);
|
|
234
|
+
if (prospect.progress !== undefined)
|
|
235
|
+
perceiveProjectProgress(p, prospect.id, prospect.progress, now);
|
|
236
|
+
if (s.behaviour === 2 && g.project)
|
|
237
|
+
g.project.peak = g.progress;
|
|
238
|
+
s.lastAdoptedAt = now;
|
|
239
|
+
s.currentGoalId = g.id;
|
|
240
|
+
}
|
|
241
|
+
/** Conservative restore: bounded fields, finite past timestamps, and no ambiguous duplicate ids. */
|
|
242
|
+
export function sanitizeProjects(input, now) {
|
|
243
|
+
if (!isObj(input) || !Array.isArray(input.prospects) || !Array.isArray(input.history))
|
|
244
|
+
return undefined;
|
|
245
|
+
if (input.behaviour !== undefined && input.behaviour !== 2)
|
|
246
|
+
return undefined;
|
|
247
|
+
const out = { prospects: [], history: [] };
|
|
248
|
+
if (input.behaviour === 2)
|
|
249
|
+
out.behaviour = 2;
|
|
250
|
+
if (input.spec !== undefined) {
|
|
251
|
+
if (!isObj(input.spec))
|
|
252
|
+
return undefined;
|
|
253
|
+
const spec = cleanSpec(input.spec);
|
|
254
|
+
if (Object.keys(spec).length !== Object.keys(input.spec).length)
|
|
255
|
+
return undefined;
|
|
256
|
+
if (Object.keys(spec).length)
|
|
257
|
+
out.spec = spec;
|
|
258
|
+
}
|
|
259
|
+
for (const key of ['lastAdoptedAt', 'lastAbandonedAt']) {
|
|
260
|
+
if (input[key] === undefined)
|
|
261
|
+
continue;
|
|
262
|
+
if (!minute(input[key], now))
|
|
263
|
+
return undefined;
|
|
264
|
+
out[key] = input[key];
|
|
265
|
+
}
|
|
266
|
+
if (input.currentGoalId !== undefined) {
|
|
267
|
+
if (!str(input.currentGoalId))
|
|
268
|
+
return undefined;
|
|
269
|
+
out.currentGoalId = input.currentGoalId;
|
|
270
|
+
}
|
|
271
|
+
const seen = new Set();
|
|
272
|
+
for (const entry of input.prospects.slice(0, PROJECTS_DEFAULTS.maxProspects)) {
|
|
273
|
+
const prospect = cleanProspect(entry);
|
|
274
|
+
if (!prospect || !isObj(entry) || !minute(entry.lastSeenAt, now))
|
|
275
|
+
continue;
|
|
276
|
+
if (seen.has(prospect.id))
|
|
277
|
+
return undefined;
|
|
278
|
+
seen.add(prospect.id);
|
|
279
|
+
if (now - entry.lastSeenAt <= setting(out, 'forgetMinutes'))
|
|
280
|
+
out.prospects.push({ ...prospect, lastSeenAt: entry.lastSeenAt });
|
|
281
|
+
}
|
|
282
|
+
seen.clear();
|
|
283
|
+
for (const h of input.history.slice(-PROJECTS_DEFAULTS.maxHistory)) {
|
|
284
|
+
if (!isObj(h) ||
|
|
285
|
+
!str(h.prospectId) ||
|
|
286
|
+
!str(h.goalId) ||
|
|
287
|
+
!minute(h.at, now) ||
|
|
288
|
+
!minute(h.adoptedAt, h.at) ||
|
|
289
|
+
(h.outcome !== 'achieved' && h.outcome !== 'abandoned'))
|
|
290
|
+
continue;
|
|
291
|
+
if (seen.has(h.goalId))
|
|
292
|
+
return undefined;
|
|
293
|
+
seen.add(h.goalId);
|
|
294
|
+
out.history.push({
|
|
295
|
+
prospectId: h.prospectId,
|
|
296
|
+
goalId: h.goalId,
|
|
297
|
+
adoptedAt: h.adoptedAt,
|
|
298
|
+
outcome: h.outcome,
|
|
299
|
+
at: h.at,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
return out;
|
|
303
|
+
}
|
|
304
|
+
/** Sanitize and reconcile agenda ownership deterministically, without RNG. @internal */
|
|
305
|
+
export function restoreProjects(p) {
|
|
306
|
+
const s = sanitizeProjects(p.projects, p.now);
|
|
307
|
+
if (!s) {
|
|
308
|
+
delete p.projects;
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
p.projects = s;
|
|
312
|
+
noteProjectStatus(p, p.now);
|
|
313
|
+
const active = p.agenda.goals
|
|
314
|
+
.filter((g) => g.project && g.status === 'active')
|
|
315
|
+
.sort((a, b) => b.adoptedAt - a.adoptedAt || cmpStr(a.id, b.id));
|
|
316
|
+
for (const g of active.slice(1)) {
|
|
317
|
+
abandonGoal(p, g.id);
|
|
318
|
+
recordProjectStatus(p, g, p.now);
|
|
319
|
+
}
|
|
320
|
+
const kept = active[0];
|
|
321
|
+
if (kept)
|
|
322
|
+
s.currentGoalId = kept.id;
|
|
323
|
+
else
|
|
324
|
+
delete s.currentGoalId;
|
|
325
|
+
}
|
|
@@ -88,6 +88,10 @@ function consequence(x, receivedAt) {
|
|
|
88
88
|
s.duration < 0 ||
|
|
89
89
|
s.duration > PROSPECTION_DEFAULTS.horizon ||
|
|
90
90
|
!unit(s.effort) ||
|
|
91
|
+
(s.advances !== undefined &&
|
|
92
|
+
(!Array.isArray(s.advances) ||
|
|
93
|
+
s.advances.length > 8 ||
|
|
94
|
+
!s.advances.every((x) => typeof x === 'string' && x.length > 0 && x.length <= 512))) ||
|
|
91
95
|
(s.focus !== undefined && !unit(s.focus)) ||
|
|
92
96
|
(s.materialCost !== undefined &&
|
|
93
97
|
(!isNum(s.materialCost) || s.materialCost < 0 || s.materialCost > PROSPECTION_DEFAULTS.maxMaterialCost)))
|
|
@@ -110,6 +114,7 @@ function consequence(x, receivedAt) {
|
|
|
110
114
|
...successorAction,
|
|
111
115
|
duration: s.duration,
|
|
112
116
|
effort: s.effort,
|
|
117
|
+
...(s.advances !== undefined ? { advances: [...s.advances] } : {}),
|
|
113
118
|
...(s.focus !== undefined ? { focus: s.focus } : {}),
|
|
114
119
|
...(s.materialCost !== undefined ? { materialCost: s.materialCost } : {}),
|
|
115
120
|
},
|
package/dist/restore.js
CHANGED
|
@@ -21,7 +21,9 @@ import { GIST_DEFAULTS, MEMORY_DEFAULTS, sanitizeGists } from './memory/index.js
|
|
|
21
21
|
import { sanitizeMethods } from './methods/index.js';
|
|
22
22
|
import { migrate } from './migrate.js';
|
|
23
23
|
import { sanitizeBonds } from './partnering/index.js';
|
|
24
|
+
import { sanitizePursuit } from './persistence/index.js';
|
|
24
25
|
import { cleanRetention, createPerson, traceLimit } from './person.js';
|
|
26
|
+
import { restoreProjects } from './projects/index.js';
|
|
25
27
|
import { sanitizePromptings } from './promptings/index.js';
|
|
26
28
|
import { sanitizePlanning, sanitizeProspection } from './prospection/index.js';
|
|
27
29
|
import { sanitizeReview } from './review/index.js';
|
|
@@ -88,6 +90,8 @@ const OPTIONAL_KEYS = new Set([
|
|
|
88
90
|
'planning',
|
|
89
91
|
'promptings',
|
|
90
92
|
'curiosity',
|
|
93
|
+
'projects',
|
|
94
|
+
'pursuit',
|
|
91
95
|
'review',
|
|
92
96
|
]);
|
|
93
97
|
/** Kind of a JSON value for the per-field type check in `restore`. */
|
|
@@ -205,6 +209,8 @@ export function restore(input) {
|
|
|
205
209
|
keepValid(out, 'planning', sanitizePlanning);
|
|
206
210
|
keepValid(out, 'prospection', (x) => sanitizeProspection(x, out.now, out.id, out.planning !== undefined));
|
|
207
211
|
keepValid(out, 'curiosity', (x) => sanitizeCuriosity(x, out.now));
|
|
212
|
+
restoreProjects(out);
|
|
213
|
+
keepValid(out, 'pursuit', (x) => sanitizePursuit(x, out.now));
|
|
208
214
|
keepValid(out, 'promptings', (x) => sanitizePromptings(x, out.now));
|
|
209
215
|
keepValid(out, 'review', (x) => sanitizeReview(x, out.now));
|
|
210
216
|
boundLists(out);
|
package/dist/types.d.ts
CHANGED
|
@@ -588,6 +588,13 @@ export interface Commitment {
|
|
|
588
588
|
recurUntil?: Minute;
|
|
589
589
|
}
|
|
590
590
|
export interface Goal {
|
|
591
|
+
/** Prospect identity; perceived projects receive progress only through perception. */
|
|
592
|
+
project?: {
|
|
593
|
+
prospectId: string;
|
|
594
|
+
perceivedProgress: boolean;
|
|
595
|
+
/** Highest perceived progress since adoption; presence opts this goal into net credit. */
|
|
596
|
+
peak?: Unit;
|
|
597
|
+
};
|
|
591
598
|
id: string;
|
|
592
599
|
label: string;
|
|
593
600
|
/** Which need/value the goal serves. */
|
|
@@ -889,7 +896,7 @@ export declare const PERSON_SCHEMA = "human/person@1";
|
|
|
889
896
|
* and an activity that would cover a closing stretch is reviewed when the stretch begins (`isUnderWay`,
|
|
890
897
|
* `dutyReviewAt`). Person shape unchanged.
|
|
891
898
|
*/
|
|
892
|
-
export declare const ENGINE_VERSION = "2.
|
|
899
|
+
export declare const ENGINE_VERSION = "2.14.1";
|
|
893
900
|
/** A pregnancy carried by this person (host-driven; see `family/`). */
|
|
894
901
|
export interface Pregnancy {
|
|
895
902
|
fatherId: PersonId;
|
|
@@ -1006,6 +1013,8 @@ export interface ReceivedConsequence {
|
|
|
1006
1013
|
effort: Unit;
|
|
1007
1014
|
focus?: Unit;
|
|
1008
1015
|
materialCost?: number;
|
|
1016
|
+
/** Goal or prospect ids this successor contributes to (bounded to 8). */
|
|
1017
|
+
advances?: string[];
|
|
1009
1018
|
};
|
|
1010
1019
|
availableFrom: Minute;
|
|
1011
1020
|
/** Latest permitted successor completion, inclusive. */
|
|
@@ -1071,6 +1080,10 @@ export interface Person {
|
|
|
1071
1080
|
promptings?: PromptingsState;
|
|
1072
1081
|
/** Intrinsic exploration and varied repetition. Owned by `curiosity/`; absent unless enabled. */
|
|
1073
1082
|
curiosity?: CuriosityState;
|
|
1083
|
+
/** Self-set projects from attended prospects; owned by projects/. */
|
|
1084
|
+
projects?: ProjectsState;
|
|
1085
|
+
/** Keeping a started goal active. Owned by persistence/; absent unless enabled. */
|
|
1086
|
+
pursuit?: PursuitState;
|
|
1074
1087
|
/** Evening self-review and its resolves (2.7.0, opt-in via `enableReview`). Owned by `review/`. */
|
|
1075
1088
|
review?: ReviewState;
|
|
1076
1089
|
/** Practice consolidation (1.8.0, opt-in via `enableSkillRetention`). Owned by `skills/`. */
|
|
@@ -1206,6 +1219,8 @@ export interface Affordance {
|
|
|
1206
1219
|
}
|
|
1207
1220
|
/** Something a person perceives. Hosts emit percepts; attention decides which are encoded. */
|
|
1208
1221
|
export interface Percept {
|
|
1222
|
+
/** Host-shown opportunity, retained only with projects enabled and when attended. */
|
|
1223
|
+
prospect?: Prospect;
|
|
1209
1224
|
at: Minute;
|
|
1210
1225
|
/**
|
|
1211
1226
|
* How it reached the person: seen, heard, told (testimony), felt (bodily), the result of an action
|
|
@@ -1529,6 +1544,10 @@ export interface PersonSpec {
|
|
|
1529
1544
|
planning?: PlanningSpec;
|
|
1530
1545
|
/** Evening self-review (2.7.0): the same as `enableReview(p, spec)` after creation. */
|
|
1531
1546
|
review?: ReviewSpec;
|
|
1547
|
+
/** Self-set projects from what the person attends to. */
|
|
1548
|
+
projects?: boolean | ProjectsSpec;
|
|
1549
|
+
/** Keep started goal pursuits across decisions. */
|
|
1550
|
+
persistence?: boolean | PersistenceSpec;
|
|
1532
1551
|
};
|
|
1533
1552
|
}
|
|
1534
1553
|
/** A known span of exertion after an act (see `Affordance.followedBy`). */
|
|
@@ -1957,3 +1976,78 @@ export interface CuriosityReading {
|
|
|
1957
1976
|
vary: Unit;
|
|
1958
1977
|
drive: number;
|
|
1959
1978
|
}
|
|
1979
|
+
/** A bounded pursuit opportunity explicitly shown by the host. */
|
|
1980
|
+
export interface Prospect {
|
|
1981
|
+
id: string;
|
|
1982
|
+
label: string;
|
|
1983
|
+
kind: 'make' | 'arrange' | 'gather' | 'finish';
|
|
1984
|
+
placeId?: EntityId;
|
|
1985
|
+
/** Up to 256 accepted; the first eight distinct ids are retained. */
|
|
1986
|
+
materialIds?: EntityId[];
|
|
1987
|
+
progress?: Unit;
|
|
1988
|
+
}
|
|
1989
|
+
export interface ProjectsSpec {
|
|
1990
|
+
forgetMinutes?: number;
|
|
1991
|
+
calmUrgency?: Unit;
|
|
1992
|
+
fearBelow?: Unit;
|
|
1993
|
+
adoptEveryMinutes?: number;
|
|
1994
|
+
cooldownMinutes?: number;
|
|
1995
|
+
adoptAppeal?: Unit;
|
|
1996
|
+
halfLifeMinutes?: number;
|
|
1997
|
+
staleMinutes?: number;
|
|
1998
|
+
}
|
|
1999
|
+
export interface ProjectHistory {
|
|
2000
|
+
prospectId: string;
|
|
2001
|
+
goalId: string;
|
|
2002
|
+
adoptedAt: Minute;
|
|
2003
|
+
outcome: 'achieved' | 'abandoned';
|
|
2004
|
+
at: Minute;
|
|
2005
|
+
}
|
|
2006
|
+
/** Optional bounded JSON state owned by projects/. */
|
|
2007
|
+
export interface ProjectsState {
|
|
2008
|
+
/** Absent retains 2.14.0 credit. Re-enabling, including on every load, opts into net credit. */
|
|
2009
|
+
behaviour?: 2;
|
|
2010
|
+
spec?: ProjectsSpec;
|
|
2011
|
+
prospects: (Prospect & {
|
|
2012
|
+
lastSeenAt: Minute;
|
|
2013
|
+
})[];
|
|
2014
|
+
history: ProjectHistory[];
|
|
2015
|
+
lastAdoptedAt?: Minute;
|
|
2016
|
+
lastAbandonedAt?: Minute;
|
|
2017
|
+
/** The adopted goal awaiting its one terminal history entry. */
|
|
2018
|
+
currentGoalId?: string;
|
|
2019
|
+
}
|
|
2020
|
+
/** Bounded engineering overrides for persistence/. Invalid overrides use defaults. */
|
|
2021
|
+
export interface PersistenceSpec {
|
|
2022
|
+
/** Half-life without goal progress, in minutes (default 30; valid 1..1440). */
|
|
2023
|
+
stallHalfLifeMinutes?: number;
|
|
2024
|
+
continueScale?: number;
|
|
2025
|
+
switchScale?: number;
|
|
2026
|
+
cap?: number;
|
|
2027
|
+
switchMargin?: number;
|
|
2028
|
+
slackMinutes?: number;
|
|
2029
|
+
maxMinutes?: number;
|
|
2030
|
+
/** Refractory interval for the same goal after maximum-duration release (default 20 minutes). */
|
|
2031
|
+
restMinutes?: number;
|
|
2032
|
+
}
|
|
2033
|
+
/** A started goal retained across decisions. */
|
|
2034
|
+
export interface Pursuit {
|
|
2035
|
+
goalId: string;
|
|
2036
|
+
since: Minute;
|
|
2037
|
+
lastServedAt: Minute;
|
|
2038
|
+
strength: Unit;
|
|
2039
|
+
}
|
|
2040
|
+
/** Optional JSON state owned by persistence/: one current pursuit and one refractory goal. */
|
|
2041
|
+
export interface PursuitState {
|
|
2042
|
+
/** Absent retains the 2.14.0 pull. Re-enabling, including on every load, opts into decay. */
|
|
2043
|
+
behaviour?: 2;
|
|
2044
|
+
spec?: PersistenceSpec;
|
|
2045
|
+
current?: Pursuit;
|
|
2046
|
+
/** Most recent maximum-duration release, withheld until this minute; agenda terms stay unchanged.
|
|
2047
|
+
* Defaults (maxMinutes 240, restMinutes 20) prevent two releases within one interval.
|
|
2048
|
+
* A shorter maximum can let a later release replace an earlier unexpired interval. */
|
|
2049
|
+
rested?: {
|
|
2050
|
+
goalId: string;
|
|
2051
|
+
until: Minute;
|
|
2052
|
+
};
|
|
2053
|
+
}
|
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.14.1';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adam0white/human-framework",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.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",
|