@dsh-agentic-roleplay/state 0.3.0 → 0.3.2
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 +1 -1
- package/lib/index.d.ts +389 -13
- package/lib/index.js +1232 -52
- package/lib/index.js.map +1 -1
- package/package.json +3 -3
package/lib/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { DatabaseSync } from "node:sqlite";
|
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { Service } from "@deepseek-ai/cordis";
|
|
5
5
|
import z from "@deepseek-ai/schemastery";
|
|
6
|
-
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { randomInt, randomUUID } from "node:crypto";
|
|
7
7
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, renameSync, rmSync } from "node:fs";
|
|
8
8
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
9
9
|
//#region ../dsh-roleplay-model/src/character.ts
|
|
@@ -141,6 +141,15 @@ const MEMORY_KINDS = [
|
|
|
141
141
|
"relationship",
|
|
142
142
|
"knowledge"
|
|
143
143
|
];
|
|
144
|
+
/** How sure a character is of what it believes, from least to most. */
|
|
145
|
+
const MEMORY_CONFIDENCES = [
|
|
146
|
+
"doubtful",
|
|
147
|
+
"likely",
|
|
148
|
+
"sure"
|
|
149
|
+
];
|
|
150
|
+
const DEFAULT_CONFIDENCE = "likely";
|
|
151
|
+
/** Whether a perception speaks for a belief or against it. */
|
|
152
|
+
const EVIDENCE_RELATIONS = ["supports", "contradicts"];
|
|
144
153
|
const THREAD_STATUSES = ["open", "resolved"];
|
|
145
154
|
/** A digest covers a stretch of recent turns closely; a chapter covers older turns briefly. */
|
|
146
155
|
const SUMMARY_LEVELS = ["digest", "chapter"];
|
|
@@ -358,6 +367,31 @@ const TRIGGER_NOTE_KINDS = ["beat", "cue"];
|
|
|
358
367
|
/** At most this many triggers in a story. */
|
|
359
368
|
const MAX_TRIGGERS = 200;
|
|
360
369
|
//#endregion
|
|
370
|
+
//#region ../dsh-roleplay-model/src/dice.ts
|
|
371
|
+
/** What a band of a check makes of an attempt; the same three results a round's outcome has. */
|
|
372
|
+
const DICE_RESULTS = [
|
|
373
|
+
"succeeded",
|
|
374
|
+
"partial",
|
|
375
|
+
"failed"
|
|
376
|
+
];
|
|
377
|
+
/** How a roll is read: by its total, or by how many of the dice came up high enough. */
|
|
378
|
+
const DICE_READINGS = ["total", "successes"];
|
|
379
|
+
/**
|
|
380
|
+
* What the value is compared with: `absolute` reads the value itself, `over` the amount by which it beats the target
|
|
381
|
+
* (a difficulty to meet or exceed), `under` the amount by which it stays below the target (a skill to roll under).
|
|
382
|
+
*/
|
|
383
|
+
const DICE_COMPARISONS = [
|
|
384
|
+
"absolute",
|
|
385
|
+
"over",
|
|
386
|
+
"under"
|
|
387
|
+
];
|
|
388
|
+
/** Rolls and checks are limited so that a story cannot grow without bound. */
|
|
389
|
+
const MAX_DICE_CHECKS = 40;
|
|
390
|
+
const MAX_DICE_BANDS = 20;
|
|
391
|
+
const MAX_DICE_PER_ROLL = 100;
|
|
392
|
+
const MAX_DICE_SIDES = 1e3;
|
|
393
|
+
const MAX_DICE_TERMS = 8;
|
|
394
|
+
//#endregion
|
|
361
395
|
//#region ../dsh-roleplay-model/src/visual.ts
|
|
362
396
|
const VISUAL_SUBJECT_KINDS = [
|
|
363
397
|
"character",
|
|
@@ -403,6 +437,227 @@ const PLOT_NUDGE_MODES = [
|
|
|
403
437
|
"breathing_room"
|
|
404
438
|
];
|
|
405
439
|
//#endregion
|
|
440
|
+
//#region ../dsh-roleplay-model/src/messages-zh.ts
|
|
441
|
+
/** The Chinese of the texts built in code, by the English that stands in the code. A `{name}` is filled in and must stand in both. */
|
|
442
|
+
const ZH = {
|
|
443
|
+
", ": ",",
|
|
444
|
+
"; ": ";",
|
|
445
|
+
": ": ":",
|
|
446
|
+
".": "。",
|
|
447
|
+
" and ": "和",
|
|
448
|
+
"doubtful": "存疑",
|
|
449
|
+
"likely": "较有把握",
|
|
450
|
+
"sure": "确信",
|
|
451
|
+
"open": "畅通",
|
|
452
|
+
"locked": "已上锁",
|
|
453
|
+
"blocked": "被堵住",
|
|
454
|
+
"night": "夜里",
|
|
455
|
+
"morning": "上午",
|
|
456
|
+
"afternoon": "下午",
|
|
457
|
+
"evening": "傍晚",
|
|
458
|
+
"spring": "春季",
|
|
459
|
+
"summer": "夏季",
|
|
460
|
+
"autumn": "秋季",
|
|
461
|
+
"winter": "冬季",
|
|
462
|
+
"today is {holidays}": "今天是{holidays}",
|
|
463
|
+
"Summary": "概述",
|
|
464
|
+
"Personality": "性格",
|
|
465
|
+
"Background": "背景",
|
|
466
|
+
"Appearance": "外貌",
|
|
467
|
+
"How you speak": "说话方式",
|
|
468
|
+
"You are {name}.": "你是{name}。",
|
|
469
|
+
"You are also called {names}.": "你也被称作{names}。",
|
|
470
|
+
"age: {value}": "年龄:{value}",
|
|
471
|
+
"gender: {value}": "性别:{value}",
|
|
472
|
+
"pronouns: {value}": "代词:{value}",
|
|
473
|
+
"- {key} ({name}): {use}": "- {key}({name}):{use}",
|
|
474
|
+
"Rolls {dice}.": "掷 {dice}。",
|
|
475
|
+
"Give a target: the difficulty, the number to meet or beat; set it by how hard the attempt is.": "需给出 target:难度,即要达到或超过的数字;按这次尝试的难易来定。",
|
|
476
|
+
"Give a target: the number to stay under; set it by how hard the attempt is.": "需给出 target:要低于的数字;按这次尝试的难易来定。",
|
|
477
|
+
"It adds the character's {variable}.": "它会加上该角色的 {variable}。",
|
|
478
|
+
"Counts the dice that show {hit} or more.": "统计显示 {hit} 或更高的骰子数。",
|
|
479
|
+
"the player's roll": "玩家的掷骰",
|
|
480
|
+
"The player rolled {dice} ({reason}) and got {total}: dice {faces}. This is a fact, decided by the dice: take it into account when you judge the attempt and decide what happens.": "玩家掷了 {dice}({reason}),得到 {total}:骰面 {faces}。这是由骰子决定的事实:在你判断这次尝试、决定发生什么时把它考虑进去。",
|
|
481
|
+
"The checks of this story:": "本故事的检定:",
|
|
482
|
+
"No checks are defined: use roll_dice only.": "没有定义检定:只用 roll_dice。",
|
|
483
|
+
"Situation:": "当前情形:",
|
|
484
|
+
"Now: {time}.": "现在是:{time}。",
|
|
485
|
+
"Where you are:": "你所在之处:",
|
|
486
|
+
"Present:": "在场的人:",
|
|
487
|
+
" (you)": "(你)",
|
|
488
|
+
"What you know is coming:": "你知道即将发生的事:",
|
|
489
|
+
"Just now you notice:": "此刻你注意到:",
|
|
490
|
+
"How you are (what you can tell of yourself):": "你的状态(你自己能感觉到的):",
|
|
491
|
+
"What you can tell of the others here:": "你能看出在场其他人的情况:",
|
|
492
|
+
"What you have on you:": "你身上带着:",
|
|
493
|
+
"Things here: ": "这里有:",
|
|
494
|
+
"Ways out: ": "出路:",
|
|
495
|
+
"Lying about: ": "散落在周围的:",
|
|
496
|
+
"hidden": "隐蔽",
|
|
497
|
+
" holding {things}": ",里面有{things}",
|
|
498
|
+
"; and {count} more": ";另有{count}项",
|
|
499
|
+
"at {feature}": "在{feature}",
|
|
500
|
+
"; and {count} more you can recall if needed": ";另有{count}项,需要时你可以回想起来",
|
|
501
|
+
"What you know exists (this does not tell you where it is now or what state it is in):": "你知道存在的事物(这并不说明它现在在哪里、处于什么状态):",
|
|
502
|
+
"People: ": "人物:",
|
|
503
|
+
"Factions: ": "组织:",
|
|
504
|
+
"Places: ": "地点:",
|
|
505
|
+
"Routes: ": "路径:",
|
|
506
|
+
"Things: ": "物品:",
|
|
507
|
+
"Your day and what you are after (plans, not orders: keep them, put them off or drop them as the moment and your character suggest):": "你的日程与你想达成的事(只是计划,不是命令:依当下情形和你的性格,可以照做、推迟或放弃):",
|
|
508
|
+
"Your day, now: {activity}{place}, until {until}{note}.": "你此刻的日程:{activity}{place},持续到{until}{note}。",
|
|
509
|
+
"Your day, next: {activity}{place}, from {from}.": "你接下来的日程:{activity}{place},{from}开始。",
|
|
510
|
+
" at {place}": ",在{place}",
|
|
511
|
+
" (not something you would drop lightly)": "(你不会轻易放下)",
|
|
512
|
+
", with {names}": ",与{names}一起",
|
|
513
|
+
"You were to: {title}{place}{with}, at {at}, which has passed and is still open.": "你本应:{title}{place}{with},时间是{at},已经过了,事情仍未了结。",
|
|
514
|
+
"You are due to: {title}{place}{with}, at {at}.": "你约定要:{title}{place}{with},时间是{at}。",
|
|
515
|
+
"What you are after:": "你想达成的事:",
|
|
516
|
+
", by {deadline}, which has passed": ",限期{deadline},已过",
|
|
517
|
+
", by {deadline}": ",限期{deadline}",
|
|
518
|
+
" Next: {plan}.": " 下一步:{plan}。",
|
|
519
|
+
"How you stand with people who bear on this moment (your own view, not objective fact):": "你与和此刻相关的人的关系(是你自己的看法,不是客观事实):",
|
|
520
|
+
" (here)": "(在场)",
|
|
521
|
+
" (not here)": "(不在场)",
|
|
522
|
+
"knows them well": "很了解对方",
|
|
523
|
+
"barely knows them": "几乎不了解对方",
|
|
524
|
+
"trusts them": "信任对方",
|
|
525
|
+
"distrusts them": "不信任对方",
|
|
526
|
+
"is fond of them": "喜欢对方",
|
|
527
|
+
"dislikes them": "不喜欢对方",
|
|
528
|
+
"things are strained between them": "彼此关系紧张",
|
|
529
|
+
"Your affiliations (their goals are pressures and commitments, not commands):": "你的所属组织(其目标是压力和承诺,不是命令):",
|
|
530
|
+
"member": "成员",
|
|
531
|
+
" Goals: {goals}.": " 目标:{goals}。",
|
|
532
|
+
"- {name}: {position}; {status}; commitment {commitment}.": "- {name}:{position};{status};投入程度 {commitment}。",
|
|
533
|
+
" Your view: {attitude}.": " 你的看法:{attitude}。",
|
|
534
|
+
"What you remember that bears on this (only what you took in yourself; do not claim to remember more, and be unsure of what is fuzzy):": "你记得的、与此相关的事(只限你自己亲身经历的;不要声称记得更多,模糊的地方要保持不确定):",
|
|
535
|
+
"What you remember that bears on this: nothing comes to mind.": "你记得的、与此相关的事:想不起任何事。",
|
|
536
|
+
"some time ago": "一段时间以前",
|
|
537
|
+
"just now": "刚才",
|
|
538
|
+
"1 day ago": "1天前",
|
|
539
|
+
"{amount} days ago": "{amount}天前",
|
|
540
|
+
"1 hour ago": "1小时前",
|
|
541
|
+
"{amount} hours ago": "{amount}小时前",
|
|
542
|
+
"1 minute ago": "1分钟前",
|
|
543
|
+
"{amount} minutes ago": "{amount}分钟前",
|
|
544
|
+
"the details are fuzzy": "细节已经模糊",
|
|
545
|
+
"you barely remember": "你几乎记不清了",
|
|
546
|
+
"you are sure of it": "你对此确信无疑",
|
|
547
|
+
"you doubt it": "你对此存疑",
|
|
548
|
+
"something you have seen since speaks against it": "此后你所见所闻有与之相悖的地方",
|
|
549
|
+
"it comes to mind with the rest": "它和其余的事一起浮现在脑海",
|
|
550
|
+
"Earlier in this scene, before this moment:": "这一幕此刻之前发生的事:",
|
|
551
|
+
"What just happened, and what you are reacting to:": "刚才发生的事,以及你在回应的是什么:",
|
|
552
|
+
"- {name} intended: {intent} ({result}) {summary}": "- {name}打算:{intent}({result}){summary}",
|
|
553
|
+
"- {name} said \"{utterance}\" and intended: {intent} ({result}) {summary}": "- {name}说了“{utterance}”,并打算:{intent}({result}){summary}",
|
|
554
|
+
"Your earlier intent did not work out: {intent}\nYou are trying again, knowing the above. Decide again what you do now.": "你先前的打算没有成功:{intent}\n你带着上面这些再试一次。重新决定你现在做什么。",
|
|
555
|
+
"Character: {name}{summary}": "角色:{name}{summary}",
|
|
556
|
+
"What they believe now:": "他们现在的看法:",
|
|
557
|
+
"(nothing yet)": "(暂时没有)",
|
|
558
|
+
"What they have taken in since:": "他们此后经历的事:",
|
|
559
|
+
"Sort their memory.": "请整理他们的记忆。",
|
|
560
|
+
"not acceptable: {problems}": "不可接受:{problems}",
|
|
561
|
+
"{kind}, importance {importance}, {confidence}{about}{dispute}": "{kind},重要性 {importance},{confidence}{about}{dispute}",
|
|
562
|
+
", about {names}": ",关于{names}",
|
|
563
|
+
", something taken in since speaks against it": ",此后经历的事与之相悖",
|
|
564
|
+
"add: at most {max} new beliefs in one pass; merge or choose": "add:一次最多 {max} 条新看法;请合并或取舍",
|
|
565
|
+
"at most {max} changes in one pass": "一次最多 {max} 处修改",
|
|
566
|
+
"{where}: there is no thing it took in numbered {id}": "{where}:没有编号为 {id} 的经历",
|
|
567
|
+
"{where}: confidence is one of {options}": "{where}:confidence 须为 {options} 之一",
|
|
568
|
+
"{where}: unknown kind \"{kind}\"": "{where}:未知的 kind “{kind}”",
|
|
569
|
+
"{where}: say what it believes": "{where}:请写出它相信什么",
|
|
570
|
+
"{where}: at most {max} characters; say it shorter": "{where}:最多 {max} 个字符;请写得更短",
|
|
571
|
+
"{where}: importance runs from {min} to {max}": "{where}:importance 取值为 {min} 到 {max}",
|
|
572
|
+
"{where}: there is no belief numbered {id}": "{where}:没有编号为 {id} 的看法",
|
|
573
|
+
"{where}: one passing moment is not yet a belief; rest it on things from at least two different turns, or on one event that turned the story": "{where}:一个转瞬即逝的瞬间还不是看法;请以至少两个不同回合的经历为依据,或以一件扭转了故事的事件为依据",
|
|
574
|
+
"{where}: to be sure of something it must rest on things from at least two different turns": "{where}:要对某事确信,必须以至少两个不同回合的经历为依据",
|
|
575
|
+
"{where}: \"{name}\" is not a character; name them as they are named": "{where}:“{name}”不是角色;请使用他们的名字",
|
|
576
|
+
"{where}: belief {id} is already changed in this pass": "{where}:看法 {id} 在这一轮中已经修改过",
|
|
577
|
+
"{where}: say what it took in since that bears on it": "{where}:请写出此后与之相关的经历",
|
|
578
|
+
"{where}: thing {id} came before the belief was formed; cite what came after": "{where}:经历 {id} 发生在看法形成之前;请引用之后发生的事",
|
|
579
|
+
"{where}: say what changes": "{where}:请写出有何改变",
|
|
580
|
+
"{where}: the text must be there and at most {max} characters; leave it out to keep the belief as it is": "{where}:文字不能为空,且最多 {max} 个字符;想保持信念原样就不要写这一项",
|
|
581
|
+
"{where}: the confidence, the importance and the numbers are fields of their own, not part of the text": "{where}:把握程度、重要性和编号是各自独立的字段,不属于文字",
|
|
582
|
+
"{where}: name what speaks against the belief in contradicted_by": "{where}:在 contradicted_by 里写出与该信念相悖的内容的编号",
|
|
583
|
+
"{where}: a belief that something contradicts must be doubted: lower its confidence": "{where}:被事实相悖的看法必须存疑:请降低其 confidence",
|
|
584
|
+
"{where}: to doubt a belief, name what contradicts it in contradicted_by": "{where}:要对看法存疑,请在 contradicted_by 中写明与之相悖的经历",
|
|
585
|
+
"{where}: merge into a belief that stays": "{where}:请合并到保留的看法中",
|
|
586
|
+
"Time: {time}.": "时间:{time}。",
|
|
587
|
+
"The story's time moves only as you say: each round takes what you give it (elapsed_seconds in resolve_round, or the default), and advance_time skips ahead.": "故事的时间只按你说的推进:每一轮耗时由你给定(resolve_round 的 elapsed_seconds,或取默认值),advance_time 可以跳过一段时间。",
|
|
588
|
+
"The player was away for {duration} of real time and the story went on meanwhile: characters have been about their own business, so do not assume anyone is where or as the player left them unless the records say so.": "玩家离开了{duration}的真实时间,其间故事仍在继续:角色们各忙各的事,除非记录另有说明,不要假定任何人还在玩家离开时的位置或状态。",
|
|
589
|
+
"The story's time follows the real clock: it cannot be skipped, and each round takes as long as it really took.": "故事时间跟随真实时钟:无法跳过,每一轮耗时就是它实际花费的时间。",
|
|
590
|
+
"By their routines since you last looked: {moves}.": "按各自的日程,自你上次查看以来:{moves}。",
|
|
591
|
+
"{name} went to {place} ({activity})": "{name}去了{place}({activity})",
|
|
592
|
+
"With the player's character, and their days: {days}. The characters decide for themselves whether to keep to their day; the moment may keep them.": "与玩家角色在一起的人及其日程:{days}。是否照日程行事由角色自己决定;当下的情形也可能拖住他们。",
|
|
593
|
+
", at {place}": ",在{place}",
|
|
594
|
+
"\"{title}\" ({with}{place}) was due {when} and is still open": "“{title}”({with}{place})原定{when},仍未了结",
|
|
595
|
+
"\"{title}\" ({with}{place}) is due {when}": "“{title}”({with}{place})定于{when}",
|
|
596
|
+
"Commitments due soon or overdue: {entries}.": "即将到期或已逾期的约定:{entries}。",
|
|
597
|
+
"named in the move": "被这一步行动点到",
|
|
598
|
+
"in the scene": "在场",
|
|
599
|
+
"own goal: {goal}": "自身目标:{goal}",
|
|
600
|
+
"faction pressure: {goal}": "组织压力:{goal}",
|
|
601
|
+
"high tension toward the player": "对玩家高度紧张",
|
|
602
|
+
"distrusts the player": "不信任玩家",
|
|
603
|
+
"cares about the player": "在意玩家",
|
|
604
|
+
"dislikes the player": "不喜欢玩家",
|
|
605
|
+
"Likely characters to activate, in priority order (advice only; activate only whoever the moment actually reaches):": "可能需要启动的角色,按优先级排列(仅供参考;只启动当下真正牵涉到的人):",
|
|
606
|
+
"socially implicated": "社交上被牵涉",
|
|
607
|
+
"The player's character is in {place}": "玩家角色在{place}",
|
|
608
|
+
"Characters here:": "在这里的角色:",
|
|
609
|
+
"Characters elsewhere (they perceive nothing here unless the moment reaches them):": "在别处的角色(除非当下波及他们,否则感知不到这里的事):",
|
|
610
|
+
"{member} - in {place} ({key})": "{member} - 在{place}({key})",
|
|
611
|
+
"Characters with no place (not part of this scene; they cannot be asked to act):": "没有所在地点的角色(不属于这一幕;不能让他们行动):",
|
|
612
|
+
"the player's character, \"{key}\"": "玩家角色,“{key}”",
|
|
613
|
+
"acts through \"{key}\"": "通过“{key}”行动",
|
|
614
|
+
"Currently: {state}": "目前:{state}",
|
|
615
|
+
"Scene before turn {turn}:": "第 {turn} 回合之前的场景:",
|
|
616
|
+
"What characters have on them:": "角色们身上带着:",
|
|
617
|
+
"The world acted since the last turn, by itself:": "自上一回合以来,世界自行发生的事:",
|
|
618
|
+
"Known beforehand, still to come (who knows of it; it will happen by itself when its time comes):": "事先已知、尚未发生的事(谁知道;时候到了会自行发生):",
|
|
619
|
+
"[known to {who}]": "[知情者:{who}]",
|
|
620
|
+
"Weave these into the story when it fits, then say you did with addressed in narrate (each stays for a few turns and is then let go):": "合适时把这些织入故事,然后在 narrate 的 addressed 中说明已处理(每条会保留几个回合,之后放弃):",
|
|
621
|
+
"What the story keeps track of (change it with variable_changes in an outcome when what happened calls for it):": "故事追踪的内容(发生的事需要时,在结果中用 variable_changes 修改):",
|
|
622
|
+
"Still open in the story:": "故事中仍未了结的事:",
|
|
623
|
+
"Earlier events that bear on what the player just did (from the records of the story):": "与玩家刚才的行动相关的早先事件(来自故事记录):",
|
|
624
|
+
"turn {turn}": "第 {turn} 回合",
|
|
625
|
+
"You were to {title}, at {when}": "你本应{title},时间是{when}",
|
|
626
|
+
"You were to {title} at {place}, at {when}": "你本应在{place}:{title},时间是{when}",
|
|
627
|
+
" ({note})": "({note})",
|
|
628
|
+
"It is {now} now and the time has passed.": "现在是{now},时间已经过了。",
|
|
629
|
+
"It is {now} now.": "现在是{now}。",
|
|
630
|
+
"It was with: {who}.": "同你一起的有:{who}。",
|
|
631
|
+
"You are not with the player's character, and nothing of this happens in front of anyone: decide what you do about it.": "你不和玩家的角色在一起,这件事也不会发生在任何人面前:决定你要怎么处理。",
|
|
632
|
+
"The time you had for \"{goal}\" has passed ({when}; it is {now} now)": "你为“{goal}”预留的时间已过({when};现在是{now})",
|
|
633
|
+
"You are not with the player's character and nothing of this happens in front of anyone: decide what you do about it.": "你不和玩家的角色在一起,这件事也不会发生在任何人面前:决定你要怎么处理。",
|
|
634
|
+
"{names} noticed: {text}": "{names}注意到:{text}",
|
|
635
|
+
"{name} went to {place}.": "{name}去了{place}。",
|
|
636
|
+
"## MCP resource servers\n\nUse {tools} with the server argument naming an MCP server.": "## MCP 资源服务器\n\n使用 {tools},server 参数填 MCP 服务器的名称。",
|
|
637
|
+
"## MCP resource servers\n\nUse {tools} with one of these names as the server argument: {servers}.": "## MCP 资源服务器\n\n使用 {tools},server 参数填下列名称之一:{servers}。"
|
|
638
|
+
};
|
|
639
|
+
//#endregion
|
|
640
|
+
//#region ../dsh-roleplay-model/src/messages.ts
|
|
641
|
+
/** The languages the texts a model reads are written in. English is the source: a text is its own key. */
|
|
642
|
+
const LANGUAGES = ["en", "zh"];
|
|
643
|
+
/** The language a setting such as `zh-CN` or `en` stands for; English when it is none that is known. */
|
|
644
|
+
function languageOf(code) {
|
|
645
|
+
return (code ?? "").trim().toLowerCase().startsWith("zh") ? "zh" : "en";
|
|
646
|
+
}
|
|
647
|
+
const fill = (template, values) => values === void 0 ? template : template.replace(/\{(\w+)\}/g, (whole, name) => name in values ? String(values[name]) : whole);
|
|
648
|
+
const CATALOGUES = { zh: ZH };
|
|
649
|
+
/**
|
|
650
|
+
* The texts the engine builds for a model (briefs, reports, refusals) in a language. The text written in the code is the English one and the key of
|
|
651
|
+
* the others; a text a language lacks is told in English, and a test keeps that from happening.
|
|
652
|
+
*/
|
|
653
|
+
function translator(language) {
|
|
654
|
+
const catalogue = language === "en" ? void 0 : CATALOGUES[language];
|
|
655
|
+
return (text, values) => fill(catalogue?.[text] ?? text, values);
|
|
656
|
+
}
|
|
657
|
+
/** The texts with a translation, for the test that every text has one. */
|
|
658
|
+
const translated = (language) => Object.keys(CATALOGUES[language]);
|
|
659
|
+
const translation = (language, text) => CATALOGUES[language][text];
|
|
660
|
+
//#endregion
|
|
406
661
|
//#region src/clock.ts
|
|
407
662
|
const systemClock = () => /* @__PURE__ */ new Date();
|
|
408
663
|
//#endregion
|
|
@@ -562,6 +817,7 @@ const toWindow = (row) => ({
|
|
|
562
817
|
id: row.id,
|
|
563
818
|
characterId: row.character_id,
|
|
564
819
|
days: parseNumberList(row.days, "routine_windows.days"),
|
|
820
|
+
on: parseStringList(row.filters, "routine_windows.filters"),
|
|
565
821
|
start: row.start_minute,
|
|
566
822
|
end: row.end_minute,
|
|
567
823
|
activity: row.activity,
|
|
@@ -595,10 +851,11 @@ var SqliteRoutineRepository = class {
|
|
|
595
851
|
if (this.db.get("SELECT 1 FROM characters WHERE id = ?", [characterId]) === void 0) throw new NotFoundError(`character ${characterId} does not exist`);
|
|
596
852
|
this.db.run("DELETE FROM routine_windows WHERE character_id = ?", [characterId]);
|
|
597
853
|
windows.forEach((window, index) => {
|
|
598
|
-
this.db.run("INSERT INTO routine_windows (character_id, ordinal, days, start_minute, end_minute, activity, location_id, feature_key, position, interruptible) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
|
854
|
+
this.db.run("INSERT INTO routine_windows (character_id, ordinal, days, filters, start_minute, end_minute, activity, location_id, feature_key, position, interruptible) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
|
599
855
|
characterId,
|
|
600
856
|
index,
|
|
601
857
|
toJson([...new Set(window.days)].sort()),
|
|
858
|
+
toJson([...new Set(window.on ?? [])]),
|
|
602
859
|
window.start,
|
|
603
860
|
window.end,
|
|
604
861
|
window.activity.trim(),
|
|
@@ -707,6 +964,226 @@ var SqliteEngagementRepository = class {
|
|
|
707
964
|
}
|
|
708
965
|
};
|
|
709
966
|
//#endregion
|
|
967
|
+
//#region src/records/calendar.ts
|
|
968
|
+
/**
|
|
969
|
+
* Which days a thing applies on, beyond the days of the week: whole months, a date every year or once, a stretch of dates, the day of the month,
|
|
970
|
+
* the first or last Monday of a month, a season, and the holidays a world names. A day is told by tokens (`dec`, `12-24`, `1912-09-21..1912-09-22`,
|
|
971
|
+
* `dom:1`, `nth:mon:1`, `season:winter`, `holiday:founders-day`, `fri`); a token with `!` before it is a day it does not apply on. A list holds on a
|
|
972
|
+
* day when it names no day to apply on or one of them is today, and none of the days to leave out is.
|
|
973
|
+
*/
|
|
974
|
+
const MONTH_NAMES = [
|
|
975
|
+
"jan",
|
|
976
|
+
"feb",
|
|
977
|
+
"mar",
|
|
978
|
+
"apr",
|
|
979
|
+
"may",
|
|
980
|
+
"jun",
|
|
981
|
+
"jul",
|
|
982
|
+
"aug",
|
|
983
|
+
"sep",
|
|
984
|
+
"oct",
|
|
985
|
+
"nov",
|
|
986
|
+
"dec"
|
|
987
|
+
];
|
|
988
|
+
const SEASONS = [
|
|
989
|
+
"spring",
|
|
990
|
+
"summer",
|
|
991
|
+
"autumn",
|
|
992
|
+
"winter"
|
|
993
|
+
];
|
|
994
|
+
const NO_CALENDAR = {
|
|
995
|
+
hemisphere: "north",
|
|
996
|
+
holidays: []
|
|
997
|
+
};
|
|
998
|
+
const MAX_TOKEN = 60;
|
|
999
|
+
const daysIn = (year, month) => new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
1000
|
+
const DATE = /^(?:(\d{4})-)?(\d{2})-(\d{2})$/;
|
|
1001
|
+
function dateOf(text) {
|
|
1002
|
+
const found = DATE.exec(text);
|
|
1003
|
+
if (found === null) return void 0;
|
|
1004
|
+
const [, year, month, day] = found;
|
|
1005
|
+
const m = Number(month);
|
|
1006
|
+
const d = Number(day);
|
|
1007
|
+
if (m < 1 || m > 12 || d < 1 || d > daysIn(year === void 0 ? 2024 : Number(year), m)) return void 0;
|
|
1008
|
+
return {
|
|
1009
|
+
...year === void 0 ? {} : { year: Number(year) },
|
|
1010
|
+
month: m,
|
|
1011
|
+
day: d
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
/** What a token says, or why it says nothing. */
|
|
1015
|
+
function parseDayToken(raw) {
|
|
1016
|
+
const negated = raw.startsWith("!");
|
|
1017
|
+
const text = (negated ? raw.slice(1) : raw).trim().toLowerCase();
|
|
1018
|
+
const fail = (problem) => ({ problem: `"${raw}": ${problem}` });
|
|
1019
|
+
if (text === "" || text.length > MAX_TOKEN) return fail("a day is told in a few words");
|
|
1020
|
+
const weekday = WEEKDAY_NAMES.indexOf(text);
|
|
1021
|
+
if (weekday >= 0) return {
|
|
1022
|
+
token: {
|
|
1023
|
+
kind: "weekday",
|
|
1024
|
+
weekday
|
|
1025
|
+
},
|
|
1026
|
+
negated
|
|
1027
|
+
};
|
|
1028
|
+
const month = MONTH_NAMES.indexOf(text);
|
|
1029
|
+
if (month >= 0) return {
|
|
1030
|
+
token: {
|
|
1031
|
+
kind: "month",
|
|
1032
|
+
month: month + 1
|
|
1033
|
+
},
|
|
1034
|
+
negated
|
|
1035
|
+
};
|
|
1036
|
+
const one = dateOf(text);
|
|
1037
|
+
if (one !== void 0) return {
|
|
1038
|
+
token: {
|
|
1039
|
+
kind: "date",
|
|
1040
|
+
...one
|
|
1041
|
+
},
|
|
1042
|
+
negated
|
|
1043
|
+
};
|
|
1044
|
+
if (text.includes("..")) {
|
|
1045
|
+
const [from, to, ...rest] = text.split("..");
|
|
1046
|
+
const start = from === void 0 ? void 0 : dateOf(from);
|
|
1047
|
+
const end = to === void 0 ? void 0 : dateOf(to);
|
|
1048
|
+
if (rest.length > 0 || start === void 0 || end === void 0 || start.year === void 0 !== (end.year === void 0)) return fail("a stretch is two dates of the same kind: `12-20..01-05` every year, or `1912-09-21..1912-09-22` once");
|
|
1049
|
+
return {
|
|
1050
|
+
token: {
|
|
1051
|
+
kind: "span",
|
|
1052
|
+
from: start,
|
|
1053
|
+
to: end
|
|
1054
|
+
},
|
|
1055
|
+
negated
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
const [head, a, b] = text.split(":");
|
|
1059
|
+
if (head === "dom" && a !== void 0 && b === void 0) {
|
|
1060
|
+
if (a === "last") return {
|
|
1061
|
+
token: {
|
|
1062
|
+
kind: "dom",
|
|
1063
|
+
day: "last"
|
|
1064
|
+
},
|
|
1065
|
+
negated
|
|
1066
|
+
};
|
|
1067
|
+
const day = Number(a);
|
|
1068
|
+
return Number.isInteger(day) && day >= 1 && day <= 31 ? {
|
|
1069
|
+
token: {
|
|
1070
|
+
kind: "dom",
|
|
1071
|
+
day
|
|
1072
|
+
},
|
|
1073
|
+
negated
|
|
1074
|
+
} : fail("the day of the month is 1 to 31, or `dom:last`");
|
|
1075
|
+
}
|
|
1076
|
+
if (head === "nth" && a !== void 0 && b !== void 0) {
|
|
1077
|
+
const day = WEEKDAY_NAMES.indexOf(a);
|
|
1078
|
+
const n = b === "last" ? "last" : Number(b);
|
|
1079
|
+
return day >= 0 && (n === "last" || Number.isInteger(n) && n >= 1 && n <= 5) ? {
|
|
1080
|
+
token: {
|
|
1081
|
+
kind: "nth",
|
|
1082
|
+
weekday: day,
|
|
1083
|
+
n
|
|
1084
|
+
},
|
|
1085
|
+
negated
|
|
1086
|
+
} : fail("`nth:mon:1` is the first Monday of a month, `nth:fri:last` the last Friday");
|
|
1087
|
+
}
|
|
1088
|
+
if (head === "season" && a !== void 0 && b === void 0) {
|
|
1089
|
+
const season = SEASONS.find((candidate) => candidate === a);
|
|
1090
|
+
return season === void 0 ? fail(`a season is one of ${SEASONS.join(", ")}`) : {
|
|
1091
|
+
token: {
|
|
1092
|
+
kind: "season",
|
|
1093
|
+
season
|
|
1094
|
+
},
|
|
1095
|
+
negated
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
if (head === "holiday" && a !== void 0 && b === void 0 && a !== "") return {
|
|
1099
|
+
token: {
|
|
1100
|
+
kind: "holiday",
|
|
1101
|
+
id: a
|
|
1102
|
+
},
|
|
1103
|
+
negated
|
|
1104
|
+
};
|
|
1105
|
+
return fail("not a day: use a weekday (`mon`), a month (`dec`), a date (`12-24`, `1912-09-21`), a stretch (`12-20..01-05`), `dom:1`, `nth:mon:1`, `season:winter` or `holiday:<id>`");
|
|
1106
|
+
}
|
|
1107
|
+
/** What is wrong with a list of day tokens, each with where it is. `holidays` are those the world names, when `holiday:` may be used. */
|
|
1108
|
+
function dayTokenProblems(tokens, path, holidays) {
|
|
1109
|
+
return tokens.flatMap((raw) => {
|
|
1110
|
+
const parsed = parseDayToken(raw);
|
|
1111
|
+
if ("problem" in parsed) return [`${path}: ${parsed.problem}`];
|
|
1112
|
+
if (parsed.token.kind === "holiday") {
|
|
1113
|
+
if (holidays === void 0) return [`${path}: "${raw}": a holiday cannot be named here`];
|
|
1114
|
+
if (holidays !== "any" && !holidays.has(parsed.token.id)) return [`${path}: "${raw}": the world names no holiday "${parsed.token.id}"`];
|
|
1115
|
+
}
|
|
1116
|
+
return [];
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
/** The season a day is in, by the months of its hemisphere (winter is December to February in the north, June to August in the south). */
|
|
1120
|
+
function seasonOf(day, hemisphere = "north") {
|
|
1121
|
+
const north = day.month === 12 || day.month <= 2 ? "winter" : day.month <= 5 ? "spring" : day.month <= 8 ? "summer" : "autumn";
|
|
1122
|
+
if (hemisphere === "north") return north;
|
|
1123
|
+
return north === "winter" ? "summer" : north === "summer" ? "winter" : north === "spring" ? "autumn" : "spring";
|
|
1124
|
+
}
|
|
1125
|
+
const key = (month, day) => month * 100 + day;
|
|
1126
|
+
function within$1(day, from, to) {
|
|
1127
|
+
if (from.year !== void 0 && to.year !== void 0) {
|
|
1128
|
+
const at = day.year * 1e4 + key(day.month, day.day);
|
|
1129
|
+
return at >= from.year * 1e4 + key(from.month, from.day) && at <= to.year * 1e4 + key(to.month, to.day);
|
|
1130
|
+
}
|
|
1131
|
+
const at = key(day.month, day.day);
|
|
1132
|
+
const start = key(from.month, from.day);
|
|
1133
|
+
const end = key(to.month, to.day);
|
|
1134
|
+
return start <= end ? at >= start && at <= end : at >= start || at <= end;
|
|
1135
|
+
}
|
|
1136
|
+
function matches(token, day, calendar) {
|
|
1137
|
+
switch (token.kind) {
|
|
1138
|
+
case "weekday": return day.weekday === token.weekday;
|
|
1139
|
+
case "month": return day.month === token.month;
|
|
1140
|
+
case "date": return day.month === token.month && day.day === token.day && (token.year === void 0 || day.year === token.year);
|
|
1141
|
+
case "span": return within$1(day, token.from, token.to);
|
|
1142
|
+
case "dom": return token.day === "last" ? day.day === daysIn(day.year, day.month) : day.day === token.day;
|
|
1143
|
+
case "nth": return day.weekday === token.weekday && (token.n === "last" ? day.day + 7 > daysIn(day.year, day.month) : Math.ceil(day.day / 7) === token.n);
|
|
1144
|
+
case "season": return seasonOf(day, calendar.hemisphere) === token.season;
|
|
1145
|
+
case "holiday": return holidaysOn(calendar, day).some((holiday) => holiday.id === token.id);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
/** Whether a list of day tokens holds on a day; tokens that say nothing sensible count as not matching. An empty list holds on every day. */
|
|
1149
|
+
function dayMatches(tokens, day, calendar = NO_CALENDAR) {
|
|
1150
|
+
const parsed = tokens.flatMap((raw) => {
|
|
1151
|
+
const found = parseDayToken(raw);
|
|
1152
|
+
return "problem" in found ? [] : [found];
|
|
1153
|
+
});
|
|
1154
|
+
const wanted = parsed.filter((entry) => !entry.negated);
|
|
1155
|
+
const unwanted = parsed.filter((entry) => entry.negated);
|
|
1156
|
+
return (wanted.length === 0 || wanted.some((entry) => matches(entry.token, day, calendar))) && !unwanted.some((entry) => matches(entry.token, day, calendar));
|
|
1157
|
+
}
|
|
1158
|
+
/** The holidays that fall on a day. A holiday is told by days that are no holidays themselves, so that none can lead back to itself. */
|
|
1159
|
+
function holidaysOn(calendar, day) {
|
|
1160
|
+
return calendar.holidays.filter((holiday) => holiday.on.length > 0 && dayMatches(holiday.on, day, {
|
|
1161
|
+
...calendar,
|
|
1162
|
+
holidays: []
|
|
1163
|
+
}));
|
|
1164
|
+
}
|
|
1165
|
+
const CALENDAR_KEY = "calendar";
|
|
1166
|
+
/** The calendar a story was started with, from its metadata; none when the world has none. */
|
|
1167
|
+
function readCalendar(metadata) {
|
|
1168
|
+
const stored = metadata.getValue(CALENDAR_KEY);
|
|
1169
|
+
if (typeof stored !== "object" || stored === null || Array.isArray(stored)) return NO_CALENDAR;
|
|
1170
|
+
const { hemisphere, holidays } = stored;
|
|
1171
|
+
return {
|
|
1172
|
+
hemisphere: hemisphere === "south" ? "south" : "north",
|
|
1173
|
+
holidays: Array.isArray(holidays) ? holidays.flatMap((entry) => {
|
|
1174
|
+
const { id, name, on } = entry ?? {};
|
|
1175
|
+
return typeof id === "string" && typeof name === "string" && Array.isArray(on) ? [{
|
|
1176
|
+
id,
|
|
1177
|
+
name,
|
|
1178
|
+
on: on.filter((token) => typeof token === "string")
|
|
1179
|
+
}] : [];
|
|
1180
|
+
}) : []
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
const writeCalendar = (metadata, calendar) => {
|
|
1184
|
+
metadata.setValue(CALENDAR_KEY, calendar);
|
|
1185
|
+
};
|
|
1186
|
+
//#endregion
|
|
710
1187
|
//#region src/records/time.ts
|
|
711
1188
|
/** Instants are UTC milliseconds; a time zone only decides how they read. */
|
|
712
1189
|
const SECOND = 1e3;
|
|
@@ -834,9 +1311,19 @@ function parseInstant(text, zone) {
|
|
|
834
1311
|
}
|
|
835
1312
|
throw new RangeError(`"${text}" is not a date and time: write it as 1912-10-04T19:30 (or with an offset, 1912-10-04T19:30+01:00)`);
|
|
836
1313
|
}
|
|
837
|
-
|
|
838
|
-
|
|
1314
|
+
const WEEKDAYS_ZH = [
|
|
1315
|
+
"星期日",
|
|
1316
|
+
"星期一",
|
|
1317
|
+
"星期二",
|
|
1318
|
+
"星期三",
|
|
1319
|
+
"星期四",
|
|
1320
|
+
"星期五",
|
|
1321
|
+
"星期六"
|
|
1322
|
+
];
|
|
1323
|
+
/** `Tuesday 4 October 1912, 21:40`, or `1912年10月4日 星期二 21:40`. */
|
|
1324
|
+
function describeInstant(instant, zone, language = "en") {
|
|
839
1325
|
const local = localTime(instant, zone);
|
|
1326
|
+
if (language === "zh") return `${local.year}年${local.month}月${local.day}日 ${WEEKDAYS_ZH[local.weekday]} ${pad$1(local.hour)}:${pad$1(local.minute)}`;
|
|
840
1327
|
return `${WEEKDAYS[local.weekday]} ${local.day} ${MONTHS[local.month - 1]} ${local.year}, ${pad$1(local.hour)}:${pad$1(local.minute)}`;
|
|
841
1328
|
}
|
|
842
1329
|
/** `1912-10-04T21:40:00` as the wall clock of a zone reads, the form a file or a person can write back. */
|
|
@@ -856,9 +1343,25 @@ function dayPart(minuteOfDay) {
|
|
|
856
1343
|
const hour = Math.floor(minuteOfDay / 60);
|
|
857
1344
|
return hour < 5 || hour >= 22 ? "night" : hour < 12 ? "morning" : hour < 17 ? "afternoon" : "evening";
|
|
858
1345
|
}
|
|
859
|
-
/** In words and rounded to what matters: `45 seconds`, `3 hours 12 minutes`, `2 days 3 hours
|
|
860
|
-
function describeDuration(milliseconds) {
|
|
1346
|
+
/** In words and rounded to what matters: `45 seconds`, `3 hours 12 minutes`, `2 days 3 hours`; or `45秒`, `3小时12分钟`, `2天3小时`. */
|
|
1347
|
+
function describeDuration(milliseconds, language = "en") {
|
|
861
1348
|
const total = Math.max(0, Math.round(milliseconds / SECOND));
|
|
1349
|
+
if (language === "zh") {
|
|
1350
|
+
if (total < 60) return `${total}秒`;
|
|
1351
|
+
const parts = [];
|
|
1352
|
+
let left = total;
|
|
1353
|
+
for (const [name, size] of [
|
|
1354
|
+
["天", 86400],
|
|
1355
|
+
["小时", 3600],
|
|
1356
|
+
["分钟", 60]
|
|
1357
|
+
]) {
|
|
1358
|
+
const count = Math.floor(left / size);
|
|
1359
|
+
left -= count * size;
|
|
1360
|
+
if (count > 0) parts.push(`${count}${name}`);
|
|
1361
|
+
if (parts.length === 2) break;
|
|
1362
|
+
}
|
|
1363
|
+
return parts.join("");
|
|
1364
|
+
}
|
|
862
1365
|
if (total < 60) return `${total} second${total === 1 ? "" : "s"}`;
|
|
863
1366
|
const units = [
|
|
864
1367
|
["day", 86400],
|
|
@@ -1114,6 +1617,8 @@ function prepareAgenda(target, edit, context) {
|
|
|
1114
1617
|
if (found < 0) problems.push(`${here}.days: "${day}" is not a day (${WEEKDAY_NAMES.join(", ")})`);
|
|
1115
1618
|
return found < 0 ? [] : [found];
|
|
1116
1619
|
});
|
|
1620
|
+
const holidays = new Set(readCalendar(target.metadata).holidays.map((holiday) => holiday.id));
|
|
1621
|
+
problems.push(...dayTokenProblems(list$1(window.on), `${here}.on`, holidays));
|
|
1117
1622
|
if (window.location !== void 0) {
|
|
1118
1623
|
placeKnown(window.location, `${here}.location`);
|
|
1119
1624
|
const place = context.places.get(window.location);
|
|
@@ -1122,6 +1627,7 @@ function prepareAgenda(target, edit, context) {
|
|
|
1122
1627
|
if (start === void 0 || end === void 0) return [];
|
|
1123
1628
|
return [{
|
|
1124
1629
|
days,
|
|
1630
|
+
on: [...list$1(window.on)],
|
|
1125
1631
|
start,
|
|
1126
1632
|
end,
|
|
1127
1633
|
activity: window.activity.trim(),
|
|
@@ -2143,8 +2649,11 @@ function parseCondition(value, path, problems, count, depth = 0) {
|
|
|
2143
2649
|
to: text(value["window"], "to") ?? ""
|
|
2144
2650
|
} : void 0;
|
|
2145
2651
|
const days = Array.isArray(value["days"]) ? value["days"].filter((day) => typeof day === "string") : void 0;
|
|
2146
|
-
|
|
2147
|
-
|
|
2652
|
+
const on = Array.isArray(value["on"]) ? value["on"].filter((day) => typeof day === "string") : void 0;
|
|
2653
|
+
if (value["on"] !== void 0 && on?.length !== value["on"]?.length) problems.push(`${path}.on: a list of days (months, dates, seasons, holidays)`);
|
|
2654
|
+
if (on !== void 0) problems.push(...dayTokenProblems(on, `${path}.on`, "any"));
|
|
2655
|
+
if (from === void 0 && to === void 0 && window === void 0 && days === void 0 && on === void 0) {
|
|
2656
|
+
problems.push(`${path}: a time condition needs from, to, window, days or on`);
|
|
2148
2657
|
return;
|
|
2149
2658
|
}
|
|
2150
2659
|
if (window !== void 0 && (parseTimeOfDay(window.from) === void 0 || parseTimeOfDay(window.to) === void 0 || window.from === window.to)) problems.push(`${path}.window: from and to are different times of day, HH:MM`);
|
|
@@ -2155,7 +2664,8 @@ function parseCondition(value, path, problems, count, depth = 0) {
|
|
|
2155
2664
|
...from === void 0 ? {} : { from },
|
|
2156
2665
|
...to === void 0 ? {} : { to },
|
|
2157
2666
|
...window === void 0 ? {} : { window },
|
|
2158
|
-
...days === void 0 ? {} : { days }
|
|
2667
|
+
...days === void 0 ? {} : { days },
|
|
2668
|
+
...on === void 0 ? {} : { on }
|
|
2159
2669
|
};
|
|
2160
2670
|
}
|
|
2161
2671
|
case "place": {
|
|
@@ -2407,7 +2917,7 @@ function conditionProblems(target, condition, path, known, self = false) {
|
|
|
2407
2917
|
const character = (key) => self && key === "@self" || target.characters.list().some((entry) => characterKey(entry) === key) ? [] : [`${path}: there is no character "${key}"`];
|
|
2408
2918
|
const place = (key) => target.locations.list().some((entry) => locationKey(entry) === key) ? [] : [`${path}: there is no place "${key}"`];
|
|
2409
2919
|
switch (condition.type) {
|
|
2410
|
-
case "time": return [];
|
|
2920
|
+
case "time": return dayTokenProblems(condition.on ?? [], `${path}.on`, new Set(readCalendar(target.metadata).holidays.map((holiday) => holiday.id)));
|
|
2411
2921
|
case "place": return [...character(condition.character), ...place(condition.location)];
|
|
2412
2922
|
case "variable": {
|
|
2413
2923
|
const def = target.variables.def(condition.variable);
|
|
@@ -2503,14 +3013,20 @@ function holds(target, condition, clock) {
|
|
|
2503
3013
|
if (condition.from !== void 0 && now < parseInstant(condition.from, zone)) return false;
|
|
2504
3014
|
if (condition.to !== void 0 && now >= parseInstant(condition.to, zone)) return false;
|
|
2505
3015
|
const local = localTime(now, zone);
|
|
3016
|
+
const calendar = readCalendar(target.metadata);
|
|
3017
|
+
/** The day a time belongs to: the morning part of a window past midnight belongs to the day before. */
|
|
3018
|
+
const dayAt = (shift) => addLocalDays(now, zone, shift);
|
|
3019
|
+
const onDay = (shift) => {
|
|
3020
|
+
const day = dayAt(shift);
|
|
3021
|
+
return (condition.days === void 0 || condition.days.length === 0 || condition.days.includes(WEEKDAY_NAMES[day.weekday] ?? "")) && (condition.on === void 0 || dayMatches(condition.on, day, calendar));
|
|
3022
|
+
};
|
|
2506
3023
|
if (condition.window !== void 0) {
|
|
2507
3024
|
const start = parseTimeOfDay(condition.window.from) ?? 0;
|
|
2508
3025
|
const end = parseTimeOfDay(condition.window.to) ?? 0;
|
|
2509
3026
|
if (!inWindow(local.minuteOfDay, start, end)) return false;
|
|
2510
|
-
|
|
2511
|
-
return condition.days === void 0 || condition.days.length === 0 || condition.days.includes(WEEKDAY_NAMES[weekday] ?? "");
|
|
3027
|
+
return onDay(start > end && local.minuteOfDay < end ? -1 : 0);
|
|
2512
3028
|
}
|
|
2513
|
-
return
|
|
3029
|
+
return onDay(0);
|
|
2514
3030
|
}
|
|
2515
3031
|
case "place": {
|
|
2516
3032
|
const who = target.characters.list().find((entry) => characterKey(entry) === condition.character);
|
|
@@ -2632,6 +3148,15 @@ function parseRecordsEdit(value) {
|
|
|
2632
3148
|
}
|
|
2633
3149
|
return found;
|
|
2634
3150
|
};
|
|
3151
|
+
const numbersOf = (entry, key) => {
|
|
3152
|
+
const found = entry[key];
|
|
3153
|
+
if (found === void 0 || found === null) return void 0;
|
|
3154
|
+
if (!Array.isArray(found) || found.some((item) => typeof item !== "number")) {
|
|
3155
|
+
problems.push(`${key} must be a list of numbers`);
|
|
3156
|
+
return;
|
|
3157
|
+
}
|
|
3158
|
+
return found;
|
|
3159
|
+
};
|
|
2635
3160
|
const text = (entry, key) => typeof entry[key] === "string" ? entry[key] : void 0;
|
|
2636
3161
|
/** A key, slot or position the model may leave blank instead of leaving out: blank is nothing given, not a name that does not exist. */
|
|
2637
3162
|
const named = (entry, key) => {
|
|
@@ -2924,13 +3449,19 @@ function parseRecordsEdit(value) {
|
|
|
2924
3449
|
kind: text(entry, "kind") ?? "",
|
|
2925
3450
|
text: text(entry, "text") ?? "",
|
|
2926
3451
|
about: strings(entry, "about"),
|
|
2927
|
-
importance: num(entry, "importance") ?? 0
|
|
3452
|
+
importance: num(entry, "importance") ?? 0,
|
|
3453
|
+
confidence: text(entry, "confidence"),
|
|
3454
|
+
evidenceFor: numbersOf(entry, "evidence_for"),
|
|
3455
|
+
evidenceAgainst: numbersOf(entry, "evidence_against")
|
|
2928
3456
|
})),
|
|
2929
3457
|
beliefEdits: array("belief_edits").map((entry) => defined({
|
|
2930
3458
|
id: num(entry, "id") ?? 0,
|
|
2931
3459
|
text: text(entry, "text"),
|
|
2932
3460
|
about: strings(entry, "about"),
|
|
2933
|
-
importance: num(entry, "importance")
|
|
3461
|
+
importance: num(entry, "importance"),
|
|
3462
|
+
confidence: text(entry, "confidence"),
|
|
3463
|
+
evidenceFor: numbersOf(entry, "evidence_for"),
|
|
3464
|
+
evidenceAgainst: numbersOf(entry, "evidence_against")
|
|
2934
3465
|
})),
|
|
2935
3466
|
beliefRetractions: numbers("belief_retractions"),
|
|
2936
3467
|
threadsOpened: Array.isArray(value["threads_opened"]) ? value["threads_opened"].filter((entry) => typeof entry === "string") : [],
|
|
@@ -3015,6 +3546,7 @@ function parseRecordsEdit(value) {
|
|
|
3015
3546
|
character: text(entry, "character") ?? "",
|
|
3016
3547
|
windows: Array.isArray(entry["windows"]) ? entry["windows"].filter(isObject$1).map((window) => defined({
|
|
3017
3548
|
days: strings(window, "days"),
|
|
3549
|
+
on: strings(window, "on"),
|
|
3018
3550
|
from: text(window, "from") ?? "",
|
|
3019
3551
|
to: text(window, "to") ?? "",
|
|
3020
3552
|
activity: text(window, "activity") ?? "",
|
|
@@ -3036,6 +3568,21 @@ const scalarValue = {
|
|
|
3036
3568
|
],
|
|
3037
3569
|
description: "A number, true or false, or one of the words of a choice."
|
|
3038
3570
|
};
|
|
3571
|
+
const CONFIDENCE = {
|
|
3572
|
+
type: "string",
|
|
3573
|
+
enum: [...MEMORY_CONFIDENCES],
|
|
3574
|
+
description: "How sure the character is: doubtful, likely (the default) or sure. Sure only for what it has seen more than once or cannot doubt."
|
|
3575
|
+
};
|
|
3576
|
+
const EVIDENCE_FOR = {
|
|
3577
|
+
type: "array",
|
|
3578
|
+
items: { type: "integer" },
|
|
3579
|
+
description: "The numbers of perceptions of the character (from read_records) that this rests on."
|
|
3580
|
+
};
|
|
3581
|
+
const EVIDENCE_AGAINST = {
|
|
3582
|
+
type: "array",
|
|
3583
|
+
items: { type: "integer" },
|
|
3584
|
+
description: "The numbers of perceptions of the character that speak against this; lower the confidence then."
|
|
3585
|
+
};
|
|
3039
3586
|
const IMPORTANCE = {
|
|
3040
3587
|
type: "integer",
|
|
3041
3588
|
enum: [
|
|
@@ -3196,7 +3743,10 @@ function recordsEditSchema(options = {}) {
|
|
|
3196
3743
|
},
|
|
3197
3744
|
text: { type: "string" },
|
|
3198
3745
|
about: keys,
|
|
3199
|
-
importance: IMPORTANCE
|
|
3746
|
+
importance: IMPORTANCE,
|
|
3747
|
+
confidence: CONFIDENCE,
|
|
3748
|
+
evidence_for: EVIDENCE_FOR,
|
|
3749
|
+
evidence_against: EVIDENCE_AGAINST
|
|
3200
3750
|
},
|
|
3201
3751
|
required: [
|
|
3202
3752
|
"character",
|
|
@@ -3893,6 +4443,11 @@ function recordsEditSchema(options = {}) {
|
|
|
3893
4443
|
},
|
|
3894
4444
|
description: "Leave out for every day."
|
|
3895
4445
|
},
|
|
4446
|
+
on: {
|
|
4447
|
+
type: "array",
|
|
4448
|
+
items: { type: "string" },
|
|
4449
|
+
description: "Other days it applies on, as well as days: months (dec), dates (12-24, 1912-09-21), stretches (12-20..01-05), dom:1, nth:mon:1 (first Monday), season:winter, holiday:<id>; a ! before one is a day it does not apply on (!holiday:festival). Leave out for every day."
|
|
4450
|
+
},
|
|
3896
4451
|
from: {
|
|
3897
4452
|
type: "string",
|
|
3898
4453
|
description: "HH:MM, 24-hour."
|
|
@@ -4258,7 +4813,10 @@ function recordsEditSchema(options = {}) {
|
|
|
4258
4813
|
id: { type: "integer" },
|
|
4259
4814
|
text: { type: "string" },
|
|
4260
4815
|
about: keys,
|
|
4261
|
-
importance: IMPORTANCE
|
|
4816
|
+
importance: IMPORTANCE,
|
|
4817
|
+
confidence: CONFIDENCE,
|
|
4818
|
+
evidence_for: EVIDENCE_FOR,
|
|
4819
|
+
evidence_against: EVIDENCE_AGAINST
|
|
4262
4820
|
},
|
|
4263
4821
|
required: ["id"],
|
|
4264
4822
|
additionalProperties: false
|
|
@@ -4604,26 +5162,49 @@ function applyChanges(target, edit) {
|
|
|
4604
5162
|
perception: one
|
|
4605
5163
|
}];
|
|
4606
5164
|
});
|
|
5165
|
+
const confidence = (value, where) => {
|
|
5166
|
+
if (value !== void 0 && !MEMORY_CONFIDENCES.includes(value)) problems.push(`${where}: confidence is one of ${MEMORY_CONFIDENCES.join(", ")}`);
|
|
5167
|
+
};
|
|
5168
|
+
/** Perceptions a belief of one character is said to rest on, or to be contradicted by: they must be the character's own. */
|
|
5169
|
+
const evidence = (value, characterId, where) => (value ?? []).flatMap((id) => {
|
|
5170
|
+
const seen = target.events.getPerception(id);
|
|
5171
|
+
if (seen === void 0) {
|
|
5172
|
+
problems.push(`${where}: there is no perception ${id}`);
|
|
5173
|
+
return [];
|
|
5174
|
+
}
|
|
5175
|
+
if (characterId !== void 0 && seen.characterId !== characterId) {
|
|
5176
|
+
problems.push(`${where}: perception ${id} is not one the character took in`);
|
|
5177
|
+
return [];
|
|
5178
|
+
}
|
|
5179
|
+
return [id];
|
|
5180
|
+
});
|
|
4607
5181
|
const beliefs = list(edit.beliefs).flatMap((belief, index) => {
|
|
4608
5182
|
const where = `beliefs[${index}]`;
|
|
4609
5183
|
const character = idOf(belief.character, where);
|
|
4610
5184
|
if (!MEMORY_KINDS.includes(belief.kind)) problems.push(`${where}: unknown kind "${String(belief.kind)}"`);
|
|
4611
5185
|
if (!present(belief.text)) problems.push(`${where}: say what the character believes or knows`);
|
|
4612
5186
|
importance(belief.importance, where);
|
|
5187
|
+
confidence(belief.confidence, where);
|
|
4613
5188
|
return character === void 0 ? [] : [{
|
|
4614
5189
|
belief,
|
|
4615
5190
|
character,
|
|
4616
|
-
about: ids(belief.about, `${where}.about`)
|
|
5191
|
+
about: ids(belief.about, `${where}.about`),
|
|
5192
|
+
evidenceFor: evidence(belief.evidenceFor, character, `${where}.evidence_for`),
|
|
5193
|
+
evidenceAgainst: evidence(belief.evidenceAgainst, character, `${where}.evidence_against`)
|
|
4617
5194
|
}];
|
|
4618
5195
|
});
|
|
4619
5196
|
const beliefEdits = list(edit.beliefEdits).map((change, index) => {
|
|
4620
5197
|
const where = `belief_edits[${index}]`;
|
|
4621
|
-
|
|
5198
|
+
const current = target.memories.get(change.id);
|
|
5199
|
+
if (current === void 0) problems.push(`${where}: there is no belief ${change.id}`);
|
|
4622
5200
|
if (change.text !== void 0 && !present(change.text)) problems.push(`${where}: say what the character believes or knows`);
|
|
4623
5201
|
importance(change.importance, where);
|
|
5202
|
+
confidence(change.confidence, where);
|
|
4624
5203
|
return {
|
|
4625
5204
|
change,
|
|
4626
|
-
about: change.about === void 0 ? void 0 : ids(change.about, `${where}.about`)
|
|
5205
|
+
about: change.about === void 0 ? void 0 : ids(change.about, `${where}.about`),
|
|
5206
|
+
evidenceFor: evidence(change.evidenceFor, current?.characterId, `${where}.evidence_for`),
|
|
5207
|
+
evidenceAgainst: evidence(change.evidenceAgainst, current?.characterId, `${where}.evidence_against`)
|
|
4627
5208
|
};
|
|
4628
5209
|
});
|
|
4629
5210
|
for (const id of list(edit.beliefRetractions)) if (target.memories.get(id) === void 0) problems.push(`belief_retractions: there is no belief ${id}`);
|
|
@@ -4898,23 +5479,30 @@ function applyChanges(target, edit) {
|
|
|
4898
5479
|
learnEventEntities(target.events.get(eventId), perception.characterId);
|
|
4899
5480
|
result.writes += 1;
|
|
4900
5481
|
}
|
|
4901
|
-
for (const { belief, character, about } of beliefs) {
|
|
4902
|
-
|
|
5482
|
+
for (const { belief, character, about, evidenceFor, evidenceAgainst } of beliefs) {
|
|
5483
|
+
const added = target.memories.add({
|
|
4903
5484
|
characterId: character,
|
|
4904
5485
|
kind: belief.kind,
|
|
4905
5486
|
text: belief.text.trim(),
|
|
4906
5487
|
about,
|
|
4907
5488
|
importance: belief.importance,
|
|
5489
|
+
...belief.confidence === void 0 ? {} : { confidence: belief.confidence },
|
|
4908
5490
|
turn: edit.turn
|
|
4909
|
-
})
|
|
5491
|
+
});
|
|
5492
|
+
result.beliefIds.push(added.id);
|
|
5493
|
+
for (const id of evidenceFor) target.memories.addEvidence(added.id, id, "supports", edit.turn);
|
|
5494
|
+
for (const id of evidenceAgainst) target.memories.addEvidence(added.id, id, "contradicts", edit.turn);
|
|
4910
5495
|
result.writes += 1;
|
|
4911
5496
|
}
|
|
4912
|
-
for (const { change, about } of beliefEdits) {
|
|
5497
|
+
for (const { change, about, evidenceFor, evidenceAgainst } of beliefEdits) {
|
|
4913
5498
|
target.memories.update(change.id, {
|
|
4914
5499
|
...change.text === void 0 ? {} : { text: change.text.trim() },
|
|
4915
5500
|
...about === void 0 ? {} : { about },
|
|
4916
|
-
...change.importance === void 0 ? {} : { importance: change.importance }
|
|
5501
|
+
...change.importance === void 0 ? {} : { importance: change.importance },
|
|
5502
|
+
...change.confidence === void 0 ? {} : { confidence: change.confidence }
|
|
4917
5503
|
});
|
|
5504
|
+
for (const id of evidenceFor) target.memories.addEvidence(change.id, id, "supports", edit.turn);
|
|
5505
|
+
for (const id of evidenceAgainst) target.memories.addEvidence(change.id, id, "contradicts", edit.turn);
|
|
4918
5506
|
result.writes += 1;
|
|
4919
5507
|
}
|
|
4920
5508
|
for (const id of list(edit.beliefRetractions)) {
|
|
@@ -5480,7 +6068,8 @@ const DEFAULT_MEMORY_POLICY = {
|
|
|
5480
6068
|
recall: 12,
|
|
5481
6069
|
overRetrieve: 4,
|
|
5482
6070
|
associate: 2,
|
|
5483
|
-
diversity: .3
|
|
6071
|
+
diversity: .3,
|
|
6072
|
+
foldedFade: .5
|
|
5484
6073
|
};
|
|
5485
6074
|
/** Story days for the memory of an unimportant event to halve; each step of importance doubles it. */
|
|
5486
6075
|
const HALF_LIFE_DAYS = 6;
|
|
@@ -5569,7 +6158,7 @@ function describeEvent({ perception, event }, names, at) {
|
|
|
5569
6158
|
vague: about === "" ? "something happened, but you cannot say what" : `something to do with ${about}`
|
|
5570
6159
|
};
|
|
5571
6160
|
}
|
|
5572
|
-
function describeBelief(memory, at) {
|
|
6161
|
+
function describeBelief(memory, at, disputed) {
|
|
5573
6162
|
return {
|
|
5574
6163
|
turn: memory.turn,
|
|
5575
6164
|
...at === void 0 ? {} : { at },
|
|
@@ -5577,6 +6166,8 @@ function describeBelief(memory, at) {
|
|
|
5577
6166
|
kind: "memory",
|
|
5578
6167
|
id: memory.id
|
|
5579
6168
|
},
|
|
6169
|
+
confidence: memory.confidence,
|
|
6170
|
+
...disputed ? { disputed: true } : {},
|
|
5580
6171
|
clear: memory.text,
|
|
5581
6172
|
faded: first(memory.text),
|
|
5582
6173
|
vague: first(memory.text)
|
|
@@ -5611,7 +6202,9 @@ const DENSE_RANGE = .5;
|
|
|
5611
6202
|
const denseScore = (similarity) => Math.min(1, Math.max(0, (similarity - DENSE_FLOOR) / DENSE_RANGE));
|
|
5612
6203
|
/** Being where something happened calls it to mind, about as much as sharing two words with it. */
|
|
5613
6204
|
const PLACE_CUE = 2;
|
|
5614
|
-
function gather(source, { characterId, characters, situation, locationId, about }) {
|
|
6205
|
+
function gather(source, { characterId, characters, situation, locationId, about, policy }) {
|
|
6206
|
+
const evidence = source.memories.evidenceIndex?.(characterId);
|
|
6207
|
+
const folding = Math.min(1, Math.max(.05, policy?.foldedFade ?? 1));
|
|
5615
6208
|
const names = new Map(characters.map((character) => [character.id, character.name]));
|
|
5616
6209
|
const peers = mentioned(situation, characters);
|
|
5617
6210
|
peers.delete(characterId);
|
|
@@ -5651,7 +6244,8 @@ function gather(source, { characterId, characters, situation, locationId, about
|
|
|
5651
6244
|
ageDays: ageDays(at),
|
|
5652
6245
|
recalls: seen.perception.recalls,
|
|
5653
6246
|
seed: seen.perception.id,
|
|
5654
|
-
durability: 1
|
|
6247
|
+
durability: evidence?.folded.has(seen.perception.id) === true ? folding : 1,
|
|
6248
|
+
...evidence?.folded.has(seen.perception.id) === true ? { covered: evidence.folded.get(seen.perception.id) } : {}
|
|
5655
6249
|
};
|
|
5656
6250
|
}), ...source.memories.list(characterId).filter((belief) => concerns(belief.about)).map((belief) => {
|
|
5657
6251
|
const at = atTurn(belief.turn);
|
|
@@ -5666,7 +6260,7 @@ function gather(source, { characterId, characters, situation, locationId, about
|
|
|
5666
6260
|
people: belief.about.filter((id) => id !== characterId),
|
|
5667
6261
|
tags: []
|
|
5668
6262
|
},
|
|
5669
|
-
recalled: describeBelief(belief, at),
|
|
6263
|
+
recalled: describeBelief(belief, at, evidence?.disputed.has(belief.id) === true),
|
|
5670
6264
|
cues: cue,
|
|
5671
6265
|
relevance: cue.people + cue.words,
|
|
5672
6266
|
importance: belief.importance,
|
|
@@ -5826,6 +6420,28 @@ function diversified(ranked, count, diversity) {
|
|
|
5826
6420
|
}
|
|
5827
6421
|
return chosen;
|
|
5828
6422
|
}
|
|
6423
|
+
/** How sure the character is of a belief, and whether something since speaks against it. */
|
|
6424
|
+
function beliefOf(candidate) {
|
|
6425
|
+
const { confidence, disputed } = candidate.recalled;
|
|
6426
|
+
return {
|
|
6427
|
+
...confidence === void 0 ? {} : { confidence },
|
|
6428
|
+
...disputed === void 0 ? {} : { disputed }
|
|
6429
|
+
};
|
|
6430
|
+
}
|
|
6431
|
+
/**
|
|
6432
|
+
* Chooses as {@link diversified} does, but does not tell what the character took in when a belief resting on it is told as well: the belief says it,
|
|
6433
|
+
* and the place goes to the next best.
|
|
6434
|
+
*/
|
|
6435
|
+
function chooseUncovered(standing, count, diversity) {
|
|
6436
|
+
let pool = standing;
|
|
6437
|
+
for (;;) {
|
|
6438
|
+
const chosen = diversified(pool, count, diversity);
|
|
6439
|
+
const told = new Set(chosen.filter((entry) => entry.candidate.recalled.source.kind === "memory").map((entry) => entry.candidate.recalled.source.id));
|
|
6440
|
+
const repeated = chosen.filter((entry) => entry.candidate.covered?.some((id) => told.has(id)) === true);
|
|
6441
|
+
if (repeated.length === 0) return chosen;
|
|
6442
|
+
pool = pool.filter((entry) => !repeated.includes(entry));
|
|
6443
|
+
}
|
|
6444
|
+
}
|
|
5829
6445
|
/**
|
|
5830
6446
|
* The last stage: choose the `policy.recall` best of the pool, spread by `policy.diversity`. With a reranker's judgement each
|
|
5831
6447
|
* candidate's relevance is mostly that (a candidate it left out counts as 0), and the rest is what
|
|
@@ -5854,13 +6470,14 @@ function finishRecall(pool, rerank) {
|
|
|
5854
6470
|
why
|
|
5855
6471
|
}];
|
|
5856
6472
|
}).sort((a, b) => b.score - a.score || b.candidate.recalled.turn - a.candidate.recalled.turn);
|
|
5857
|
-
const chosen =
|
|
6473
|
+
const chosen = chooseUncovered(standing, policy.recall, policy.diversity ?? 0);
|
|
5858
6474
|
const result = chosen.map(({ candidate, clarity, text, why }) => ({
|
|
5859
6475
|
clarity,
|
|
5860
6476
|
text,
|
|
5861
6477
|
turn: candidate.recalled.turn,
|
|
5862
6478
|
...candidate.recalled.at === void 0 ? {} : { at: candidate.recalled.at },
|
|
5863
6479
|
source: candidate.recalled.source,
|
|
6480
|
+
...beliefOf(candidate),
|
|
5864
6481
|
...why === void 0 ? {} : { why }
|
|
5865
6482
|
}));
|
|
5866
6483
|
const anchors = chosen.filter((entry) => entry.relevance >= CUE_RELEVANCE);
|
|
@@ -5882,6 +6499,7 @@ function finishRecall(pool, rerank) {
|
|
|
5882
6499
|
turn: candidate.recalled.turn,
|
|
5883
6500
|
...candidate.recalled.at === void 0 ? {} : { at: candidate.recalled.at },
|
|
5884
6501
|
source: candidate.recalled.source,
|
|
6502
|
+
...beliefOf(candidate),
|
|
5885
6503
|
linked: true,
|
|
5886
6504
|
...pool.query.explain === true ? { why: {
|
|
5887
6505
|
cues: candidate.cues,
|
|
@@ -6031,15 +6649,21 @@ function readRecords(target, scope = {}) {
|
|
|
6031
6649
|
gist: entry.gist
|
|
6032
6650
|
}))
|
|
6033
6651
|
})),
|
|
6034
|
-
beliefs: characters.flatMap((character) => target.memories.list(character.id).filter((belief) => inScope(belief.turn)).map((belief) =>
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6652
|
+
beliefs: characters.flatMap((character) => target.memories.list(character.id).filter((belief) => inScope(belief.turn)).map((belief) => {
|
|
6653
|
+
const evidence = target.memories.evidenceOf(belief.id);
|
|
6654
|
+
return {
|
|
6655
|
+
id: belief.id,
|
|
6656
|
+
character: characterKey(character),
|
|
6657
|
+
kind: belief.kind,
|
|
6658
|
+
text: belief.text,
|
|
6659
|
+
about: belief.about.map(keyOf),
|
|
6660
|
+
importance: belief.importance,
|
|
6661
|
+
confidence: belief.confidence,
|
|
6662
|
+
evidence_for: evidence.filter((entry) => entry.relation === "supports").map((entry) => entry.perceptionId),
|
|
6663
|
+
evidence_against: evidence.filter((entry) => entry.relation === "contradicts").map((entry) => entry.perceptionId),
|
|
6664
|
+
turn: belief.turn
|
|
6665
|
+
};
|
|
6666
|
+
})),
|
|
6043
6667
|
open_threads: target.threads.list("open").map((thread) => ({
|
|
6044
6668
|
id: thread.id,
|
|
6045
6669
|
text: thread.text,
|
|
@@ -6400,6 +7024,7 @@ const toMemory = (row) => ({
|
|
|
6400
7024
|
text: row.text,
|
|
6401
7025
|
about: parseNumberList(row.about, "memories.about"),
|
|
6402
7026
|
importance: row.importance,
|
|
7027
|
+
confidence: row.confidence,
|
|
6403
7028
|
turn: row.turn,
|
|
6404
7029
|
recalls: row.recalls,
|
|
6405
7030
|
...row.last_recalled_turn === null ? {} : { lastRecalledTurn: row.last_recalled_turn },
|
|
@@ -6416,12 +7041,13 @@ var SqliteMemoryRepository = class {
|
|
|
6416
7041
|
}
|
|
6417
7042
|
add(memory) {
|
|
6418
7043
|
return guard(() => {
|
|
6419
|
-
const { lastInsertRowid } = this.db.run("INSERT INTO memories (character_id, kind, text, about, importance, turn, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)", [
|
|
7044
|
+
const { lastInsertRowid } = this.db.run("INSERT INTO memories (character_id, kind, text, about, importance, confidence, turn, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [
|
|
6420
7045
|
memory.characterId,
|
|
6421
7046
|
memory.kind,
|
|
6422
7047
|
memory.text,
|
|
6423
7048
|
toJson(memory.about),
|
|
6424
7049
|
memory.importance,
|
|
7050
|
+
memory.confidence ?? "likely",
|
|
6425
7051
|
memory.turn,
|
|
6426
7052
|
this.clock().toISOString()
|
|
6427
7053
|
]);
|
|
@@ -6445,10 +7071,11 @@ var SqliteMemoryRepository = class {
|
|
|
6445
7071
|
update(id, patch) {
|
|
6446
7072
|
return guard(() => {
|
|
6447
7073
|
const current = this.require(id);
|
|
6448
|
-
this.db.run("UPDATE memories SET text = ?, about = ?, importance = ? WHERE id = ?", [
|
|
7074
|
+
this.db.run("UPDATE memories SET text = ?, about = ?, importance = ?, confidence = ? WHERE id = ?", [
|
|
6449
7075
|
patch.text ?? current.text,
|
|
6450
7076
|
toJson(patch.about ?? current.about),
|
|
6451
7077
|
patch.importance ?? current.importance,
|
|
7078
|
+
patch.confidence ?? current.confidence,
|
|
6452
7079
|
id
|
|
6453
7080
|
]);
|
|
6454
7081
|
const changed = this.require(id);
|
|
@@ -6470,6 +7097,36 @@ var SqliteMemoryRepository = class {
|
|
|
6470
7097
|
this.index.remove("memory", id);
|
|
6471
7098
|
return this.db.run("DELETE FROM memories WHERE id = ?", [id]).changes > 0;
|
|
6472
7099
|
}
|
|
7100
|
+
addEvidence(memoryId, perceptionId, relation, turn) {
|
|
7101
|
+
this.require(memoryId);
|
|
7102
|
+
guard(() => this.db.run("INSERT INTO memory_evidence (memory_id, perception_id, relation, turn) VALUES (?, ?, ?, ?) ON CONFLICT (memory_id, perception_id) DO UPDATE SET relation = excluded.relation, turn = excluded.turn", [
|
|
7103
|
+
memoryId,
|
|
7104
|
+
perceptionId,
|
|
7105
|
+
relation,
|
|
7106
|
+
turn
|
|
7107
|
+
]));
|
|
7108
|
+
}
|
|
7109
|
+
evidenceOf(memoryId) {
|
|
7110
|
+
return this.db.all("SELECT * FROM memory_evidence WHERE memory_id = ? ORDER BY perception_id", [memoryId]).map((row) => ({
|
|
7111
|
+
memoryId: row.memory_id,
|
|
7112
|
+
perceptionId: row.perception_id,
|
|
7113
|
+
relation: row.relation,
|
|
7114
|
+
turn: row.turn
|
|
7115
|
+
}));
|
|
7116
|
+
}
|
|
7117
|
+
evidenceIndex(characterId) {
|
|
7118
|
+
const rows = this.db.all("SELECT e.memory_id, e.perception_id, e.relation FROM memory_evidence e JOIN memories m ON m.id = e.memory_id WHERE m.character_id = ? ORDER BY e.memory_id", [characterId]);
|
|
7119
|
+
const folded = /* @__PURE__ */ new Map();
|
|
7120
|
+
const disputed = /* @__PURE__ */ new Set();
|
|
7121
|
+
for (const row of rows) {
|
|
7122
|
+
folded.set(row.perception_id, [...folded.get(row.perception_id) ?? [], row.memory_id]);
|
|
7123
|
+
if (row.relation === "contradicts") disputed.add(row.memory_id);
|
|
7124
|
+
}
|
|
7125
|
+
return {
|
|
7126
|
+
folded,
|
|
7127
|
+
disputed
|
|
7128
|
+
};
|
|
7129
|
+
}
|
|
6473
7130
|
require(id) {
|
|
6474
7131
|
const memory = this.get(id);
|
|
6475
7132
|
if (memory === void 0) throw new NotFoundError(`memory ${id} does not exist`);
|
|
@@ -6920,6 +7577,108 @@ var SqliteTriggerRepository = class {
|
|
|
6920
7577
|
}
|
|
6921
7578
|
};
|
|
6922
7579
|
//#endregion
|
|
7580
|
+
//#region src/sqlite/dice-repository.ts
|
|
7581
|
+
const MAX_ROLLS_LISTED = 200;
|
|
7582
|
+
function parseDefinition(text) {
|
|
7583
|
+
try {
|
|
7584
|
+
return JSON.parse(text);
|
|
7585
|
+
} catch (error) {
|
|
7586
|
+
throw new CorruptDataError("a dice check's definition is not valid JSON", { cause: error });
|
|
7587
|
+
}
|
|
7588
|
+
}
|
|
7589
|
+
var SqliteDiceRepository = class {
|
|
7590
|
+
db;
|
|
7591
|
+
clock;
|
|
7592
|
+
constructor(db, clock) {
|
|
7593
|
+
this.db = db;
|
|
7594
|
+
this.clock = clock;
|
|
7595
|
+
}
|
|
7596
|
+
define(definition) {
|
|
7597
|
+
if ((this.db.get("SELECT count(*) AS count FROM dice_checks")?.count ?? 0) >= 40) throw new InvalidValueError(`a story has at most 40 dice checks`);
|
|
7598
|
+
const { key, name, use, ...rest } = definition;
|
|
7599
|
+
return guard(() => {
|
|
7600
|
+
const { lastInsertRowid } = this.db.run("INSERT INTO dice_checks (key, name, use_text, definition) VALUES (?, ?, ?, ?)", [
|
|
7601
|
+
key,
|
|
7602
|
+
name,
|
|
7603
|
+
use,
|
|
7604
|
+
toJson(rest)
|
|
7605
|
+
]);
|
|
7606
|
+
return this.require(Number(lastInsertRowid));
|
|
7607
|
+
});
|
|
7608
|
+
}
|
|
7609
|
+
checks() {
|
|
7610
|
+
return this.db.all("SELECT * FROM dice_checks ORDER BY id").map((row) => this.toCheck(row));
|
|
7611
|
+
}
|
|
7612
|
+
check(key) {
|
|
7613
|
+
const row = this.db.get("SELECT * FROM dice_checks WHERE key = ?", [key]);
|
|
7614
|
+
return row === void 0 ? void 0 : this.toCheck(row);
|
|
7615
|
+
}
|
|
7616
|
+
removeCheck(key) {
|
|
7617
|
+
if (this.db.run("DELETE FROM dice_checks WHERE key = ?", [key]).changes === 0) throw new NotFoundError(`there is no dice check "${key}"`);
|
|
7618
|
+
}
|
|
7619
|
+
record(roll) {
|
|
7620
|
+
const at = this.clock().toISOString();
|
|
7621
|
+
const { lastInsertRowid } = this.db.run("INSERT INTO dice_rolls (turn, check_key, expression, faces, kept, modifier, target, total, value, readout, reason, character, by, at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
|
7622
|
+
roll.turn,
|
|
7623
|
+
roll.check ?? null,
|
|
7624
|
+
roll.expression,
|
|
7625
|
+
toJson(roll.faces),
|
|
7626
|
+
toJson(roll.kept),
|
|
7627
|
+
roll.modifier,
|
|
7628
|
+
roll.target ?? null,
|
|
7629
|
+
roll.total,
|
|
7630
|
+
roll.value,
|
|
7631
|
+
roll.readout === void 0 ? null : toJson(roll.readout),
|
|
7632
|
+
roll.reason,
|
|
7633
|
+
roll.character ?? null,
|
|
7634
|
+
roll.by,
|
|
7635
|
+
at
|
|
7636
|
+
]);
|
|
7637
|
+
return {
|
|
7638
|
+
...roll,
|
|
7639
|
+
id: Number(lastInsertRowid),
|
|
7640
|
+
at
|
|
7641
|
+
};
|
|
7642
|
+
}
|
|
7643
|
+
rolls(filter = {}) {
|
|
7644
|
+
const limit = Math.min(filter.limit ?? MAX_ROLLS_LISTED, MAX_ROLLS_LISTED);
|
|
7645
|
+
return (filter.turn === void 0 ? this.db.all("SELECT * FROM (SELECT * FROM dice_rolls ORDER BY id DESC LIMIT ?) ORDER BY id", [limit]) : this.db.all("SELECT * FROM dice_rolls WHERE turn = ? ORDER BY id LIMIT ?", [filter.turn, limit])).map((row) => this.toRoll(row));
|
|
7646
|
+
}
|
|
7647
|
+
require(id) {
|
|
7648
|
+
const row = this.db.get("SELECT * FROM dice_checks WHERE id = ?", [id]);
|
|
7649
|
+
if (row === void 0) throw new NotFoundError(`there is no dice check ${id}`);
|
|
7650
|
+
return this.toCheck(row);
|
|
7651
|
+
}
|
|
7652
|
+
toCheck(row) {
|
|
7653
|
+
return {
|
|
7654
|
+
id: row.id,
|
|
7655
|
+
key: row.key,
|
|
7656
|
+
name: row.name,
|
|
7657
|
+
use: row.use_text,
|
|
7658
|
+
...parseDefinition(row.definition)
|
|
7659
|
+
};
|
|
7660
|
+
}
|
|
7661
|
+
toRoll(row) {
|
|
7662
|
+
return {
|
|
7663
|
+
id: row.id,
|
|
7664
|
+
turn: row.turn,
|
|
7665
|
+
expression: row.expression,
|
|
7666
|
+
faces: parseNumberList(row.faces, "dice_rolls.faces"),
|
|
7667
|
+
kept: parseNumberList(row.kept, "dice_rolls.kept"),
|
|
7668
|
+
modifier: row.modifier,
|
|
7669
|
+
total: row.total,
|
|
7670
|
+
value: row.value,
|
|
7671
|
+
reason: row.reason,
|
|
7672
|
+
by: row.by,
|
|
7673
|
+
at: row.at,
|
|
7674
|
+
...row.check_key === null ? {} : { check: row.check_key },
|
|
7675
|
+
...row.target === null ? {} : { target: row.target },
|
|
7676
|
+
...row.readout === null ? {} : { readout: JSON.parse(row.readout) },
|
|
7677
|
+
...row.character === null ? {} : { character: row.character }
|
|
7678
|
+
};
|
|
7679
|
+
}
|
|
7680
|
+
};
|
|
7681
|
+
//#endregion
|
|
6923
7682
|
//#region src/sqlite/clock-repository.ts
|
|
6924
7683
|
const toChange = (row) => ({
|
|
6925
7684
|
id: row.id,
|
|
@@ -8063,7 +8822,7 @@ var SqliteMetadataRepository = class {
|
|
|
8063
8822
|
//#region src/sqlite/schema.ts
|
|
8064
8823
|
/**
|
|
8065
8824
|
* Migrations are applied in order and never edited once released (0.1.0 released
|
|
8066
|
-
* versions 1 to 6; tests/sqlite/migrate.spec.ts pins
|
|
8825
|
+
* versions 1 to 6; tests/sqlite/migrate.spec.ts pins the released ones): a change to the schema
|
|
8067
8826
|
* is a new entry. Enumerations are spelled out so that a later change to the model
|
|
8068
8827
|
* cannot alter what an old migration did.
|
|
8069
8828
|
*/
|
|
@@ -8627,6 +9386,51 @@ const MIGRATIONS = [
|
|
|
8627
9386
|
INSERT INTO entity_knowledge SELECT * FROM old_entity_knowledge;
|
|
8628
9387
|
DROP TABLE old_entity_knowledge;
|
|
8629
9388
|
CREATE INDEX entity_knowledge_subject ON entity_knowledge (subject_kind, subject_id);
|
|
9389
|
+
`
|
|
9390
|
+
},
|
|
9391
|
+
{
|
|
9392
|
+
version: 7,
|
|
9393
|
+
sql: `
|
|
9394
|
+
ALTER TABLE memories ADD COLUMN confidence TEXT NOT NULL DEFAULT 'likely' CHECK (confidence IN ('doubtful', 'likely', 'sure'));
|
|
9395
|
+
CREATE TABLE memory_evidence (
|
|
9396
|
+
memory_id INTEGER NOT NULL REFERENCES memories (id) ON DELETE CASCADE,
|
|
9397
|
+
perception_id INTEGER NOT NULL REFERENCES perceptions (id) ON DELETE CASCADE,
|
|
9398
|
+
relation TEXT NOT NULL CHECK (relation IN ('supports', 'contradicts')),
|
|
9399
|
+
turn INTEGER NOT NULL,
|
|
9400
|
+
PRIMARY KEY (memory_id, perception_id)
|
|
9401
|
+
) STRICT;
|
|
9402
|
+
CREATE INDEX memory_evidence_perception ON memory_evidence (perception_id);
|
|
9403
|
+
ALTER TABLE routine_windows ADD COLUMN filters TEXT NOT NULL DEFAULT '[]';
|
|
9404
|
+
`
|
|
9405
|
+
},
|
|
9406
|
+
{
|
|
9407
|
+
version: 8,
|
|
9408
|
+
sql: `
|
|
9409
|
+
CREATE TABLE dice_checks (
|
|
9410
|
+
id INTEGER PRIMARY KEY,
|
|
9411
|
+
key TEXT NOT NULL UNIQUE,
|
|
9412
|
+
name TEXT NOT NULL,
|
|
9413
|
+
use_text TEXT NOT NULL,
|
|
9414
|
+
definition TEXT NOT NULL
|
|
9415
|
+
) STRICT;
|
|
9416
|
+
CREATE TABLE dice_rolls (
|
|
9417
|
+
id INTEGER PRIMARY KEY,
|
|
9418
|
+
turn INTEGER NOT NULL,
|
|
9419
|
+
check_key TEXT,
|
|
9420
|
+
expression TEXT NOT NULL,
|
|
9421
|
+
faces TEXT NOT NULL,
|
|
9422
|
+
kept TEXT NOT NULL,
|
|
9423
|
+
modifier INTEGER NOT NULL,
|
|
9424
|
+
target INTEGER,
|
|
9425
|
+
total INTEGER NOT NULL,
|
|
9426
|
+
value INTEGER NOT NULL,
|
|
9427
|
+
readout TEXT,
|
|
9428
|
+
reason TEXT NOT NULL,
|
|
9429
|
+
character TEXT,
|
|
9430
|
+
by TEXT NOT NULL CHECK (by IN ('agent', 'player')),
|
|
9431
|
+
at TEXT NOT NULL
|
|
9432
|
+
) STRICT;
|
|
9433
|
+
CREATE INDEX dice_rolls_turn ON dice_rolls (turn, id);
|
|
8630
9434
|
`
|
|
8631
9435
|
}
|
|
8632
9436
|
];
|
|
@@ -9375,6 +10179,7 @@ var SqliteSessionStore = class {
|
|
|
9375
10179
|
routines;
|
|
9376
10180
|
engagements;
|
|
9377
10181
|
triggers;
|
|
10182
|
+
dice;
|
|
9378
10183
|
recallIndex;
|
|
9379
10184
|
eventRepository;
|
|
9380
10185
|
constructor(db, clock, vectors) {
|
|
@@ -9405,6 +10210,7 @@ var SqliteSessionStore = class {
|
|
|
9405
10210
|
this.routines = new SqliteRoutineRepository(db);
|
|
9406
10211
|
this.engagements = new SqliteEngagementRepository(db);
|
|
9407
10212
|
this.triggers = new SqliteTriggerRepository(db);
|
|
10213
|
+
this.dice = new SqliteDiceRepository(db, clock);
|
|
9408
10214
|
this.eventRepository = new SqliteEventRepository(db, clock, this.recallIndex, (id) => this.locations.get(id)?.name, (id) => this.items.get(id)?.name);
|
|
9409
10215
|
this.events = this.eventRepository;
|
|
9410
10216
|
this.memories = new SqliteMemoryRepository(db, clock, this.recallIndex);
|
|
@@ -9811,6 +10617,375 @@ function identityOf(database, storeOptions) {
|
|
|
9811
10617
|
}
|
|
9812
10618
|
}
|
|
9813
10619
|
//#endregion
|
|
10620
|
+
//#region src/records/dice.ts
|
|
10621
|
+
const systemRoller = (sides) => randomInt(1, sides + 1);
|
|
10622
|
+
const TERM = /^(?:(\d*)d(\d+|%)(?:(kh|kl|dh|dl)(\d*))?|(\d+))$/;
|
|
10623
|
+
/** Reads dice notation: `2d6+1`, `d20`, `4d6kh3`, `d%`, `1d8+1d4-1`. @throws {InvalidValueError} with what is wrong. */
|
|
10624
|
+
function parseNotation(input) {
|
|
10625
|
+
const source = input.toLowerCase().replace(/\s+/g, "");
|
|
10626
|
+
if (source === "") throw new InvalidValueError("dice notation is empty: write it like 2d6+1");
|
|
10627
|
+
const parts = source.split(/(?=[+-])/);
|
|
10628
|
+
if (parts.length > 8) throw new InvalidValueError(`dice notation has more than 8 terms`);
|
|
10629
|
+
const terms = parts.map((part, index) => {
|
|
10630
|
+
const sign = part.startsWith("-") ? -1 : 1;
|
|
10631
|
+
const body = part.replace(/^[+-]/, "");
|
|
10632
|
+
if (index > 0 && !/^[+-]/.test(part)) throw new InvalidValueError(`"${input}" is not dice notation: write it like 2d6+1`);
|
|
10633
|
+
const match = TERM.exec(body);
|
|
10634
|
+
if (match === null) throw new InvalidValueError(`"${part}" is not dice notation: write it like 2d6+1, 4d6kh3 or d100`);
|
|
10635
|
+
if (match[5] !== void 0) return {
|
|
10636
|
+
sign,
|
|
10637
|
+
constant: Number(match[5])
|
|
10638
|
+
};
|
|
10639
|
+
const count = match[1] === "" || match[1] === void 0 ? 1 : Number(match[1]);
|
|
10640
|
+
const sides = match[2] === "%" ? 100 : Number(match[2]);
|
|
10641
|
+
if (count < 1 || count > 100) throw new InvalidValueError(`"${part}": roll from 1 to 100 dice at a time`);
|
|
10642
|
+
if (sides < 2 || sides > 1e3) throw new InvalidValueError(`"${part}": a die has from 2 to ${MAX_DICE_SIDES} sides`);
|
|
10643
|
+
const mode = match[3];
|
|
10644
|
+
if (mode === void 0) return {
|
|
10645
|
+
sign,
|
|
10646
|
+
count,
|
|
10647
|
+
sides
|
|
10648
|
+
};
|
|
10649
|
+
const n = match[4] === "" || match[4] === void 0 ? 1 : Number(match[4]);
|
|
10650
|
+
if (n < 1 || n >= count) throw new InvalidValueError(`"${part}": ${mode.startsWith("k") ? "keep" : "drop"} at least 1 and fewer than the ${count} dice`);
|
|
10651
|
+
return {
|
|
10652
|
+
sign,
|
|
10653
|
+
count,
|
|
10654
|
+
sides,
|
|
10655
|
+
keep: {
|
|
10656
|
+
mode,
|
|
10657
|
+
n
|
|
10658
|
+
}
|
|
10659
|
+
};
|
|
10660
|
+
});
|
|
10661
|
+
if (!terms.some((term) => "sides" in term)) throw new InvalidValueError(`"${input}" has no dice: write it like 2d6+1`);
|
|
10662
|
+
return {
|
|
10663
|
+
text: source,
|
|
10664
|
+
terms
|
|
10665
|
+
};
|
|
10666
|
+
}
|
|
10667
|
+
/** The lowest and highest total the dice and constants can give. */
|
|
10668
|
+
function notationRange(notation) {
|
|
10669
|
+
let min = 0;
|
|
10670
|
+
let max = 0;
|
|
10671
|
+
for (const term of notation.terms) {
|
|
10672
|
+
if ("constant" in term) {
|
|
10673
|
+
min += term.sign * term.constant;
|
|
10674
|
+
max += term.sign * term.constant;
|
|
10675
|
+
continue;
|
|
10676
|
+
}
|
|
10677
|
+
const kept = term.keep === void 0 ? term.count : term.keep.mode.startsWith("k") ? term.keep.n : term.count - term.keep.n;
|
|
10678
|
+
const low = kept;
|
|
10679
|
+
const high = kept * term.sides;
|
|
10680
|
+
min += term.sign === 1 ? low : -high;
|
|
10681
|
+
max += term.sign === 1 ? high : -low;
|
|
10682
|
+
}
|
|
10683
|
+
return {
|
|
10684
|
+
min,
|
|
10685
|
+
max
|
|
10686
|
+
};
|
|
10687
|
+
}
|
|
10688
|
+
function keepOf(faces, keep) {
|
|
10689
|
+
const ordered = faces.map((face, index) => ({
|
|
10690
|
+
face,
|
|
10691
|
+
index
|
|
10692
|
+
})).sort((a, b) => a.face - b.face || a.index - b.index);
|
|
10693
|
+
const count = keep.mode.startsWith("k") ? keep.n : faces.length - keep.n;
|
|
10694
|
+
return (keep.mode === "kh" || keep.mode === "dl" ? ordered.slice(ordered.length - count) : ordered.slice(0, count)).sort((a, b) => a.index - b.index).map((entry) => entry.face);
|
|
10695
|
+
}
|
|
10696
|
+
function rollNotation(notation, roll = systemRoller) {
|
|
10697
|
+
const faces = [];
|
|
10698
|
+
const kept = [];
|
|
10699
|
+
let constant = 0;
|
|
10700
|
+
let total = 0;
|
|
10701
|
+
for (const term of notation.terms) {
|
|
10702
|
+
if ("constant" in term) {
|
|
10703
|
+
constant += term.sign * term.constant;
|
|
10704
|
+
total += term.sign * term.constant;
|
|
10705
|
+
continue;
|
|
10706
|
+
}
|
|
10707
|
+
const thrown = Array.from({ length: term.count }, () => roll(term.sides));
|
|
10708
|
+
const counted = term.keep === void 0 ? thrown : keepOf(thrown, term.keep);
|
|
10709
|
+
faces.push(...thrown);
|
|
10710
|
+
kept.push(...counted);
|
|
10711
|
+
total += term.sign * counted.reduce((sum, face) => sum + face, 0);
|
|
10712
|
+
}
|
|
10713
|
+
return {
|
|
10714
|
+
faces,
|
|
10715
|
+
kept,
|
|
10716
|
+
constant,
|
|
10717
|
+
total
|
|
10718
|
+
};
|
|
10719
|
+
}
|
|
10720
|
+
const within = (band, value) => (band.min === void 0 || value >= band.min) && (band.max === void 0 || value <= band.max);
|
|
10721
|
+
/** What the bands of a check make of a value. @throws {InvalidValueError} when no band holds it. */
|
|
10722
|
+
function readBands(bands, value) {
|
|
10723
|
+
const band = bands.find((candidate) => within(candidate, value));
|
|
10724
|
+
if (band === void 0) throw new InvalidValueError(`no band of this check holds ${value}: the check's bands leave a gap`);
|
|
10725
|
+
return {
|
|
10726
|
+
label: band.label,
|
|
10727
|
+
result: band.result,
|
|
10728
|
+
critical: band.critical === true
|
|
10729
|
+
};
|
|
10730
|
+
}
|
|
10731
|
+
/**
|
|
10732
|
+
* Rolls a check: the dice, the character's modifier and any bonus added, then the value read against the bands. With the reading
|
|
10733
|
+
* `successes` the total is the number of dice that hit. A check read against a target needs one.
|
|
10734
|
+
* @throws {InvalidValueError} for a target a check needs and is not given, or bands that leave the value unread.
|
|
10735
|
+
*/
|
|
10736
|
+
function rollCheck(check, input, roll = systemRoller) {
|
|
10737
|
+
const rolled = rollNotation(parseNotation(check.dice), roll);
|
|
10738
|
+
const comparison = check.compare ?? "absolute";
|
|
10739
|
+
if (comparison !== "absolute" && input.target === void 0) throw new InvalidValueError(`the check "${check.key}" is read against a target (${comparison === "over" ? "the number to meet or beat" : "the number to stay under"}): give one`);
|
|
10740
|
+
const added = (input.modifier ?? 0) + (input.bonus ?? 0);
|
|
10741
|
+
const total = check.reading === "successes" ? rolled.kept.filter((face) => face >= (check.hit ?? 0)).length + (input.bonus ?? 0) : rolled.total + added;
|
|
10742
|
+
const value = comparison === "over" ? total - (input.target ?? 0) : comparison === "under" ? (input.target ?? 0) - total : total;
|
|
10743
|
+
return {
|
|
10744
|
+
faces: rolled.faces,
|
|
10745
|
+
kept: rolled.kept,
|
|
10746
|
+
modifier: check.reading === "successes" ? input.bonus ?? 0 : rolled.constant + added,
|
|
10747
|
+
...input.target === void 0 ? {} : { target: input.target },
|
|
10748
|
+
total,
|
|
10749
|
+
value,
|
|
10750
|
+
readout: readBands(check.bands, value)
|
|
10751
|
+
};
|
|
10752
|
+
}
|
|
10753
|
+
/** What is wrong with a check as a world defines it; nothing when it is sound. */
|
|
10754
|
+
function checkProblems(check) {
|
|
10755
|
+
const problems = [];
|
|
10756
|
+
if (!/^[a-z][a-z0-9_-]{0,39}$/.test(check.key)) problems.push(`the key "${check.key}" must be lower-case letters, digits, - or _, starting with a letter`);
|
|
10757
|
+
if (check.name.trim() === "") problems.push("it needs a name");
|
|
10758
|
+
if (check.use.trim() === "") problems.push("it needs a \"use\": when the game master rolls it");
|
|
10759
|
+
if (check.compare !== void 0 && !DICE_COMPARISONS.includes(check.compare)) problems.push(`"compare" must be one of: ${DICE_COMPARISONS.join(", ")}`);
|
|
10760
|
+
if (check.reading !== void 0 && !DICE_READINGS.includes(check.reading)) problems.push(`"reading" must be one of: ${DICE_READINGS.join(", ")}`);
|
|
10761
|
+
let notation;
|
|
10762
|
+
try {
|
|
10763
|
+
notation = parseNotation(check.dice);
|
|
10764
|
+
} catch (error) {
|
|
10765
|
+
problems.push(`"dice": ${error instanceof Error ? error.message : String(error)}`);
|
|
10766
|
+
}
|
|
10767
|
+
if (check.bands.length === 0) problems.push("it needs at least one band");
|
|
10768
|
+
if (check.bands.length > 20) problems.push(`it has more than 20 bands`);
|
|
10769
|
+
check.bands.forEach((band, index) => {
|
|
10770
|
+
if (band.label.trim() === "") problems.push(`band ${index + 1} needs a label`);
|
|
10771
|
+
if (!DICE_RESULTS.includes(band.result)) problems.push(`band ${index + 1}: "result" must be one of: ${DICE_RESULTS.join(", ")}`);
|
|
10772
|
+
if (band.min !== void 0 && band.max !== void 0 && band.min > band.max) problems.push(`band ${index + 1}: min is above max`);
|
|
10773
|
+
});
|
|
10774
|
+
if (check.reading === "successes") {
|
|
10775
|
+
const sides = notation?.terms.flatMap((term) => "sides" in term ? [term.sides] : []) ?? [];
|
|
10776
|
+
if (check.hit === void 0 || !Number.isInteger(check.hit) || check.hit < 1 || sides.length > 0 && check.hit > Math.max(...sides)) problems.push("the reading \"successes\" needs \"hit\": the lowest face that counts");
|
|
10777
|
+
}
|
|
10778
|
+
if (notation !== void 0 && problems.length === 0 && (check.compare ?? "absolute") === "absolute" && check.modifier === void 0) {
|
|
10779
|
+
const { min, max } = check.reading === "successes" ? {
|
|
10780
|
+
min: 0,
|
|
10781
|
+
max: notation.terms.reduce((sum, term) => sum + ("count" in term ? term.count : 0), 0)
|
|
10782
|
+
} : notationRange(notation);
|
|
10783
|
+
for (let value = min; value <= max; value += 1) if (!check.bands.some((band) => within(band, value))) {
|
|
10784
|
+
problems.push(`no band holds ${value}, which the dice can give`);
|
|
10785
|
+
break;
|
|
10786
|
+
}
|
|
10787
|
+
}
|
|
10788
|
+
return problems;
|
|
10789
|
+
}
|
|
10790
|
+
function rollPlain(expression, roll = systemRoller) {
|
|
10791
|
+
const notation = parseNotation(expression);
|
|
10792
|
+
const rolled = rollNotation(notation, roll);
|
|
10793
|
+
return {
|
|
10794
|
+
expression: notation.text,
|
|
10795
|
+
faces: rolled.faces,
|
|
10796
|
+
kept: rolled.kept,
|
|
10797
|
+
modifier: rolled.constant,
|
|
10798
|
+
total: rolled.total
|
|
10799
|
+
};
|
|
10800
|
+
}
|
|
10801
|
+
//#endregion
|
|
10802
|
+
//#region src/tools/dice.ts
|
|
10803
|
+
const ROLL_DICE_TOOL = "roll_dice";
|
|
10804
|
+
const ROLL_CHECK_TOOL = "roll_check";
|
|
10805
|
+
const LIST_CHECKS_TOOL = "list_checks";
|
|
10806
|
+
const sign = (value) => value >= 0 ? `+${value}` : String(value);
|
|
10807
|
+
/** How a roll reads to the agent that asked for it: what fell, what it adds up to, and what the world's check makes of it. */
|
|
10808
|
+
function describeRoll(roll, check) {
|
|
10809
|
+
const dice = `[${roll.faces.join(", ")}]${roll.kept.length < roll.faces.length ? ` keeping [${roll.kept.join(", ")}]` : ""}`;
|
|
10810
|
+
const lines = [`Roll ${roll.id}: ${roll.expression}${check === void 0 ? "" : ` for the check "${check.name}"`}${roll.character === void 0 ? "" : ` (${roll.character})`} - ${dice}${roll.modifier === 0 ? "" : ` ${sign(roll.modifier)}`} = ${roll.total}${check?.reading === "successes" ? " successes" : ""}.`];
|
|
10811
|
+
if (roll.target !== void 0) lines.push(`Against ${roll.target}: ${roll.value >= 0 ? "+" : ""}${roll.value}.`);
|
|
10812
|
+
if (roll.readout !== void 0) lines.push(`That is: ${roll.readout.label}${roll.readout.critical ? " (critical)" : ""}; the result is ${roll.readout.result}. This is decided by the dice: do not change it, narrate it.${check === void 0 || roll.character === void 0 ? "" : ` In resolve_round give ${roll.character}'s outcome this result and cite the roll: roll ${roll.id}.`}`);
|
|
10813
|
+
return lines.join(" ");
|
|
10814
|
+
}
|
|
10815
|
+
const rolled = (_args, value) => [{
|
|
10816
|
+
type: "text",
|
|
10817
|
+
text: value.text
|
|
10818
|
+
}];
|
|
10819
|
+
/**
|
|
10820
|
+
* Tools that roll dice for an agent, so that numbers are not made up. `roll_check` rolls a check the story defines and reads
|
|
10821
|
+
* the result with the check's own bands; `roll_dice` rolls plain notation for what has no check. The roller is the system's
|
|
10822
|
+
* source of randomness unless one is given.
|
|
10823
|
+
*/
|
|
10824
|
+
function createDiceTools(resolve, options = {}) {
|
|
10825
|
+
const roller = options.roller ?? systemRoller;
|
|
10826
|
+
const linkOf = (agent) => {
|
|
10827
|
+
const found = agent === void 0 ? void 0 : resolve(agent.session.id);
|
|
10828
|
+
if (found === void 0) throw new NotFoundError("this agent is not working in a roleplay session, so there are no dice to roll");
|
|
10829
|
+
return found;
|
|
10830
|
+
};
|
|
10831
|
+
return [
|
|
10832
|
+
defineTool({
|
|
10833
|
+
name: LIST_CHECKS_TOOL,
|
|
10834
|
+
description: "List the checks this story defines for settling uncertain attempts: when each is used, the dice it rolls and how its result is read.",
|
|
10835
|
+
parameters: {},
|
|
10836
|
+
output: {
|
|
10837
|
+
schema: { type: "json" },
|
|
10838
|
+
render: (_args, value) => [{
|
|
10839
|
+
type: "text",
|
|
10840
|
+
text: JSON.stringify(value, null, 2)
|
|
10841
|
+
}]
|
|
10842
|
+
},
|
|
10843
|
+
isConcurrencySafe: () => true,
|
|
10844
|
+
execute(_args, exec) {
|
|
10845
|
+
const { session } = linkOf(exec.agent);
|
|
10846
|
+
return Promise.resolve(session.dice.checks().map((check) => ({
|
|
10847
|
+
key: check.key,
|
|
10848
|
+
name: check.name,
|
|
10849
|
+
use: check.use,
|
|
10850
|
+
dice: check.dice,
|
|
10851
|
+
compare: check.compare ?? "absolute",
|
|
10852
|
+
...check.modifier === void 0 ? {} : { modifier: `the character's ${check.modifier}` },
|
|
10853
|
+
bands: check.bands.map((band) => ({
|
|
10854
|
+
...band.min === void 0 ? {} : { from: band.min },
|
|
10855
|
+
...band.max === void 0 ? {} : { to: band.max },
|
|
10856
|
+
label: band.label,
|
|
10857
|
+
result: band.result
|
|
10858
|
+
}))
|
|
10859
|
+
})));
|
|
10860
|
+
}
|
|
10861
|
+
}),
|
|
10862
|
+
defineTool({
|
|
10863
|
+
name: ROLL_CHECK_TOOL,
|
|
10864
|
+
description: "Roll one of the story's checks for an attempt whose outcome is uncertain and matters: the dice are rolled for you and the result is read with the check's own bands, so you never choose or make up a number. Use the check whose \"use\" fits (list_checks shows them). Roll once per attempt, before you decide what happens, and let the result stand: a failed roll is a failure, whatever the story would like.",
|
|
10865
|
+
parameters: {
|
|
10866
|
+
check: {
|
|
10867
|
+
type: "string",
|
|
10868
|
+
required: true,
|
|
10869
|
+
description: "The key of the check, as list_checks gives it."
|
|
10870
|
+
},
|
|
10871
|
+
reason: {
|
|
10872
|
+
type: "string",
|
|
10873
|
+
required: true,
|
|
10874
|
+
description: "What is attempted, by whom, in a sentence."
|
|
10875
|
+
},
|
|
10876
|
+
character: {
|
|
10877
|
+
type: "string",
|
|
10878
|
+
required: true,
|
|
10879
|
+
description: "The key of the character the attempt is for (the player's character included): the roll is theirs, and their outcome in resolve_round takes its result from it."
|
|
10880
|
+
},
|
|
10881
|
+
target: {
|
|
10882
|
+
type: "integer",
|
|
10883
|
+
description: "When the check is read against a target (list_checks says compare over or under): the difficulty, the number the total must meet or beat (or stay under). Set it by how hard the attempt is, using what the check's \"use\" says about difficulty; never 0 to make it easy."
|
|
10884
|
+
},
|
|
10885
|
+
bonus: {
|
|
10886
|
+
type: "integer",
|
|
10887
|
+
description: "What the circumstances add to or take from the roll, from -10 to 10; leave out for none."
|
|
10888
|
+
}
|
|
10889
|
+
},
|
|
10890
|
+
output: {
|
|
10891
|
+
schema: { type: "json" },
|
|
10892
|
+
render: rolled
|
|
10893
|
+
},
|
|
10894
|
+
isConcurrencySafe: () => false,
|
|
10895
|
+
execute(args, exec) {
|
|
10896
|
+
const { session, turn } = linkOf(exec.agent);
|
|
10897
|
+
const check = session.dice.check(args.check);
|
|
10898
|
+
if (check === void 0) throw new NotFoundError(`there is no check "${args.check}"; list_checks shows the story's checks`);
|
|
10899
|
+
if (session.characters.list().every((entry) => characterKey(entry) !== args.character)) throw new NotFoundError(`there is no character "${args.character}"; list_characters shows the keys`);
|
|
10900
|
+
let modifier;
|
|
10901
|
+
if (check.modifier !== void 0) {
|
|
10902
|
+
const who = session.characters.list().find((entry) => characterKey(entry) === args.character);
|
|
10903
|
+
if (who === void 0) throw new NotFoundError(`there is no character "${args.character}"; list_characters shows the keys`);
|
|
10904
|
+
const value = session.variables.value(check.modifier, "character", who.id);
|
|
10905
|
+
if (typeof value !== "number") throw new NotFoundError(`the character's ${check.modifier} is not a number, so the check cannot add it`);
|
|
10906
|
+
modifier = value;
|
|
10907
|
+
}
|
|
10908
|
+
const bonus = Math.max(-10, Math.min(10, args.bonus ?? 0));
|
|
10909
|
+
const made = rollCheck(check, {
|
|
10910
|
+
...modifier === void 0 ? {} : { modifier },
|
|
10911
|
+
bonus,
|
|
10912
|
+
...args.target === void 0 ? {} : { target: args.target }
|
|
10913
|
+
}, roller);
|
|
10914
|
+
const roll = session.dice.record({
|
|
10915
|
+
turn,
|
|
10916
|
+
check: check.key,
|
|
10917
|
+
expression: check.dice,
|
|
10918
|
+
faces: made.faces,
|
|
10919
|
+
kept: made.kept,
|
|
10920
|
+
modifier: made.modifier,
|
|
10921
|
+
...made.target === void 0 ? {} : { target: made.target },
|
|
10922
|
+
total: made.total,
|
|
10923
|
+
value: made.value,
|
|
10924
|
+
readout: made.readout,
|
|
10925
|
+
reason: args.reason,
|
|
10926
|
+
character: args.character,
|
|
10927
|
+
by: options.by ?? "agent"
|
|
10928
|
+
});
|
|
10929
|
+
return Promise.resolve({
|
|
10930
|
+
roll: roll.id,
|
|
10931
|
+
check: check.key,
|
|
10932
|
+
total: roll.total,
|
|
10933
|
+
result: made.readout.result,
|
|
10934
|
+
label: made.readout.label,
|
|
10935
|
+
critical: made.readout.critical,
|
|
10936
|
+
text: describeRoll(roll, check)
|
|
10937
|
+
});
|
|
10938
|
+
}
|
|
10939
|
+
}),
|
|
10940
|
+
defineTool({
|
|
10941
|
+
name: ROLL_DICE_TOOL,
|
|
10942
|
+
description: "Roll dice for something the story has no check for (damage, a random pick, who goes first, a rumour table): the dice are rolled for you, so you never make up a number. Write the notation like 1d6, 2d4+1, 4d6kh3 (keep the highest 3) or d100. What the number means is for you to decide from the rules you were given; when a check fits the attempt, use roll_check instead.",
|
|
10943
|
+
parameters: {
|
|
10944
|
+
expression: {
|
|
10945
|
+
type: "string",
|
|
10946
|
+
required: true,
|
|
10947
|
+
description: "Dice notation, for example 2d6+1."
|
|
10948
|
+
},
|
|
10949
|
+
reason: {
|
|
10950
|
+
type: "string",
|
|
10951
|
+
required: true,
|
|
10952
|
+
description: "What the roll is for, in a sentence."
|
|
10953
|
+
},
|
|
10954
|
+
character: {
|
|
10955
|
+
type: "string",
|
|
10956
|
+
description: "The key of the character it is for, when it is for one."
|
|
10957
|
+
}
|
|
10958
|
+
},
|
|
10959
|
+
output: {
|
|
10960
|
+
schema: { type: "json" },
|
|
10961
|
+
render: rolled
|
|
10962
|
+
},
|
|
10963
|
+
isConcurrencySafe: () => false,
|
|
10964
|
+
execute(args, exec) {
|
|
10965
|
+
const { session, turn } = linkOf(exec.agent);
|
|
10966
|
+
const made = rollPlain(args.expression, roller);
|
|
10967
|
+
const roll = session.dice.record({
|
|
10968
|
+
turn,
|
|
10969
|
+
expression: made.expression,
|
|
10970
|
+
faces: made.faces,
|
|
10971
|
+
kept: made.kept,
|
|
10972
|
+
modifier: made.modifier,
|
|
10973
|
+
total: made.total,
|
|
10974
|
+
value: made.total,
|
|
10975
|
+
reason: args.reason,
|
|
10976
|
+
...args.character === void 0 ? {} : { character: args.character },
|
|
10977
|
+
by: options.by ?? "agent"
|
|
10978
|
+
});
|
|
10979
|
+
return Promise.resolve({
|
|
10980
|
+
roll: roll.id,
|
|
10981
|
+
total: roll.total,
|
|
10982
|
+
text: describeRoll(roll)
|
|
10983
|
+
});
|
|
10984
|
+
}
|
|
10985
|
+
})
|
|
10986
|
+
];
|
|
10987
|
+
}
|
|
10988
|
+
//#endregion
|
|
9814
10989
|
//#region src/records/lint.ts
|
|
9815
10990
|
/**
|
|
9816
10991
|
* What is inconsistent or unfinished in the world's graph of places and characters. Things made up in
|
|
@@ -10004,8 +11179,9 @@ function lintWorld(target) {
|
|
|
10004
11179
|
//#region src/records/agenda.ts
|
|
10005
11180
|
/** An engagement still pending this long after it was due (or over) is overdue. */
|
|
10006
11181
|
const ENGAGEMENT_GRACE = 30 * MINUTE;
|
|
10007
|
-
function occurrenceOn(window, day, zone) {
|
|
11182
|
+
function occurrenceOn(window, day, zone, calendar) {
|
|
10008
11183
|
if (window.days.length > 0 && !window.days.includes(day.weekday)) return void 0;
|
|
11184
|
+
if (window.on.length > 0 && !dayMatches(window.on, day, calendar)) return void 0;
|
|
10009
11185
|
const startsAt = zonedInstant({
|
|
10010
11186
|
...day,
|
|
10011
11187
|
hour: Math.floor(window.start / 60),
|
|
@@ -10022,15 +11198,15 @@ function occurrenceOn(window, day, zone) {
|
|
|
10022
11198
|
};
|
|
10023
11199
|
}
|
|
10024
11200
|
/** The window that holds at an instant (the first of the list that does), with the stretch of this occurrence of it. */
|
|
10025
|
-
function occurrenceAt(windows, now, zone) {
|
|
11201
|
+
function occurrenceAt(windows, now, zone, calendar = NO_CALENDAR) {
|
|
10026
11202
|
const days = [addLocalDays(now, zone, 0), addLocalDays(now, zone, -1)];
|
|
10027
11203
|
for (const window of windows) for (const day of days) {
|
|
10028
|
-
const occurrence = occurrenceOn(window, day, zone);
|
|
11204
|
+
const occurrence = occurrenceOn(window, day, zone, calendar);
|
|
10029
11205
|
if (occurrence !== void 0 && occurrence.startsAt <= now && now < occurrence.endsAt) return occurrence;
|
|
10030
11206
|
}
|
|
10031
11207
|
}
|
|
10032
11208
|
/** The window that starts next after an instant, within `horizon`. */
|
|
10033
|
-
function nextOccurrence(windows, now, zone, horizon) {
|
|
11209
|
+
function nextOccurrence(windows, now, zone, horizon, calendar = NO_CALENDAR) {
|
|
10034
11210
|
let best;
|
|
10035
11211
|
for (const offset of [
|
|
10036
11212
|
0,
|
|
@@ -10039,7 +11215,7 @@ function nextOccurrence(windows, now, zone, horizon) {
|
|
|
10039
11215
|
]) {
|
|
10040
11216
|
const day = addLocalDays(now, zone, offset);
|
|
10041
11217
|
for (const window of windows) {
|
|
10042
|
-
const occurrence = occurrenceOn(window, day, zone);
|
|
11218
|
+
const occurrence = occurrenceOn(window, day, zone, calendar);
|
|
10043
11219
|
if (occurrence === void 0 || occurrence.startsAt <= now || occurrence.startsAt > now + horizon) continue;
|
|
10044
11220
|
if (best === void 0 || occurrence.startsAt < best.startsAt) best = occurrence;
|
|
10045
11221
|
}
|
|
@@ -10072,8 +11248,9 @@ const DEFAULTS = {
|
|
|
10072
11248
|
function agendaAt(source, characterId, now, zone, options = {}) {
|
|
10073
11249
|
const horizon = options.horizon ?? DEFAULTS.horizon;
|
|
10074
11250
|
const windows = source.routines.list(characterId);
|
|
10075
|
-
const
|
|
10076
|
-
const
|
|
11251
|
+
const calendar = source.metadata === void 0 ? NO_CALENDAR : readCalendar(source.metadata);
|
|
11252
|
+
const current = occurrenceAt(windows, now, zone, calendar);
|
|
11253
|
+
const next = nextOccurrence(windows, now, zone, horizon, calendar);
|
|
10077
11254
|
const engagements = source.engagements.list({
|
|
10078
11255
|
status: "pending",
|
|
10079
11256
|
participant: characterId,
|
|
@@ -11396,7 +12573,8 @@ function createTools(resolve, options) {
|
|
|
11396
12573
|
//#region src/plugin.ts
|
|
11397
12574
|
const Config = z.object({
|
|
11398
12575
|
journalMode: z.union(["delete", "wal"]).default("delete"),
|
|
11399
|
-
writeTools: z.boolean().default(false)
|
|
12576
|
+
writeTools: z.boolean().default(false),
|
|
12577
|
+
diceTools: z.boolean().default(false)
|
|
11400
12578
|
});
|
|
11401
12579
|
/**
|
|
11402
12580
|
* Roleplay session state for other plugins. It creates and opens sessions
|
|
@@ -11422,6 +12600,7 @@ var RoleplayState = class extends Service {
|
|
|
11422
12600
|
};
|
|
11423
12601
|
ctx.inject(["tools"], (scope) => {
|
|
11424
12602
|
for (const tool of createTools(resolve, { write: config.writeTools ?? false })) scope.tools.register(tool);
|
|
12603
|
+
if (config.diceTools === true) for (const tool of createDiceTools(resolve)) scope.tools.register(tool);
|
|
11425
12604
|
});
|
|
11426
12605
|
}
|
|
11427
12606
|
/**
|
|
@@ -11557,6 +12736,7 @@ var RoleplayState = class extends Service {
|
|
|
11557
12736
|
routines: store.routines,
|
|
11558
12737
|
engagements: store.engagements,
|
|
11559
12738
|
triggers: store.triggers,
|
|
12739
|
+
dice: store.dice,
|
|
11560
12740
|
planMove: (characterId, move) => store.planMove(characterId, move),
|
|
11561
12741
|
planKnownMove: (characterId, move) => store.planKnownMove(characterId, move),
|
|
11562
12742
|
move: (characterId, move) => store.move(characterId, move),
|
|
@@ -11826,6 +13006,6 @@ function createCharacterTools(link, characterId) {
|
|
|
11826
13006
|
];
|
|
11827
13007
|
}
|
|
11828
13008
|
//#endregion
|
|
11829
|
-
export { ACCURACIES, CHARACTER_CONTROLS, CHARACTER_KINDS, CHARACTER_ROLES, CHARACTER_TIERS, CLOCK_MODES, COMPARISONS, CONSOLIDATION_KINDS, CONTAINER_STATES, Config, ConflictError, CorruptDataError, DAY, DAY_PARTS, DEFAULT_MEMORY_POLICY, DEFAULT_SLOTS, EMOTION_VARIABLES, ENGAGEMENT_GRACE, ENGAGEMENT_STATUSES, EXIT_STATES, HOUR, IMPORTANCE_MAX, IMPORTANCE_MIN, INTENT_HORIZONS, INTENT_STATUSES, INTENT_TYPES, ITEM_ACTIONS, InvalidEditError, InvalidMoveError, InvalidValueError, KNOWLEDGE_KINDS, LORE_KINDS, LORE_SUBJECT_KINDS, MAX_LOCATION_FEATURES, MAX_ROUTINE_WINDOWS, MAX_SHOWN, MAX_TRIGGERS, MAX_VARIABLES, MEMBERSHIP_STATUSES, MEMORY_KINDS, MIGRATION_BACKUP_FOLDER, MINUTE, NotFoundError, PERCEPTION_MODES, PLACEABLE_KINDS, PLOT_NUDGE_MODES, PLOT_STATUSES, RECALL_ITEM_KINDS, RoleplayState, LATEST_VERSION as SCHEMA_VERSION, SECOND, SELF, SESSION_DATABASE_FILE, SLOT_ID, SUMMARY_LEVELS, SchemaError, StateError, TEMPLATE_KINDS, THREAD_STATUSES, TIME_CHANGE_KINDS, TRIGGER_NOTE_KINDS, TRIGGER_STATUSES, UnwritableError, VARIABLE_KEY, VARIABLE_KINDS, VARIABLE_SUBJECTS, VARIABLE_VISIBILITIES, VISUAL_SUBJECT_KINDS, WEEKDAY_NAMES, addLocalDays, agendaAt, announcements, apply, applyItemPlan, applyRecordsEdit, audienceIds, catchUpPlan, changeItem, changeVariable, characterKey, clarityAt, clockTime, conditionHolds, createCharacterTools, createSession, createSessionStore, createTools, dayPart, deleteSession, deliverNotes, denseScore, describeDuration, describeHolder, describeInstant, describeValue, finishRecall, fuseScores, inAudience, inWindow, isSessionId, isTimeZone, itemKey, jitter, judgedStatements, lapseNotes, lintWorld, listSessions, localIso, localTime, locationKey, mentioned, migrationBackupPath, moveCharacter, name, nextOccurrence, nextTimeOfDay, nodeSqliteDriver, occurrenceAt, openSession, openSessionStore, overlap, parseInstant, parseRecordsEdit, parseTimeOfDay, parseTriggerDefinition, placeholdersOf, planItemChange, planKnownMove, planMove, planVariableChange, poolCandidates, rankGoals, readRecords, readTriggerDefinition, recall, recallCandidates, recallPool, recordsEditSchema, renderWords, retention, runTriggers, seededRoll, sessionDatabase, sessionFolder, shownVariables, subjectOf, systemClock, systemTimeZone, tokens, triggerReferenceProblems, valueIssue, zonedInstant };
|
|
13009
|
+
export { ACCURACIES, CHARACTER_CONTROLS, CHARACTER_KINDS, CHARACTER_ROLES, CHARACTER_TIERS, CLOCK_MODES, COMPARISONS, CONSOLIDATION_KINDS, CONTAINER_STATES, Config, ConflictError, CorruptDataError, DAY, DAY_PARTS, DEFAULT_CONFIDENCE, DEFAULT_MEMORY_POLICY, DEFAULT_SLOTS, DICE_COMPARISONS, DICE_READINGS, DICE_RESULTS, EMOTION_VARIABLES, ENGAGEMENT_GRACE, ENGAGEMENT_STATUSES, EVIDENCE_RELATIONS, EXIT_STATES, HOUR, IMPORTANCE_MAX, IMPORTANCE_MIN, INTENT_HORIZONS, INTENT_STATUSES, INTENT_TYPES, ITEM_ACTIONS, InvalidEditError, InvalidMoveError, InvalidValueError, KNOWLEDGE_KINDS, LANGUAGES, LIST_CHECKS_TOOL, LORE_KINDS, LORE_SUBJECT_KINDS, MAX_DICE_BANDS, MAX_DICE_CHECKS, MAX_DICE_PER_ROLL, MAX_DICE_SIDES, MAX_DICE_TERMS, MAX_LOCATION_FEATURES, MAX_ROUTINE_WINDOWS, MAX_SHOWN, MAX_TRIGGERS, MAX_VARIABLES, MEMBERSHIP_STATUSES, MEMORY_CONFIDENCES, MEMORY_KINDS, MIGRATION_BACKUP_FOLDER, MINUTE, MONTH_NAMES, NO_CALENDAR, NotFoundError, PERCEPTION_MODES, PLACEABLE_KINDS, PLOT_NUDGE_MODES, PLOT_STATUSES, RECALL_ITEM_KINDS, ROLL_CHECK_TOOL, ROLL_DICE_TOOL, RoleplayState, LATEST_VERSION as SCHEMA_VERSION, SEASONS, SECOND, SELF, SESSION_DATABASE_FILE, SLOT_ID, SUMMARY_LEVELS, SchemaError, StateError, TEMPLATE_KINDS, THREAD_STATUSES, TIME_CHANGE_KINDS, TRIGGER_NOTE_KINDS, TRIGGER_STATUSES, UnwritableError, VARIABLE_KEY, VARIABLE_KINDS, VARIABLE_SUBJECTS, VARIABLE_VISIBILITIES, VISUAL_SUBJECT_KINDS, WEEKDAY_NAMES, addLocalDays, agendaAt, announcements, apply, applyItemPlan, applyRecordsEdit, audienceIds, catchUpPlan, changeItem, changeVariable, characterKey, checkProblems, clarityAt, clockTime, conditionHolds, createCharacterTools, createDiceTools, createSession, createSessionStore, createTools, dayMatches, dayPart, dayTokenProblems, deleteSession, deliverNotes, denseScore, describeDuration, describeHolder, describeInstant, describeRoll, describeValue, finishRecall, fuseScores, holidaysOn, inAudience, inWindow, isSessionId, isTimeZone, itemKey, jitter, judgedStatements, languageOf, lapseNotes, lintWorld, listSessions, localIso, localTime, locationKey, mentioned, migrationBackupPath, moveCharacter, name, nextOccurrence, nextTimeOfDay, nodeSqliteDriver, notationRange, occurrenceAt, openSession, openSessionStore, overlap, parseDayToken, parseInstant, parseNotation, parseRecordsEdit, parseTimeOfDay, parseTriggerDefinition, placeholdersOf, planItemChange, planKnownMove, planMove, planVariableChange, poolCandidates, rankGoals, readBands, readCalendar, readRecords, readTriggerDefinition, recall, recallCandidates, recallPool, recordsEditSchema, renderWords, retention, rollCheck, rollNotation, rollPlain, runTriggers, seasonOf, seededRoll, sessionDatabase, sessionFolder, shownVariables, subjectOf, systemClock, systemRoller, systemTimeZone, tokens, translated, translation, translator, triggerReferenceProblems, valueIssue, writeCalendar, zonedInstant };
|
|
11830
13010
|
|
|
11831
13011
|
//# sourceMappingURL=index.js.map
|