@adam0white/human-framework 2.17.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.
@@ -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
+ };
@@ -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.js CHANGED
@@ -38,6 +38,7 @@ import { intentionFor, narrateDecision, voiceLine } from './narrate/index.js';
38
38
  import { advanceNeeds, createNeeds, levelOf, meanSatisfaction, readNeeds, satisfy, urgencyOf, } from './needs/index.js';
39
39
  import { widow } from './partnering/index.js';
40
40
  import { beginPursuit, enablePersistence, releasePursuit } from './persistence/index.js';
41
+ import { commitmentLabel } from './presentation.js';
41
42
  import { considerProjects, enableProjects, noteProjectAct, noteProjectStatus, perceiveProspect, } from './projects/index.js';
42
43
  import { enablePromptings, nafsPatterns, notePromptings } from './promptings/index.js';
43
44
  import { bodyForecastFor, consumeConsequences, enablePlanning, forecastLine, forecastThresholds, pruneConsequences, receiveConsequences, } from './prospection/index.js';
@@ -304,7 +305,11 @@ function recordMissed(p, broken, now, brokenBy) {
304
305
  action,
305
306
  actorId: p.id,
306
307
  valence: -0.5 * c.importance,
307
- summary: `missed ${c.kind}: ${action}`,
308
+ summary: p.lexicon?.plainEnglish
309
+ ? c.kind === 'abstain'
310
+ ? `I broke this abstention (${commitmentLabel(c, p.lexicon)}) by doing this: ${action.replace(/[-_]/g, ' ')}.`
311
+ : `I did not complete this commitment (${commitmentLabel(c, p.lexicon)}). It required this action: ${action.replace(/[-_]/g, ' ')}.`
312
+ : `missed ${c.kind}: ${action}`,
308
313
  tags: ['missed', c.kind, ...(isNight(now) ? ['night'] : [])],
309
314
  ...(c.toId !== undefined && c.toId !== 'self' ? { targetId: c.toId } : {}),
310
315
  });
@@ -488,7 +493,13 @@ function crisisStep(p, at) {
488
493
  action: ev.behaviourId,
489
494
  actorId: p.id,
490
495
  valence: ev.kind === 'onset' ? -0.6 : 0.2,
491
- summary: ev.kind === 'onset' ? `I broke down (${label})` : 'I came back to myself',
496
+ summary: p.lexicon?.plainEnglish
497
+ ? ev.kind === 'onset'
498
+ ? `I had a breakdown (${label}).`
499
+ : 'My breakdown ended.'
500
+ : ev.kind === 'onset'
501
+ ? `I broke down (${label})`
502
+ : 'I came back to myself',
492
503
  tags: ['break', ev.kind],
493
504
  });
494
505
  if (ev.kind === 'onset')
@@ -510,7 +521,13 @@ function rememberDowned(p, at, kind, reason) {
510
521
  action: reason,
511
522
  actorId: p.id,
512
523
  valence: kind === 'down' ? -0.5 : 0.3,
513
- summary: kind === 'down' ? 'I went down and could not get up' : 'I got back on my feet',
524
+ summary: p.lexicon?.plainEnglish
525
+ ? kind === 'down'
526
+ ? 'I could not stand up.'
527
+ : 'I could stand up again.'
528
+ : kind === 'down'
529
+ ? 'I went down and could not get up'
530
+ : 'I got back on my feet',
514
531
  tags: ['downed', kind, reason],
515
532
  });
516
533
  }
@@ -940,7 +957,9 @@ export function decide(p, affordances, opts = {}) {
940
957
  actorId: res.voiceId,
941
958
  targetId: p.id,
942
959
  valence: -0.3,
943
- summary: `${res.voiceId} insisted that I ${chosen?.label ?? 'do as told'}`,
960
+ summary: p.lexicon?.plainEnglish
961
+ ? `${p.lexicon.names?.[res.voiceId] ?? res.voiceId} insisted: ${chosen?.label ?? 'follow the request'}.`
962
+ : `${res.voiceId} insisted that I ${chosen?.label ?? 'do as told'}`,
944
963
  tags: ['suggestion', 'insist'],
945
964
  voiceId: res.voiceId,
946
965
  });
@@ -992,7 +1011,9 @@ function applyCommandOutcome(p, co, list) {
992
1011
  actorId: c.voiceId,
993
1012
  targetId: p.id,
994
1013
  valence: -0.2 - 0.3 * Math.min(1, co.margin),
995
- summary: `${c.voiceId} took charge and had me ${target?.label ?? 'do as ordered'}`,
1014
+ summary: p.lexicon?.plainEnglish
1015
+ ? `${p.lexicon.names?.[c.voiceId] ?? c.voiceId} ordered: ${target?.label ?? 'follow the order'}.`
1016
+ : `${c.voiceId} took charge and had me ${target?.label ?? 'do as ordered'}`,
996
1017
  tags: ['suggestion', 'command'],
997
1018
  voiceId: c.voiceId,
998
1019
  });
