@adam0white/human-framework 2.18.0 → 2.20.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.
Files changed (49) hide show
  1. package/README.md +51 -0
  2. package/dist/agenda/agenda.d.ts +47 -0
  3. package/dist/agenda/agenda.js +4 -1
  4. package/dist/body/body.d.ts +10 -0
  5. package/dist/body/body.js +15 -0
  6. package/dist/chronicle/story.js +3 -2
  7. package/dist/cognition/cognition.d.ts +37 -0
  8. package/dist/cognition/cognition.js +3 -2
  9. package/dist/conversation/conversation.js +10 -4
  10. package/dist/core/index.d.ts +1 -0
  11. package/dist/core/index.js +1 -0
  12. package/dist/core/keys.d.ts +6 -0
  13. package/dist/core/keys.js +8 -0
  14. package/dist/family/family.js +5 -5
  15. package/dist/index.d.ts +4 -1
  16. package/dist/index.js +4 -1
  17. package/dist/longrun.js +1 -1
  18. package/dist/memory/memory.d.ts +26 -0
  19. package/dist/memory/memory.js +3 -9
  20. package/dist/narrate/lexicon.d.ts +2 -1
  21. package/dist/narrate/lexicon.js +2 -1
  22. package/dist/narrate/narrate.d.ts +14 -1
  23. package/dist/narrate/narrate.js +81 -39
  24. package/dist/narrate/plain.d.ts +7 -0
  25. package/dist/narrate/plain.js +293 -0
  26. package/dist/observed-reputation.d.ts +4 -0
  27. package/dist/observed-reputation.js +16 -0
  28. package/dist/partnering/partnering.js +1 -1
  29. package/dist/person.d.ts +5 -0
  30. package/dist/person.js +119 -11
  31. package/dist/presentation.d.ts +10 -0
  32. package/dist/presentation.js +88 -0
  33. package/dist/promptings/promptings.d.ts +1 -0
  34. package/dist/promptings/promptings.js +40 -4
  35. package/dist/prospection/forecast.d.ts +2 -0
  36. package/dist/prospection/forecast.js +15 -1
  37. package/dist/qalb.d.ts +28 -0
  38. package/dist/qalb.js +67 -0
  39. package/dist/review/review.d.ts +32 -0
  40. package/dist/review/review.js +6 -2
  41. package/dist/scenarios/village.js +63 -26
  42. package/dist/sim/longrun.js +20 -3
  43. package/dist/sim/sim.d.ts +1 -1
  44. package/dist/sim/sim.js +93 -15
  45. package/dist/social/social.js +12 -2
  46. package/dist/types.d.ts +6 -0
  47. package/dist/will/will.d.ts +12 -0
  48. package/dist/will/will.js +43 -4
  49. package/package.json +1 -1
@@ -5,23 +5,23 @@
5
5
  * consuming the person's RNG. When memory shaped an option (the record's `recalled` episodes), the narration
6
6
  * cites the episode summary. People are named through `nameOf` (lexicon name, role phrase, or id). This is
7
7
  * presentation only: nothing here reads back into the simulation, and no claim is made that the templates
8
- * reflect how people actually explain themselves.
8
+ * reflect how people actually explain themselves. `Lexicon.plainEnglish` opts into sentence intentions
9
+ * and summaries; arbitrary host prose is not translated or grammatically inferred.
9
10
  */
10
11
  import { NORM_SCOPE } from '../conscience/index.js';
12
+ import { labelLine } from '../presentation.js';
11
13
  import { capitalize, linesFor, lowerFirst, nameOf, phraseLine, pickLine } from './lexicon.js';
12
14
  /**
13
15
  * Devotional intention: 'for Allah' for norms the built-in catalog scopes as religious, else 'for God'. The phrase
14
16
  * follows the norm, not the person (review 2026-10-03): a host whose people differ in faith overrides
15
17
  * `devotion.allah` / `devotion.god` per person through `Person.lexicon` (from `PersonSpec.lexicon`).
16
18
  */
