@adam0white/human-framework 2.18.0 → 2.19.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.
@@ -1,3 +1,35 @@
1
+ /**
2
+ * SCOPE (evening self-review, muhasaba, 2.7.0): once a day, from `at` (default 21:00), a person looks back over
3
+ * the commitments the chronicle recorded as kept or broken since their last review, and for each one they broke
4
+ * forms or renews a *resolve*: an if–then intention for that commitment's next occasion (Gollwitzer & Sheeran
5
+ * 2006, research/reconciliation.md E1). While a matching commitment is pending, the resolve adds one bounded term,
6
+ * `review:resolve` (each resolve counted once per option, the sum clamped to ±`resolveScale`; 2.9.0: the term's
7
+ * `ref` is the resolve's key when exactly one resolve weighed in), with the sign of the commitment's own term (toward a duty, away from an abstention's breaking
8
+ * act), scaled by its strength. Strength grows with diminishing gains at each further lapse, relaxes once the
9
+ * commitment is kept, and fades over `halfLifeDays` of disuse. Released and excused commitments (exemptions,
10
+ * sleep, necessity) form no resolve: what was beyond capacity is not held against the person (2:286). The review
11
+ * also runs when the day closes, if the evening passed without it or a lapse came after it.
12
+ *
13
+ * Held readings (`ReviewStyle`): `accounting` is self-accounting as the tradition describes it, naming the lapse
14
+ * with a view to tomorrow (Q 59:18, whose "tomorrow" most exegetes read as the hereafter, so a daily review is an
15
+ * inference; ʿUmar's saying reported by Tirmidhi after 2459, an athar, not a prophetic
16
+ * hadith) and turning back without despair (3:135, 39:53); it forms full resolves and adds no emotion. Reading accounting as
17
+ * non-punishing, like self-compassion after failure raising the motivation to repair (Breines & Chen 2012, E3),
18
+ * is an engineering mapping, not a finding about muhasaba. `ruminating` goes over
19
+ * the lapse abstractly: weaker resolves and a small distress emotion (Watkins 2008, E4: abstract, unconstructive
20
+ * repetitive thought). `none` never reviews. The review reads only chronicle notes and changes no other module's
21
+ * state except that one emotion.
22
+ *
23
+ * Every number is an engineering default, not fitted. Does not claim: divine reckoning or acceptance, a measure of
24
+ * piety, repentance as a ruling (tawba conditions are not modelled), effects on sleep onset (Harvey's model, E6, is
25
+ * not evidence enough), or that review improves outcomes beyond the borrowed if–then shape. It draws no randomness;
26
+ * without `enableReview` nothing here runs and existing runs replay byte for byte.
27
+ *
28
+ * Host notes (2.9.0, docs only). `reviewLine` sets commitment labels into "I didn't keep …" and "Today I kept …",
29
+ * so a label should be a noun phrase ("my call to Mum", "the evening prayer"), not a verb phrase ("calling Mum").
30
+ * A recurring commitment lapses on any day its window passes unlived: a host that skips a day (no service, a
31
+ * fast-forward) should still live, or release, that day's occasions, or the review counts each one as missed.
32
+ */
1
33
  import type { DayRecord, Minute, Person, ReviewRecord, ReviewSpec, ReviewState, ReviewStyle, Term } from '../types.ts';
