@adam0white/human-framework 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -3
- package/dist/agenda/agenda.js +45 -0
- package/dist/cognition/cognition.js +37 -21
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/methods/methods.d.ts +1 -1
- package/dist/methods/methods.js +6 -7
- package/dist/migrate.js +11 -1
- package/dist/person.js +8 -0
- package/dist/prospection/index.d.ts +1 -0
- package/dist/prospection/index.js +1 -0
- package/dist/prospection/prospection.d.ts +23 -0
- package/dist/prospection/prospection.js +278 -0
- package/dist/restore.js +3 -0
- package/dist/types.d.ts +62 -2
- package/dist/types.js +7 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -123,6 +123,48 @@ The framework has no world of its own. The host supplies three things and receiv
|
|
|
123
123
|
- **Decisions** (`DecisionRecord`) come back: the chosen offer, the top `considered` options with every
|
|
124
124
|
utility term, the suggestion verdict(s), a private `intention`, and a `narration`.
|
|
125
125
|
|
|
126
|
+
### Expected benefits and observed outcomes
|
|
127
|
+
|
|
128
|
+
An offer describes expected effects. An outcome reports what happened. The host resolves space,
|
|
129
|
+
contact, inventory and payment; HF uses the reported outcome to update the person. Do not copy
|
|
130
|
+
`Affordance.advertises` into `Outcome.needs` as a general completion rule.
|
|
131
|
+
|
|
132
|
+
| Host situation | Offer | Completion report |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| A person sleeps for part of the planned night. | Advertise typical sleep/rest benefits and set `mode: 'sleep'`. | Leave `sleep` and `rest` absent from `Outcome.needs`. `finish` advances the body to the actual end time and measures their change since the activity began. Explicit values replace that measurement; copying the advertisement can overstate the realized recovery reported to learning. |
|
|
135
|
+
| A task promises four coins but actually pays two. | Set expected `Affordance.material` to four host units. | Update the host ledger by two and report `Outcome.material: 2`. A completed task does not imply the promised payment arrived. Report zero for no payment and a negative value for an actual cost. HF does not maintain the host's money ledger. |
|
|
136
|
+
| A person can talk with someone who is present. | Tag the offer `social` when it really involves social contact; put participants in `with`. | Resolve actual participation. An empty room, a sleeping neighbour or someone who arrives after the conversation ends is not a completed encounter. Do not award contact or belonging merely because the activity uses a shared place. |
|
|
137
|
+
|
|
138
|
+
For in-person activities, the host must establish co-presence and overlapping participation. HF has
|
|
139
|
+
no map and does not discover companions. `with` names people taking part, not everyone assigned to a
|
|
140
|
+
place. A `social` tag is also a claim about the offered activity, not a request to find someone. If
|
|
141
|
+
contact becomes impossible, interrupt or replan the activity instead of completing the declared
|
|
142
|
+
encounter. Report only the need effects that the actual interaction supports.
|
|
143
|
+
|
|
144
|
+
The current behavior is defined in [`Affordance` and `Outcome`](src/types.ts) and
|
|
145
|
+
[`finish`](src/person.ts). Existing controls cover [body advancement and waking after sleep](test/person.test.ts),
|
|
146
|
+
[body recovery](test/body.test.ts), [material valuation under scarcity](test/cognition-scarcity.test.ts),
|
|
147
|
+
[actual material in day records](test/chronicle.test.ts), and [relationship-aware social terms](test/social.test.ts).
|
|
148
|
+
These boundaries require correct host reports; they do not imply a framework behavior change.
|
|
149
|
+
|
|
150
|
+
### Scheduled activity reviews
|
|
151
|
+
|
|
152
|
+
A custom host must process `activity.reviewAt`, including during sleep. `tick` advances the person;
|
|
153
|
+
it does not dispatch a decision or automatically end the host's job. An `activity.interrupt` is also
|
|
154
|
+
a request to review, not an instruction to cancel every activity.
|
|
155
|
+
|
|
156
|
+
At a due review, call `decide` with physically feasible alternatives. It includes the current activity
|
|
157
|
+
with its remaining duration. If that activity is chosen again, call `reviewed(p)` and keep its original
|
|
158
|
+
job, arrival time and reservations. If another activity wins, reconcile the old physical job, call
|
|
159
|
+
`finish` with an interrupted outcome, then `begin` the chosen activity with its decision record.
|
|
160
|
+
Do not restart a continued activity or refund its resources. Hard capacity vetoes still apply.
|
|
161
|
+
|
|
162
|
+
Sleep reviews can move earlier for ordinary pending appointments. The appointment's time pressure
|
|
163
|
+
can permit waking; an obligatory norm is not required. A host that ignores the scheduled review can
|
|
164
|
+
make someone miss an obligation HF was ready to reconsider. This does not imply advance planning
|
|
165
|
+
for unavailable future actions. See [`reviewed` and the decision lifecycle](src/person.ts) and
|
|
166
|
+
[`wakeReviewAt` and commitment-based waking](src/will/will.ts).
|
|
167
|
+
|
|
126
168
|
You can drive a person by hand: `decide(p, offers, { suggestion })` → `begin(p, offer, record)` →
|
|
127
169
|
`tick(p, now)` → `finish(p, outcome)`, with `perceive(p, percepts)` and `interrupt(p, now, reason)` between.
|
|
128
170
|
Or implement `World` and let `stepCommunity` sequence everyone in deterministic (time, id) order:
|
|
@@ -177,13 +219,13 @@ reference host: `createVillage`.
|
|
|
177
219
|
|
|
178
220
|
There are two version numbers, and they move independently.
|
|
179
221
|
|
|
180
|
-
- **The release version** is the package version (`package.json`, now `2.2.
|
|
222
|
+
- **The release version** is the package version (`package.json`, now `2.2.1`) and `FRAMEWORK_VERSION`, the same
|
|
181
223
|
string compiled into the build; a test keeps them equal. It versions the public API under
|
|
182
224
|
[semver](https://semver.org/): a breaking change to an exported name or signature bumps the major, a new
|
|
183
225
|
faculty or export the minor, a fix the patch. Each release is a tag `vX.Y.Z` and a GitHub release whose notes
|
|
184
226
|
are that version's section of the root [CHANGELOG.md](../../CHANGELOG.md), with the package tarball attached
|
|
185
227
|
(`npm run release`, run on a clean, pushed `main`).
|
|
186
|
-
- **`ENGINE_VERSION`** (exported; `2.
|
|
228
|
+
- **`ENGINE_VERSION`** (exported; `2.2.0` at release 2.2.1) versions simulation behaviour and the save format. It
|
|
187
229
|
changes when the same seed and inputs would give different decisions, or when person state changes shape.
|
|
188
230
|
`restore` upgrades saves from engine 1.4.0 and later through `migrate` and refuses older ones. Every release's notes name the
|
|
189
231
|
`ENGINE_VERSION` it ships, so a host can tell whether its saves still restore.
|
|
@@ -301,4 +343,61 @@ const evidence = methodEvidence(learner, 'send-north', 'new-parcel');
|
|
|
301
343
|
clearMethodContext(learner); // when leaving or switching the task
|
|
302
344
|
```
|
|
303
345
|
|
|
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
|
|
346
|
+
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.
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
## Receiving a possible future route
|
|
350
|
+
|
|
351
|
+
The pending 2.3.0 increment lets an attended explanation connect one current action to one possible successor. The host supplies the explanation the person received, not a private success bonus.
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
import {
|
|
355
|
+
type Affordance, type ReceivedConsequence,
|
|
356
|
+
consequenceEvidence, createPerson, decide, perceive,
|
|
357
|
+
} from '@adam0white/human-framework';
|
|
358
|
+
|
|
359
|
+
const person = createPerson({
|
|
360
|
+
id: 'courier', name: 'Courier', seed: 19, now: 600,
|
|
361
|
+
bornAt: -30 * 365 * 1440, sex: 'female',
|
|
362
|
+
goals: [{
|
|
363
|
+
id: 'delivery', label: 'Deliver the parcel', serves: ['achievement'],
|
|
364
|
+
advancedBy: [{ action: 'deliver', amount: 1 }], importance: 0.8,
|
|
365
|
+
temporal: { halfLifeMinutes: 30 },
|
|
366
|
+
}],
|
|
367
|
+
});
|
|
368
|
+
const route: ReceivedConsequence = {
|
|
369
|
+
id: 'access-window', revision: 0,
|
|
370
|
+
trigger: { action: 'ask-access', targetId: 'keeper' },
|
|
371
|
+
successor: { action: 'deliver', duration: 20, effort: 0.1 },
|
|
372
|
+
availableFrom: 620, availableUntil: 680, expiresAt: 800,
|
|
373
|
+
chance: 0.8, confidence: 0.9,
|
|
374
|
+
};
|
|
375
|
+
perceive(person, [{
|
|
376
|
+
at: 600, channel: 'told', kind: 'explanation', actorId: 'keeper',
|
|
377
|
+
targetId: person.id, salience: 1,
|
|
378
|
+
summary: 'If your request succeeds, this route may become available.',
|
|
379
|
+
consequences: [route],
|
|
380
|
+
}]);
|
|
381
|
+
const request: Affordance = {
|
|
382
|
+
id: 'ask', action: 'ask-access', label: 'Request access', targetId: 'keeper',
|
|
383
|
+
duration: 10, effort: 0.1, advertises: {},
|
|
384
|
+
};
|
|
385
|
+
const offers: Affordance[] = [request,
|
|
386
|
+
{ id: 'direct', action: 'deliver', label: 'Deliver directly',
|
|
387
|
+
duration: 180, effort: 0.1, advertises: {} },
|
|
388
|
+
];
|
|
389
|
+
const decision = decide(person, offers);
|
|
390
|
+
const evidence = consequenceEvidence(person, request); // pure; no RNG consumed
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
`consequences` and `withdrawConsequences: [{ id, revision }]` are accepted only on attended `told` percepts whose explicit `actorId` names someone else. `Percept.targetId` may identify this person or be omitted for a broadcast; testimony explicitly addressed to another recipient is rejected. A recipient-specific opportunity must be addressed to that recipient. Trigger action, target and place match exactly; an omitted target/place matches only an omitted value. `availableUntil` is the latest successor **completion**, inclusive. The whole successor must fit after the current action and `availableFrom`.
|
|
394
|
+
|
|
395
|
+
`Goal.temporal.halfLifeMinutes` must be finite and positive. It enables expected completion valuation for direct actions **as well as** received successors: existing goal value × estimated action success × time discount. Existing `deadline` remains urgency, never a hard cutoff. Keep the same temporal goal in knowledge-intact, withheld and removed-knowledge controls; compare temporal-off separately. Legacy goals retain their prior formula. A received successor serves only goals listing its action in `Goal.advancedBy`; it does not inherit an `Affordance.advances` hint from a current offer.
|
|
396
|
+
|
|
397
|
+
The received access estimate is current-action success × `chance` × `confidence` × current source trust. `chance` means access conditional on successful asking; it does not establish consent. One coherent route may contribute to goals. Expected successor effort and material costs use the same readers as direct actions, are charged once, and remain undiscounted and unscaled by maturity. A favorable explanation need not win against other motives.
|
|
398
|
+
|
|
399
|
+
Only actual terminal outcomes advance terminal goals. Finishing one's own request consumes matching received opportunities even if a refusal has not been heard. It grants no access and executes no successor. The host still decides physical availability, reports the other person's actual response, and filters repeated or impossible requests. Do not mark asking as `advances` merely to make the route attractive.
|
|
400
|
+
|
|
401
|
+
Knowledge lives in optional `Person.prospection` and survives snapshot/restore. A percept admits at most four additions/withdrawals combined, atomically. Invalid entries or duplicate source/id identities reject the entire batch. Equal/lower revisions do not reinforce or refresh; a higher revision replaces. There are at most 16 entries and 64 revision records. Consumed entries are evicted first, then the oldest remaining entries, while their revision records remain; a batch requiring more still-valid revision records is rejected. Entries and revision memory expire within seven days. Deduplication is bounded, not a durable audit. Capacity is shared across sources and admission does not allocate by trust. One prolific source can occupy the finite entry/revision capacity, causing other new ids to be rejected until revision space expires; this is bounded memory, not a source-fairness or trust firewall. Hosts must bound the premises they deliver. The pure query exposes retained entries, present feasibility and retained revisions.
|
|
402
|
+
|
|
403
|
+
[The frozen control](examples/received-consequences.ts) publishes useful, late, costly, uncertain, withheld, removed-knowledge, removed-goal, temporal-off and refused arms in two renamed domains. This faculty does not search action graphs, predict future body state, price all waiting opportunities or guarantee consent or success. Its numerical bounds and control parameters are engineering assumptions.
|
package/dist/agenda/agenda.js
CHANGED
|
@@ -48,6 +48,7 @@
|
|
|
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
50
|
import { clamp01, dayOf, dpow, hasNumbers, isObj } from '../core/index.js';
|
|
51
|
+
import { expectedEffect } from '../memory/index.js';
|
|
51
52
|
import { MINUTES_PER_DAY } from '../types.js';
|
|
52
53
|
export const AGENDA_DEFAULTS = {
|
|
53
54
|
/** Minutes before a window opens during which a commitment already pulls (to get there). */
|
|
@@ -141,8 +142,23 @@ function copyArrays(c) {
|
|
|
141
142
|
* Spec commitments keep a given id; entries without one get a free `c<n>` id after all given ids are placed, so
|
|
142
143
|
* `prayerWindows()` output can be passed straight into `PersonSpec.commitments`.
|
|
143
144
|
*/
|
|
145
|
+
/** Copy valid temporal settings and drop invalid ones at every agenda entry point. */
|
|
146
|
+
function sanitizeGoalTemporal(g) {
|
|
147
|
+
const t = g.temporal;
|
|
148
|
+
if (t !== undefined &&
|
|
149
|
+
!(isObj(t) &&
|
|
150
|
+
typeof t.halfLifeMinutes === 'number' &&
|
|
151
|
+
Number.isFinite(t.halfLifeMinutes) &&
|
|
152
|
+
t.halfLifeMinutes > 0))
|
|
153
|
+
delete g.temporal;
|
|
154
|
+
else if (g.temporal)
|
|
155
|
+
g.temporal = { halfLifeMinutes: g.temporal.halfLifeMinutes };
|
|
156
|
+
}
|
|
144
157
|
/** Restore-time check of `agenda.lapse` (1.7.0), in place: a malformed one is dropped (absent means none). @internal */
|
|
145
158
|
export function sanitizeAgenda(a) {
|
|
159
|
+
for (const g of a.goals)
|
|
160
|
+
if (isObj(g))
|
|
161
|
+
sanitizeGoalTemporal(g);
|
|
146
162
|
const lapse = a.lapse;
|
|
147
163
|
if (lapse !== undefined && !(isObj(lapse) && hasNumbers(lapse, 'since', 'missed')))
|
|
148
164
|
delete a.lapse;
|
|
@@ -177,6 +193,7 @@ export function createAgenda(spec, now) {
|
|
|
177
193
|
lastAdvancedAt: now,
|
|
178
194
|
});
|
|
179
195
|
}
|
|
196
|
+
sanitizeAgenda(state);
|
|
180
197
|
return state;
|
|
181
198
|
}
|
|
182
199
|
export function promise(p, c) {
|
|
@@ -471,7 +488,27 @@ function deadlinePressure(g, now) {
|
|
|
471
488
|
const span = Math.max(1, g.deadline - g.adoptedAt);
|
|
472
489
|
return 1 + AGENDA_DEFAULTS.goalDeadlineBoost * clamp01((now - g.adoptedAt) / span);
|
|
473
490
|
}
|
|
491
|
+
/** Shared existing amount and urgency reader; no future knowledge is consulted here. @internal */
|
|
492
|
+
export function goalValue(g, aff, now) {
|
|
493
|
+
const amount = goalAmount(g, aff);
|
|
494
|
+
return amount > 0
|
|
495
|
+
? g.importance * clamp01(amount * AGENDA_DEFAULTS.goalAmountGain) * deadlinePressure(g, now)
|
|
496
|
+
: 0;
|
|
497
|
+
}
|
|
498
|
+
/** Opt-in completion-time and success estimate, shared by direct and received routes. @internal */
|
|
499
|
+
export function temporalGoalValue(p, g, aff, now, completion) {
|
|
500
|
+
const base = goalValue(g, aff, now);
|
|
501
|
+
return g.temporal
|
|
502
|
+
? base *
|
|
503
|
+
expectedEffect(p, aff).successRate *
|
|
504
|
+
dpow(0.5, Math.max(0, completion - now) / g.temporal.halfLifeMinutes)
|
|
505
|
+
: base;
|
|
506
|
+
}
|
|
474
507
|
export function agendaTerms(p, aff, now) {
|
|
508
|
+
return agendaTermsWithGoalEstimates(p, aff, now);
|
|
509
|
+
}
|
|
510
|
+
/** Internal route integration; hosts cannot provide goal overrides through the public API. @internal */
|
|
511
|
+
export function agendaTermsWithGoalEstimates(p, aff, now, goalOverrides) {
|
|
475
512
|
const d = AGENDA_DEFAULTS;
|
|
476
513
|
const terms = [];
|
|
477
514
|
for (const c of p.agenda.commitments) {
|
|
@@ -506,6 +543,13 @@ export function agendaTerms(p, aff, now) {
|
|
|
506
543
|
for (const g of p.agenda.goals) {
|
|
507
544
|
if (g.status !== 'active')
|
|
508
545
|
continue;
|
|
546
|
+
const override = goalOverrides?.get(g.id);
|
|
547
|
+
if (override !== undefined || g.temporal) {
|
|
548
|
+
const value = override ?? temporalGoalValue(p, g, aff, now, now + Math.max(0, aff.duration));
|
|
549
|
+
if (value > 0)
|
|
550
|
+
terms.push({ source: `goal:${g.id}`, value });
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
509
553
|
const amount = goalAmount(g, aff);
|
|
510
554
|
if (amount <= 0)
|
|
511
555
|
continue;
|
|
@@ -632,6 +676,7 @@ export function adoptGoal(p, g, now) {
|
|
|
632
676
|
adoptedAt: now,
|
|
633
677
|
lastAdvancedAt: now,
|
|
634
678
|
};
|
|
679
|
+
sanitizeGoalTemporal(goal);
|
|
635
680
|
p.agenda.goals.push(goal);
|
|
636
681
|
return goal;
|
|
637
682
|
}
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* `safety` need: `security` is a value, not a need, and no need is added.
|
|
26
26
|
*/
|
|
27
27
|
import { emotionLevel, inBreak } from '../affect/index.js';
|
|
28
|
-
import {
|
|
28
|
+
import { agendaTermsWithGoalEstimates } from '../agenda/index.js';
|
|
29
29
|
import { trustOf } from '../beliefs/index.js';
|
|
30
30
|
import { normTerms } from '../conscience/index.js';
|
|
31
31
|
import { clamp01, cmpStr, dexp, minuteOfDay, round } from '../core/index.js';
|
|
@@ -34,6 +34,7 @@ import { expectedEffect } from '../memory/index.js';
|
|
|
34
34
|
import { methodEvidence } from '../methods/index.js';
|
|
35
35
|
import { urgencyOf } from '../needs/index.js';
|
|
36
36
|
import { partneringTerms } from '../partnering/index.js';
|
|
37
|
+
import { receivedGoalRoute } from '../prospection/index.js';
|
|
37
38
|
import { companionSteadiness, socialTerms } from '../social/index.js';
|
|
38
39
|
import { PHYSIOLOGICAL_NEEDS, VALUE_KEYS } from '../types.js';
|
|
39
40
|
import { adviceWeight, resolveChoice, safeUtility, standingAdvice, suggestionTargets, voicesIn, } from '../will/index.js';
|
|
@@ -200,6 +201,29 @@ export function socialContext(ctx) {
|
|
|
200
201
|
satiation: K.socialSatiationFloor + (1 - K.socialSatiationFloor) * (1 - clamp01(belongingLevel)),
|
|
201
202
|
};
|
|
202
203
|
}
|
|
204
|
+
/** Same perceived-body effort estimate for actual offers and received successors. @internal */
|
|
205
|
+
export function effortValue(p, aff, ctx) {
|
|
206
|
+
const K = COGNITION_DEFAULTS;
|
|
207
|
+
const per = ctx.body.perceived;
|
|
208
|
+
const effort = clamp01(aff.effort);
|
|
209
|
+
const focus = clamp01(aff.focus ?? 0);
|
|
210
|
+
// A cued habit eases initiation and attention (N4: the depleted fall back on habits), not physical exertion.
|
|
211
|
+
const ease = p.habits.length === 0
|
|
212
|
+
? 1
|
|
213
|
+
: habitEase(p, aff, { ...ctx.habit, placeId: aff.placeId ?? ctx.habit.placeId });
|
|
214
|
+
const effortCost = (effort * K.effortBase + focus * per.sleepiness) * ease +
|
|
215
|
+
effort * per.fatigue * (1 - clamp01(p.body.fitness));
|
|
216
|
+
return -K.effortScale * effortCost;
|
|
217
|
+
}
|
|
218
|
+
/** Same material valuation for actual offers and received successor costs. @internal */
|
|
219
|
+
export function materialValue(p, material, scarcity) {
|
|
220
|
+
const K = COGNITION_DEFAULTS;
|
|
221
|
+
const v = p.values;
|
|
222
|
+
const weight = (v.security + v.achievement + v.power) / 3;
|
|
223
|
+
const scale = K.materialScale * (1 - K.scarcityScaleShrink * scarcity);
|
|
224
|
+
const sat = Math.sign(material) * (1 - dexp(-Math.abs(material) / scale));
|
|
225
|
+
return K.materialWeight * sat * 2 * weight * (1 + K.scarcityMaterialGain * scarcity);
|
|
226
|
+
}
|
|
203
227
|
/** Score one option. Pure apart from reads; terms are rounded for stable snapshots. */
|
|
204
228
|
export function consider(p, aff, ctx) {
|
|
205
229
|
const K = COGNITION_DEFAULTS;
|
|
@@ -264,8 +288,13 @@ export function consider(p, aff, ctx) {
|
|
|
264
288
|
}
|
|
265
289
|
}
|
|
266
290
|
const maturity = clamp01(ctx.mods.maturity);
|
|
267
|
-
|
|
268
|
-
|
|
291
|
+
const maturityScale = 0.5 + 0.5 * maturity;
|
|
292
|
+
const received = receivedGoalRoute(p, aff, ctx.now, maturityScale, (successor) => effortValue(p, successor, ctx) +
|
|
293
|
+
(successor.material ? materialValue(p, successor.material, scarcity) : 0));
|
|
294
|
+
for (const t of agendaTermsWithGoalEstimates(p, aff, ctx.now, received?.goals))
|
|
295
|
+
push(t.source, t.value * maturityScale);
|
|
296
|
+
if (received)
|
|
297
|
+
push(received.cost.source, received.cost.value);
|
|
269
298
|
// Habit: cue-matched automaticity (cued by the option's own place), stronger before planning matures.
|
|
270
299
|
const habit = p.habits.length === 0
|
|
271
300
|
? 0
|
|
@@ -316,17 +345,9 @@ export function consider(p, aff, ctx) {
|
|
|
316
345
|
}
|
|
317
346
|
}
|
|
318
347
|
// Effort and mental load, read from the perceived body.
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
// A cued habit eases initiation and attention (N4: the depleted fall back on habits), not physical exertion.
|
|
323
|
-
const ease = p.habits.length === 0
|
|
324
|
-
? 1
|
|
325
|
-
: habitEase(p, aff, { ...ctx.habit, placeId: aff.placeId ?? ctx.habit.placeId });
|
|
326
|
-
const effortCost = (effort * K.effortBase + focus * per.sleepiness) * ease +
|
|
327
|
-
effort * per.fatigue * (1 - clamp01(p.body.fitness));
|
|
328
|
-
if (effortCost > 0)
|
|
329
|
-
push('effort', -K.effortScale * effortCost);
|
|
348
|
+
const effort = effortValue(p, aff, ctx);
|
|
349
|
+
if (effort < 0)
|
|
350
|
+
push('effort', effort);
|
|
330
351
|
// Risk: chance × severity × (fear + emotionality).
|
|
331
352
|
if (aff.risk && aff.risk.chance > 0 && aff.risk.severity > 0) {
|
|
332
353
|
const fear = emotionLevel(p, 'fear');
|
|
@@ -340,13 +361,8 @@ export function consider(p, aff, ctx) {
|
|
|
340
361
|
}
|
|
341
362
|
}
|
|
342
363
|
// Material: saturating value of host units, scaled by security/achievement/power.
|
|
343
|
-
if (aff.material)
|
|
344
|
-
|
|
345
|
-
const weight = (v.security + v.achievement + v.power) / 3;
|
|
346
|
-
const scale = K.materialScale * (1 - K.scarcityScaleShrink * scarcity);
|
|
347
|
-
const sat = Math.sign(aff.material) * (1 - dexp(-Math.abs(aff.material) / scale));
|
|
348
|
-
push('material', K.materialWeight * sat * 2 * weight * (1 + K.scarcityMaterialGain * scarcity));
|
|
349
|
-
}
|
|
364
|
+
if (aff.material)
|
|
365
|
+
push('material', materialValue(p, aff.material, scarcity));
|
|
350
366
|
// Precommitments inside their daily window.
|
|
351
367
|
const mod = minuteOfDay(ctx.now);
|
|
352
368
|
for (const pc of p.will.precommitments) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Human Framework v1 public surface. */
|
|
2
|
-
export declare const FRAMEWORK_VERSION = "2.
|
|
2
|
+
export declare const FRAMEWORK_VERSION = "2.3.0";
|
|
3
3
|
export type { CrisisEvent, PracticeKind } from './affect/index.ts';
|
|
4
4
|
export { AFFECT_DEFAULTS, actionTendencies, advanceAffect, appraise, breakAllows, breakBehaviour, breakHazard, CRISIS_DEFAULTS, checkCrisis, createAffect, easeBreak, emotionLevel, enableBreaks, feel, inBreak, readAffect, regulate, release as releaseEmotion, skipCrisis, strain, tendencyEmotions, } from './affect/index.ts';
|
|
5
5
|
export type { GoalTemplate, PrayerCalendar, PrayerSchedule, PrayerTimes, Retimer, } from './agenda/index.ts';
|
|
@@ -28,6 +28,8 @@ export * from './narrate/index.ts';
|
|
|
28
28
|
export * from './needs/index.ts';
|
|
29
29
|
export * from './partnering/index.ts';
|
|
30
30
|
export * from './person.ts';
|
|
31
|
+
export type { ConsequenceEvidence } from './prospection/index.ts';
|
|
32
|
+
export { consequenceEvidence, PROSPECTION_DEFAULTS } from './prospection/index.ts';
|
|
31
33
|
export * from './restore.ts';
|
|
32
34
|
export * from './scenarios/index.ts';
|
|
33
35
|
export * from './sim/index.ts';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Human Framework v1 public surface. */
|
|
2
|
-
export const FRAMEWORK_VERSION = '2.
|
|
2
|
+
export const FRAMEWORK_VERSION = '2.3.0';
|
|
3
3
|
export { AFFECT_DEFAULTS, actionTendencies, advanceAffect, appraise, breakAllows, breakBehaviour, breakHazard, CRISIS_DEFAULTS, checkCrisis, createAffect, easeBreak, emotionLevel, enableBreaks, feel, inBreak, readAffect, regulate, release as releaseEmotion, skipCrisis, strain, tendencyEmotions, } from './affect/index.js';
|
|
4
4
|
// `agenda.release` collides with `affect.release`; the agenda one is re-exported under a clearer name.
|
|
5
5
|
export { AGENDA_DEFAULTS, abandonGoal, adoptGoal, advanceAgenda, agendaTerms, applyExemptions, CARE_DEFAULTS, calendarRetimer, careDuty, commitmentPressure, createAgenda, DEFAULT_PRAYER_TIMES, EID_PRAYER_IMPORTANCE, EXEMPTION_DEFAULTS, eidPrayer, eidWindow, fastWindow, iftarWindow, inMakruhTime, isUnderWay, MAKRUH_DEFAULTS, makruhWindows, missedExcuse, onFinished, owedMakeUps, PRAYER_IMPORTANCE, PURPOSE_DEFAULTS, prayerWindows, pressureReachedAt, promise, proposeGoals, RAMADAN_DEFAULTS, ramadanFast, ramadanMeals, release as releaseCommitment, retimeCommitments, revisePurposes, scheduleMakeUp, spanMeetsWindow, suhoorWindow, timesFor, violatesAbstention, } from './agenda/index.js';
|
|
@@ -25,6 +25,7 @@ export * from './narrate/index.js';
|
|
|
25
25
|
export * from './needs/index.js';
|
|
26
26
|
export * from './partnering/index.js';
|
|
27
27
|
export * from './person.js';
|
|
28
|
+
export { consequenceEvidence, PROSPECTION_DEFAULTS } from './prospection/index.js';
|
|
28
29
|
export * from './restore.js';
|
|
29
30
|
export * from './scenarios/index.js';
|
|
30
31
|
export * from './sim/index.js';
|
|
@@ -27,7 +27,7 @@ export interface MethodDemonstration {
|
|
|
27
27
|
attention?: number;
|
|
28
28
|
}
|
|
29
29
|
export interface MethodEvidence {
|
|
30
|
-
/** Net matching evidence for this action, -1..1;
|
|
30
|
+
/** Net matching evidence for this action, -1..1; its failures reduce it. */
|
|
31
31
|
support: number;
|
|
32
32
|
terms: Term[];
|
|
33
33
|
matched: {
|
package/dist/methods/methods.js
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* Successful and unsuccessful demonstrations become person-owned evidence; the
|
|
5
5
|
* same reader supplies host queries and ordinary cognition terms. Unknown cues
|
|
6
6
|
* do not mean false. Context identity prevents one task's observations being used
|
|
7
|
-
* for another task.
|
|
8
|
-
*
|
|
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.
|
|
9
10
|
* These are explicit engineering choices, not fitted human learning parameters.
|
|
10
11
|
* This module does not infer cues, invent actions, plan sequences, certify skill,
|
|
11
12
|
* access host truth, compute religious worth, or guarantee that a person obeys.
|
|
@@ -155,15 +156,13 @@ export function methodEvidence(p, action, contextId) {
|
|
|
155
156
|
result.matched.push({ id: rule.id, action: rule.action, sourceId, support });
|
|
156
157
|
}
|
|
157
158
|
}
|
|
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.
|
|
159
161
|
const strengths = new Map();
|
|
160
162
|
for (const match of result.matched)
|
|
161
163
|
strengths.set(match.action, (strengths.get(match.action) ?? 0) + match.support);
|
|
162
164
|
const own = strengths.get(action) ?? 0;
|
|
163
|
-
|
|
164
|
-
const total = Math.max(1, ...[...strengths.values()].map((v) => Math.abs(v)));
|
|
165
|
-
const net = own > 0 ? Math.max(0, own - rival) : own;
|
|
166
|
-
result.support = Math.max(-1, Math.min(1, net / total));
|
|
165
|
+
result.support = own / Math.max(1, Math.abs(own));
|
|
167
166
|
const scale = own === 0 ? 0 : result.support / own;
|
|
168
167
|
for (const match of result.matched) {
|
|
169
168
|
if (match.action !== action || match.support === 0)
|
package/dist/migrate.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* field of the optional surroundings slice (`ambient.now` → `ambient.percept`) and changes no behaviour (the optional `social.impressions` and `social.reserve`, like `retention`, are optional and start
|
|
9
9
|
* absent in a migrated save); `restore` then fills and sanitizes
|
|
10
10
|
* the slices as for any save. 2.0.0 to 2.1.0 adds optional conditional method state, absent
|
|
11
|
-
* until explicitly used, so existing hosts retain their previous behavior; that step stamps the version. A migrated save continues under the current engine's rules: it restores and runs,
|
|
11
|
+
* until explicitly used, so existing hosts retain their previous behavior; that step stamps the version. 2.1.0 to 2.2.0 preserves optional method records and stamps the version; successful alternatives now retain independent support. 2.2.0 to 2.3.0 stamps the version; received consequences and temporal goals remain absent unless explicitly supplied. A migrated save continues under the current engine's rules: it restores and runs,
|
|
12
12
|
* but it does not reproduce what the old engine would have done next where the rules changed (1.4.0 to 1.5.0
|
|
13
13
|
* changed how standing advice is heard; 1.8.0 to 1.9.0 changed the omission rule). Saves older than 1.4.0 and unknown versions are refused with an error.
|
|
14
14
|
*
|
|
@@ -58,6 +58,16 @@ export const MIGRATIONS = {
|
|
|
58
58
|
note: 'the surroundings field `ambient.now` is renamed `ambient.percept`; per-person retention is optional and starts absent; behaviour unchanged',
|
|
59
59
|
apply: renameAmbient,
|
|
60
60
|
},
|
|
61
|
+
'2.2.0': {
|
|
62
|
+
to: '2.3.0',
|
|
63
|
+
note: 'received consequences and temporal goals are optional; existing hosts retain exact behavior',
|
|
64
|
+
apply: stamp,
|
|
65
|
+
},
|
|
66
|
+
'2.1.0': {
|
|
67
|
+
to: '2.2.0',
|
|
68
|
+
note: 'person shape unchanged; method evidence now grades each action independently, preserving successful alternatives',
|
|
69
|
+
apply: stamp,
|
|
70
|
+
},
|
|
61
71
|
'2.0.0': {
|
|
62
72
|
to: '2.1.0',
|
|
63
73
|
note: 'conditional method state and received cues are optional and start absent; existing host decisions unchanged',
|
package/dist/person.js
CHANGED
|
@@ -36,6 +36,7 @@ import { advanceMemory, createMemory, enableGists, foldGists, learnOutcome, reca
|
|
|
36
36
|
import { intentionFor, narrateDecision, voiceLine } from './narrate/index.js';
|
|
37
37
|
import { advanceNeeds, createNeeds, levelOf, meanSatisfaction, readNeeds, satisfy, urgencyOf, } from './needs/index.js';
|
|
38
38
|
import { widow } from './partnering/index.js';
|
|
39
|
+
import { consumeConsequences, pruneConsequences, receiveConsequences } from './prospection/index.js';
|
|
39
40
|
import { enableSkillRetention, learnByWatching, practise, seedSkills, } from './skills/index.js';
|
|
40
41
|
import { advanceSocial, applyReputationBelief, careFor, closeness, judge, markDeceased, relationshipWith, seedRelationships, socialEvent, threatAppraisal, } from './social/index.js';
|
|
41
42
|
import { ENGINE_VERSION, MAX_MINUTE, PERSON_SCHEMA, PHYSIOLOGICAL_NEEDS, PSYCHOLOGICAL_NEEDS, TRAIT_KEYS, VALUE_KEYS, } from './types.js';
|
|
@@ -353,6 +354,7 @@ export function tick(p, now) {
|
|
|
353
354
|
return;
|
|
354
355
|
if (!p.body.alive) {
|
|
355
356
|
p.now = now;
|
|
357
|
+
pruneConsequences(p);
|
|
356
358
|
return;
|
|
357
359
|
}
|
|
358
360
|
const seg = PERSON_DEFAULTS.maxSegment;
|
|
@@ -366,13 +368,16 @@ export function tick(p, now) {
|
|
|
366
368
|
// A minute so large that the grid step rounds away cannot advance (security review H2): stop, never spin.
|
|
367
369
|
if (!(end > p.now)) {
|
|
368
370
|
p.now = now;
|
|
371
|
+
pruneConsequences(p);
|
|
369
372
|
return;
|
|
370
373
|
}
|
|
371
374
|
advanceSegment(p, end - p.now, load, active && act ? act.affordance : undefined);
|
|
372
375
|
p.now = end;
|
|
376
|
+
pruneConsequences(p);
|
|
373
377
|
if (!p.body.alive) {
|
|
374
378
|
p.activity = null;
|
|
375
379
|
p.now = now;
|
|
380
|
+
pruneConsequences(p);
|
|
376
381
|
return;
|
|
377
382
|
}
|
|
378
383
|
// Discrete bookkeeping runs only on grid boundaries, so it happens at the same minutes whatever the
|
|
@@ -517,6 +522,7 @@ export function skip(p, to) {
|
|
|
517
522
|
p.activity = null;
|
|
518
523
|
skipBody(p, to);
|
|
519
524
|
p.now = to;
|
|
525
|
+
pruneConsequences(p);
|
|
520
526
|
skipAffect(p, to);
|
|
521
527
|
advanceAgenda(p, to);
|
|
522
528
|
// Lasting gists (1.8.0, opt-in): episodes now past the horizon fold into gists.
|
|
@@ -535,6 +541,7 @@ function socialKindOf(kind) {
|
|
|
535
541
|
return SOCIAL_ALIASES[kind];
|
|
536
542
|
}
|
|
537
543
|
function perceiveOne(p, pc) {
|
|
544
|
+
receiveConsequences(p, pc);
|
|
538
545
|
const at = Math.min(Math.max(pc.at, p.affect.lastUpdated), p.now);
|
|
539
546
|
const actor = pc.actorId;
|
|
540
547
|
const target = pc.targetId;
|
|
@@ -1191,6 +1198,7 @@ export function finish(p, outcome, opts = {}) {
|
|
|
1191
1198
|
}
|
|
1192
1199
|
const prev = lastAction(p);
|
|
1193
1200
|
if (completed) {
|
|
1201
|
+
consumeConsequences(p, aff);
|
|
1194
1202
|
const hctx = { now };
|
|
1195
1203
|
if (aff.placeId !== undefined)
|
|
1196
1204
|
hctx.placeId = aff.placeId;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './prospection.ts';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './prospection.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Affordance, ConsequenceRevision, Minute, Person, RetainedConsequence } from '../types.ts';
|
|
2
|
+
export declare const PROSPECTION_DEFAULTS: {
|
|
3
|
+
maxEntries: number;
|
|
4
|
+
maxRevisions: number;
|
|
5
|
+
maxBatch: number;
|
|
6
|
+
maxIdentifierLength: number;
|
|
7
|
+
horizon: number;
|
|
8
|
+
maxMaterialCost: number;
|
|
9
|
+
};
|
|
10
|
+
export interface ConsequenceEvidence {
|
|
11
|
+
entry: RetainedConsequence;
|
|
12
|
+
matches: boolean;
|
|
13
|
+
feasible: boolean;
|
|
14
|
+
successorStartsAt: Minute;
|
|
15
|
+
successorEndsAt: Minute;
|
|
16
|
+
accessProbability: number;
|
|
17
|
+
terminalProbability: number;
|
|
18
|
+
}
|
|
19
|
+
/** Read current retained knowledge and feasibility. Does not mutate, prune or consume RNG. */
|
|
20
|
+
export declare function consequenceEvidence(p: Person, aff: Affordance, now?: Minute): {
|
|
21
|
+
entries: ConsequenceEvidence[];
|
|
22
|
+
revisions: ConsequenceRevision[];
|
|
23
|
+
};
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SCOPE: one received current-action → successor consequence, acquired only through
|
|
3
|
+
* attended external testimony for this person or an untargeted broadcast. Knowledge is person-owned, source-labelled, bounded and saved.
|
|
4
|
+
* Temporal goals value expected completion using time, success and source reliability.
|
|
5
|
+
* One coherent route may contribute; it never executes actions, grants consent or
|
|
6
|
+
* advances a goal. Own completed triggers consume their received opportunity.
|
|
7
|
+
* This is not graph search, inferred procedure learning or a model of future bodies,
|
|
8
|
+
* waiting opportunity cost or another person's agency. Parameters are engineering
|
|
9
|
+
* bounds, not fitted psychological estimates. Revision memory is bounded, not an audit.
|
|
10
|
+
*/
|
|
11
|
+
import { temporalGoalValue } from '../agenda/agenda.js';
|
|
12
|
+
import { trustOf } from '../beliefs/index.js';
|
|
13
|
+
import { cmpStr, isNum, isObj, round } from '../core/index.js';
|
|
14
|
+
import { expectedEffect } from '../memory/index.js';
|
|
15
|
+
import { MAX_MINUTE, MINUTES_PER_DAY } from '../types.js';
|
|
16
|
+
export const PROSPECTION_DEFAULTS = {
|
|
17
|
+
maxEntries: 16,
|
|
18
|
+
maxRevisions: 64,
|
|
19
|
+
maxBatch: 4,
|
|
20
|
+
maxIdentifierLength: 96,
|
|
21
|
+
horizon: 7 * MINUTES_PER_DAY,
|
|
22
|
+
maxMaterialCost: 1_000_000,
|
|
23
|
+
};
|
|
24
|
+
const id = (x) => typeof x === 'string' &&
|
|
25
|
+
x.length > 0 &&
|
|
26
|
+
x.length <= PROSPECTION_DEFAULTS.maxIdentifierLength &&
|
|
27
|
+
x !== '__proto__' &&
|
|
28
|
+
x !== 'constructor' &&
|
|
29
|
+
x !== 'prototype';
|
|
30
|
+
const minute = (x) => isNum(x) && Math.abs(x) <= MAX_MINUTE;
|
|
31
|
+
const unit = (x) => isNum(x) && x >= 0 && x <= 1;
|
|
32
|
+
const revision = (x) => typeof x === 'number' && Number.isSafeInteger(x) && x >= 0;
|
|
33
|
+
const identity = (a, b) => a.sourceId === b.sourceId && a.id === b.id;
|
|
34
|
+
const order = (a, b) => a.receivedAt - b.receivedAt || cmpStr(a.sourceId, b.sourceId) || cmpStr(a.id, b.id);
|
|
35
|
+
function action(x) {
|
|
36
|
+
if (!isObj(x) ||
|
|
37
|
+
!id(x.action) ||
|
|
38
|
+
(x.targetId !== undefined && !id(x.targetId)) ||
|
|
39
|
+
(x.placeId !== undefined && !id(x.placeId)))
|
|
40
|
+
return undefined;
|
|
41
|
+
return {
|
|
42
|
+
action: x.action,
|
|
43
|
+
...(x.targetId !== undefined ? { targetId: x.targetId } : {}),
|
|
44
|
+
...(x.placeId !== undefined ? { placeId: x.placeId } : {}),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function consequence(x, receivedAt) {
|
|
48
|
+
if (!isObj(x) || !id(x.id) || !revision(x.revision))
|
|
49
|
+
return undefined;
|
|
50
|
+
const trigger = action(x.trigger);
|
|
51
|
+
const successorAction = action(x.successor);
|
|
52
|
+
const s = x.successor;
|
|
53
|
+
if (!trigger ||
|
|
54
|
+
!successorAction ||
|
|
55
|
+
!isObj(s) ||
|
|
56
|
+
!isNum(s.duration) ||
|
|
57
|
+
s.duration < 0 ||
|
|
58
|
+
s.duration > PROSPECTION_DEFAULTS.horizon ||
|
|
59
|
+
!unit(s.effort) ||
|
|
60
|
+
(s.focus !== undefined && !unit(s.focus)) ||
|
|
61
|
+
(s.materialCost !== undefined &&
|
|
62
|
+
(!isNum(s.materialCost) || s.materialCost < 0 || s.materialCost > PROSPECTION_DEFAULTS.maxMaterialCost)))
|
|
63
|
+
return undefined;
|
|
64
|
+
if (!minute(x.availableFrom) ||
|
|
65
|
+
!minute(x.availableUntil) ||
|
|
66
|
+
!minute(x.expiresAt) ||
|
|
67
|
+
x.availableFrom > x.availableUntil ||
|
|
68
|
+
x.availableUntil > x.expiresAt ||
|
|
69
|
+
x.expiresAt < receivedAt ||
|
|
70
|
+
x.expiresAt > receivedAt + PROSPECTION_DEFAULTS.horizon ||
|
|
71
|
+
!unit(x.chance) ||
|
|
72
|
+
!unit(x.confidence))
|
|
73
|
+
return undefined;
|
|
74
|
+
return {
|
|
75
|
+
id: x.id,
|
|
76
|
+
revision: x.revision,
|
|
77
|
+
trigger,
|
|
78
|
+
successor: {
|
|
79
|
+
...successorAction,
|
|
80
|
+
duration: s.duration,
|
|
81
|
+
effort: s.effort,
|
|
82
|
+
...(s.focus !== undefined ? { focus: s.focus } : {}),
|
|
83
|
+
...(s.materialCost !== undefined ? { materialCost: s.materialCost } : {}),
|
|
84
|
+
},
|
|
85
|
+
availableFrom: x.availableFrom,
|
|
86
|
+
availableUntil: x.availableUntil,
|
|
87
|
+
expiresAt: x.expiresAt,
|
|
88
|
+
chance: x.chance,
|
|
89
|
+
confidence: x.confidence,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function retained(x, now) {
|
|
93
|
+
if (!isObj(x) || !id(x.sourceId) || x.channel !== 'told' || !minute(x.receivedAt) || x.receivedAt > now)
|
|
94
|
+
return undefined;
|
|
95
|
+
const c = consequence(x, x.receivedAt);
|
|
96
|
+
if (!c ||
|
|
97
|
+
(x.triggeredAt !== undefined &&
|
|
98
|
+
(!minute(x.triggeredAt) || x.triggeredAt < x.receivedAt || x.triggeredAt > now)))
|
|
99
|
+
return undefined;
|
|
100
|
+
return {
|
|
101
|
+
...c,
|
|
102
|
+
sourceId: x.sourceId,
|
|
103
|
+
channel: 'told',
|
|
104
|
+
receivedAt: x.receivedAt,
|
|
105
|
+
...(x.triggeredAt !== undefined ? { triggeredAt: x.triggeredAt } : {}),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function cleaned(state, now) {
|
|
109
|
+
const revisions = state.revisions.filter((r) => now - r.receivedAt <= PROSPECTION_DEFAULTS.horizon);
|
|
110
|
+
return {
|
|
111
|
+
entries: state.entries.filter((e) => e.expiresAt >= now && revisions.some((r) => identity(r, e) && r.revision === e.revision)),
|
|
112
|
+
revisions,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function store(p, s) {
|
|
116
|
+
if (s.entries.length || s.revisions.length)
|
|
117
|
+
p.prospection = s;
|
|
118
|
+
else
|
|
119
|
+
delete p.prospection;
|
|
120
|
+
}
|
|
121
|
+
/** Expiry changes only existing optional state. @internal */
|
|
122
|
+
export function pruneConsequences(p, now = p.now) {
|
|
123
|
+
if (p.prospection)
|
|
124
|
+
store(p, cleaned(p.prospection, now));
|
|
125
|
+
}
|
|
126
|
+
/** Called only for an attended percept. Invalid combined batches mutate no prospection state. @internal */
|
|
127
|
+
export function receiveConsequences(p, pc) {
|
|
128
|
+
if (pc.consequences === undefined && pc.withdrawConsequences === undefined)
|
|
129
|
+
return false;
|
|
130
|
+
if (pc.channel !== 'told' ||
|
|
131
|
+
!id(pc.actorId) ||
|
|
132
|
+
pc.actorId === p.id ||
|
|
133
|
+
(pc.targetId !== undefined && pc.targetId !== p.id))
|
|
134
|
+
return false;
|
|
135
|
+
const adds = pc.consequences ?? [];
|
|
136
|
+
const removes = pc.withdrawConsequences ?? [];
|
|
137
|
+
if (!Array.isArray(adds) ||
|
|
138
|
+
!Array.isArray(removes) ||
|
|
139
|
+
adds.length + removes.length > PROSPECTION_DEFAULTS.maxBatch ||
|
|
140
|
+
adds.length + removes.length === 0)
|
|
141
|
+
return false;
|
|
142
|
+
const additions = adds.map((x) => consequence(x, p.now));
|
|
143
|
+
if (additions.some((x) => x === undefined) ||
|
|
144
|
+
removes.some((x) => !isObj(x) || !id(x.id) || !revision(x.revision)))
|
|
145
|
+
return false;
|
|
146
|
+
const withdrawals = removes;
|
|
147
|
+
const batch = [...additions.map((x) => ({ id: x?.id, revision: x?.revision })), ...withdrawals];
|
|
148
|
+
if (new Set(batch.map((x) => x.id)).size !== batch.length)
|
|
149
|
+
return false;
|
|
150
|
+
const state = cleaned(structuredClone(p.prospection ?? { entries: [], revisions: [] }), p.now);
|
|
151
|
+
const sourceId = pc.actorId;
|
|
152
|
+
const updates = batch.filter((b) => !state.revisions.some((r) => r.sourceId === sourceId && r.id === b.id && r.revision >= (b.revision ?? 0)));
|
|
153
|
+
const newIds = updates.filter((b) => !state.revisions.some((r) => r.sourceId === sourceId && r.id === b.id));
|
|
154
|
+
// Never evict a still-valid tombstone to admit a batch; dedup holds within the stated horizon.
|
|
155
|
+
if (state.revisions.length + newIds.length > PROSPECTION_DEFAULTS.maxRevisions)
|
|
156
|
+
return false;
|
|
157
|
+
for (const b of updates) {
|
|
158
|
+
state.entries = state.entries.filter((e) => !(e.sourceId === sourceId && e.id === b.id));
|
|
159
|
+
state.revisions = state.revisions.filter((r) => !(r.sourceId === sourceId && r.id === b.id));
|
|
160
|
+
const addition = additions.find((a) => a?.id === b.id);
|
|
161
|
+
if (addition)
|
|
162
|
+
state.entries.push({ ...addition, sourceId, channel: 'told', receivedAt: p.now });
|
|
163
|
+
state.revisions.push({ sourceId, id: b.id, revision: b.revision, receivedAt: p.now });
|
|
164
|
+
}
|
|
165
|
+
if (state.entries.length > PROSPECTION_DEFAULTS.maxEntries) {
|
|
166
|
+
const oldest = [...state.entries]
|
|
167
|
+
.sort((a, b) => Number(b.triggeredAt !== undefined) - Number(a.triggeredAt !== undefined) || order(a, b))
|
|
168
|
+
.slice(0, state.entries.length - PROSPECTION_DEFAULTS.maxEntries);
|
|
169
|
+
state.entries = state.entries.filter((e) => !oldest.includes(e));
|
|
170
|
+
}
|
|
171
|
+
store(p, state);
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
const matches = (a, b) => a.action === b.action && a.targetId === b.targetId && a.placeId === b.placeId;
|
|
175
|
+
/** Own actual completed trigger consumes an opportunity, without predicting consent. @internal */
|
|
176
|
+
export function consumeConsequences(p, completed) {
|
|
177
|
+
for (const e of p.prospection?.entries ?? [])
|
|
178
|
+
if (e.triggeredAt === undefined && matches(e.trigger, completed))
|
|
179
|
+
e.triggeredAt = p.now;
|
|
180
|
+
}
|
|
181
|
+
/** Read current retained knowledge and feasibility. Does not mutate, prune or consume RNG. */
|
|
182
|
+
export function consequenceEvidence(p, aff, now = p.now) {
|
|
183
|
+
return {
|
|
184
|
+
entries: (p.prospection?.entries ?? []).map((entry) => {
|
|
185
|
+
const start = Math.max(now + Math.max(0, aff.duration), entry.availableFrom);
|
|
186
|
+
const end = start + entry.successor.duration;
|
|
187
|
+
const match = matches(entry.trigger, aff);
|
|
188
|
+
const feasible = match &&
|
|
189
|
+
entry.triggeredAt === undefined &&
|
|
190
|
+
now <= entry.expiresAt &&
|
|
191
|
+
end <= entry.availableUntil &&
|
|
192
|
+
Number.isFinite(end);
|
|
193
|
+
const accessProbability = expectedEffect(p, aff).successRate * entry.chance * entry.confidence * trustOf(p, entry.sourceId);
|
|
194
|
+
return {
|
|
195
|
+
entry: structuredClone(entry),
|
|
196
|
+
matches: match,
|
|
197
|
+
feasible,
|
|
198
|
+
successorStartsAt: start,
|
|
199
|
+
successorEndsAt: end,
|
|
200
|
+
accessProbability,
|
|
201
|
+
terminalProbability: accessProbability * expectedEffect(p, successorOffer(entry)).successRate,
|
|
202
|
+
};
|
|
203
|
+
}),
|
|
204
|
+
revisions: structuredClone((p.prospection?.revisions ?? []).filter((r) => now - r.receivedAt <= PROSPECTION_DEFAULTS.horizon)),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function successorOffer(e) {
|
|
208
|
+
const { materialCost, ...rest } = e.successor;
|
|
209
|
+
return {
|
|
210
|
+
...rest,
|
|
211
|
+
id: 'prospective-successor',
|
|
212
|
+
label: 'Received successor',
|
|
213
|
+
advertises: {},
|
|
214
|
+
...(materialCost !== undefined ? { material: -materialCost } : {}),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/** Select a whole route in the exact rounded units cognition emits. @internal */
|
|
218
|
+
export function receivedGoalRoute(p, aff, now, maturityScale, costs) {
|
|
219
|
+
const goals = p.agenda.goals.filter((g) => g.status === 'active' && g.temporal !== undefined);
|
|
220
|
+
if (!goals.length || !p.prospection?.entries.length)
|
|
221
|
+
return undefined;
|
|
222
|
+
const direct = goals.map((g) => temporalGoalValue(p, g, aff, now, now + Math.max(0, aff.duration)));
|
|
223
|
+
const baseline = direct.reduce((sum, value) => sum + round(value * maturityScale), 0);
|
|
224
|
+
let bestScore = baseline;
|
|
225
|
+
let best;
|
|
226
|
+
const entries = consequenceEvidence(p, aff, now).entries.sort((a, b) => cmpStr(a.entry.sourceId, b.entry.sourceId) || cmpStr(a.entry.id, b.entry.id));
|
|
227
|
+
for (const e of entries) {
|
|
228
|
+
if (!e.feasible)
|
|
229
|
+
continue;
|
|
230
|
+
const successor = successorOffer(e.entry);
|
|
231
|
+
const values = goals.map((g, i) => Math.max(direct[i] ?? 0, e.accessProbability * temporalGoalValue(p, g, successor, now, e.successorEndsAt)));
|
|
232
|
+
const cost = round(e.accessProbability * Math.min(0, costs(successor)));
|
|
233
|
+
const total = values.reduce((sum, value) => sum + round(value * maturityScale), 0) + cost;
|
|
234
|
+
if (!(round(total - bestScore) > 0))
|
|
235
|
+
continue;
|
|
236
|
+
bestScore = total;
|
|
237
|
+
best = {
|
|
238
|
+
goals: new Map(goals.map((g, i) => [g.id, values[i] ?? 0])),
|
|
239
|
+
cost: { source: `prospection:cost:${e.entry.sourceId}:${e.entry.id}`, value: cost },
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
return best;
|
|
243
|
+
}
|
|
244
|
+
/** Conservative optional-slice restore; ambiguous identities drop the slice. @internal */
|
|
245
|
+
export function sanitizeProspection(input, now, personId) {
|
|
246
|
+
if (!isObj(input) ||
|
|
247
|
+
!Array.isArray(input.entries) ||
|
|
248
|
+
!Array.isArray(input.revisions) ||
|
|
249
|
+
input.entries.length > PROSPECTION_DEFAULTS.maxEntries ||
|
|
250
|
+
input.revisions.length > PROSPECTION_DEFAULTS.maxRevisions)
|
|
251
|
+
return undefined;
|
|
252
|
+
const entries = input.entries.map((x) => retained(x, now)).filter((e) => e?.sourceId !== personId);
|
|
253
|
+
const revisions = [];
|
|
254
|
+
for (const r of input.revisions) {
|
|
255
|
+
if (!isObj(r) ||
|
|
256
|
+
!id(r.sourceId) ||
|
|
257
|
+
r.sourceId === personId ||
|
|
258
|
+
!id(r.id) ||
|
|
259
|
+
!revision(r.revision) ||
|
|
260
|
+
!minute(r.receivedAt) ||
|
|
261
|
+
r.receivedAt > now)
|
|
262
|
+
continue;
|
|
263
|
+
if (revisions.some((old) => identity(old, r)))
|
|
264
|
+
return undefined;
|
|
265
|
+
revisions.push({ sourceId: r.sourceId, id: r.id, revision: r.revision, receivedAt: r.receivedAt });
|
|
266
|
+
}
|
|
267
|
+
const valid = [];
|
|
268
|
+
for (const e of entries) {
|
|
269
|
+
if (!e)
|
|
270
|
+
continue;
|
|
271
|
+
if (valid.some((old) => identity(old, e)))
|
|
272
|
+
return undefined;
|
|
273
|
+
if (revisions.some((r) => identity(r, e) && r.revision === e.revision && r.receivedAt === e.receivedAt))
|
|
274
|
+
valid.push(e);
|
|
275
|
+
}
|
|
276
|
+
const state = cleaned({ entries: valid, revisions }, now);
|
|
277
|
+
return state.entries.length || state.revisions.length ? state : undefined;
|
|
278
|
+
}
|
package/dist/restore.js
CHANGED
|
@@ -20,6 +20,7 @@ import { sanitizeMethods } from './methods/index.js';
|
|
|
20
20
|
import { migrate } from './migrate.js';
|
|
21
21
|
import { sanitizeBonds } from './partnering/index.js';
|
|
22
22
|
import { cleanRetention, createPerson, traceLimit } from './person.js';
|
|
23
|
+
import { sanitizeProspection } from './prospection/index.js';
|
|
23
24
|
import { sanitizeSkillRetention } from './skills/index.js';
|
|
24
25
|
import { sanitizeGroups, sanitizeImpressions } from './social/index.js';
|
|
25
26
|
import { ENGINE_VERSION, MAX_MINUTE, PERSON_SCHEMA } from './types.js';
|
|
@@ -59,6 +60,7 @@ const OPTIONAL_KEYS = new Set([
|
|
|
59
60
|
'ambient',
|
|
60
61
|
'retention',
|
|
61
62
|
'methods',
|
|
63
|
+
'prospection',
|
|
62
64
|
]);
|
|
63
65
|
/** Kind of a JSON value for the per-field type check in `restore`. */
|
|
64
66
|
const kindOf = (x) => (Array.isArray(x) ? 'array' : x === null ? 'null' : typeof x);
|
|
@@ -171,6 +173,7 @@ export function restore(input) {
|
|
|
171
173
|
keepValid(out, 'ambient', sanitizeAmbient);
|
|
172
174
|
keepValid(out, 'retention', cleanRetention);
|
|
173
175
|
keepValid(out, 'methods', sanitizeMethods);
|
|
176
|
+
keepValid(out, 'prospection', (x) => sanitizeProspection(x, out.now, out.id));
|
|
174
177
|
boundLists(out);
|
|
175
178
|
return out;
|
|
176
179
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -543,6 +543,10 @@ export interface Goal {
|
|
|
543
543
|
status: 'active' | 'achieved' | 'abandoned';
|
|
544
544
|
adoptedAt: Minute;
|
|
545
545
|
deadline?: Minute;
|
|
546
|
+
/** Opt-in expected completion valuation; deadline remains urgency, never a cutoff. */
|
|
547
|
+
temporal?: {
|
|
548
|
+
halfLifeMinutes: number;
|
|
549
|
+
};
|
|
546
550
|
/** Last minute the goal was advanced (or adopted); neglect is counted from here. */
|
|
547
551
|
lastAdvancedAt?: Minute;
|
|
548
552
|
/** Importance before neglect decay; `revisePurposes` recomputes `importance` from it. */
|
|
@@ -787,7 +791,12 @@ export interface Activity {
|
|
|
787
791
|
}
|
|
788
792
|
export declare const PERSON_SCHEMA = "human/person@1";
|
|
789
793
|
/**
|
|
790
|
-
* Current: 2.
|
|
794
|
+
* Current: 2.3.0 (2026-10-17): optional temporal goals and received consequences.
|
|
795
|
+
* 2.2.0 (2026-10-14): method evidence grades each action independently.
|
|
796
|
+
* Successful alternatives no longer count as failures of another action. Person state shape is unchanged.
|
|
797
|
+
* 2.1.0 saves migrate by stamping the version.
|
|
798
|
+
*
|
|
799
|
+
* 2.1.0 (2026-10-10): optional acquired conditional methods and explicit received cue contexts.
|
|
791
800
|
* Normal cognition reads learned method evidence only on offers carrying the matching context.
|
|
792
801
|
* Existing hosts with no method state keep their previous decisions; 2.0.0 saves migrate by stamping the version.
|
|
793
802
|
*
|
|
@@ -818,7 +827,7 @@ export declare const PERSON_SCHEMA = "human/person@1";
|
|
|
818
827
|
* and an activity that would cover a closing stretch is reviewed when the stretch begins (`isUnderWay`,
|
|
819
828
|
* `dutyReviewAt`). Person shape unchanged.
|
|
820
829
|
*/
|
|
821
|
-
export declare const ENGINE_VERSION = "2.
|
|
830
|
+
export declare const ENGINE_VERSION = "2.3.0";
|
|
822
831
|
/** A pregnancy carried by this person (host-driven; see `family/`). */
|
|
823
832
|
export interface Pregnancy {
|
|
824
833
|
fatherId: PersonId;
|
|
@@ -919,6 +928,49 @@ export interface MethodsState {
|
|
|
919
928
|
cues: MethodCue[];
|
|
920
929
|
};
|
|
921
930
|
}
|
|
931
|
+
/** Exact current-action identity; omitted target/place match only omitted values. */
|
|
932
|
+
export interface ConsequenceAction {
|
|
933
|
+
action: string;
|
|
934
|
+
targetId?: EntityId;
|
|
935
|
+
placeId?: EntityId;
|
|
936
|
+
}
|
|
937
|
+
/** A received conditional opportunity, never another person's consent or an actual outcome. */
|
|
938
|
+
export interface ReceivedConsequence {
|
|
939
|
+
id: string;
|
|
940
|
+
revision: number;
|
|
941
|
+
trigger: ConsequenceAction;
|
|
942
|
+
successor: ConsequenceAction & {
|
|
943
|
+
duration: number;
|
|
944
|
+
effort: Unit;
|
|
945
|
+
focus?: Unit;
|
|
946
|
+
materialCost?: number;
|
|
947
|
+
};
|
|
948
|
+
availableFrom: Minute;
|
|
949
|
+
/** Latest permitted successor completion, inclusive. */
|
|
950
|
+
availableUntil: Minute;
|
|
951
|
+
expiresAt: Minute;
|
|
952
|
+
/** P(access | trigger completes successfully), not consent. */
|
|
953
|
+
chance: Unit;
|
|
954
|
+
confidence: Unit;
|
|
955
|
+
}
|
|
956
|
+
export interface ConsequenceRevision {
|
|
957
|
+
sourceId: EntityId;
|
|
958
|
+
id: string;
|
|
959
|
+
revision: number;
|
|
960
|
+
receivedAt: Minute;
|
|
961
|
+
}
|
|
962
|
+
export interface RetainedConsequence extends ReceivedConsequence {
|
|
963
|
+
sourceId: EntityId;
|
|
964
|
+
channel: 'told';
|
|
965
|
+
receivedAt: Minute;
|
|
966
|
+
/** Own completed trigger; does not establish access or goal progress. */
|
|
967
|
+
triggeredAt?: Minute;
|
|
968
|
+
}
|
|
969
|
+
/** Optional person-owned received knowledge. Deduplication lasts only within bounded revision memory. */
|
|
970
|
+
export interface ProspectionState {
|
|
971
|
+
entries: RetainedConsequence[];
|
|
972
|
+
revisions: ConsequenceRevision[];
|
|
973
|
+
}
|
|
922
974
|
export interface Person {
|
|
923
975
|
schema: typeof PERSON_SCHEMA;
|
|
924
976
|
/** Engine version that last wrote this state. */
|
|
@@ -938,6 +990,8 @@ export interface Person {
|
|
|
938
990
|
skills: Record<string, Skill>;
|
|
939
991
|
/** Acquired conditional method evidence and current received cues; absent until used. */
|
|
940
992
|
methods?: MethodsState;
|
|
993
|
+
/** Received future consequences, absent until attended testimony supplies them. */
|
|
994
|
+
prospection?: ProspectionState;
|
|
941
995
|
/** Practice consolidation (1.8.0, opt-in via `enableSkillRetention`). Owned by `skills/`. */
|
|
942
996
|
skillRetention?: {
|
|
943
997
|
consolidationHours: number;
|
|
@@ -1103,6 +1157,12 @@ export interface Percept {
|
|
|
1103
1157
|
minutes: number;
|
|
1104
1158
|
domain?: LearningDomain;
|
|
1105
1159
|
};
|
|
1160
|
+
/** At most four additions/withdrawals combined; attended external testimony to this person or a broadcast. */
|
|
1161
|
+
consequences?: readonly ReceivedConsequence[];
|
|
1162
|
+
withdrawConsequences?: readonly {
|
|
1163
|
+
id: string;
|
|
1164
|
+
revision: number;
|
|
1165
|
+
}[];
|
|
1106
1166
|
}
|
|
1107
1167
|
/**
|
|
1108
1168
|
* Domains with distinct age curves for learning rate (`lifecourse.learningMultiplier`). 'general' is
|
package/dist/types.js
CHANGED
|
@@ -84,7 +84,12 @@ export const EMOTIONS = [
|
|
|
84
84
|
// ---------------------------------------------------------------------------------------------
|
|
85
85
|
export const PERSON_SCHEMA = 'human/person@1';
|
|
86
86
|
/**
|
|
87
|
-
* Current: 2.
|
|
87
|
+
* Current: 2.3.0 (2026-10-17): optional temporal goals and received consequences.
|
|
88
|
+
* 2.2.0 (2026-10-14): method evidence grades each action independently.
|
|
89
|
+
* Successful alternatives no longer count as failures of another action. Person state shape is unchanged.
|
|
90
|
+
* 2.1.0 saves migrate by stamping the version.
|
|
91
|
+
*
|
|
92
|
+
* 2.1.0 (2026-10-10): optional acquired conditional methods and explicit received cue contexts.
|
|
88
93
|
* Normal cognition reads learned method evidence only on offers carrying the matching context.
|
|
89
94
|
* Existing hosts with no method state keep their previous decisions; 2.0.0 saves migrate by stamping the version.
|
|
90
95
|
*
|
|
@@ -115,4 +120,4 @@ export const PERSON_SCHEMA = 'human/person@1';
|
|
|
115
120
|
* and an activity that would cover a closing stretch is reviewed when the stretch begins (`isUnderWay`,
|
|
116
121
|
* `dutyReviewAt`). Person shape unchanged.
|
|
117
122
|
*/
|
|
118
|
-
export const ENGINE_VERSION = '2.
|
|
123
|
+
export const ENGINE_VERSION = '2.3.0';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adam0white/human-framework",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Deterministic, explainable simulated people: body, needs, affect, memory, beliefs, skills, habits, relationships, values and a will that can refuse a suggestion",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"simulation",
|