@@ -1429,7 +1450,10 @@ export function finish(p, outcome, opts = {}) {
1429
1450
  action: aff.action,
1430
1451
  actorId: p.id,
1431
1452
  valence: felt,
1432
- summary: outcome.summary ?? `${aff.label}: ${outcome.status}`,
1453
+ summary: outcome.summary ??
1454
+ (p.lexicon?.plainEnglish
1455
+ ? `${outcome.status === 'completed' ? 'I completed' : outcome.status === 'interrupted' ? 'I was interrupted during' : 'I could not complete'} this action: ${aff.label}.`
1456
+ : `${aff.label}: ${outcome.status}`),
1433
1457
  tags: [...(aff.tags ?? []), outcome.status, ...(isNight(act.startedAt) ? ['night'] : [])],
1434
1458
  };
1435
1459
  if (aff.targetId !== undefined)
@@ -1552,5 +1576,8 @@ export function nafsReading(p) {
1552
1576
  */
1553
1577
  export function bodyForecast(p, aff) {
1554
1578
  const spans = bodyForecastFor(p, aff, lifeModifiers(p), fastingCtx(p));
1555
- return { spans, line: forecastLine(spans, forecastThresholds(aff)) };
1579
+ return {
1580
+ spans,
1581
+ line: forecastLine(spans, forecastThresholds(aff), { plainEnglish: p.lexicon?.plainEnglish }),
1582
+ };
1556
1583
  }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * SCOPE: opt-in English display labels for framework-owned tokens. These readers do not rewrite stored
3
+ * identifiers, labels or host prose. They do not change decisions, religious understandings or RNG state.
4
+ * Unknown reason codes have no plain display text; the host supplies their meaning.
5
+ */
6
+ import type { Commitment, Lexicon, NormDefinition } from './types.ts';
7
+ export declare function labelLine(label: string, lexicon?: Lexicon): string;
8
+ export declare function commitmentLabel(c: Pick<Commitment, 'label' | 'kind'>, lexicon?: Lexicon): string;
9
+ export declare function normLabel(norm: Pick<NormDefinition, 'id' | 'label'>, lexicon?: Lexicon): string;
10
+ export declare function reasonLine(reason: string, lexicon?: Lexicon): string;
@@ -0,0 +1,88 @@
1
+ const LABELS = {
2
+ Fajr: 'the dawn prayer',
3
+ Dhuhr: 'the midday prayer',
4
+ Asr: 'the afternoon prayer',
5
+ Maghrib: 'the sunset prayer',
6
+ Isha: 'the night prayer',
7
+ suhoor: 'the meal before dawn',
8
+ iftar: 'the meal to end the fast',
9
+ 'Eid prayer': 'the Eid holiday prayer',
10
+ 'make-friend': 'making a friend',
11
+ 'master-skill': 'learning a skill',
12
+ 'earn-respect': 'earning respect',
13
+ serve: 'helping others',
14
+ 'make-up': 'a missed commitment to complete later',
15
+ };
16
+ export function labelLine(label, lexicon) {
17
+ if (!lexicon?.plainEnglish)
18
+ return label;
19
+ const known = Object.hasOwn(LABELS, label) ? LABELS[label] : undefined;
20
+ if (known !== undefined)
21
+ return known;
22
+ const missedName = label.startsWith('make-up ') ? label.slice(8) : '';
23
+ const missed = Object.hasOwn(LABELS, missedName) ? LABELS[missedName] : undefined;
24
+ return missed === undefined ? label : `${missed}, performed later because it was missed`;
25
+ }
26
+ export function commitmentLabel(c, lexicon) {
27
+ if (lexicon?.plainEnglish && c.label === undefined) {
28
+ const kinds = {
29
+ worship: 'my prayer',
30
+ promise: 'my promise',
31
+ duty: 'my duty',
32
+ appointment: 'my appointment',
33
+ job: 'my work shift',
34
+ abstain: 'my abstention',
35
+ };
36
+ return Object.hasOwn(kinds, c.kind) ? (kinds[c.kind] ?? c.kind) : c.kind;
37
+ }
38
+ return labelLine(c.label ?? c.kind, lexicon);
39
+ }
40
+ export function normLabel(norm, lexicon) {
41
+ if (!lexicon?.plainEnglish)
42
+ return norm.label;
43
+ const labels = {
44
+ zakat: 'Give the required share of wealth to those entitled to it',
45
+ charity: 'Give voluntary charity',
46
+ backbiting: 'Speaking about someone in a way they would dislike',
47
+ 'kin-marriage': 'Do not marry someone forbidden by the held rules about family relationships',
48
+ };
49
+ return Object.hasOwn(labels, norm.id) ? (labels[norm.id] ?? norm.label) : norm.label;
50
+ }
51
+ export function reasonLine(reason, lexicon) {
52
+ if (!lexicon?.plainEnglish)
53
+ return reason;
54
+ const lines = {
55
+ dead: 'The person has died.',
56
+ invalid: 'That action does not make sense.',
57
+ break: 'I cannot do that during this breakdown.',
58
+ downed: 'I cannot get up.',
59
+ asleep: 'I am asleep.',
60
+ 'not-sleepy': 'I am not sleepy enough.',
61
+ capacity: 'I cannot manage that physically.',
62
+ unavailable: 'That option is not available.',
63
+ distrust: 'I do not trust this advice.',
64
+ commanded: 'I am following an order.',
65
+ command: 'I am following an order.',
66
+ busy: 'I am doing something else right now.',
67
+ 'need:food': 'I need to eat first.',
68
+ 'need:water': 'I need to drink first.',
69
+ 'need:sleep': 'I need to sleep first.',
70
+ 'need:rest': 'I need to rest first.',
71
+ 'need:survival': 'I need to meet an urgent bodily need first.',
72
+ };
73
+ if (Object.hasOwn(lines, reason))
74
+ return lines[reason] ?? '';
75
+ if (reason.startsWith('duty:'))
76
+ return 'I need to keep this duty.';
77
+ if (reason.startsWith('norm:'))
78
+ return 'This conflicts with a rule I hold.';
79
+ if (reason.startsWith('rule:'))
80
+ return 'This conflicts with a personal rule I set.';
81
+ if (reason.startsWith('capacity:'))
82
+ return 'I cannot manage that physically.';
83
+ if (reason.startsWith('skill:'))
84
+ return 'I do not have enough skill for that.';
85
+ if (reason.startsWith('voice:'))
86
+ return 'I am following another person’s request.';
87
+ return '';
88
+ }
@@ -72,6 +72,7 @@ export declare function notePromptings(p: Person, seen: readonly PromptingSeen[]
72
72
  /** Vocabulary for one prompt under a held reading (narration only; it changes nothing). */
73
73
  export declare function promptingLine(reading: PromptingReading, outcome: PromptingRecord['outcome'], kind?: PromptingResponseKind, options?: {
74
74
  voice?: 'first' | 'third';
75
+ plainEnglish?: boolean;
75
76
  }): string;
76
77
  /** Read-only summary of recent prompts (a lens; computes nothing new about the person). */
77
78
  export interface PromptingsReadingView {
@@ -257,6 +257,34 @@ export function notePromptings(p, seen, chosenId, now) {
257
257
  }
258
258
  /** Vocabulary for one prompt under a held reading (narration only; it changes nothing). */
259
259
  export function promptingLine(reading, outcome, kind, options = {}) {
260
+ if (options.plainEnglish) {
261
+ const third = options.voice === 'third';
262
+ const who = third ? 'The person' : 'I';
263
+ const felt = third
264
+ ? 'The person felt an urge.'
265
+ : {
266
+ whisper: 'I felt a whisper urging me to act.',
267
+ inclination: 'I felt an urge from my own desires.',
268
+ intrusion: 'I had a thought. It does not define who I am.',
269
+ fault: 'I had a thought that felt like wrongdoing.',
270
+ none: 'I felt an urge.',
271
+ }[reading];
272
+ if (outcome === 'assented') {
273
+ const urge = third
274
+ ? 'The person felt an urge.'
275
+ : reading === 'whisper' || reading === 'inclination'
276
+ ? felt
277
+ : 'I felt an urge.';
278
+ return `${urge} ${who} acted on it.`;
279
+ }
280
+ if (outcome === 'passed')
281
+ return `${felt} It passed without action.`;
282
+ if (kind === 'redirect')
283
+ return `${felt} ${who} chose something else and let it pass.`;
284
+ if (kind === 'refuge')
285
+ return `${felt} ${who} sought refuge and let it pass.`;
286
+ return `${felt} ${who} did not act on it.`;
287
+ }
260
288
  // Plain third-person observations describe the response, without inferring the prompt's origin.
261
289
  if (options.voice === 'third') {
262
290
  if (outcome === 'assented')
@@ -313,7 +341,9 @@ export function promptingsReading(p, now = p.now) {
313
341
  practice: round(practiceAt(r, now, halfLifeOf(s))),
314
342
  ...(r.cue !== undefined ? { cue: r.cue } : {}),
315
343
  })),
316
- lines: s.recent.slice(-5).map((r) => promptingLine(s.reading, r.outcome, kindOf(r.responseId))),
344
+ lines: s.recent
345
+ .slice(-5)
346
+ .map((r) => promptingLine(s.reading, r.outcome, kindOf(r.responseId), { plainEnglish: p.lexicon?.plainEnglish })),
317
347
  };
318
348
  if (s.readingSource !== undefined)
319
349
  view.readingSource = s.readingSource;
@@ -329,19 +359,25 @@ export function nafsPatterns(p, inputs) {
329
359
  name: 'urging',
330
360
  ref: 'Qur’an 12:53',
331
361
  evident: assentedAgainstNorm.length > 0,
332
- evidence: `${assentedAgainstNorm.length} recent prompts followed against a held norm`,
362
+ evidence: p.lexicon?.plainEnglish
363
+ ? `The person acted on ${assentedAgainstNorm.length} recent urges despite a rule they hold.`
364
+ : `${assentedAgainstNorm.length} recent prompts followed against a held norm`,
333
365
  },
334
366
  {
335
367
  name: 'reproaching',
336
368
  ref: 'Qur’an 75:2',
337
369
  evident: reproach,
338
- evidence: `open-breach weight ${round(inputs.guiltLoad, 2)}, felt guilt ${round(inputs.guilt, 2)}`,
370
+ evidence: p.lexicon?.plainEnglish
371
+ ? `The weight of unresolved breaches is ${round(inputs.guiltLoad, 2)}. The strength of felt guilt is ${round(inputs.guilt, 2)}.`
372
+ : `open-breach weight ${round(inputs.guiltLoad, 2)}, felt guilt ${round(inputs.guilt, 2)}`,
339
373
  },
340
374
  {
341
375
  name: 'at rest',
342
376
  ref: 'Qur’an 89:27',
343
377
  evident: letPass > 0 && assentedAgainstNorm.length === 0 && !reproach,
344
- evidence: `${letPass} recent prompts let pass, none followed against a norm, no felt reproach`,
378
+ evidence: p.lexicon?.plainEnglish
379
+ ? `${letPass} recent urges passed without action. ${assentedAgainstNorm.length} were acted on against a held rule. Current guilt or unresolved breach: ${reproach ? 'yes' : 'no'}.`
380
+ : `${letPass} recent prompts let pass, none followed against a norm, no felt reproach`,
345
381
  },
346
382
  ];
347
383
  }