17
- function devotion(p, normId, lex) {
19
+ function devotion(p, normId, lex, say = phraseLine) {
18
20
  if (p.values.tradition < 0.6)
19
- return normId === undefined
20
- ? phraseLine('devotion.keep', {}, lex)
21
- : phraseLine('intention.right', {}, lex);
21
+ return normId === undefined ? say('devotion.keep', {}, lex) : say('intention.right', {}, lex);
22
22
  if (normId !== undefined && NORM_SCOPE[normId] === 'religious')
23
- return phraseLine('devotion.allah', {}, lex);
24
- return phraseLine('devotion.god', {}, lex);
23
+ return say('devotion.allah', {}, lex);
24
+ return say('devotion.god', {}, lex);
25
25
  }
26
26
  const topTerm = (c) => {
27
27
  let best;
@@ -33,77 +33,108 @@ const topTerm = (c) => {
33
33
  /** Voices the narration addresses as "you" (the host's own voice); other voices are named when a lexicon names them. */
34
34
  const PLAYER_VOICES = new Set(['player', 'you']);
35
35
  /** Intention string for a decision: 'for Allah', 'to feed myself', 'to keep my promise to X', ... */
36
- export function intentionFor(p, record, lexicon) {
36
+ function intentionPhrase(p, record, lexicon, selected) {
37
37
  const lex = lexicon ?? p.lexicon;
38
+ const say = (key, vars = {}, lex) => {
39
+ if (selected)
40
+ selected.key = key;
41
+ return phraseLine(key, vars, lex);
42
+ };
38
43
  const chosen = record.considered.find((c) => c.affordanceId === record.chosenAffordanceId);
39
44
  if (!chosen)
40
- return phraseLine('intention.none', {}, lex);
45
+ return say('intention.none', {}, lex);
41
46
  if (record.suggestion?.verdict === 'complied')
42
- return phraseLine('intention.complied', {}, lex);
47
+ return say('intention.complied', {}, lex);
43
48
  if (record.suggestion?.verdict === 'commanded')
44
- return phraseLine('intention.commanded', {}, lex);
49
+ return say('intention.commanded', {}, lex);
45
50
  const top = topTerm(chosen);
46
51
  if (!top)
47
- return phraseLine('intention.default', {}, lex);
52
+ return say('intention.default', {}, lex);
48
53
  const [kind, rest] = [top.source.split(':')[0] ?? '', top.source.slice(top.source.indexOf(':') + 1)];
49
54
  switch (kind) {
50
55
  case 'need':
51
56
  return linesFor(`intention:${rest}`, lex).length > 0
52
- ? phraseLine(`intention:${rest}`, {}, lex)
53
- : phraseLine('intention.need', { need: rest }, lex);
57
+ ? say(`intention:${rest}`, {}, lex)
58
+ : say('intention.need', { need: rest }, lex);
54
59
  case 'norm': {
55
60
  const tracked = p.agenda.commitments.some((c) => c.normId === rest && c.kind === 'worship');
56
61
  if (NORM_SCOPE[rest] === 'religious' || tracked)
57
- return devotion(p, rest, lex);
58
- return phraseLine('intention.right', {}, lex);
62
+ return devotion(p, rest, lex, say);
63
+ return say('intention.right', {}, lex);
59
64
  }
60
65
  case 'conscience':
61
- return phraseLine(rest === 'repair' ? 'intention.repair' : 'intention.repent', {}, lex);
66
+ return say(rest === 'repair' ? 'intention.repair' : 'intention.repent', {}, lex);
62
67
  case 'commitment': {
63
68
  const c = p.agenda.commitments.find((x) => x.id === rest);
64
69
  if (c?.kind === 'worship')
65
70
  return c.normId === undefined || p.values.tradition < 0.6
66
- ? devotion(p, undefined, lex)
67
- : devotion(p, c.normId, lex);
71
+ ? devotion(p, undefined, lex, say)
72
+ : devotion(p, c.normId, lex, say);
68
73
  if (c?.toId && c.toId !== 'self')
69
- return phraseLine('intention.promiseTo', { who: nameOf(p, c.toId, lex) }, lex);
74
+ return say('intention.promiseTo', { who: nameOf(p, c.toId, lex) }, lex);
70
75
  // A host may phrase a labelled commitment its own way ('intention.label:suhoor' → "for suhoor").
71
76
  if (c?.label !== undefined && linesFor(`intention.label:${c.label}`, lex).length > 0)
72
- return phraseLine(`intention.label:${c.label}`, {}, lex);
73
- return phraseLine('intention.word', {}, lex);
77
+ return say(`intention.label:${c.label}`, {}, lex);
78
+ return say('intention.word', {}, lex);
74
79
  }
75
80
  case 'goal': {
76
81
  const g = p.agenda.goals.find((x) => x.id === rest);
77
82
  return g
78
- ? phraseLine('intention.goal', { goal: g.label.replace(/-/g, ' ') }, lex)
79
- : phraseLine('intention.goalFallback', {}, lex);
83
+ ? say('intention.goal', { goal: lex?.plainEnglish ? labelLine(g.label, lex) : g.label.replace(/-/g, ' ') }, lex)
84
+ : say('intention.goalFallback', {}, lex);
80
85
  }
81
86
  case 'social':
82
87
  return rest === 'group'
83
- ? phraseLine('intention.group', {}, lex)
84
- : phraseLine('intention.for', { who: nameOf(p, rest, lex) }, lex);
88
+ ? say('intention.group', {}, lex)
89
+ : say('intention.for', { who: nameOf(p, rest, lex) }, lex);
85
90
  case 'suggestion': {
86
91
  const voice = rest.startsWith('remembered:') ? rest.slice('remembered:'.length) : rest;
87
92
  if (lex?.names?.[voice] !== undefined && !PLAYER_VOICES.has(voice))
88
- return phraseLine('intention.suggestionBy', { who: nameOf(p, voice, lex) }, lex);
89
- return phraseLine('intention.suggestion', {}, lex);
93
+ return say('intention.suggestionBy', { who: nameOf(p, voice, lex) }, lex);
94
+ return say('intention.suggestion', {}, lex);
90
95
  }
91
96
  case 'habit':
92
- return phraseLine('intention.habit', {}, lex);
97
+ return say('intention.habit', {}, lex);
93
98
  case 'emotion':
94
- return phraseLine('intention.emotion', {}, lex);
99
+ return say('intention.emotion', {}, lex);
95
100
  case 'material':
96
101
  return lex?.materialMeaning !== undefined
97
- ? phraseLine('intention.material.named', { material: lex.materialMeaning }, lex)
98
- : phraseLine('intention.material', {}, lex);
102
+ ? say('intention.material.named', { material: lex.materialMeaning }, lex)
103
+ : say('intention.material', {}, lex);
99
104
  case 'precommit':
100
- return phraseLine('intention.precommit', {}, lex);
105
+ return say('intention.precommit', {}, lex);
101
106
  case 'expectation':
102
- return phraseLine('intention.expectation', {}, lex);
107
+ return say('intention.expectation', {}, lex);
103
108
  default:
104
- return phraseLine('intention.default', {}, lex);
109
+ return say('intention.default', {}, lex);
105
110
  }
106
111
  }
112
+ /**
113
+ * A plain intention is a sentence; legacy intentions remain phrases for existing hosts.
114
+ * Host-authored sentences pass through. This renderer never changes the record or RNG.
115
+ */
116
+ export function intentionFor(p, record, lexicon) {
117
+ const lex = lexicon ?? p.lexicon;
118
+ const selected = {};
119
+ const phrase = intentionPhrase(p, record, lex, selected);
120
+ if (selected.key !== undefined && lex?.lines?.[selected.key] !== undefined)
121
+ return phrase;
122
+ if (!lex?.plainEnglish || /[.!?]$/.test(phrase))
123
+ return phrase;
124
+ if (phrase === 'nothing to do')
125
+ return 'There is nothing for me to do.';
126
+ if (phrase === 'to earn')
127
+ return 'I want to earn money.';
128
+ if (phrase.startsWith('to '))
129
+ return `I want ${phrase}.`;
130
+ if (phrase.startsWith('because '))
131
+ return `I am doing this ${phrase}.`;
132
+ if (phrase.startsWith('for '))
133
+ return `I am doing this ${phrase}.`;
134
+ if (phrase === 'out of habit')
135
+ return 'I am doing this out of habit.';
136
+ return `My reason is ${phrase}.`;
137
+ }
107
138
  function citeEpisode(p, ids, key, lex) {
108
139
  if (!ids || ids.length === 0)
109
140
  return undefined;
@@ -111,7 +142,7 @@ function citeEpisode(p, ids, key, lex) {
111
142
  if (!ep)
112
143
  return undefined;
113
144
  const summary = ep.summary.endsWith('.') ? ep.summary : `${ep.summary}.`;
114
- return pickLine(ep.valence < 0 ? 'cite.bad' : 'cite.good', key, { summary: lowerFirst(summary) }, lex);
145
+ return pickLine(ep.valence < 0 ? 'cite.bad' : 'cite.good', key, { summary: lex?.plainEnglish ? summary : lowerFirst(summary) }, lex);
115
146
  }
116
147
  /** A rival within this fraction of the chosen utility makes the choice "a close call". */
117
148
  const CLOSE_CALL_FRACTION = 0.2;
@@ -212,13 +243,17 @@ export function voiceLine(p, res, key = res.reason, lexicon) {
212
243
  }
213
244
  case 'modified': {
214
245
  const alt = res.counterOffer?.label ?? phraseLine('voice.modified.default', {}, lex);
215
- return pickLine('voice.modified', key, { alt: lowerFirst(alt) }, lex);
246
+ return pickLine('voice.modified', key, { alt: lex?.plainEnglish ? alt : lowerFirst(alt) }, lex);
216
247
  }
217
248
  case 'refused': {
218
249
  if (res.reason === 'distrust') {
219
250
  const ep = res.episodeId ? p.memory.episodes.find((e) => e.id === res.episodeId) : undefined;
220
251
  if (ep)
221
- return pickLine('voice.distrust.episode', key, { what: lowerFirst(ep.summary.replace(/\.$/, '')) }, lex);
252
+ return pickLine('voice.distrust.episode', key, {
253
+ what: lex?.plainEnglish
254
+ ? ep.summary.replace(/[.!?]$/, '')
255
+ : lowerFirst(ep.summary.replace(/\.$/, '')),
256
+ }, lex);
222
257
  return pickLine('voice.distrust', key, {}, lex);
223
258
  }
224
259
  if (res.kind === 'willNot' && res.commitmentId !== undefined) {
@@ -228,7 +263,7 @@ export function voiceLine(p, res, key = res.reason, lexicon) {
228
263
  : duty?.label
229
264
  ? duty.label
230
265
  : phraseLine('voice.omission.default', {}, lex);
231
- return pickLine('voice.omission', key, { what }, lex);
266
+ return pickLine('voice.omission', key, { what: labelLine(what, lex) }, lex);
232
267
  }
233
268
  if (res.kind === 'willNot' && res.reason.startsWith('duty:')) {
234
269
  // An abstention held as a duty (the fast): the veto names what is being kept, not a forbidden act.
@@ -296,6 +331,13 @@ export function describePerson(p, lexicon) {
296
331
  const act = p.activity
297
332
  ? `${p.activity.action}${p.activity.targetId ? ` (${p.activity.targetId})` : ''}`
298
333
  : phraseLine('describe.idle', {}, lex);
334
+ if (lex?.plainEnglish) {
335
+ const state = bits.length > 0 ? bits.join(', ') : phraseLine('describe.well', {}, lex);
336
+ const activity = p.activity
337
+ ? `${p.name} is doing this: ${p.activity.affordance.label}${p.activity.targetId ? ` (${nameOf(p, p.activity.targetId, lex)})` : ''}.`
338
+ : `${p.name} is ${phraseLine('describe.idle', {}, lex)}.`;
339
+ return phraseLine('describe.line', { name: p.name, act: lex.lines?.['describe.line'] ? act : activity, state }, lex);
340
+ }
299
341
  const state = bits.length > 0 ? bits.join(', ') : phraseLine('describe.well', {}, lex);
300
342
  return phraseLine('describe.line', { name: p.name, act, state }, lex);
301
343
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * SCOPE: literal English narration for hosts that opt in to plain English. Keys, slot names,
3
+ * template counts and ordering match the legacy English pack, preserving deterministic selection.
4
+ * Internal word and intention slots remain phrases for composition. This pack changes presentation
5
+ * only; it does not change simulation state, choose templates, or rewrite host-authored text.
6
+ */
7
+ export declare const PLAIN_EN_LINES: Readonly<Record<string, readonly string[]>>;
@@ -0,0 +1,293 @@
1
+ /**
2
+ * SCOPE: literal English narration for hosts that opt in to plain English. Keys, slot names,
3
+ * template counts and ordering match the legacy English pack, preserving deterministic selection.
4
+ * Internal word and intention slots remain phrases for composition. This pack changes presentation
5
+ * only; it does not change simulation state, choose templates, or rewrite host-authored text.
6
+ */
7
+ export const PLAIN_EN_LINES = {
8
+ // Decision lines by dominant need (no slots).
9
+ 'need:food': [
10
+ 'I am very hungry, so I will eat first.',
11
+ 'My stomach is growling. I will eat first.',
12
+ 'I need to eat.',
13
+ ],
14
+ 'need:water': [
15
+ 'I am very thirsty. I will get water first.',
16
+ 'I need a drink of water.',
17
+ 'I am thirsty, so I will go to the well.',
18
+ ],
19
+ 'need:sleep': [
20
+ 'I cannot keep my eyes open. I need to go to bed.',
21
+ 'I am exhausted. I need to sleep.',
22
+ 'I need to sleep before I do anything else.',
23
+ ],
24
+ 'need:rest': ['I need to sit down for a while.', 'I will rest before I do anything else.'],
25
+ 'need:relief': [
26
+ 'I need to do something about this pain.',
27
+ 'The pain makes it hard to think about anything else.',
28
+ ],
29
+ 'need:belonging': ["I've been alone too long. I want company.", 'Let me find someone to talk to.'],
30
+ 'need:leisure': ['I need a break from all this.', 'I want to do something easier.'],
31
+ 'need:meaning': ['I want to do something that matters.', 'I want to do something worthwhile for once.'],
32
+ 'need:competence': ['I want to get better at this.', 'Practice will help me work more steadily.'],
33
+ 'need:esteem': ['I want people to see me doing well.', 'I want to show what I can do.'],
34
+ 'need:autonomy': ['I want to make my own choice this time.', 'I will do what I decide to do.'],
35
+ 'need:safety': ['I want to feel safe first.', 'I will be careful for now.'],
36
+ /** {need} */
37
+ 'need:fallback': ['I need {need}.'],
38
+ // Decision lines by other dominant sources.
39
+ 'source:habit': [
40
+ 'I am doing what I always do.',
41
+ 'I am doing this out of habit.',
42
+ 'It is what I do at this hour.',
43
+ ],
44
+ 'source:effort': ['I will choose something easy for now.', 'I will avoid anything strenuous.'],
45
+ 'review.cause': [
46
+ 'After {cause}, I left {missed} undone. {next}',
47
+ 'I left {missed} unfinished after {cause}. {next}',
48
+ 'I left {missed} unfinished because of {cause}. {next}',
49
+ ],
50
+ 'review.accounting': [
51
+ 'I did not keep {missed}. Next time I will be ready.',
52
+ 'I left {missed} undone. I will prepare for the next occasion.',
53
+ 'Today I left {missed} unfinished. Next time I will make time for it.',
54
+ ],
55
+ 'review.ruminating': [
56
+ 'I keep thinking about {missed}.',
57
+ 'My thoughts keep returning to {missed}.',
58
+ 'I cannot stop thinking about {missed}.',
59
+ ],
60
+ 'source:material.named': ['It brings {material}.', 'We can use the {material}.'],
61
+ 'intention.material.named': ['for {material}'],
62
+ 'source:material': ['I can earn money from it.', 'It pays.'],
63
+ 'source:expectation': ['It went well last time.', 'I know what happens when I do this.'],
64
+ 'source:conscience:repair': ['I owe them that much.', 'I have to correct what I did wrong.'],
65
+ 'source:conscience:repent': ['I need to repent to God.', 'I need to stop doing wrong.'],
66
+ 'source:preference': [
67
+ 'I see no reason not to do this.',
68
+ 'This is good enough for me.',
69
+ 'I see no reason to choose something else.',
70
+ ],
71
+ // Verbs for refusing a forbidden act ("I won't {verb}").
72
+ 'normVerb:theft': ['steal'],
73
+ 'normVerb:lying': ['lie'],
74
+ 'normVerb:harm-others': ['hurt anyone'],
75
+ 'normVerb:intoxicants': ['drink that'],
76
+ 'normVerb:backbiting': ['say bad things about them when they are absent'],
77
+ 'normVerb:forbidden-food': ['eat that'],
78
+ 'normVerb:default': ['do that'],
79
+ 'normLabel:sawm-ramadan': ['my fast'],
80
+ 'normLabel:default': ['what I have undertaken'],
81
+ // Lines for fulfilling a held norm.
82
+ 'normFulfil:salah': [
83
+ 'It is time to pray.',
84
+ 'I will pray first. The rest can wait.',
85
+ 'I will pray, then decide what to do.',
86
+ ],
87
+ 'normFulfil:charity': ['I can spare some for others.', 'I will give to charity while I can.'],
88
+ 'normFulfil:help-neighbor': ['They need help.', 'A neighbour should help.'],
89
+ 'normFulfil:keep-promise': ['I made a promise.', 'I said I would do it.'],
90
+ 'normFulfil:kindness-to-parents': ['I am doing this for my parents.'],
91
+ 'normFulfil:gratitude': ['I should say thank you.'],
92
+ 'normFulfil:default': ['It is the right thing to do.', 'I am doing this because I should.'],
93
+ // Emotion lines keyed by the tag the tendency acts through.
94
+ 'emotion:social': ['I want company.', "I'd like someone to talk to."],
95
+ 'emotion:worship': ['I want to pray.', 'I want to be near God.'],
96
+ 'emotion:novel': ['I want something different.', "I'm restless."],
97
+ 'emotion:rest': ['I need a break.', "I've had enough for now."],
98
+ 'emotion:comfort': ['I need some comfort.', 'I want to feel safe.'],
99
+ 'emotion:repair': ['I have to correct what I did wrong.', 'I feel troubled by it.'],
100
+ 'emotion:risky': ['I feel like doing it.', 'I see no reason not to do it.'],
101
+ 'emotion:default': ['I feel like doing it.', 'I want to do it because of how I feel.'],
102
+ // Other decision lines. decision.social: {who}.
103
+ 'decision.nothing': ['There is nothing I can do right now.', 'I can only wait.'],
104
+ 'decision.complied': ['You insisted, so I will do it.', 'I would rather not do it, but you insist.'],
105
+ 'decision.commanded': ['I was told to do it, so I am doing it.', 'I am following an order.'],
106
+ 'decision.commitment': ['I said I would do it, so I will.', 'I have to keep my commitment.'],
107
+ 'decision.goal': ['This helps me get closer to what I want.', 'I am continuing to work toward my goal.'],
108
+ 'decision.social': ['I would like to see {who}.', 'I want to spend time with {who}.'],
109
+ 'decision.suggestion': ['You asked, so I will do it.', 'I will do it since you suggest it.'],
110
+ 'decision.closeCall': ['I almost chose something else.', 'I nearly chose something else.'],
111
+ /** {summary}: the episode summary, lower-cased, ending with a full stop. */
112
+ 'cite.bad': ['Last time, {summary}'],
113
+ 'cite.good': ['I remember: {summary}'],
114
+ // Intentions (single phrases). intention.need: {need}; promiseTo/for/suggestionBy: {who}; goal: {goal}.
115
+ 'intention:food': ['to feed myself'],
116
+ 'intention:water': ['to drink'],
117
+ 'intention:sleep': ['to sleep'],
118
+ 'intention:rest': ['to rest'],
119
+ 'intention:relief': ['to ease the pain'],
120
+ 'intention:belonging': ['to be with people'],
121
+ 'intention:leisure': ['to enjoy myself'],
122
+ 'intention:meaning': ['to do something that matters'],
123
+ 'intention:competence': ['to get better'],
124
+ 'intention:esteem': ['to be respected'],
125
+ 'intention:autonomy': ['to choose for myself'],
126
+ 'intention:safety': ['to be safe'],
127
+ 'intention.need': ['to meet my need for {need}'],
128
+ 'intention.none': ['nothing to do'],
129
+ 'intention.complied': ['because you insisted'],
130
+ 'intention.commanded': ['because I was ordered to'],
131
+ 'intention.default': ['because it suits me'],
132
+ 'intention.right': ['because it is right'],
133
+ 'intention.repair': ['to repair the harm I caused'],
134
+ 'intention.repent': ['to repent for my wrongs'],
135
+ 'intention.promiseTo': ['to keep my promise to {who}'],
136
+ 'intention.word': ['to keep my promise'],
137
+ 'intention.goal': ['I want to achieve this goal: {goal}.'],
138
+ 'intention.goalFallback': ['for my goal'],
139
+ 'intention.group': ['to be among people'],
140
+ 'intention.for': ['for {who}'],
141
+ 'intention.suggestion': ['because you asked'],
142
+ 'intention.suggestionBy': ['because {who} asked'],
143
+ 'intention.habit': ['out of habit'],
144
+ 'intention.emotion': ['because I feel like it'],
145
+ 'intention.material': ['to earn'],
146
+ 'intention.precommit': ['because I decided to do this'],
147
+ 'intention.expectation': ['because it worked before'],
148
+ 'devotion.allah': ['for Allah'],
149
+ 'devotion.god': ['for God'],
150
+ 'devotion.keep': ['to keep my devotion'],
151
+ // Spoken replies to a suggestion. deferred: {After}; modified: {alt}; distrust.episode: {what};
152
+ // omission: {what}; willNot: {verb}.
153
+ 'voice.assented.trusted': ['I trust you, so I will go.', 'That is a good idea. I will do it.'],
154
+ 'voice.assented': ['I will do it.', 'I agree to do it.'],
155
+ 'voice.complied': ['I will do it since you insist.', 'I will do it if you insist, but I would rather not.'],
156
+ 'voice.commanded': ['I will do it.', 'I will do what you say.'],
157
+ 'voice.commanded.reluctant': [
158
+ 'I will do what you say. I would choose something else.',
159
+ 'I will do it. I do not like it.',
160
+ ],
161
+ 'voice.underOrders': ['I have my orders.', 'I cannot do that because I am following an order.'],
162
+ 'voice.break': ['I cannot do it now.', 'Leave me alone.'],
163
+ 'voice.downed': ['I cannot get up.', 'I cannot do it because I cannot get up.'],
164
+ 'voice.deferred': ['I cannot do it now. {After}, I will do it.', '{After}, I will do it.'],
165
+ 'voice.deferred.default': ['later'],
166
+ 'voice.modified': ['I will do this instead: {alt}.', 'I will not do that. I will do this: {alt}.'],
167
+ 'voice.modified.default': ['do something similar'],
168
+ 'voice.distrust.episode': [
169
+ 'I will not follow your suggestion again. I remember what happened last time: {what}.',
170
+ 'I will not do it. I remember what happened last time: {what}.',
171
+ ],
172
+ 'voice.distrust': ['Why would I listen to you?', 'You have pressured me enough.'],
173
+ 'voice.omission': [
174
+ 'I will not miss {what}, whatever you say.',
175
+ 'I will not do it if it means missing {what}.',
176
+ ],
177
+ 'voice.omission.prayer': ['my prayer'],
178
+ 'voice.omission.default': ['my duty'],
179
+ 'voice.willNot': ['I will not {verb}, whatever you say.', 'I will not do it. I do not {verb}.'],
180
+ 'voice.duty': [
181
+ 'I will not do it while I am keeping {what}.',
182
+ 'I will not do it because it goes against {what}.',
183
+ ],
184
+ 'voice.asleep': ["I'm asleep."],
185
+ 'voice.survival': ['I cannot do it in my current condition.'],
186
+ 'voice.notSleepy': ["I'm not tired."],
187
+ 'voice.unavailable': ['That is not available here now.', 'I cannot do that here now.'],
188
+ 'voice.invalid': ["That doesn't make sense."],
189
+ 'voice.capacity': ['I cannot do it. I am exhausted.', 'I do not have the strength.'],
190
+ 'voice.skill': ['I do not know how to do that.'],
191
+ 'voice.cannot': ['I cannot do it right now.', 'That is not possible.'],
192
+ 'needFirst:need:food': ['I have to eat first.'],
193
+ 'needFirst:need:water': ['I have to drink first.'],
194
+ 'needFirst:need:sleep': ['I have to sleep first.'],
195
+ 'needFirst:need:rest': ['I have to rest first.'],
196
+ // Person summary. describe.line: {name} {act} {state}; describe.dead: {name}; describe.feeling: {emotion}.
197
+ 'describe.dead': ['{name} has died.'],
198
+ 'describe.line': ['{name} is {state}. {act}'],
199
+ 'describe.idle': ['idle'],
200
+ 'describe.well': ['well'],
201
+ 'describe.asleep': ['asleep'],
202
+ 'describe.hungry': ['hungry'],
203
+ 'describe.thirsty': ['thirsty'],
204
+ 'describe.tired': ['tired'],
205
+ 'describe.pain': ['in pain'],
206
+ 'describe.feeling': ['feeling {emotion}'],
207
+ // Names and roles. role.phrase: {poss} {role}.
208
+ 'word.them': ['them'],
209
+ 'role.phrase': ['{poss} {role}'],
210
+ 'role:spouse': ['spouse'],
211
+ 'role:parent': ['parent'],
212
+ 'role:child': ['child'],
213
+ // Chronicle (third/first person via {Subj}/{subj}/{obj}/{poss}/{Poss}). See `chronicle/story.ts`.
214
+ /** {label} {n} {days} */
215
+ 'chronicle.prayer.all': ['{Subj} prayed {label} every day.'],
216
+ 'chronicle.prayer.one': ['{Subj} prayed {label}.'],
217
+ 'chronicle.prayer': ['{Subj} prayed {label} on {n} of {days} days.'],
218
+ 'chronicle.prayer.never': ['{Subj} did not pray {label}.'],
219
+ /** {past} {n} {days} */
220
+ 'chronicle.action.all': ['{Subj} {past} every day.'],
221
+ 'chronicle.action': ['{Subj} {past} on {n} of {days} days.'],
222
+ /** {kept} {total} {kinds} */
223
+ 'chronicle.kept': ['{Subj} kept {kept} of {total} {kinds}.'],
224
+ /** {kind}: the one commitment of its kind that day, kept or not */
225
+ 'chronicle.kept.one': ['{Subj} kept {kind}.'],
226
+ 'chronicle.broke.one': ['{Subj} did not keep {kind}.'],
227
+ /** {who} */
228
+ 'chronicle.trust.up': ['{Subj} came to trust {who} more.'],
229
+ 'chronicle.trust.down': ['{Subj} trusted {who} less by the end.'],
230
+ /** {gerund} {Gerund} {when} (' around 05:00' or '') */
231
+ 'chronicle.habit.formed': ['{Gerund}{when} had become a habit.'],
232
+ 'chronicle.habit.faded': ['The habit of {gerund}{when} faded.'],
233
+ /** {hour} */
234
+ 'chronicle.when': [' around {hour}'],
235
+ /** Verb forms for a prayer by label. {label} */
236
+ 'prayer.base': ['pray {label}'],
237
+ 'prayer.past': ['prayed {label}'],
238
+ 'prayer.gerund': ['praying {label}'],
239
+ /** {times} ('once' / '3 times') {n} {m} */
240
+ /** {m} repairs to a wronged person, {k} breaches without a victim the person turned back from */
241
+ 'chronicle.breaches': [
242
+ '{Subj} acted against {poss} own standards {times} and took action to repair the harm from {m} of those actions.',
243
+ ],
244
+ 'chronicle.breaches.turned': [
245
+ '{Subj} acted against {poss} own standards {times} and repented for {k} of those actions.',
246
+ ],
247
+ 'chronicle.breaches.both': [
248
+ '{Subj} acted against {poss} own standards {times}, took action to repair the harm from {m} of those actions and repented for {k}.',
249
+ ],
250
+ 'chronicle.breaches.none': ['{Subj} acted against {poss} own standards {times}.'],
251
+ /** {n} */
252
+ 'word.once': ['once'],
253
+ 'word.times': ['{n} times'],
254
+ /** {amount} */
255
+ 'chronicle.material.gain': ['{Subj} gained {amount}.'],
256
+ 'chronicle.material.loss': ['{Subj} lost {amount}.'],
257
+ /** {kinds} */
258
+ 'chronicle.illness': ['{Subj} became ill with {kinds}.'],
259
+ 'chronicle.mood.up': ['{Poss} mood improved over those days.'],
260
+ 'chronicle.mood.down': ['{Poss} mood worsened over those days.'],
261
+ /** {summary} */
262
+ 'chronicle.episode': ['This event was memorable for {obj}: {summary}'],
263
+ /** {day} */
264
+ 'chronicle.died': ['{Subj} died on day {day}.'],
265
+ 'chronicle.empty': ['Nothing was recorded.'],
266
+ /** {past} {label} */
267
+ 'diff.unprompted': ['By the end {subj} {past} without being told.'],
268
+ 'diff.stillPrompted': ['{Subj} still needed reminders to {base}.'],
269
+ 'diff.started': ['By the end {subj} {past} most days, which {subj} had rarely done before.'],
270
+ 'diff.stopped': ['By the end {subj} had mostly stopped {gerund}.'],
271
+ /** {who} */
272
+ 'diff.trust.up': ['{Subj} trusted {who} more than before.'],
273
+ 'diff.trust.down': ['{Subj} trusted {who} less than before.'],
274
+ 'diff.mood.up': ['{Poss} mood had improved.'],
275
+ 'diff.mood.down': ['{Poss} mood had worsened.'],
276
+ 'diff.none': ['Little had changed.'],
277
+ // Plural nouns for commitment kinds in chronicle counts.
278
+ 'kinds:promise': ['promises'],
279
+ 'kinds:duty': ['duties'],
280
+ 'kinds:appointment': ['appointments'],
281
+ 'kinds:worship': ['prayers'],
282
+ 'kinds:job': ['work shifts'],
283
+ 'kinds:abstain': ['abstentions'],
284
+ 'kinds:default': ['commitments'],
285
+ // Singular, with an article, for a count of one.
286
+ 'kind:promise': ['a promise'],
287
+ 'kind:duty': ['a duty'],
288
+ 'kind:appointment': ['an appointment'],
289
+ 'kind:worship': ['a prayer'],
290
+ 'kind:job': ['a work shift'],
291
+ 'kind:abstain': ['an abstention'],
292
+ 'kind:default': ['a commitment'],
293
+ };
@@ -0,0 +1,4 @@
1
+ import { type TraitEffect } from './social/social.ts';
2
+ import type { EntityId, Minute, Person } from './types.ts';
3
+ /** Returns the same detached, sorted misled-source ids as confirm; no relationship alias escapes. */
4
+ export declare function confirmObservedReputation(p: Person, prop: string, truth: boolean, now: Minute, table?: Record<string, TraitEffect>): EntityId[];
@@ -0,0 +1,16 @@
1
+ /**
2
+ * SCOPE: opt-in composition of beliefs.confirm and social.applyReputationBelief, in that order.
3
+ * Owns no state or formula. This is the Hearsay 2 wrapper: direct observation corrects the belief,
4
+ * recalibrates its sources and applies the credence change to the subject's relationship. It does
5
+ * not infer opposite traits, accuse a source of lying, run attention or record a percept. Hosts
6
+ * supply only observations their person has access to. Existing callers remain unchanged.
7
+ */
8
+ import { confirm, credence } from './beliefs/beliefs.js';
9
+ import { applyReputationBelief } from './social/social.js';
10
+ /** Returns the same detached, sorted misled-source ids as confirm; no relationship alias escapes. */
11
+ export function confirmObservedReputation(p, prop, truth, now, table) {
12
+ const before = credence(p, prop);
13
+ const misled = confirm(p, prop, truth, now);
14
+ applyReputationBelief(p, prop, before, credence(p, prop), now, table);
15
+ return misled;
16
+ }
@@ -419,7 +419,7 @@ export function marry(a, b, at, custom = GENERIC_CUSTOM, opts = {}) {
419
419
  targetId: y.id,
420
420
  valence: 0.9,
421
421
  salience: 0.95,
422
- summary: 'married',
422
+ summary: x.lexicon?.plainEnglish ? `I married ${y.name}.` : 'married',
423
423
  tags: ['marriage', 'family'],
424
424
  });
425
425
  feel(x, 'joy', 0.7, `event:marriage:${y.id}`, at, y.id);
package/dist/person.d.ts CHANGED
@@ -50,6 +50,11 @@ export declare const PERSON_DEFAULTS: {
50
50
  nightFrom: number;
51
51
  nightTo: number;
52
52
  };
53
+ /**
54
+ * SCOPE (host setup): optional habits and expectations copy authored records, including nested cues and
55
+ * need deltas, in source order. Invalid numbers, domains or collection bounds reject with RangeError;
56
+ * setup never clamps, evicts, learns, advances time or consumes RNG. Omitted options keep legacy state.
57
+ */
53
58
  export declare function createPerson(spec: PersonSpec): Person;
54
59
  export interface PersonReadout {
55
60
  body: BodyReadout;