2
34
  export declare const REVIEW_DEFAULTS: {
3
35
  /** Default evening review time, minute of day. */
@@ -32,6 +32,7 @@
32
32
  */
33
33
  import { clamp01, dexp, isObj, minuteOfDay, round } from '../core/index.js';
34
34
  import { pickLine } from '../narrate/lexicon.js';
35
+ import { labelLine } from '../presentation.js';
35
36
  export const REVIEW_DEFAULTS = {
36
37
  /** Default evening review time, minute of day. */
37
38
  at: 21 * 60,
@@ -198,7 +199,10 @@ export function reviewLine(p, options = {}) {
198
199
  const r = st?.last;
199
200
  if (!st || st.style === 'none' || !r)
200
201
  return undefined;
201
- const list = (xs) => xs.length <= 1 ? (xs[0] ?? '') : `${xs.slice(0, -1).join(', ')} and ${xs.at(-1)}`;
202
+ const list = (items) => {
203
+ const xs = items.map((item) => labelLine(item, p.lexicon));
204
+ return xs.length <= 1 ? (xs[0] ?? '') : `${xs.slice(0, -1).join(', ')} and ${xs.at(-1)}`;
205
+ };
202
206
  if (r.missed.length === 0)
203
207
  return r.kept.length > 0 ? `Today I kept ${list(r.kept)}.` : undefined;
204
208
  if (options.cause) {
@@ -208,7 +212,7 @@ export function reviewLine(p, options = {}) {
208
212
  : 'Next time I will make room to return to it.';
209
213
  return pickLine('review.cause', `${p.id}:${r.day}`, { cause, missed: list(r.missed), next }, p.lexicon);
210
214
  }
211
- if (options.variety ?? p.lexicon?.variety) {
215
+ if (p.lexicon?.plainEnglish || (options.variety ?? p.lexicon?.variety)) {
212
216
  return pickLine(`review.${st.style}`, `${p.id}:${r.day}`, { missed: list(r.missed) }, p.lexicon);
213
217
  }
214
218
  if (st.style === 'ruminating')
@@ -257,6 +257,7 @@ export function createVillage(people, opts) {
257
257
  return out;
258
258
  };
259
259
  const resolve = (p, act, reason) => {
260
+ const say = (legacy, plain) => (p.lexicon?.plainEnglish ? plain : legacy);
260
261
  const now = p.now;
261
262
  state.now = Math.max(state.now, now);
262
263
  const aff = act.affordance;
@@ -272,12 +273,16 @@ export function createVillage(people, opts) {
272
273
  // Partial work counts: a field shift left early yields and pays pro rata for the time put in.
273
274
  const span = Math.max(1, act.endsAt - act.startedAt);
274
275
  const progress = clamp01((now - act.startedAt) / span);
275
- const out = { ...base, status: 'interrupted', summary: `${aff.label}: interrupted` };
276
+ const out = {
277
+ ...base,
278
+ status: 'interrupted',
279
+ summary: say(`${aff.label}: interrupted`, `I was interrupted during the task “${aff.label}”.`),
280
+ };
276
281
  if (act.action === 'work-field' && progress > 0) {
277
282
  state.food += V.fieldYield * progress;
278
283
  out.material = V.wage * progress;
279
284
  out.needs = { competence: 0.1 * progress };
280
- out.summary = `${aff.label}: stopped ${Math.round(progress * 100)}% of the way through`;
285
+ out.summary = say(`${aff.label}: stopped ${Math.round(progress * 100)}% of the way through`, `I stopped the task “${aff.label}” after completing ${Math.round(progress * 100)}% of it.`);
281
286
  }
282
287
  return out;
283
288
  }
@@ -291,17 +296,29 @@ export function createVillage(people, opts) {
291
296
  case 'eat':
292
297
  if (state.food >= 1) {
293
298
  state.food -= 1;
294
- return done({ ...base, needs: { food: 0.6 }, summary: 'ate at the kitchen' });
299
+ return done({
300
+ ...base,
301
+ needs: { food: 0.6 },
302
+ summary: say('ate at the kitchen', 'I ate at the kitchen.'),
303
+ });
295
304
  }
296
- return { ...base, status: 'failed', summary: 'found the kitchen empty' };
305
+ return {
306
+ ...base,
307
+ status: 'failed',
308
+ summary: say('found the kitchen empty', 'I found the kitchen empty.'),
309
+ };
297
310
  case 'drink':
298
- return done({ ...base, needs: { water: 0.6 }, summary: 'drank at the well' });
311
+ return done({
312
+ ...base,
313
+ needs: { water: 0.6 },
314
+ summary: say('drank at the well', 'I drank at the well.'),
315
+ });
299
316
  case 'sleep':
300
- return done({ ...base, summary: 'slept' });
317
+ return done({ ...base, summary: say('slept', 'I slept.') });
301
318
  case 'rest':
302
- return done({ ...base, needs: { leisure: 0.1 }, summary: 'rested' });
319
+ return done({ ...base, needs: { leisure: 0.1 }, summary: say('rested', 'I rested.') });
303
320
  case 'wait':
304
- return done({ ...base, summary: 'waited' });
321
+ return done({ ...base, summary: say('waited', 'I waited.') });
305
322
  case 'work-field': {
306
323
  const ok = chance(state.rng, successChance(p, 'farming', 0.3, capacity));
307
324
  if (ok) {
@@ -310,7 +327,7 @@ export function createVillage(people, opts) {
310
327
  ...base,
311
328
  needs: { competence: 0.1 },
312
329
  material: V.wage,
313
- summary: 'worked the field; a good shift',
330
+ summary: say('worked the field; a good shift', 'I worked in the field. The shift went well.'),
314
331
  });
315
332
  }
316
333
  state.food += 1;
@@ -318,7 +335,7 @@ export function createVillage(people, opts) {
318
335
  ...base,
319
336
  status: 'failed',
320
337
  needs: { competence: 0.02 },
321
- summary: 'worked the field; a poor shift',
338
+ summary: say('worked the field; a poor shift', 'I worked in the field. The shift went poorly.'),
322
339
  };
323
340
  }
324
341
  case 'forage': {
@@ -329,23 +346,35 @@ export function createVillage(people, opts) {
329
346
  status: 'failed',
330
347
  injury: { part: 'leg', severity: risk.severity, healRatePerDay: 0.1 },
331
348
  summary: isNightAt(act.startedAt)
332
- ? 'went to the forest at night and got hurt by an animal'
333
- : 'got hurt by an animal in the forest',
349
+ ? say('went to the forest at night and got hurt by an animal', 'I went to the forest at night. An animal hurt me.')
350
+ : say('got hurt by an animal in the forest', 'An animal hurt me in the forest.'),
334
351
  };
335
352
  }
336
353
  state.food += V.forageYield;
337
- return done({ ...base, needs: { food: 0.3 }, summary: 'foraged in the forest' });
354
+ return done({
355
+ ...base,
356
+ needs: { food: 0.3 },
357
+ summary: say('foraged in the forest', 'I gathered food in the forest.'),
358
+ });
338
359
  }
339
360
  case 'cook': {
340
361
  const ok = chance(state.rng, successChance(p, 'cooking', 0.3, capacity));
341
362
  if (ok) {
342
363
  state.food += V.cookYield;
343
- return done({ ...base, needs: { competence: 0.08 }, summary: 'cooked a good meal' });
364
+ return done({
365
+ ...base,
366
+ needs: { competence: 0.08 },
367
+ summary: say('cooked a good meal', 'I cooked a good meal.'),
368
+ });
344
369
  }
345
- return { ...base, status: 'failed', summary: 'burnt the meal' };
370
+ return { ...base, status: 'failed', summary: say('burnt the meal', 'I burnt the meal.') };
346
371
  }
347
372
  case 'pray':
348
- return done({ ...base, needs: { meaning: 0.1 }, summary: 'prayed at the mosque' });
373
+ return done({
374
+ ...base,
375
+ needs: { meaning: 0.1 },
376
+ summary: say('prayed at the mosque', 'I prayed at the mosque.'),
377
+ });
349
378
  case 'apologize': {
350
379
  const other = act.targetId;
351
380
  if (other !== undefined) {
@@ -357,10 +386,13 @@ export function createVillage(people, opts) {
357
386
  targetId: other,
358
387
  valence: 0.3,
359
388
  salience: 0.6,
360
- summary: `${p.name} apologised`,
389
+ summary: say(`${p.name} apologised`, `${p.name} apologised.`),
361
390
  });
362
391
  }
363
- return done({ ...base, summary: `apologised to ${other ?? 'a neighbour'}` });
392
+ return done({
393
+ ...base,
394
+ summary: say(`apologised to ${other ?? 'a neighbour'}`, `I apologised to ${other ?? 'a neighbour'}.`),
395
+ });
364
396
  }
365
397
  case 'chat': {
366
398
  const other = aff.with?.[0];
@@ -373,13 +405,13 @@ export function createVillage(people, opts) {
373
405
  targetId: other,
374
406
  valence: 0.3,
375
407
  salience: 0.4,
376
- summary: `${p.name} stopped to chat`,
408
+ summary: say(`${p.name} stopped to chat`, `${p.name} stopped to chat.`),
377
409
  });
378
410
  }
379
411
  return done({
380
412
  ...base,
381
413
  needs: { belonging: 0.3, leisure: 0.1 },
382
- summary: `chatted with ${other ?? 'a neighbour'}`,
414
+ summary: say(`chatted with ${other ?? 'a neighbour'}`, `I chatted with ${other ?? 'a neighbour'}.`),
383
415
  });
384
416
  }
385
417
  case 'help': {
@@ -394,13 +426,13 @@ export function createVillage(people, opts) {
394
426
  valence: 0.5,
395
427
  salience: 0.6,
396
428
  norms: [{ normId: 'help-neighbor', relation: 'fulfills' }],
397
- summary: `${p.name} helped with the work`,
429
+ summary: say(`${p.name} helped with the work`, `${p.name} helped with the work.`),
398
430
  });
399
431
  }
400
432
  return done({
401
433
  ...base,
402
434
  needs: { meaning: 0.2, esteem: 0.05 },
403
- summary: `helped ${other ?? 'a neighbour'}`,
435
+ summary: say(`helped ${other ?? 'a neighbour'}`, `I helped ${other ?? 'a neighbour'}.`),
404
436
  percepts: other
405
437
  ? [
406
438
  {
@@ -411,7 +443,7 @@ export function createVillage(people, opts) {
411
443
  targetId: p.id,
412
444
  valence: 0.3,
413
445
  salience: 0.5,
414
- summary: `${other} said thank you`,
446
+ summary: say(`${other} said thank you`, `${other} said thank you.`),
415
447
  },
416
448
  ]
417
449
  : [],
@@ -429,13 +461,18 @@ export function createVillage(people, opts) {
429
461
  valence: -0.5,
430
462
  salience: 0.8,
431
463
  norms: [{ normId: 'theft', relation: 'violates' }],
432
- summary: `${p.name} took bread from the house`,
464
+ summary: say(`${p.name} took bread from the house`, `${p.name} took bread from the house.`),
433
465
  });
434
466
  }
435
- return done({ ...base, needs: { food: 0.5 }, material: 1, summary: 'took bread that was not mine' });
467
+ return done({
468
+ ...base,
469
+ needs: { food: 0.5 },
470
+ material: 1,
471
+ summary: say('took bread that was not mine', 'I took bread that was not mine.'),
472
+ });
436
473
  }
437
474
  default:
438
- return done({ ...base, summary: `${aff.label}: done` });
475
+ return done({ ...base, summary: say(`${aff.label}: done`, `I completed the task “${aff.label}”.`) });
439
476
  }
