@adam0white/human-framework 2.14.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.
@@ -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 = {
@@ -150,8 +150,11 @@ function sanitizeGoalTemporal(g) {
150
150
  typeof project.prospectId === 'string' &&
151
151
  project.prospectId.length > 0 &&
152
152
  project.prospectId.length <= 512 &&
153
- typeof project.perceivedProgress === 'boolean')
153
+ typeof project.perceivedProgress === 'boolean') {
154
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
+ }
155
158
  else
156
159
  delete g.project;
157
160
  }
@@ -690,9 +693,12 @@ export function onFinished(p, outcome, startedAt = outcome.at, opts = {}) {
690
693
  const amount = own > 0 ? own : namesGoal(g, outcome.advances) ? AGENDA_DEFAULTS.defaultAdvanceAmount : 0;
691
694
  if (amount <= 0)
692
695
  continue;
693
- // Credit the contribution for narration/reward, but only perception sets this project's progress.
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.
694
699
  if (g.project?.perceivedProgress) {
695
- advanced.push(g);
700
+ if (g.project.peak === undefined || g.progress >= g.project.peak)
701
+ advanced.push(g);
696
702
  continue;
697
703
  }
698
704
  g.progress = clamp01(g.progress + amount);
@@ -730,7 +736,11 @@ export function perceiveProjectProgress(p, prospectId, progress, now) {
730
736
  for (const g of p.agenda.goals) {
731
737
  if (g.status !== 'active' || !g.project?.perceivedProgress || g.project.prospectId !== prospectId)
732
738
  continue;
733
- if (progress > g.progress) {
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);
734
744
  g.lastAdvancedAt = now;
735
745
  if (g.baseImportance !== undefined)
736
746
  g.importance = g.baseImportance;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Human Framework public surface. */
2
- export declare const FRAMEWORK_VERSION = "2.14.0";
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';
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Human Framework public surface. */
2
- export const FRAMEWORK_VERSION = '2.14.0';
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';
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.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
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,11 @@ 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
+ },
73
79
  '2.13.0': {
74
80
  to: '2.14.0',
75
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',
@@ -11,6 +11,7 @@ export declare const PERSISTENCE_DEFAULTS: {
11
11
  readonly slackMinutes: 20;
12
12
  readonly maxMinutes: 240;
13
13
  readonly restMinutes: 20;
14
+ readonly stallHalfLifeMinutes: 30;
14
15
  };
15
16
  /** Enable or reconfigure, retaining a current pursuit. Invalid overrides use defaults. */
16
17
  export declare function enablePersistence(p: Person, spec?: PersistenceSpec): PursuitState;
@@ -4,15 +4,19 @@
4
4
  * unrelated options pay a switching cost (goal shielding: Shah, Friedman & Kruglanski 2002;
5
5
  * task switching: Monsell 2003). Unused activation expires (Altmann & Trafton 2002); short
6
6
  * interruptions allow resumption (Ovsiankina 1928). See research/projects-and-persistence.md.
7
- * Maximum-duration release rests that goal for one bounded interval before pursuit can form again.
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.
8
10
  * These are shapes, not fitted values; all coefficients and time bounds are engineering defaults.
9
11
  * This is a bounded bias plus a switching cost, not a planner. It chooses no goals, executes no
10
12
  * plans, reorders no steps and changes no agenda valuation. It imports no projects, uses no RNG,
11
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.
12
16
  * Without the slice, it adds no state or terms.
13
17
  */
14
18
  import { emotionLevel } from '../affect/index.js';
15
- import { clamp01, cmpStr, isNum, isObj, round } from '../core/index.js';
19
+ import { clamp01, cmpStr, dpow, isNum, isObj, round } from '../core/index.js';
16
20
  import { MAX_MINUTE, PHYSIOLOGICAL_NEEDS } from '../types.js';
17
21
  /** Engineering defaults, relative to cognition's need scale 4, pleasure .6 and curiosity .8/.15.
18
22
  * Continuation and switching together outweigh mild relaxed distraction; urgency and fear gate both.
@@ -26,6 +30,7 @@ export const PERSISTENCE_DEFAULTS = {
26
30
  slackMinutes: 20,
27
31
  maxMinutes: 240,
28
32
  restMinutes: 20,
33
+ stallHalfLifeMinutes: 30,
29
34
  };
30
35
  const KEYS = [
31
36
  'continueScale',
@@ -35,6 +40,7 @@ const KEYS = [
35
40
  'slackMinutes',
36
41
  'maxMinutes',
37
42
  'restMinutes',
43
+ 'stallHalfLifeMinutes',
38
44
  ];
39
45
  function cleanSpec(input) {
40
46
  const out = {};
@@ -42,8 +48,12 @@ function cleanSpec(input) {
42
48
  return out;
43
49
  for (const key of KEYS) {
44
50
  const x = input[key];
45
- const max = key === 'slackMinutes' || key === 'maxMinutes' || key === 'restMinutes' ? 1440 : 1;
46
- if (isNum(x) && x >= 0 && x <= max)
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))
47
57
  out[key] = x;
48
58
  }
49
59
  return out;
@@ -53,7 +63,7 @@ const identifier = (x) => typeof x === 'string' && x.length > 0 && x.length <= 5
53
63
  const active = (p, id) => p.agenda.goals.find((g) => g.id === id && g.status === 'active');
54
64
  /** Enable or reconfigure, retaining a current pursuit. Invalid overrides use defaults. */
55
65
  export function enablePersistence(p, spec = {}) {
56
- const out = {};
66
+ const out = { behaviour: 2 };
57
67
  const clean = cleanSpec(spec);
58
68
  if (Object.keys(clean).length)
59
69
  out.spec = clean;
@@ -126,8 +136,13 @@ export function beginPursuit(p, terms) {
126
136
  export function persistenceTerms(p, terms, ctx) {
127
137
  const s = p.pursuit;
128
138
  const c = s?.current;
129
- if (!s || !c || !active(p, c.goalId))
139
+ if (!s || !c)
140
+ return [];
141
+ const goal = active(p, c.goalId);
142
+ if (!goal)
130
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;
131
146
  let urgency = 0;
132
147
  for (const n of ctx.needs)
133
148
  if (n.id === 'safety' || PHYSIOLOGICAL_NEEDS.includes(n.id))
@@ -137,7 +152,7 @@ export function persistenceTerms(p, terms, ctx) {
137
152
  return [
138
153
  {
139
154
  source: `pursuit:${c.goalId}`,
140
- value: round(Math.min(setting(s, 'cap'), setting(s, 'continueScale') * c.strength * gate)),
155
+ value: round(Math.min(setting(s, 'cap'), setting(s, 'continueScale') * c.strength * gate) * decay),
141
156
  },
142
157
  ];
143
158
  if (terms.some((t) => t.source.startsWith('commitment:')))
@@ -145,7 +160,7 @@ export function persistenceTerms(p, terms, ctx) {
145
160
  return [
146
161
  {
147
162
  source: 'pursuit:switch',
148
- value: -round(Math.min(setting(s, 'cap'), setting(s, 'switchScale') * c.strength * gate)),
163
+ value: -round(Math.min(setting(s, 'cap'), setting(s, 'switchScale') * c.strength * gate) * decay),
149
164
  },
150
165
  ];
151
166
  }
@@ -154,7 +169,11 @@ const minute = (x, now) => isNum(x) && Math.abs(x) <= MAX_MINUTE && x <= now;
154
169
  export function sanitizePursuit(input, now) {
155
170
  if (!isObj(input) || (input.spec !== undefined && !isObj(input.spec)))
156
171
  return undefined;
172
+ if (input.behaviour !== undefined && input.behaviour !== 2)
173
+ return undefined;
157
174
  const out = {};
175
+ if (input.behaviour === 2)
176
+ out.behaviour = 2;
158
177
  if (input.spec !== undefined) {
159
178
  const spec = cleanSpec(input.spec);
160
179
  if (Object.keys(spec).length !== Object.keys(input.spec).length)
@@ -7,6 +7,7 @@
7
7
  * models a pull to continue, not a Zeigarnik memory advantage. See research/projects-and-persistence.md.
8
8
  * All thresholds and coefficients are engineering defaults. This owns only p.projects; agenda owns
9
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.
10
11
  * Restore recovers one active project, keeping the newest (id breaks ties) and abandoning extras.
11
12
  * No invented prospects, host task interpretation, world search, persistence, shielding, faith lens,
12
13
  * LLM or RNG. Without the slice, perception and minute bookkeeping write nothing.
@@ -70,7 +71,7 @@ function cleanProspect(x) {
70
71
  p.placeId = x.placeId;
71
72
  if (x.progress !== undefined)
72
73
  p.progress = x.progress;
73
- // A long list is kept, not rejected: the first eight distinct ids in the host's order (island rerun, 2026-10-20).
74
+ // A long list is kept, not rejected: the first eight distinct ids in the host's order (host rerun, 2026-10-20).
74
75
  if (x.materialIds !== undefined)
75
76
  p.materialIds = [...new Set(x.materialIds)].slice(0, 8);
76
77
  return p;
@@ -78,6 +79,10 @@ function cleanProspect(x) {
78
79
  /** Enable or reconfigure without discarding retained prospects and history. */
79
80
  export function enableProjects(p, spec = {}) {
80
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;
81
86
  const clean = cleanSpec(spec);
82
87
  delete state.spec;
83
88
  if (Object.keys(clean).length)
@@ -228,6 +233,8 @@ export function considerProjects(p, now) {
228
233
  }, now);
229
234
  if (prospect.progress !== undefined)
230
235
  perceiveProjectProgress(p, prospect.id, prospect.progress, now);
236
+ if (s.behaviour === 2 && g.project)
237
+ g.project.peak = g.progress;
231
238
  s.lastAdoptedAt = now;
232
239
  s.currentGoalId = g.id;
233
240
  }
@@ -235,7 +242,11 @@ export function considerProjects(p, now) {
235
242
  export function sanitizeProjects(input, now) {
236
243
  if (!isObj(input) || !Array.isArray(input.prospects) || !Array.isArray(input.history))
237
244
  return undefined;
245
+ if (input.behaviour !== undefined && input.behaviour !== 2)
246
+ return undefined;
238
247
  const out = { prospects: [], history: [] };
248
+ if (input.behaviour === 2)
249
+ out.behaviour = 2;
239
250
  if (input.spec !== undefined) {
240
251
  if (!isObj(input.spec))
241
252
  return undefined;
package/dist/types.d.ts CHANGED
@@ -592,6 +592,8 @@ export interface Goal {
592
592
  project?: {
593
593
  prospectId: string;
594
594
  perceivedProgress: boolean;
595
+ /** Highest perceived progress since adoption; presence opts this goal into net credit. */
596
+ peak?: Unit;
595
597
  };
596
598
  id: string;
597
599
  label: string;
@@ -894,7 +896,7 @@ export declare const PERSON_SCHEMA = "human/person@1";
894
896
  * and an activity that would cover a closing stretch is reviewed when the stretch begins (`isUnderWay`,
895
897
  * `dutyReviewAt`). Person shape unchanged.
896
898
  */
897
- export declare const ENGINE_VERSION = "2.14.0";
899
+ export declare const ENGINE_VERSION = "2.14.1";
898
900
  /** A pregnancy carried by this person (host-driven; see `family/`). */
899
901
  export interface Pregnancy {
900
902
  fatherId: PersonId;
@@ -2003,6 +2005,8 @@ export interface ProjectHistory {
2003
2005
  }
2004
2006
  /** Optional bounded JSON state owned by projects/. */
2005
2007
  export interface ProjectsState {
2008
+ /** Absent retains 2.14.0 credit. Re-enabling, including on every load, opts into net credit. */
2009
+ behaviour?: 2;
2006
2010
  spec?: ProjectsSpec;
2007
2011
  prospects: (Prospect & {
2008
2012
  lastSeenAt: Minute;
@@ -2015,6 +2019,8 @@ export interface ProjectsState {
2015
2019
  }
2016
2020
  /** Bounded engineering overrides for persistence/. Invalid overrides use defaults. */
2017
2021
  export interface PersistenceSpec {
2022
+ /** Half-life without goal progress, in minutes (default 30; valid 1..1440). */
2023
+ stallHalfLifeMinutes?: number;
2018
2024
  continueScale?: number;
2019
2025
  switchScale?: number;
2020
2026
  cap?: number;
@@ -2033,9 +2039,13 @@ export interface Pursuit {
2033
2039
  }
2034
2040
  /** Optional JSON state owned by persistence/: one current pursuit and one refractory goal. */
2035
2041
  export interface PursuitState {
2042
+ /** Absent retains the 2.14.0 pull. Re-enabling, including on every load, opts into decay. */
2043
+ behaviour?: 2;
2036
2044
  spec?: PersistenceSpec;
2037
2045
  current?: Pursuit;
2038
- /** One goal withheld from pursuit formation until this minute; agenda terms remain unchanged. */
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. */
2039
2049
  rested?: {
2040
2050
  goalId: string;
2041
2051
  until: Minute;
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.14.0';
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.14.0",
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",