@@ -54,4 +54,6 @@ export declare function bodyForecastTerm(p: Person, aff: Affordance, mods: LifeM
54
54
  export declare function forecastLine(spans: readonly BodyForecastSpan[], at?: {
55
55
  comfortAt: number;
56
56
  spentAt: number;
57
+ }, options?: {
58
+ plainEnglish?: boolean;
57
59
  }): string;
@@ -103,7 +103,7 @@ export function bodyForecastTerm(p, aff, mods, perception) {
103
103
  return v < 0 ? { source: 'forecast:body', value: v } : undefined;
104
104
  }
105
105
  /** A first-person line for the forecast (narration only); `at` is the offer's thresholds (2.8.0, default as before). */
106
- export function forecastLine(spans, at = FORECAST_DEFAULTS) {
106
+ export function forecastLine(spans, at = FORECAST_DEFAULTS, options = {}) {
107
107
  const after = spans.slice(1);
108
108
  if (after.length === 0)
109
109
  return '';
@@ -111,6 +111,20 @@ export function forecastLine(spans, at = FORECAST_DEFAULTS) {
111
111
  for (const s of after)
112
112
  if (s.worst > w.worst)
113
113
  w = s;
114
+ if (options.plainEnglish) {
115
+ const label = w.label === 'after' ? 'what comes next' : w.label;
116
+ const state = {
117
+ fatigue: 'too tired',
118
+ sleepiness: 'too sleepy',
119
+ hunger: 'too hungry',
120
+ thirst: 'too thirsty',
121
+ }[w.worstOf];
122
+ if (w.worst >= at.spentAt)
123
+ return `I can do this. I will be ${state} for ${label}.`;
124
+ if (w.worst >= at.comfortAt)
125
+ return `I can do this, but ${label} will be hard.`;
126
+ return `I can manage ${label} afterward.`;
127
+ }
114
128
  const what = { fatigue: 'spent', sleepiness: 'too sleepy', hunger: 'too hungry', thirst: 'too thirsty' }[w.worstOf];
115
129
  if (w.worst >= at.spentAt)
116
130
  return `I can do it, but I'll be ${what} for ${w.label}.`;