440
477
  };
441
478
  const perceptsFor = (p, since, until) => {
@@ -77,11 +77,28 @@ export function liveCommunity(c, until, opts) {
77
77
  days[p.id] = list;
78
78
  }
79
79
  for (const kind of r.onsets)
80
- events.push({ at: r.at, personId: p.id, kind: 'onset', detail: `${p.name}: ${kind}` });
80
+ events.push({
81
+ at: r.at,
82
+ personId: p.id,
83
+ kind: 'onset',
84
+ detail: p.lexicon?.plainEnglish ? `${p.name} developed ${kind}.` : `${p.name}: ${kind}`,
85
+ });
81
86
  if (r.stage)
82
- events.push({ at: r.at, personId: p.id, kind: 'stage', detail: `${p.name} is now ${r.stage}` });
87
+ events.push({
88
+ at: r.at,
89
+ personId: p.id,
90
+ kind: 'stage',
91
+ detail: p.lexicon?.plainEnglish
92
+ ? `${p.name} entered the ${r.stage} stage of life.`
93
+ : `${p.name} is now ${r.stage}`,
94
+ });
83
95
  if (r.died) {
84
- events.push({ at: r.at, personId: p.id, kind: 'died', detail: `${p.name} died` });
96
+ events.push({
97
+ at: r.at,
98
+ personId: p.id,
99
+ kind: 'died',
100
+ detail: p.lexicon?.plainEnglish ? `${p.name} died.` : `${p.name} died`,
101
+ });
85
102
  died.push(p);
86
103
  }
87
104
  }
