@adam0white/human-framework 2.22.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -55
- package/dist/agenda/agenda.js +1 -1
- package/dist/beliefs/beliefs.js +2 -2
- package/dist/body/doze.d.ts +6 -3
- package/dist/body/doze.js +7 -4
- package/dist/conscience/conscience.d.ts +2 -4
- package/dist/conscience/conscience.js +3 -5
- package/dist/conversation/conversation.js +11 -10
- package/dist/core/index.d.ts +0 -1
- package/dist/core/index.js +0 -1
- package/dist/core/math.d.ts +0 -2
- package/dist/core/math.js +3 -3
- package/dist/family/family.d.ts +0 -5
- package/dist/family/family.js +12 -6
- package/dist/impression.d.ts +2 -7
- package/dist/impression.js +5 -7
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/longrun.d.ts +2 -2
- package/dist/longrun.js +1 -2
- package/dist/memory/memory.d.ts +0 -4
- package/dist/memory/memory.js +20 -6
- package/dist/migrate.js +5 -0
- package/dist/narrate/narrate.js +3 -1
- package/dist/observed-reputation.js +1 -1
- package/dist/person.d.ts +5 -1
- package/dist/person.js +16 -10
- package/dist/persona.js +1 -0
- package/dist/presentation.js +1 -0
- package/dist/promptings/promptings.js +2 -2
- package/dist/qalb.d.ts +0 -12
- package/dist/qalb.js +22 -8
- package/dist/sim/longrun.d.ts +3 -3
- package/dist/sim/longrun.js +1 -1
- package/dist/sim/sim.js +9 -4
- package/dist/social/impressions.d.ts +0 -12
- package/dist/social/impressions.js +13 -8
- package/dist/social/social.js +2 -2
- package/dist/tick-input.d.ts +24 -0
- package/dist/tick-input.js +56 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/package.json +1 -1
package/dist/impression.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* - `outwardSigns(target)`: perceived fatigue, pain and fear and the felt mood, each reduced by the target's reserve;
|
|
5
5
|
* a limp (lost moving capacity) shows through any reserve. `selfReport(target)` is the same in words, which hide
|
|
6
6
|
* more (`IMPRESSION_DEFAULTS.wordsReserve`): "I'm fine".
|
|
7
|
-
* - `
|
|
7
|
+
* - `glimpsePerson(observer, target, at, clarity?)` = `glimpse` of the target's outward signs; `acquaintPerson` hands the target's
|
|
8
8
|
* traits and family ties (public roles) to `acquaint`.
|
|
9
9
|
* - `imagine(observer, target, now)`: the target as the observer pictures them — a copy whose traits, fear, pain,
|
|
10
10
|
* fatigue, mood, ties and trust in voices are the observer's beliefs (each pulled to the prior by its uncertainty)
|
|
@@ -61,14 +61,12 @@ export function selfReport(target) {
|
|
|
61
61
|
s.pain = round(body.perceived.pain * (1 - clamp01(reserveOf(target, 'pain') * IMPRESSION_DEFAULTS.wordsReserve)));
|
|
62
62
|
return s;
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
else
|
|
68
|
-
glimpse(observer, target.id, outwardSigns(target), when.at, when.clarity);
|
|
64
|
+
/** `observer` looks at `target` at `at` (clarity 0..1: a clear look near by, or a distant figure; default 1). */
|
|
65
|
+
export function glimpsePerson(observer, target, at, clarity) {
|
|
66
|
+
glimpse(observer, target.id, outwardSigns(target), at, clarity);
|
|
69
67
|
}
|
|
70
68
|
/** `observer` has known `target` for a long time (familiarity 0..1): traits and family ties, condensed. */
|
|
71
|
-
export function
|
|
69
|
+
export function acquaintPerson(observer, target, familiarity, at) {
|
|
72
70
|
const ties = {};
|
|
73
71
|
for (const r of target.social.relationships)
|
|
74
72
|
if (r.roles.some((x) => PUBLIC_ROLES.includes(x)))
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Human Framework public surface. */
|
|
2
|
-
export declare const FRAMEWORK_VERSION = "
|
|
2
|
+
export declare const FRAMEWORK_VERSION = "3.0.0";
|
|
3
3
|
export type { CrisisEvent, PracticeKind } from './affect/index.ts';
|
|
4
4
|
export { AFFECT_DEFAULTS, actionTendencies, advanceAffect, appraise, breakAllows, breakBehaviour, breakHazard, CRISIS_DEFAULTS, checkCrisis, createAffect, easeBreak, emotionLevel, enableBreaks, feel, inBreak, readAffect, regulate, release as releaseEmotion, skipCrisis, strain, tendencyEmotions, } from './affect/index.ts';
|
|
5
5
|
export type { GoalTemplate, MakeUpReceipt, PrayerCalendar, PrayerSchedule, PrayerTimes, Retimer, } from './agenda/index.ts';
|
|
@@ -46,5 +46,6 @@ export * from './scenarios/index.ts';
|
|
|
46
46
|
export * from './sim/index.ts';
|
|
47
47
|
export * from './skills/index.ts';
|
|
48
48
|
export * from './social/index.ts';
|
|
49
|
+
export { canonicalTickInputs, type TickInput, type TickInputKey } from './tick-input.ts';
|
|
49
50
|
export * from './types.ts';
|
|
50
51
|
export * from './will/index.ts';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Human Framework public surface. */
|
|
2
|
-
export const FRAMEWORK_VERSION = '
|
|
2
|
+
export const FRAMEWORK_VERSION = '3.0.0';
|
|
3
3
|
export { AFFECT_DEFAULTS, actionTendencies, advanceAffect, appraise, breakAllows, breakBehaviour, breakHazard, CRISIS_DEFAULTS, checkCrisis, createAffect, easeBreak, emotionLevel, enableBreaks, feel, inBreak, readAffect, regulate, release as releaseEmotion, skipCrisis, strain, tendencyEmotions, } from './affect/index.js';
|
|
4
4
|
// `agenda.release` collides with `affect.release`; the agenda one is re-exported under a clearer name.
|
|
5
5
|
export { AGENDA_DEFAULTS, abandonGoal, adoptGoal, advanceAgenda, agendaTerms, applyExemptions, CARE_DEFAULTS, calendarRetimer, careDuty, commitmentPressure, createAgenda, DEFAULT_PRAYER_TIMES, EID_PRAYER_IMPORTANCE, EXEMPTION_DEFAULTS, eidPrayer, eidWindow, fastWindow, iftarWindow, inMakruhTime, isContinuingCommitment, isUnderWay, MAKRUH_DEFAULTS, makruhWindows, missedExcuse, onFinished, owedMakeUps, PRAYER_IMPORTANCE, PURPOSE_DEFAULTS, prayerWindows, pressureReachedAt, promise, proposeGoals, RAMADAN_DEFAULTS, ramadanFast, ramadanMeals, release as releaseCommitment, retimeCommitments, retirePaidMakeUps, revisePurposes, scheduleMakeUp, spanMeetsWindow, suhoorWindow, timesFor, violatesAbstention, } from './agenda/index.js';
|
|
@@ -42,5 +42,6 @@ export * from './scenarios/index.js';
|
|
|
42
42
|
export * from './sim/index.js';
|
|
43
43
|
export * from './skills/index.js';
|
|
44
44
|
export * from './social/index.js';
|
|
45
|
+
export { canonicalTickInputs } from './tick-input.js';
|
|
45
46
|
export * from './types.js';
|
|
46
47
|
export * from './will/index.js';
|
package/dist/longrun.d.ts
CHANGED
|
@@ -87,8 +87,8 @@ export interface RoutineDay {
|
|
|
87
87
|
* Live one routine day from `p.now` (see SCOPE). A dead person only moves the clock. Returns what happened.
|
|
88
88
|
*/
|
|
89
89
|
export declare function routineDay(p: Person, routine: Routine, opts?: RoutineOptions): RoutineDay;
|
|
90
|
-
/**
|
|
91
|
-
export type RoutineSource =
|
|
90
|
+
/** Choose a routine for a person at the start of an absolute day. Capture host context in a closure. */
|
|
91
|
+
export type RoutineSource = (p: Person, day: number) => Routine;
|
|
92
92
|
/**
|
|
93
93
|
* Live from `p.now` to `to` by routine (see SCOPE): the running activity is dropped and the open chronicle day closed
|
|
94
94
|
* first, whole days are lived with `routineDay`, a remainder shorter than a day is skipped (`skip`), and past
|
package/dist/longrun.js
CHANGED
|
@@ -181,7 +181,6 @@ export function routineDay(p, routine, opts = {}) {
|
|
|
181
181
|
report.stage = stageAfter;
|
|
182
182
|
return report;
|
|
183
183
|
}
|
|
184
|
-
const routineOf = (src, p) => typeof src === 'function' ? src(p, dayOf(p.now)) : src;
|
|
185
184
|
/**
|
|
186
185
|
* Live from `p.now` to `to` by routine (see SCOPE): the running activity is dropped and the open chronicle day closed
|
|
187
186
|
* first, whole days are lived with `routineDay`, a remainder shorter than a day is skipped (`skip`), and past
|
|
@@ -199,7 +198,7 @@ export function liveRoutine(p, to, routine, opts = {}) {
|
|
|
199
198
|
endDay(p);
|
|
200
199
|
p.activity = null;
|
|
201
200
|
while (p.now + MINUTES_PER_DAY <= to && p.body.alive) {
|
|
202
|
-
const r = routineDay(p,
|
|
201
|
+
const r = routineDay(p, routine(p, dayOf(p.now)), opts);
|
|
203
202
|
if (r.episodes.length > 0 || r.onsets.length > 0 || r.stage || r.died)
|
|
204
203
|
out.push(r);
|
|
205
204
|
}
|
package/dist/memory/memory.d.ts
CHANGED
|
@@ -139,10 +139,6 @@ export interface RetellOptions {
|
|
|
139
139
|
* from the listener to others (a host may call it again with the listener as teller).
|
|
140
140
|
*/
|
|
141
141
|
export declare function retell(teller: Person, listener: Person, at: Minute, opts?: RetellOptions): Gist[];
|
|
142
|
-
/** @deprecated since 2.1: pass `at` (and the optional knobs) positionally. Removed in 3.0. */
|
|
143
|
-
export declare function retell(teller: Person, listener: Person, opts: RetellOptions & {
|
|
144
|
-
at: Minute;
|
|
145
|
-
}): Gist[];
|
|
146
142
|
/** Encode an episode. Evicts the weakest (salience × recency) episode beyond the bound. */
|
|
147
143
|
export declare function remember(p: Person, e: Omit<Episode, 'id' | 'salience'> & {
|
|
148
144
|
salience?: Unit;
|
package/dist/memory/memory.js
CHANGED
|
@@ -287,8 +287,22 @@ export const RETELL_DEFAULTS = {
|
|
|
287
287
|
/** Gists weaker than this (teller's salience) are not worth telling. */
|
|
288
288
|
minSalience: 0.1,
|
|
289
289
|
};
|
|
290
|
-
|
|
291
|
-
|
|
290
|
+
/**
|
|
291
|
+
* `teller` tells `listener` at `at` the gist of what mattered to them (2.0, L2: stories passed in a household or around a
|
|
292
|
+
* fire). The listener must have gists on (`enableGists`); otherwise nothing happens. The teller's strongest gists
|
|
293
|
+
* (up to `limit`, optionally only those about `placeIds`) become the listener's own gists with the tag `told`, the
|
|
294
|
+
* teller's valence and summary, and a salience of the teller's × `trust` × `RETELL_DEFAULTS.share`. A listener who
|
|
295
|
+
* already holds a gist about the same thing (kind, action, target and place, whoever was there) from their own
|
|
296
|
+
* experience keeps it unchanged; one told before keeps the
|
|
297
|
+
* stronger telling (retelling the same story does not keep raising it). So a child of parents who fear a place
|
|
298
|
+
* weighs that fear (cognition's `memory` term, by place at half weight) before ever standing there, and their own
|
|
299
|
+
* later visits outweigh it as they would any gist. No randomness; returns the gists written or strengthened.
|
|
300
|
+
* It does not model distortion in the retelling, the listener doubting the teller beyond `trust`, or retelling on
|
|
301
|
+
* from the listener to others (a host may call it again with the listener as teller).
|
|
302
|
+
*/
|
|
303
|
+
export function retell(teller, listener, at, opts = {}) {
|
|
304
|
+
if (!Number.isFinite(at))
|
|
305
|
+
throw new TypeError('retell requires a finite positional time');
|
|
292
306
|
const told = teller.memory.gists;
|
|
293
307
|
const list = listener.memory.gists;
|
|
294
308
|
if (!told || !list || teller.id === listener.id)
|
|
@@ -312,7 +326,7 @@ export function retell(teller, listener, when, knobs = {}) {
|
|
|
312
326
|
if (salience > mine.salience) {
|
|
313
327
|
mine.salience = salience;
|
|
314
328
|
mine.weight = salience;
|
|
315
|
-
mine.lastAt =
|
|
329
|
+
mine.lastAt = at;
|
|
316
330
|
out.push(mine);
|
|
317
331
|
}
|
|
318
332
|
continue;
|
|
@@ -321,15 +335,15 @@ export function retell(teller, listener, when, knobs = {}) {
|
|
|
321
335
|
listener.memory.nextGist = (listener.memory.nextGist ?? 0) + 1;
|
|
322
336
|
const g = {
|
|
323
337
|
id,
|
|
324
|
-
at
|
|
338
|
+
at,
|
|
325
339
|
kind: src.kind,
|
|
326
340
|
valence: src.valence,
|
|
327
341
|
salience,
|
|
328
342
|
summary: src.summary,
|
|
329
343
|
tags: ['told', ...src.tags.filter((t) => t !== 'told')].slice(0, GIST_DEFAULTS.maxTags),
|
|
330
344
|
count: 0,
|
|
331
|
-
firstAt:
|
|
332
|
-
lastAt:
|
|
345
|
+
firstAt: at,
|
|
346
|
+
lastAt: at,
|
|
333
347
|
weight: salience,
|
|
334
348
|
peak: salience,
|
|
335
349
|
};
|
package/dist/migrate.js
CHANGED
|
@@ -41,6 +41,11 @@ function renameAmbient(json) {
|
|
|
41
41
|
}
|
|
42
42
|
/** The chain, keyed by the version each step upgrades from. */
|
|
43
43
|
export const MIGRATIONS = {
|
|
44
|
+
'2.21.0': {
|
|
45
|
+
to: '2.23.0',
|
|
46
|
+
note: 'shape unchanged; doze delivery and plain narration fixes apply to opted-in runs',
|
|
47
|
+
apply: stamp,
|
|
48
|
+
},
|
|
44
49
|
'2.17.0': {
|
|
45
50
|
to: '2.21.0',
|
|
46
51
|
note: 'doze and bladder state start absent; old callers retain their continuation',
|
package/dist/narrate/narrate.js
CHANGED
|
@@ -332,7 +332,9 @@ export function describePerson(p, lexicon) {
|
|
|
332
332
|
? `${p.activity.action}${p.activity.targetId ? ` (${p.activity.targetId})` : ''}`
|
|
333
333
|
: phraseLine('describe.idle', {}, lex);
|
|
334
334
|
if (lex?.plainEnglish) {
|
|
335
|
-
const state = bits.length >
|
|
335
|
+
const state = bits.length > 1
|
|
336
|
+
? `${bits.slice(0, -1).join(', ')} and ${bits.at(-1)}`
|
|
337
|
+
: (bits[0] ?? phraseLine('describe.well', {}, lex));
|
|
336
338
|
const activity = p.activity
|
|
337
339
|
? `${p.name} is doing this: ${p.activity.affordance.label}${p.activity.targetId ? ` (${nameOf(p, p.activity.targetId, lex)})` : ''}.`
|
|
338
340
|
: `${p.name} is ${phraseLine('describe.idle', {}, lex)}.`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SCOPE: opt-in composition of beliefs.confirm and social.applyReputationBelief, in that order.
|
|
3
|
-
* Owns no state or formula. This
|
|
3
|
+
* Owns no state or formula. This host composition handles observed-trait correction: direct observation corrects the belief,
|
|
4
4
|
* recalibrates its sources and applies the credence change to the subject's relationship. It does
|
|
5
5
|
* not infer opposite traits, accuse a source of lying, run attention or record a percept. Hosts
|
|
6
6
|
* supply only observations their person has access to. Existing callers remain unchanged.
|
package/dist/person.d.ts
CHANGED
|
@@ -196,7 +196,11 @@ export interface BeginOptions {
|
|
|
196
196
|
/** Joint-activity proposal id (set by `sim/` when both partners begin together). */
|
|
197
197
|
jointId?: string;
|
|
198
198
|
}
|
|
199
|
-
/**
|
|
199
|
+
/**
|
|
200
|
+
* Start an activity chosen by `record`. Returns null when dead, or when dozed work
|
|
201
|
+
* is unresolved. Call `finish` for the stopped activity before starting any replacement,
|
|
202
|
+
* including voluntary sleep or an activity chosen after a waking percept.
|
|
203
|
+
*/
|
|
200
204
|
export declare function begin(p: Person, aff: Affordance, record: DecisionRecord, opts?: BeginOptions): Activity | null;
|
|
201
205
|
/**
|
|
202
206
|
* Push the review time forward after a decision to continue the current activity, refresh the cached
|
package/dist/person.js
CHANGED
|
@@ -29,7 +29,8 @@ import { ageCharacter, enableCharacterChange, noteCharacterDay, noteCharacterSoc
|
|
|
29
29
|
import { closeDay, enableYearbook, endDay, noteAnswer, noteCommitments, noteDecision, noteMood, noteOutcome, trimChronicle, } from './chronicle/index.js';
|
|
30
30
|
import { decide as cognitionDecide, desperationOf, prospectiveRoute, scoreAll } from './cognition/index.js';
|
|
31
31
|
import { createConscience, guiltLoad, heldNorms, normVeto, recordDeed, recordRepair, repent, } from './conscience/index.js';
|
|
32
|
-
import { clamp01, clampSigned, createRng, dayOf, isObj,
|
|
32
|
+
import { clamp01, clampSigned, createRng, dayOf, isObj, minuteOfDay, round } from './core/index.js';
|
|
33
|
+
import { keysOf } from './core/keys.js';
|
|
33
34
|
import { enableCuriosity, noteCuriosity } from './curiosity/index.js';
|
|
34
35
|
import { ambientBodyParams, ambientModifiers, ambientMood, ambientNeeds } from './environment/index.js';
|
|
35
36
|
import { aptitudeOf, createFamily, pregnancyModifiers } from './family/index.js';
|
|
@@ -865,17 +866,18 @@ function judgeDeed(p, actor, pc, at) {
|
|
|
865
866
|
export function perceive(p, percepts, options = {}) {
|
|
866
867
|
if (percepts.length === 0 || !p.body.alive)
|
|
867
868
|
return [];
|
|
868
|
-
|
|
869
|
+
const dozeSince = p.body.doze?.since;
|
|
870
|
+
if (dozeSince !== undefined) {
|
|
869
871
|
const wakes = percepts.some((pc) => Number.isFinite(pc.at) &&
|
|
870
|
-
pc.at
|
|
872
|
+
pc.at >= dozeSince &&
|
|
873
|
+
pc.at <= p.now &&
|
|
871
874
|
Number.isFinite(pc.salience) &&
|
|
872
875
|
pc.salience >= DOZE_DEFAULTS.wakeSalience &&
|
|
873
876
|
pc.near !== false &&
|
|
874
877
|
(pc.targetId === p.id || pc.near === true) &&
|
|
875
878
|
(pc.channel === 'heard' || pc.channel === 'felt' || pc.channel === 'social'));
|
|
876
|
-
if (
|
|
877
|
-
|
|
878
|
-
wakeDoze(p, p.now);
|
|
879
|
+
if (wakes)
|
|
880
|
+
wakeDoze(p, p.now);
|
|
879
881
|
}
|
|
880
882
|
const body = readBodyOf(p);
|
|
881
883
|
const attended = attend(p, [...percepts], {
|
|
@@ -1170,9 +1172,13 @@ function thresholdFor(p, load, review) {
|
|
|
1170
1172
|
const t = nextBodyThreshold(p, load, segmentModifiers(p), PERSON_DEFAULTS.interruptThresholds, review, p.ambient ? ambientBodyParams(p, BODY_DEFAULTS) : BODY_DEFAULTS, fastingCtx(p));
|
|
1171
1173
|
return Number.isFinite(t) ? p.now + t : undefined;
|
|
1172
1174
|
}
|
|
1173
|
-
/**
|
|
1175
|
+
/**
|
|
1176
|
+
* Start an activity chosen by `record`. Returns null when dead, or when dozed work
|
|
1177
|
+
* is unresolved. Call `finish` for the stopped activity before starting any replacement,
|
|
1178
|
+
* including voluntary sleep or an activity chosen after a waking percept.
|
|
1179
|
+
*/
|
|
1174
1180
|
export function begin(p, aff, record, opts = {}) {
|
|
1175
|
-
if (!p.body.alive)
|
|
1181
|
+
if (!p.body.alive || p.activity?.dozedAt !== undefined)
|
|
1176
1182
|
return null;
|
|
1177
1183
|
if (p.body.doze?.since !== undefined && aff.mode !== 'sleep')
|
|
1178
1184
|
return null;
|
|
@@ -1385,7 +1391,7 @@ function respondToConscience(p, act, now, completed, minutes) {
|
|
|
1385
1391
|
// Worship closes breaches against God alone; guilt toward a wronged person waits for repair.
|
|
1386
1392
|
const repented = new Set();
|
|
1387
1393
|
for (const b of p.conscience.breaches) {
|
|
1388
|
-
if (!b.repaired && b.victimId === undefined && repent(p, b.id
|
|
1394
|
+
if (!b.repaired && b.victimId === undefined && repent(p, b.id))
|
|
1389
1395
|
repented.add(b.normId);
|
|
1390
1396
|
}
|
|
1391
1397
|
for (const b of p.conscience.breaches)
|
|
@@ -1396,7 +1402,7 @@ function respondToConscience(p, act, now, completed, minutes) {
|
|
|
1396
1402
|
regulate(p, minutes, 'worship');
|
|
1397
1403
|
}
|
|
1398
1404
|
if (completed && (tags.includes('repair') || tags.includes('apologize')) && aff.targetId !== undefined) {
|
|
1399
|
-
recordRepair(p, aff.targetId
|
|
1405
|
+
recordRepair(p, aff.targetId);
|
|
1400
1406
|
release(p, { id: 'guilt', targetId: aff.targetId });
|
|
1401
1407
|
socialEvent(p, { at: now, kind: 'apology', otherId: aff.targetId, byMe: true, magnitude: 0.7 });
|
|
1402
1408
|
}
|
package/dist/persona.js
CHANGED
|
@@ -188,6 +188,7 @@ export function validatePersonaProjection(input) {
|
|
|
188
188
|
keys(input.source, ['schema', 'engine', 'id', 'now']);
|
|
189
189
|
// The 2.14.1 and 2.17.0 portable fields retain their semantics across the bodily opt-ins.
|
|
190
190
|
const compatibleEngine = input.source.engine === ENGINE_VERSION ||
|
|
191
|
+
input.source.engine === '2.21.0' ||
|
|
191
192
|
input.source.engine === '2.17.0' ||
|
|
192
193
|
input.source.engine === '2.14.1';
|
|
193
194
|
if (input.source.schema !== PERSON_SCHEMA || !compatibleEngine)
|
package/dist/presentation.js
CHANGED
|
@@ -68,6 +68,7 @@ export function reasonLine(reason, lexicon) {
|
|
|
68
68
|
'need:water': 'I need to drink first.',
|
|
69
69
|
'need:sleep': 'I need to sleep first.',
|
|
70
70
|
'need:rest': 'I need to rest first.',
|
|
71
|
+
'need:bladder': 'I need to use the toilet first.',
|
|
71
72
|
'need:survival': 'I need to meet an urgent bodily need first.',
|
|
72
73
|
};
|
|
73
74
|
if (Object.hasOwn(lines, reason))
|
|
@@ -360,7 +360,7 @@ export function nafsPatterns(p, inputs) {
|
|
|
360
360
|
ref: 'Qur’an 12:53',
|
|
361
361
|
evident: assentedAgainstNorm.length > 0,
|
|
362
362
|
evidence: p.lexicon?.plainEnglish
|
|
363
|
-
? `The person acted on ${assentedAgainstNorm.length} recent urges despite a rule they hold.`
|
|
363
|
+
? `The person acted on ${assentedAgainstNorm.length} recent ${assentedAgainstNorm.length === 1 ? 'urge' : 'urges'} despite a rule they hold.`
|
|
364
364
|
: `${assentedAgainstNorm.length} recent prompts followed against a held norm`,
|
|
365
365
|
},
|
|
366
366
|
{
|
|
@@ -376,7 +376,7 @@ export function nafsPatterns(p, inputs) {
|
|
|
376
376
|
ref: 'Qur’an 89:27',
|
|
377
377
|
evident: letPass > 0 && assentedAgainstNorm.length === 0 && !reproach,
|
|
378
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'}.`
|
|
379
|
+
? `${letPass} recent ${letPass === 1 ? 'urge' : 'urges'} passed without action. ${assentedAgainstNorm.length} ${assentedAgainstNorm.length === 1 ? 'was' : 'were'} acted on against a held rule. Current guilt or unresolved breach: ${reproach ? 'yes' : 'no'}.`
|
|
380
380
|
: `${letPass} recent prompts let pass, none followed against a norm, no felt reproach`,
|
|
381
381
|
},
|
|
382
382
|
];
|
package/dist/qalb.d.ts
CHANGED
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SCOPE: a read-only narration lens over retained attention evidence, held norms and conviction,
|
|
3
|
-
* mood and the three distinct trust stores. Owns no state and supplies no decision input. It does
|
|
4
|
-
* not infer spiritual health, rank people, judge norms, measure the ruh or compute divine acceptance.
|
|
5
|
-
* Retained memories are evidence available for narration, not a complete attention history.
|
|
6
|
-
*
|
|
7
|
-
* Provenance: research/reconciliation.md §2–4, §7 (Q7: Qur'an 22:46; H9: Bukhari 52 /
|
|
8
|
-
* Muslim 1599a); research/islamic-foundations.md §2 (qalb as an overlapping interpretive view);
|
|
9
|
-
* research/decisions.md, “Qalb, nafs, fitra, ʿaql as state”. These support the lens, not a
|
|
10
|
-
* diagnostic mapping. Sign/zero categories below are engineering descriptions of stored values,
|
|
11
|
-
* not religious categories. Mood and interpersonal trust imply nothing about faith.
|
|
12
|
-
*/
|
|
13
1
|
import type { Person } from './types.ts';
|
|
14
2
|
export interface QalbDescription {
|
|
15
3
|
facet: 'attention' | 'held-norm' | 'mood' | 'trust';
|
package/dist/qalb.js
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SCOPE: a read-only narration lens over retained attention evidence, held norms and conviction,
|
|
3
|
+
* mood and the three distinct trust stores. Owns no state and supplies no decision input. It does
|
|
4
|
+
* not infer spiritual health, rank people, judge norms, measure the ruh or compute divine acceptance.
|
|
5
|
+
* Retained memories are evidence available for narration, not a complete attention history.
|
|
6
|
+
*
|
|
7
|
+
* Provenance: research/reconciliation.md §2–4, §7 (Q7: Qur'an 22:46; H9: Bukhari 52 /
|
|
8
|
+
* Muslim 1599a); research/islamic-foundations.md §2 (qalb as an overlapping interpretive view);
|
|
9
|
+
* research/decisions.md, “Qalb, nafs, fitra, ʿaql as state”. These support the lens, not a
|
|
10
|
+
* diagnostic mapping. Sign/zero categories below are engineering descriptions of stored values,
|
|
11
|
+
* not religious categories. Mood and interpersonal trust imply nothing about faith.
|
|
12
|
+
*/
|
|
13
|
+
import { DEFAULT_NORMS } from './conscience/catalog.js';
|
|
14
|
+
import { nameOf } from './narrate/lexicon.js';
|
|
15
|
+
import { normLabel } from './presentation.js';
|
|
1
16
|
/**
|
|
2
17
|
* Plain English descriptions; no scalar summary, comparison or hidden update.
|
|
3
18
|
* Hosts must not present these records as spiritual health or a religious assessment.
|
|
@@ -19,18 +34,17 @@ export function qalbReading(p) {
|
|
|
19
34
|
if (!p.conscience.norms.length)
|
|
20
35
|
add('held-norm', 'unrecorded', 'Their records contain no held rules.', ['conscience.norms']);
|
|
21
36
|
for (const n of p.conscience.norms) {
|
|
37
|
+
const rule = normLabel(DEFAULT_NORMS.find((rule) => rule.id === n.normId) ?? { id: n.normId, label: n.normId }, { ...p.lexicon, plainEnglish: true });
|
|
22
38
|
add('held-norm', n.conviction === 0 ? 'no-conviction' : 'held-understanding', n.conviction === 0
|
|
23
|
-
?
|
|
24
|
-
:
|
|
39
|
+
? `They have a recorded rule, “${rule}”, with no stated conviction.`
|
|
40
|
+
: `They hold an understanding of the rule “${rule}”. This is their view.`, [`norm:${n.normId}`, `standing:${n.standing}`, 'conscience.norms.conviction']);
|
|
25
41
|
}
|
|
26
42
|
add('mood', p.affect.mood.valence < 0 ? 'unpleasant' : p.affect.mood.valence > 0 ? 'pleasant' : 'neutral', p.affect.mood.valence < 0
|
|
27
43
|
? 'Their recorded mood feels unpleasant.'
|
|
28
44
|
: p.affect.mood.valence > 0
|
|
29
45
|
? 'Their recorded mood feels pleasant.'
|
|
30
46
|
: 'Their recorded mood feels neutral.', ['affect.mood.valence']);
|
|
31
|
-
add('mood', p.affect.mood.arousal === 0 ? 'no-arousal' : 'some-arousal', p.affect.mood.arousal === 0
|
|
32
|
-
? 'Their recorded mood has no arousal.'
|
|
33
|
-
: 'Their recorded mood has some arousal.', ['affect.mood.arousal']);
|
|
47
|
+
add('mood', p.affect.mood.arousal === 0 ? 'no-arousal' : 'some-arousal', p.affect.mood.arousal === 0 ? 'Their recorded mood is calm.' : 'Their recorded mood is stirred.', ['affect.mood.arousal']);
|
|
34
48
|
// Keep stores distinct. Do not substitute relationship trust for source or advising-voice trust.
|
|
35
49
|
const ties = p.social.relationships.map((r) => ({
|
|
36
50
|
store: 'social.relationships',
|
|
@@ -53,10 +67,10 @@ export function qalbReading(p) {
|
|
|
53
67
|
for (const t of trusts) {
|
|
54
68
|
// 0.5 is the numeric range midpoint, not a faith threshold or personal baseline.
|
|
55
69
|
const subject = t.store === 'social.relationships'
|
|
56
|
-
?
|
|
70
|
+
? `the person ${nameOf(p, t.id)}`
|
|
57
71
|
: t.store === 'will.voices'
|
|
58
|
-
?
|
|
59
|
-
:
|
|
72
|
+
? `the advising voice ${nameOf(p, t.id)}`
|
|
73
|
+
: `the information source ${nameOf(p, t.id)}`;
|
|
60
74
|
add('trust', t.trust < 0.5 ? 'below-midpoint' : t.trust > 0.5 ? 'above-midpoint' : 'at-midpoint', t.trust < 0.5
|
|
61
75
|
? `Their trust in ${subject} is below the middle of its range.`
|
|
62
76
|
: t.trust > 0.5
|
package/dist/sim/longrun.d.ts
CHANGED
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
* since a routine day writes only its own person. Births and arrivals are the host's (`birth`, `addPerson` in
|
|
16
16
|
* `onDay`); a person added during the stretch starts on the next day.
|
|
17
17
|
*/
|
|
18
|
-
import { type
|
|
18
|
+
import { type RoutineDay, type RoutineSource } from '../longrun.ts';
|
|
19
19
|
import type { SkillTransfer } from '../skills/index.ts';
|
|
20
|
-
import type { Minute,
|
|
20
|
+
import type { Minute, PersonId } from '../types.ts';
|
|
21
21
|
import { type Community, type LifecourseOptions, type SimEvent } from './sim.ts';
|
|
22
22
|
export declare const LIVE_COMMUNITY_DEFAULTS: {
|
|
23
23
|
/** Upbringing time a day with the parents (engineering assumption: a few waking hours together). */
|
|
@@ -25,7 +25,7 @@ export declare const LIVE_COMMUNITY_DEFAULTS: {
|
|
|
25
25
|
};
|
|
26
26
|
export interface LiveCommunityOptions {
|
|
27
27
|
/** Each living person's routine for a day (`day` is the day number being lived). */
|
|
28
|
-
routineFor
|
|
28
|
+
routineFor: RoutineSource;
|
|
29
29
|
/** At each dawn, before anyone's day: births, arrivals, routine changes. */
|
|
30
30
|
onDay?(c: Community, day: number, at: Minute): void;
|
|
31
31
|
/** Tell each death to everyone with a tie (default true; see `tellDeath`). */
|
package/dist/sim/longrun.js
CHANGED
|
@@ -70,7 +70,7 @@ export function liveCommunity(c, until, opts) {
|
|
|
70
70
|
tick(p, now);
|
|
71
71
|
if (p.now !== now)
|
|
72
72
|
continue; // added mid-stretch with a later clock: joins when the day catches up
|
|
73
|
-
const r = routineDay(p, opts.routineFor(p, day
|
|
73
|
+
const r = routineDay(p, opts.routineFor(p, day), routineOpts);
|
|
74
74
|
if (r.episodes.length > 0 || r.onsets.length > 0 || r.stage || r.died) {
|
|
75
75
|
const list = days[p.id] ?? [];
|
|
76
76
|
list.push(r);
|
package/dist/sim/sim.js
CHANGED
|
@@ -840,10 +840,15 @@ export function stepCommunity(c, world, until, opts = {}) {
|
|
|
840
840
|
at: t,
|
|
841
841
|
personId: p.id,
|
|
842
842
|
kind: 'finish',
|
|
843
|
-
detail:
|
|
843
|
+
detail: outcome.summary ??
|
|
844
|
+
(p.lexicon?.plainEnglish
|
|
845
|
+
? `${p.name} stopped the task “${act.affordance.label}” because they dozed off.`
|
|
846
|
+
: `${act.action} interrupted by doze`),
|
|
847
|
+
decisionId: act.decisionId,
|
|
848
|
+
affordanceId: act.affordanceId,
|
|
844
849
|
action: act.action,
|
|
845
850
|
status: outcome.status,
|
|
846
|
-
});
|
|
851
|
+
}, outcome.summary !== undefined);
|
|
847
852
|
}
|
|
848
853
|
c.idleUntil[p.id] = t + quantum;
|
|
849
854
|
if (!ordinaryDue)
|
|
@@ -995,12 +1000,12 @@ export function stepCommunity(c, world, until, opts = {}) {
|
|
|
995
1000
|
detail: p.lexicon?.plainEnglish
|
|
996
1001
|
? proposal.status === 'declined'
|
|
997
1002
|
? `The shared task “${chosen.label}” could not start because a partner was unavailable.`
|
|
998
|
-
: `${chosen.label} with ${proposal.partnerIds.map((id) => personById(c, id)?.name ?? id).join(', ')}
|
|
1003
|
+
: `${p.name} proposed the shared task “${chosen.label}” with ${proposal.partnerIds.map((id) => personById(c, id)?.name ?? id).join(', ')}.`
|
|
999
1004
|
: `${chosen.label} with ${proposal.partnerIds.join(', ')}`,
|
|
1000
1005
|
affordanceId: chosen.id,
|
|
1001
1006
|
action: chosen.action,
|
|
1002
1007
|
jointId: proposal.id,
|
|
1003
|
-
}, Boolean(p.lexicon?.plainEnglish
|
|
1008
|
+
}, Boolean(p.lexicon?.plainEnglish));
|
|
1004
1009
|
continue;
|
|
1005
1010
|
}
|
|
1006
1011
|
if (act) {
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { EntityId, Minute, NormTag, Person, PersonId, Signed, StateCue, Traits, Unit } from '../types.ts';
|
|
2
2
|
export type CueKind = 'state' | 'place' | 'trait' | 'tie' | 'trust' | 'skill' | 'regard';
|
|
3
3
|
export declare const STATE_CUES: readonly StateCue[];
|
|
4
|
-
/** @deprecated since 2.1: use `TRAIT_KEYS` (types.ts), the same list. Removed in 3.0. */
|
|
5
|
-
export declare const TRAIT_NAMES: readonly (keyof Traits)[];
|
|
6
4
|
export declare const IMPRESSION_DEFAULTS: {
|
|
7
5
|
/** Weight half-life by kind, minutes (0 = no decay). */
|
|
8
6
|
halfLife: Record<CueKind, number>;
|
|
@@ -88,20 +86,10 @@ export declare function setReserve(p: Person, reserve: Partial<Record<StateCue,
|
|
|
88
86
|
* the evidence and how true it is (a figure in the dark tells little and tells it wrong).
|
|
89
87
|
*/
|
|
90
88
|
export declare function glimpse(observer: Person, targetId: PersonId, signs: OutwardSigns, at: Minute, clarity?: Unit): void;
|
|
91
|
-
/** @deprecated since 2.1: pass `at` (and the optional knobs) positionally. Removed in 3.0. */
|
|
92
|
-
export declare function glimpse(observer: Person, targetId: PersonId, signs: OutwardSigns, opts: {
|
|
93
|
-
at: Minute;
|
|
94
|
-
clarity?: Unit;
|
|
95
|
-
}): void;
|
|
96
89
|
/** What someone was seen to do: place fear, ties, trust in a voice and trait evidence (see `ObservedAct`). */
|
|
97
90
|
export declare function observeAct(observer: Person, targetId: PersonId, act: ObservedAct, table?: Record<string, Partial<Record<keyof Traits, Unit>>>): void;
|
|
98
91
|
/** Testimony about someone (their own words, or another's): `value` on `key`, weighted (e.g. by trust in the teller). */
|
|
99
92
|
export declare function hear(observer: Person, targetId: PersonId, key: string, value: number, at: Minute, weight?: Unit): void;
|
|
100
|
-
/** @deprecated since 2.1: pass `at` (and the optional knobs) positionally. Removed in 3.0. */
|
|
101
|
-
export declare function hear(observer: Person, targetId: PersonId, key: string, value: number, opts: {
|
|
102
|
-
at: Minute;
|
|
103
|
-
weight?: Unit;
|
|
104
|
-
}): void;
|
|
105
93
|
/**
|
|
106
94
|
* Years of history condensed (host call, e.g. at the start of a run for people who already know each other): one
|
|
107
95
|
* sample per trait from `traits` with an error that shrinks with familiarity, at weight
|
|
@@ -30,8 +30,6 @@ import { clamp, clamp01, cmpStr, decay, latestPerId, newestN, round } from '../c
|
|
|
30
30
|
import { MINUTES_PER_DAY, MINUTES_PER_YEAR, TRAIT_KEYS } from '../types.js';
|
|
31
31
|
import { judge, SOCIAL_DEFAULTS } from './social.js';
|
|
32
32
|
export const STATE_CUES = ['fatigue', 'pain', 'fear', 'mood'];
|
|
33
|
-
/** @deprecated since 2.1: use `TRAIT_KEYS` (types.ts), the same list. Removed in 3.0. */
|
|
34
|
-
export const TRAIT_NAMES = TRAIT_KEYS;
|
|
35
33
|
export const IMPRESSION_DEFAULTS = {
|
|
36
34
|
/** Weight half-life by kind, minutes (0 = no decay). */
|
|
37
35
|
halfLife: {
|
|
@@ -208,11 +206,16 @@ function erred(observer, targetId, key, at, value, clarity) {
|
|
|
208
206
|
const e = (hash01(`${observer.id}|${targetId}|${key}|${at}`) - 0.5) * IMPRESSION_DEFAULTS.noise;
|
|
209
207
|
return value + e * (1 - clamp01(clarity));
|
|
210
208
|
}
|
|
211
|
-
|
|
209
|
+
/**
|
|
210
|
+
* A look at someone's outward signs (from the composite's `outwardSigns`). `clarity` 0..1 scales both the weight of
|
|
211
|
+
* the evidence and how true it is (a figure in the dark tells little and tells it wrong).
|
|
212
|
+
*/
|
|
213
|
+
export function glimpse(observer, targetId, signs, at, clarity) {
|
|
214
|
+
if (!Number.isFinite(at))
|
|
215
|
+
throw new TypeError('glimpse requires a finite positional time');
|
|
212
216
|
if (targetId === observer.id)
|
|
213
217
|
return;
|
|
214
|
-
const
|
|
215
|
-
const clear = clamp01((typeof when === 'number' ? clarity : when.clarity) ?? 1);
|
|
218
|
+
const clear = clamp01(clarity ?? 1);
|
|
216
219
|
if (clear <= 0)
|
|
217
220
|
return;
|
|
218
221
|
const imp = impressionFor(observer, targetId, true, at);
|
|
@@ -252,11 +255,13 @@ export function observeAct(observer, targetId, act, table = IMPRESSION_DEFAULTS.
|
|
|
252
255
|
}
|
|
253
256
|
}
|
|
254
257
|
}
|
|
255
|
-
|
|
258
|
+
/** Testimony about someone (their own words, or another's): `value` on `key`, weighted (e.g. by trust in the teller). */
|
|
259
|
+
export function hear(observer, targetId, key, value, at, weight) {
|
|
260
|
+
if (!Number.isFinite(at))
|
|
261
|
+
throw new TypeError('hear requires a finite positional time');
|
|
256
262
|
if (targetId === observer.id)
|
|
257
263
|
return;
|
|
258
|
-
const
|
|
259
|
-
const w = clamp01((typeof when === 'number' ? weight : when.weight) ?? 0.5);
|
|
264
|
+
const w = clamp01(weight ?? 0.5);
|
|
260
265
|
if (w <= 0)
|
|
261
266
|
return;
|
|
262
267
|
const imp = impressionFor(observer, targetId, true, at);
|
package/dist/social/social.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* further down; insiders, outsiders and threats in `groups.ts`); the
|
|
13
13
|
* coefficients are engineering defaults, not calibrated estimates.
|
|
14
14
|
*/
|
|
15
|
-
import { clamp, clamp01, clampSigned, cmpStr, decay, dlog,
|
|
15
|
+
import { clamp, clamp01, clampSigned, cmpStr, decay, dlog, round, sigmoid } from '../core/index.js';
|
|
16
16
|
import { MINUTES_PER_DAY, MINUTES_PER_YEAR } from '../types.js';
|
|
17
17
|
import { careFor } from './groups.js';
|
|
18
18
|
import { regardOf } from './impressions.js';
|
|
@@ -554,7 +554,7 @@ export function reputation(people, targetId, table = REPUTATION_TRAITS) {
|
|
|
554
554
|
continue;
|
|
555
555
|
knows = true;
|
|
556
556
|
const slot = traitSum[parsed.trait] ?? { sum: 0, n: 0 };
|
|
557
|
-
slot.sum +=
|
|
557
|
+
slot.sum += sigmoid(b.logOdds);
|
|
558
558
|
slot.n += 1;
|
|
559
559
|
traitSum[parsed.trait] = slot;
|
|
560
560
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SCOPE: opt-in host input ordering for one declared logical tick. Waits for the
|
|
3
|
+
* caller's complete expected identity set, then sorts by sender and sequence.
|
|
4
|
+
* Lexical sender priority is deterministic host policy, not fairness or
|
|
5
|
+
* consensus. For conflicting same-voice requests, earlier senders take priority.
|
|
6
|
+
* Owns no Person, clock, queue or RNG state. Does not supply transport, consensus,
|
|
7
|
+
* payload validation, retries or snapshot storage. Payloads remain caller-owned;
|
|
8
|
+
* hosts must agree on their meaning and save pending input alongside host state.
|
|
9
|
+
*/
|
|
10
|
+
export interface TickInputKey {
|
|
11
|
+
sender: string;
|
|
12
|
+
sequence: number;
|
|
13
|
+
}
|
|
14
|
+
export interface TickInput<T> extends TickInputKey {
|
|
15
|
+
tick: number;
|
|
16
|
+
payload: T;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Returns undefined while a declared input is missing. Unexpected, duplicate or
|
|
20
|
+
* wrong-tick inputs throw. An empty expected set is a complete barrier. This
|
|
21
|
+
* reader never mutates input arrays or envelopes and never reads payload fields.
|
|
22
|
+
* Call only when explicitly opting into this host scheduling convention.
|
|
23
|
+
*/
|
|
24
|
+
export declare function canonicalTickInputs<T>(tick: number, inputs: readonly TickInput<T>[], expected: readonly TickInputKey[]): TickInput<T>[] | undefined;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const MAX_INPUTS = 256;
|
|
2
|
+
function identity(key) {
|
|
3
|
+
if (typeof key.sender !== 'string' ||
|
|
4
|
+
key.sender.length === 0 ||
|
|
5
|
+
key.sender.length > 128 ||
|
|
6
|
+
!Number.isSafeInteger(key.sequence) ||
|
|
7
|
+
key.sequence < 0) {
|
|
8
|
+
throw new Error('Invalid tick input identity');
|
|
9
|
+
}
|
|
10
|
+
return JSON.stringify([key.sender, key.sequence]);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Returns undefined while a declared input is missing. Unexpected, duplicate or
|
|
14
|
+
* wrong-tick inputs throw. An empty expected set is a complete barrier. This
|
|
15
|
+
* reader never mutates input arrays or envelopes and never reads payload fields.
|
|
16
|
+
* Call only when explicitly opting into this host scheduling convention.
|
|
17
|
+
*/
|
|
18
|
+
export function canonicalTickInputs(tick, inputs, expected) {
|
|
19
|
+
if (!Number.isSafeInteger(tick) || tick < 0)
|
|
20
|
+
throw new Error('Invalid logical tick');
|
|
21
|
+
if (inputs.length > MAX_INPUTS || expected.length > MAX_INPUTS) {
|
|
22
|
+
throw new Error('Tick input limit exceeded');
|
|
23
|
+
}
|
|
24
|
+
const wanted = new Set();
|
|
25
|
+
for (const key of expected) {
|
|
26
|
+
const id = identity(key);
|
|
27
|
+
if (wanted.has(id))
|
|
28
|
+
throw new Error('Duplicate expected tick input');
|
|
29
|
+
wanted.add(id);
|
|
30
|
+
}
|
|
31
|
+
const received = new Set();
|
|
32
|
+
for (const input of inputs) {
|
|
33
|
+
const id = identity(input);
|
|
34
|
+
if (input.tick !== tick)
|
|
35
|
+
throw new Error('Input belongs to another tick');
|
|
36
|
+
if (!wanted.has(id))
|
|
37
|
+
throw new Error('Unexpected tick input');
|
|
38
|
+
if (received.has(id))
|
|
39
|
+
throw new Error('Duplicate tick input');
|
|
40
|
+
received.add(id);
|
|
41
|
+
}
|
|
42
|
+
if (received.size !== wanted.size)
|
|
43
|
+
return undefined;
|
|
44
|
+
return inputs
|
|
45
|
+
.map((input) => ({
|
|
46
|
+
tick: input.tick,
|
|
47
|
+
sender: input.sender,
|
|
48
|
+
sequence: input.sequence,
|
|
49
|
+
payload: input.payload,
|
|
50
|
+
}))
|
|
51
|
+
.sort((a, b) => {
|
|
52
|
+
if (a.sender !== b.sender)
|
|
53
|
+
return a.sender < b.sender ? -1 : 1;
|
|
54
|
+
return a.sequence - b.sequence;
|
|
55
|
+
});
|
|
56
|
+
}
|