package/dist/sim/sim.d.ts CHANGED
@@ -280,7 +280,7 @@ export declare function declineJoint(c: Community, proposal: JointProposal, prop
280
280
  * activities are resolved as interrupted first, then the proposer begins the offer and each partner their
281
281
  * mirror. Returns the activities begun (empty while other partners have yet to answer).
282
282
  */
283
- export declare function acceptJoint(c: Community, world: World, proposal: JointProposal, partner: Person, partnerRecord: DecisionRecord, log?: (e: SimEvent) => void): Activity[];
283
+ export declare function acceptJoint(c: Community, world: World, proposal: JointProposal, partner: Person, partnerRecord: DecisionRecord, writeLog?: (e: SimEvent) => void): Activity[];
284
284
  /**
285
285
  * Chance that `lead` succeeds at a skilled task with `partners` helping: `successChance` with
286
286
  * support = Σ partner skill × partner capacity / max(difficulty, 0.1), clamped to 0..1.
package/dist/sim/sim.js CHANGED
@@ -79,6 +79,54 @@ function finishOpts(world) {
79
79
  o.transfer = world.skillTransfer;
80
80
  return o;
81
81
  }
82
+ /** Complete sentences for generated event details. Host outcome summaries bypass this reader. */
83
+ function plainEventDetail(p, event) {
84
+ const name = p.name;
85
+ const detail = event.detail;
86
+ switch (event.kind) {
87
+ case 'decide':
88
+ return detail;
89
+ case 'idle':
90
+ return `${name} did not choose a task.`;
91
+ case 'died':
92
+ return `${name} died.`;
93
+ case 'begin':
94
+ case 'continue':
95
+ case 'finish':
96
+ return detail;
97
+ case 'perceive': {
98
+ const counts = /^(\d+)\/(\d+) percepts$/.exec(detail);
99
+ return counts ? `${name} noticed ${counts[1]} of ${counts[2]} events.` : `${name} noticed “${detail}”.`;
100
+ }
101
+ case 'interrupt':
102
+ return `${name} was interrupted by the event “${detail.replace(/^percept:/, '')}”.`;
103
+ case 'propose':
104
+ return `${name} proposed the shared task “${detail}”.`;
105
+ case 'accept':
106
+ return `${name} joined the shared task “${detail.replace(/^joins /, '')}”.`;
107
+ case 'decline':
108
+ return `${name} declined the shared task “${detail.replace(/^declined /, '')}”.`;
109
+ case 'converse':
110
+ return `${name} spoke. The exchange included: “${detail}”.`;
111
+ case 'contagion':
112
+ return `${name} ${detail}.`;
113
+ case 'command':
114
+ return `${name} received the order “${detail}”.`;
115
+ case 'release':
116
+ return `${name} was released from the order.`;
117
+ case 'onset':
118
+ return `${name} developed the condition “${detail}”.`;
119
+ case 'stage':
120
+ return `${name} entered the life stage “${detail}”.`;
121
+ }
122
+ }
123
+ function taskEndLine(p, label, status) {
124
+ if (status === 'completed')
125
+ return `${p.name} completed the task “${label}”.`;
126
+ if (status === 'failed')
127
+ return `${p.name} failed to complete the task “${label}”.`;
128
+ return `${p.name} was interrupted during the task “${label}”.`;
129
+ }
82
130
  /**
83
131
  * Whether standing suggestion `s` is heard by `p` at a decision over `affordances` (standing-advice SCOPE): its target
84
132
  * must be on offer or be the running activity, and if the action keeps commitments and was completed since the suggestion was given, doing it
@@ -208,7 +256,7 @@ export function tellDeath(c, dead, at, opts = {}) {
208
256
  targetId: dead.id,
209
257
  valence: -0.8,
210
258
  salience: 0.9,
211
- summary: `${dead.name} died`,
259
+ summary: q.lexicon?.plainEnglish ? `${dead.name} died.` : `${dead.name} died`,
212
260
  },
213
261
  ]);
214
262
  // A spouse is widowed even if the news went unattended.
@@ -355,7 +403,11 @@ export function declineJoint(c, proposal, proposer) {
355
403
  * activities are resolved as interrupted first, then the proposer begins the offer and each partner their
356
404
  * mirror. Returns the activities begun (empty while other partners have yet to answer).
357
405
  */
358
- export function acceptJoint(c, world, proposal, partner, partnerRecord, log = () => { }) {
406
+ export function acceptJoint(c, world, proposal, partner, partnerRecord, writeLog = () => { }) {
407
+ const log = (e) => {
408
+ const person = personById(c, e.personId);
409
+ writeLog(person?.lexicon?.plainEnglish ? { ...e, detail: plainEventDetail(person, e) } : e);
410
+ };
359
411
  if (proposal.status !== 'pending' || !proposal.partnerIds.includes(partner.id))
360
412
  return [];
361
413
  if (!proposal.accepted.includes(partner.id))
@@ -387,7 +439,9 @@ export function acceptJoint(c, world, proposal, partner, partnerRecord, log = ()
387
439
  at,
388
440
  personId: person.id,
389
441
  kind: 'finish',
390
- detail: `${act.action} ${outcome.status}`,
442
+ detail: person.lexicon?.plainEnglish
443
+ ? taskEndLine(person, act.affordance.label, outcome.status)
444
+ : `${act.action} ${outcome.status}`,
391
445
  decisionId: act.decisionId,
392
446
  affordanceId: act.affordanceId,
393
447
  action: act.action,
@@ -404,7 +458,9 @@ export function acceptJoint(c, world, proposal, partner, partnerRecord, log = ()
404
458
  at,
405
459
  personId: person.id,
406
460
  kind: 'begin',
407
- detail: `${offer.label} (joint ${proposal.id})`,
461
+ detail: person.lexicon?.plainEnglish
462
+ ? `${person.name} began the shared task “${offer.label}”. ${record.intention}`
463
+ : `${offer.label} (joint ${proposal.id})`,
408
464
  decisionId: record.id,
409
465
  affordanceId: offer.id,
410
466
  action: offer.action,
@@ -703,9 +759,12 @@ export function stepCommunity(c, world, until, opts = {}) {
703
759
  // Communities created before joint support (plain JSON from an older host) get the new bookkeeping.
704
760
  c.interrupts ??= {};
705
761
  c.joint ??= { nextId: 0, proposals: [], excluded: {} };
706
- const log = (e) => {
707
- if (events.length < maxEvents)
708
- events.push(e);
762
+ // Presentation only: retain host-authored summaries and every event field other than generated detail.
763
+ const log = (e, authored = false) => {
764
+ const person = personById(c, e.personId);
765
+ if (events.length < maxEvents) {
766
+ events.push(person?.lexicon?.plainEnglish && !authored ? { ...e, detail: plainEventDetail(person, e) } : e);
767
+ }
709
768
  };
710
769
  const contagion = opts.contagion ?? true;
711
770
  c.queued ??= {};
@@ -772,14 +831,17 @@ export function stepCommunity(c, world, until, opts = {}) {
772
831
  at: t,
773
832
  personId: p.id,
774
833
  kind: 'finish',
775
- detail: outcome.summary ?? `${act.action} ${outcome.status}`,
834
+ detail: outcome.summary ??
835
+ (p.lexicon?.plainEnglish
836
+ ? taskEndLine(p, act.affordance.label, outcome.status)
837
+ : `${act.action} ${outcome.status}`),
776
838
  decisionId: act.decisionId,
777
839
  affordanceId: act.affordanceId,
778
840
  action: act.action,
779
841
  };
780
842
  if (report)
781
843
  ev.status = report.status;
782
- log(ev);
844
+ log(ev, outcome.summary !== undefined);
783
845
  if (outcome.status === 'completed') {
784
846
  noteStandingDone(c, p, act, t);
785
847
  if (pairMinute !== t) {
@@ -837,14 +899,22 @@ export function stepCommunity(c, world, until, opts = {}) {
837
899
  detail: `joins ${acceptedProposal.offer.label}`,
838
900
  jointId: acceptedProposal.id,
839
901
  });
840
- acceptJoint(c, world, acceptedProposal, p, record, log);
902
+ acceptJoint(c, world, acceptedProposal, p, record, (event) => log(event, true));
841
903
  continue;
842
904
  }
843
905
  const chosen = affordances.find((a) => a.id === record.chosenAffordanceId);
844
906
  const continuing = act !== null && (record.chosenAffordanceId === null || record.chosenAffordanceId === act.affordanceId);
845
907
  if (act && continuing) {
846
908
  reviewed(p);
847
- log({ at: t, personId: p.id, kind: 'continue', detail: act.action, affordanceId: act.affordanceId });
909
+ log({
910
+ at: t,
911
+ personId: p.id,
912
+ kind: 'continue',
913
+ detail: p.lexicon?.plainEnglish
914
+ ? `${p.name} continued the task “${act.affordance.label}”.`
915
+ : act.action,
916
+ affordanceId: act.affordanceId,
917
+ });
848
918
  continue;
849
919
  }
850
920
  if (chosen && isJointOffer(c, p, chosen)) {
@@ -854,11 +924,15 @@ export function stepCommunity(c, world, until, opts = {}) {
854
924
  at: t,
855
925
  personId: p.id,
856
926
  kind: proposal.status === 'declined' ? 'decline' : 'propose',
857
- detail: `${chosen.label} with ${proposal.partnerIds.join(', ')}`,
927
+ detail: p.lexicon?.plainEnglish
928
+ ? proposal.status === 'declined'
929
+ ? `The shared task “${chosen.label}” could not start because a partner was unavailable.`
930
+ : `${chosen.label} with ${proposal.partnerIds.map((id) => personById(c, id)?.name ?? id).join(', ')}`
931
+ : `${chosen.label} with ${proposal.partnerIds.join(', ')}`,
858
932
  affordanceId: chosen.id,
859
933
  action: chosen.action,
860
934
  jointId: proposal.id,
861
- });
935
+ }, Boolean(p.lexicon?.plainEnglish && proposal.status === 'declined'));
862
936
  continue;
863
937
  }
864
938
  if (act) {
@@ -868,7 +942,9 @@ export function stepCommunity(c, world, until, opts = {}) {
868
942
  at: t,
869
943
  personId: p.id,
870
944
  kind: 'finish',
871
- detail: `${act.action} interrupted`,
945
+ detail: p.lexicon?.plainEnglish
946
+ ? taskEndLine(p, act.affordance.label, 'interrupted')
947
+ : `${act.action} interrupted`,
872
948
  decisionId: act.decisionId,
873
949
  affordanceId: act.affordanceId,
874
950
  action: act.action,
@@ -889,7 +965,9 @@ export function stepCommunity(c, world, until, opts = {}) {
889
965
  at: t,
890
966
  personId: p.id,
891
967
  kind: 'begin',
892
- detail: `${chosen.label} (${record.intention})`,
968
+ detail: p.lexicon?.plainEnglish
969
+ ? `${p.name} began the task “${chosen.label}”. ${record.intention}`
970
+ : `${chosen.label} (${record.intention})`,
893
971
  decisionId: record.id,
894
972
  affordanceId: chosen.id,
895
973
  action: chosen.action,
package/dist/types.d.ts CHANGED
@@ -1767,6 +1767,8 @@ export interface VoiceConflict {
1767
1767
  * slots documented next to each key.
1768
1768
  */
1769
1769
  export interface Lexicon {
1770
+ /** Use complete, plain English in built-in narration. Omitted keeps exact legacy wording. */
1771
+ plainEnglish?: boolean;
1770
1772
  /** Include person identity and stable temperament in assent/deferral selection (no RNG draws). */
1771
1773
  variety?: boolean;
1772
1774
  /** What material gains mean here, e.g. 'food', 'household keep' or 'resources'; absent keeps legacy money lines. */
package/dist/will/will.js CHANGED
@@ -37,6 +37,7 @@ import { commitmentPressure, isUnderWay, pressureReachedAt } from '../agenda/ind
37
37
  import { downedAllows, readCapacities } from '../body/index.js';
38
38
  import { CONSCIENCE_DEFAULTS, normVeto } from '../conscience/index.js';
39
39
  import { clamp01, cmpStr, decay, dexp, dpow, hasNumbers, isObj, random, round } from '../core/index.js';
40
+ import { commitmentLabel } from '../presentation.js';
40
41
  import { skillLevel } from '../skills/index.js';
41
42
  import { MINUTES_PER_DAY, MINUTES_PER_HOUR } from '../types.js';
42
43
  export const WILL_DEFAULTS = {
@@ -829,7 +830,10 @@ function judgeVoices(p, ctx, suggestions, prepared, selection) {
829
830
  kind: 'notNow',
830
831
  commitmentId: v.omission,
831
832
  ...(winnerAff ? { insteadAffordanceId: winnerAff.id } : {}),
832
- counterOffer: { affordanceId: suggested[0]?.affordanceId, label: dutyLabel(duty, winnerAff) },
833
+ counterOffer: {
834
+ affordanceId: suggested[0]?.affordanceId,
835
+ label: dutyLabel(duty, winnerAff, p),
836
+ },
833
837
  }));
834
838
  }
835
839
  return out(resolution('refused', v.reason, { kind: 'willNot', commitmentId: v.omission }));
@@ -886,7 +890,12 @@ function judgeVoices(p, ctx, suggestions, prepared, selection) {
886
890
  kind: 'notNow',
887
891
  insteadAffordanceId: winnerAff?.id,
888
892
  counterOffer: winnerAff
889
- ? { affordanceId: bestSuggested.affordanceId, label: `after I ${winnerAff.label}` }
893
+ ? {
894
+ affordanceId: bestSuggested.affordanceId,
895
+ label: p.lexicon?.plainEnglish
896
+ ? `after I finish this task (${winnerAff.label})`
897
+ : `after I ${winnerAff.label}`,
898
+ }
890
899
  : { affordanceId: bestSuggested.affordanceId, label: 'later' },
891
900
  }));
892
901
  });
@@ -940,7 +949,11 @@ function arbitrateInsistence(p, judged, considered, affById, ev) {
940
949
  insteadAffordanceId: forced,
941
950
  counterOffer: {
942
951
  ...(firstTarget !== undefined ? { affordanceId: firstTarget } : {}),
943
- label: forcedAff ? `after I ${forcedAff.label}` : 'later',
952
+ label: forcedAff
953
+ ? p.lexicon?.plainEnglish
954
+ ? `after I finish this task (${forcedAff.label})`
955
+ : `after I ${forcedAff.label}`
956
+ : 'later',
944
957
  },
945
958
  };
946
959
  if (j.res.likelihood !== undefined)
@@ -1134,7 +1147,12 @@ export function conflictBetweenVoices(considered, resolutions, chosenAffordanceI
1134
1147
  };
1135
1148
  }
1136
1149
  /** Counter-offer for a request deferred behind a duty: "after I pray", "after I feed the children". */
1137
- function dutyLabel(duty, winner) {
1150
+ function dutyLabel(duty, winner, p) {
1151
+ if (p.lexicon?.plainEnglish) {
1152
+ if (duty)
1153
+ return `after I finish this duty (${commitmentLabel(duty, p.lexicon)})`;
1154
+ return winner ? `after I finish this task (${winner.label})` : 'after my duty';
1155
+ }
1138
1156
  if (duty?.kind === 'worship')
1139
1157
  return duty.label ? `after I pray ${duty.label}` : 'after I pray';
1140
1158
  if (duty?.label)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adam0white/human-framework",
3
- "version": "2.18.0",
3
+ "version": "2.19.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",