holonovel 2026.8.22
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/LICENSE.md +21 -0
- package/bin/holonovel.js +5 -0
- package/dist/core/character-creation.d.ts +116 -0
- package/dist/core/character-creation.js +346 -0
- package/dist/core/character-creation.js.map +1 -0
- package/dist/core/enrichment.d.ts +45 -0
- package/dist/core/enrichment.js +269 -0
- package/dist/core/enrichment.js.map +1 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.js +5 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/macros.d.ts +21 -0
- package/dist/core/macros.js +50 -0
- package/dist/core/macros.js.map +1 -0
- package/dist/core/rng.d.ts +17 -0
- package/dist/core/rng.js +72 -0
- package/dist/core/rng.js.map +1 -0
- package/dist/core/server.d.ts +18 -0
- package/dist/core/server.js +40 -0
- package/dist/core/server.js.map +1 -0
- package/dist/core/state.d.ts +396 -0
- package/dist/core/state.js +1080 -0
- package/dist/core/state.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4051 -0
- package/dist/index.js.map +1 -0
- package/dist/rulesets.d.ts +65 -0
- package/dist/rulesets.js +212 -0
- package/dist/rulesets.js.map +1 -0
- package/dist/world/index.d.ts +4 -0
- package/dist/world/index.js +3 -0
- package/dist/world/index.js.map +1 -0
- package/dist/world/model.d.ts +125 -0
- package/dist/world/model.js +492 -0
- package/dist/world/model.js.map +1 -0
- package/dist/world/parser.d.ts +18 -0
- package/dist/world/parser.js +462 -0
- package/dist/world/parser.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
// Enrichment Manifest — Tier 1 vendor + ruleset-native enrichment
|
|
2
|
+
// REQ-080: additive only, never modifies mechanics
|
|
3
|
+
// REQ-225: Tier 1 enrichment — vendor content processed at build time
|
|
4
|
+
// REQ-227: Tier 1 (ruleset-native + vendor, never removed by revert_synthesis)
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
const SPEC_VERSION = JSON.parse(fs.readFileSync(new URL("../../package.json", import.meta.url), "utf-8")).version;
|
|
7
|
+
export const DEFAULT_ENRICHMENT = {
|
|
8
|
+
collected_at: new Date().toISOString(),
|
|
9
|
+
spec_version: SPEC_VERSION,
|
|
10
|
+
voice_examples: [
|
|
11
|
+
{
|
|
12
|
+
content: "NPC voice design framework — define pitch, speed, tone, accent, and quirks for each named NPC. Use descriptive voice tags ('raspy baritone', 'clipped military cadence') rather than acting instructions. Voice descriptions should suggest character history: a hoarse whisper implies decades of secrets, a booming laugh implies untested confidence.",
|
|
13
|
+
source_url: "narrative_world_model/narrative/dmcp/README.md",
|
|
14
|
+
confidence: "HIGH",
|
|
15
|
+
tag: "vendor",
|
|
16
|
+
badge_scope: "game_master",
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
briefing_order: {
|
|
20
|
+
sections: ["scene", "active_threads", "immediate_situation", "entity_states", "lore_triggered", "pending_decisions", "guidance"],
|
|
21
|
+
reason: "Prioritize actionable narrative state over reference content — scene first, then thread urgency, then immediate player-facing situation, then entity states and triggered memories, then pending decisions the GM flagged, then guidance",
|
|
22
|
+
source_url: "narrative_world_model/narrative/dmcp/README.md",
|
|
23
|
+
confidence: "HIGH",
|
|
24
|
+
},
|
|
25
|
+
lore_templates: [
|
|
26
|
+
{
|
|
27
|
+
content: "Worldbuilding seed — a location, faction, or event that shapes the setting. Three known facts, one secret, one rumor. NPCs who know each. A clock tracking how soon it becomes relevant.",
|
|
28
|
+
source_url: "narrative_world_model/narrative/if-craft-corpus/README.md",
|
|
29
|
+
confidence: "HIGH",
|
|
30
|
+
tag: "vendor",
|
|
31
|
+
badge_scope: "game_master",
|
|
32
|
+
category: "worldbuilding",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
content: "Faction relationship network — for each faction, record allies, rivals, neutral parties, and a hidden agenda. When a faction takes action, ripple effects propagate through the relationship graph.",
|
|
36
|
+
source_url: "narrative_world_model/narrative/dmcp/README.md",
|
|
37
|
+
confidence: "HIGH",
|
|
38
|
+
tag: "vendor",
|
|
39
|
+
badge_scope: "game_master",
|
|
40
|
+
category: "faction_network",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
content: "Kind hierarchy design — the world model classifies everything under 'thing', subdivided into kinds: container (openable, lockable), supporter (sit/stand), door (connects rooms), device (switched on/off), vehicle (enterable, mobile), person (NPCs), backdrop (scenery), region (spatial grouping). Each kind carries mechanical contracts: a container holds inventory, a door blocks passage when closed, a device changes state, a vehicle has an interior that functions as a contained space. Choose the kind that matches the object's behavior, not just its name.",
|
|
44
|
+
source_url: "narrative_world_model/world/world-model-provider.md",
|
|
45
|
+
confidence: "HIGH",
|
|
46
|
+
tag: "vendor",
|
|
47
|
+
badge_scope: "game_master",
|
|
48
|
+
category: "kind_hierarchy",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
content: "Device world design — devices are the simplest form of machine: always in one of two states, switched on or switched off. Use devices where binary state changes the fiction: a powered generator changes what's visible, an active alarm changes who's present, a running machine changes the environment turn by turn. The device's state is separate from its description — switching it changes the fiction without changing the object. Design puzzles where device states cascade: switching one changes what another does.",
|
|
52
|
+
source_url: "narrative_world_model/world/world-model-provider.md",
|
|
53
|
+
confidence: "HIGH",
|
|
54
|
+
tag: "vendor",
|
|
55
|
+
badge_scope: "game_master",
|
|
56
|
+
category: "device_world_design",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
content: "Vehicles as world fragments — a vehicle is a mobile room container. Its interior is a sub-world with its own contents and exits. Boarding a vehicle changes what the player can see, touch, and interact with. Exits from a vehicle interior connect to the outside world when the vehicle is stationary. Design vehicles as story engines: a ship's crew quarters, an elevator's single-button control, a train's dining car filling with water. The vehicle is never just transport — it's a location that moves.",
|
|
60
|
+
source_url: "narrative_world_model/world/world-model-provider.md",
|
|
61
|
+
confidence: "HIGH",
|
|
62
|
+
tag: "vendor",
|
|
63
|
+
badge_scope: "game_master",
|
|
64
|
+
category: "vehicle_world_design",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
content: "Environmental text storytelling — readable objects deliver exposition through discovery, not narration. A readable inscription on a wall, a diary entry in a drawer, a warning carved into a doorframe — each tells the player something the world's past occupants chose to record. The read_text property holds discoverable text; the description property holds immediately visible text. Use the gap: a readable object that says one thing while its description shows another creates tension (a cheerful sign on a crumbling wall, a locked diary with a bookmark at a crucial entry).",
|
|
68
|
+
source_url: "narrative_world_model/world/world-model-provider.md",
|
|
69
|
+
confidence: "HIGH",
|
|
70
|
+
tag: "vendor",
|
|
71
|
+
badge_scope: "game_master",
|
|
72
|
+
category: "environmental_text",
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
action_patterns: [
|
|
76
|
+
{
|
|
77
|
+
intent: "present structured choices to the player",
|
|
78
|
+
expected_categories: ["Command"],
|
|
79
|
+
ruleset_section: "Guidance — DMCP player interaction",
|
|
80
|
+
source_url: "narrative_world_model/narrative/dmcp/README.md",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
intent: "manage combat with initiative, conditions, and turn order",
|
|
84
|
+
expected_categories: ["Resolution", "Command"],
|
|
85
|
+
ruleset_section: "Guidance — DMCP combat management",
|
|
86
|
+
source_url: "narrative_world_model/narrative/dmcp/README.md",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
intent: "interact with world objects using parser commands",
|
|
90
|
+
expected_categories: ["Command"],
|
|
91
|
+
ruleset_section: "World Model — parser command dispatch (REQ-319)",
|
|
92
|
+
source_url: "narrative_world_model/world/world-model-provider.md",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
intent: "turn devices and machines on or off",
|
|
96
|
+
expected_categories: ["Command"],
|
|
97
|
+
ruleset_section: "World Model — device kind (REQ-316)",
|
|
98
|
+
source_url: "narrative_world_model/world/world-model-provider.md",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
intent: "enter, exit, or navigate aboard a vehicle",
|
|
102
|
+
expected_categories: ["Command"],
|
|
103
|
+
ruleset_section: "World Model — vehicle kind (REQ-317)",
|
|
104
|
+
source_url: "narrative_world_model/world/world-model-provider.md",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
intent: "wear, remove, eat, drink, or climb an object",
|
|
108
|
+
expected_categories: ["Command"],
|
|
109
|
+
ruleset_section: "World Model — extended property commands (REQ-318)",
|
|
110
|
+
source_url: "narrative_world_model/world/world-model-provider.md",
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
intent: "read text or inspect readable objects",
|
|
114
|
+
expected_categories: ["Command"],
|
|
115
|
+
ruleset_section: "World Model — readable property (REQ-318)",
|
|
116
|
+
source_url: "narrative_world_model/world/world-model-provider.md",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
intent: "ask, tell, give, show, or throw something at an NPC",
|
|
120
|
+
expected_categories: ["Command"],
|
|
121
|
+
ruleset_section: "World Model — narrative-intent verbs (REQ-320)",
|
|
122
|
+
source_url: "narrative_world_model/world/world-model-provider.md",
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
supplementary_guidance: [
|
|
126
|
+
{
|
|
127
|
+
content: "Separate mechanics from fiction at the table. Track dice rolls, modifiers, and system resolution on one axis; track narrative outcomes, character reactions, and scene evolution on another. Both coexist without blurring — the Lonelog notation model (action → roll → outcome → oracle).",
|
|
128
|
+
source_url: "narrative_world_model/narrative/lonelog/lonelog.md",
|
|
129
|
+
confidence: "HIGH",
|
|
130
|
+
tag: "vendor",
|
|
131
|
+
badge_scope: "game_master",
|
|
132
|
+
category: "session_notation",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
content: "Before ending a session, capture pause context: current scene description, immediate situation, scene atmosphere, pending player action, short-term GM plans, long-term GM plans, active threads with urgency, NPC attitude snapshots, and player apparent goals. This context enables seamless resumption.",
|
|
136
|
+
source_url: "narrative_world_model/narrative/dmcp/README.md",
|
|
137
|
+
confidence: "HIGH",
|
|
138
|
+
tag: "vendor",
|
|
139
|
+
badge_scope: "game_master",
|
|
140
|
+
category: "session_management",
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
content: "Use clocks to track progressive tension — for dangers approaching (alert level, pursuit proximity), races against time (escape vs capture), linked multi-phase obstacles (defense then vulnerability), faction projects ticking forward during downtime, and tug-of-war situations where events can fill or empty the clock.",
|
|
144
|
+
source_url: "narrative_world_model/narrative/bitd/progress-clocks.md",
|
|
145
|
+
confidence: "HIGH",
|
|
146
|
+
tag: "vendor",
|
|
147
|
+
badge_scope: "game_master",
|
|
148
|
+
category: "clock_design",
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
content: "Parser command tier system — the world-model parser organizes commands into four tiers. Core (go, look, examine, take, drop, inventory, wait, open, close, unlock, lock) covers fundamental interaction every player needs. Standard (wear, remove, eat, drink, climb, enter, exit, switch on, switch off, sit, stand, push, pull, light, extinguish, listen, smell, touch, insert, read) extends to object properties and sensory verbs. Narrative (ask, tell, give, show, throw) covers NPC-directed social verbs that route to the GM surface. Meta (again, g, help, verbs) covers parser-level operations. Unknown commands fall through to the GM for narrative interpretation — the parser is a floor, not a ceiling.",
|
|
152
|
+
source_url: "narrative_world_model/world/world-model-provider.md",
|
|
153
|
+
confidence: "HIGH",
|
|
154
|
+
tag: "vendor",
|
|
155
|
+
badge_scope: "game_master",
|
|
156
|
+
category: "parser_tiers",
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
content: "Device interaction patterns — devices work at the fiction level, not the mechanics level. Switching a device on or off changes what the world describes, not what dice resolve. Design device puzzles around observation: a player who understands what each device controls can solve the puzzle without a skill check. Group devices by effect domain — the lighting devices, the access devices, the alarm devices — so patterns are discoverable. A device whose effect is remote (switching it changes something in another room) creates exploration incentives.",
|
|
160
|
+
source_url: "narrative_world_model/world/world-model-provider.md",
|
|
161
|
+
confidence: "HIGH",
|
|
162
|
+
tag: "vendor",
|
|
163
|
+
badge_scope: "game_master",
|
|
164
|
+
category: "device_patterns",
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
content: "Vehicle design for interactive worlds — treat a vehicle's interior as a room with dynamic exits. The exits from a vehicle change with context: boarding creates 'out' exits to the embarkation point, docking creates exits to the dock, crash-landing creates exits to the crash site. Design vehicle events as environmental changes — a flood filling a compartment, a fire spreading through a hold, a hull breach exposing a room to vacuum. Passengers act independently: NPCs aboard a vehicle may pilot, repair, guard, or panic based on their disposition and the vehicle's state.",
|
|
168
|
+
source_url: "narrative_world_model/world/world-model-provider.md",
|
|
169
|
+
confidence: "HIGH",
|
|
170
|
+
tag: "vendor",
|
|
171
|
+
badge_scope: "game_master",
|
|
172
|
+
category: "vehicle_patterns",
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
content: "Extended property design patterns — wearable creates inventory slots on a character's body, not in their pack (invisible to 'take' unless removed first). Edible and drinkable are single-use, consumed on use (the object is removed after eating/drinking) — use for potions, rations, and consumable clues. Climbable associates with adjacent directional exits (a climbable rope next to an 'up' exit becomes that exit's access method). Readable separates discoverable text from visible description — a book's description is 'a leather-bound tome', its read_text is the passage the player discovers. Transparent lets players see container contents without opening — a glass display case, a jar of specimens, a window.",
|
|
176
|
+
source_url: "narrative_world_model/world/world-model-provider.md",
|
|
177
|
+
confidence: "HIGH",
|
|
178
|
+
tag: "vendor",
|
|
179
|
+
badge_scope: "game_master",
|
|
180
|
+
category: "extended_properties",
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
adventure_advice: {
|
|
184
|
+
templates: [
|
|
185
|
+
{
|
|
186
|
+
content: "Open scenes in media res — place characters at a decision point that tests their values, personality, or background. The first scene should ask a question the player answers through action.",
|
|
187
|
+
source_url: "narrative_world_model/narrative/if-craft-corpus/README.md",
|
|
188
|
+
confidence: "HIGH",
|
|
189
|
+
tag: "vendor",
|
|
190
|
+
badge_scope: "game_master",
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
content: "Design obstacles, not solutions. A progress clock tracks the obstacle ('Interior Patrols', 'The Tower') — never the method ('Sneak Past the Guards'). This preserves player agency over how they overcome each challenge.",
|
|
194
|
+
source_url: "narrative_world_model/narrative/bitd/progress-clocks.md",
|
|
195
|
+
confidence: "HIGH",
|
|
196
|
+
tag: "vendor",
|
|
197
|
+
badge_scope: "game_master",
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
content: "Interactive object design principles — every object in a room should do or tell something. A lever that does nothing, a book that says nothing, a locked door with no key — these are dead ends that teach the player not to investigate. The world-model property system makes object behavior explicit: a readable object carries a discoverable text, a switchable object changes state, an edible object is consumable. Design rooms where at least one object responds to each parser tier (core, standard, narrative) — this rewards thorough investigation.",
|
|
201
|
+
source_url: "narrative_world_model/world/world-model-provider.md",
|
|
202
|
+
confidence: "HIGH",
|
|
203
|
+
tag: "vendor",
|
|
204
|
+
badge_scope: "game_master",
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
content: "Compound puzzle design — combine object kinds to create multi-step interactions. A device in a locked container behind a climbable barrier that the player must read about in a readable inscription. The player learns the solution by reading, reaches the device by climbing, opens the container by unlocking, then switches the device. Each step uses a different parser tier. Design compound puzzles so the order matters — the inscription mentions the device's location, the climbable object is only accessible after unlocking, the container's key is in another room.",
|
|
208
|
+
source_url: "narrative_world_model/world/world-model-provider.md",
|
|
209
|
+
confidence: "HIGH",
|
|
210
|
+
tag: "vendor",
|
|
211
|
+
badge_scope: "game_master",
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
scenario_starters: [
|
|
215
|
+
{
|
|
216
|
+
content: "A faction clock reaches its final segment — the world changes independently of the players. What ripple effects reach their location? Who benefits? Who is harmed? How does the status quo shift?",
|
|
217
|
+
source_url: "narrative_world_model/narrative/bitd/progress-clocks.md",
|
|
218
|
+
confidence: "HIGH",
|
|
219
|
+
tag: "vendor",
|
|
220
|
+
badge_scope: "game_master",
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
content: "Two racing clocks — the party's objective versus an environmental or antagonist clock. Both advance with consequences. If the party's clock fills first, they succeed at a cost. If the antagonist clock fills first, the situation escalates but the party has a new angle.",
|
|
224
|
+
source_url: "narrative_world_model/narrative/bitd/progress-clocks.md",
|
|
225
|
+
confidence: "HIGH",
|
|
226
|
+
tag: "vendor",
|
|
227
|
+
badge_scope: "game_master",
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
content: "Device-based mystery — a room with three devices and no explanation of what they control. Each device changes something in a different room. The player must explore, toggle, and observe to deduce the mapping. The solution is not a correct combination — it's understanding what each device does. The puzzle teaches the player how to read the world before the real challenge begins.",
|
|
231
|
+
source_url: "narrative_world_model/world/world-model-provider.md",
|
|
232
|
+
confidence: "HIGH",
|
|
233
|
+
tag: "vendor",
|
|
234
|
+
badge_scope: "game_master",
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
content: "Vehicle journey — a multi-room vehicle with an onboard crisis. The engine room floods, the passenger cabin loses pressure, the bridge loses navigation. Players move between interiors, each with its own challenge. Exits from the vehicle change as the crisis evolves — a damaged hatch becomes an escape route, a sealed bulkhead creates a new boundary. The vehicle is both location and antagonist.",
|
|
238
|
+
source_url: "narrative_world_model/world/world-model-provider.md",
|
|
239
|
+
confidence: "HIGH",
|
|
240
|
+
tag: "vendor",
|
|
241
|
+
badge_scope: "game_master",
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
table_expansions: [],
|
|
245
|
+
},
|
|
246
|
+
narrative_voices: [
|
|
247
|
+
{
|
|
248
|
+
name: "Horror — Atmospheric Dread",
|
|
249
|
+
source: "narrative_world_model/narrative/if-craft-corpus/README.md",
|
|
250
|
+
media_title: "IF Craft Corpus (genre-conventions cluster)",
|
|
251
|
+
media_type: "other",
|
|
252
|
+
description: "Build tension through what is withheld rather than revealed. Use environmental detail as emotional register — oppressive silence, wrong shadows, sounds that shouldn't be there. The horror is in the anticipation, not the reveal. Players fill gaps with their own fears.",
|
|
253
|
+
confidence: "HIGH",
|
|
254
|
+
tag: "vendor",
|
|
255
|
+
badge_scope: "game_master",
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: "Fiction-First — Blades in the Dark",
|
|
259
|
+
source: "narrative_world_model/narrative/bitd/progress-clocks.md",
|
|
260
|
+
media_title: "Blades in the Dark (John Harper)",
|
|
261
|
+
media_type: "game",
|
|
262
|
+
description: "Clocks reflect the fictional situation — they show speed, they don't determine it. Start from what makes sense in the story, then choose the mechanic. Obstacles are named for what they are in the world, not how the players intend to overcome them. The fiction drives the mechanics, not the reverse.",
|
|
263
|
+
confidence: "HIGH",
|
|
264
|
+
tag: "vendor",
|
|
265
|
+
badge_scope: "game_master",
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
};
|
|
269
|
+
//# sourceMappingURL=enrichment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enrichment.js","sourceRoot":"","sources":["../../src/core/enrichment.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { expandMacros } from "./macros.js";
|
|
2
|
+
export type { MacroContext } from "./macros.js";
|
|
3
|
+
export { StateManager, normalizeBadge, migrateNovelData } from "./state.js";
|
|
4
|
+
export type { Badge, NovelState, NovelEntity, NpcState, LoreEntry, Countdown, CombatState, AuditEntry, } from "./state.js";
|
|
5
|
+
export { initServer, getBadge, requireGM, requirePlayer, requireNotObserver, requireNovel, novelSnapshot, withForbiddenAudit, } from "./server.js";
|
|
6
|
+
export type { ToolCtx, ToolHandler } from "./server.js";
|
|
7
|
+
export { DEFAULT_ENRICHMENT } from "./enrichment.js";
|
|
8
|
+
export type { EnrichmentItem, ActionPattern, EnrichmentManifest, NarrativeVoice, } from "./enrichment.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { expandMacros } from "./macros.js";
|
|
2
|
+
export { StateManager, normalizeBadge, migrateNovelData } from "./state.js";
|
|
3
|
+
export { initServer, getBadge, requireGM, requirePlayer, requireNotObserver, requireNovel, novelSnapshot, withForbiddenAudit, } from "./server.js";
|
|
4
|
+
export { DEFAULT_ENRICHMENT } from "./enrichment.js";
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAW5E,OAAO,EACL,UAAU,EACV,QAAQ,EACR,SAAS,EACT,aAAa,EACb,kBAAkB,EAClB,YAAY,EACZ,aAAa,EACb,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface MacroContext {
|
|
2
|
+
entityName?: string;
|
|
3
|
+
sceneCurrent?: string;
|
|
4
|
+
sceneLocation?: string;
|
|
5
|
+
sceneTimeOfDay?: string;
|
|
6
|
+
sceneAtmosphere?: string;
|
|
7
|
+
sceneType?: string;
|
|
8
|
+
countdowns?: Record<string, {
|
|
9
|
+
remaining: number;
|
|
10
|
+
total: number;
|
|
11
|
+
scope?: string;
|
|
12
|
+
direction?: string;
|
|
13
|
+
}>;
|
|
14
|
+
novelSlug?: string;
|
|
15
|
+
badgeActive?: string;
|
|
16
|
+
partySize?: number;
|
|
17
|
+
currentRoom?: string;
|
|
18
|
+
worldRoomCount?: number;
|
|
19
|
+
worldThingCount?: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function expandMacros(text: string, ctx: MacroContext): string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Macro expansion for {{entity.name}}, {{scene.current}}, etc.
|
|
2
|
+
// REQ-085: macro tokens of the form {{<path>}}
|
|
3
|
+
export function expandMacros(text, ctx) {
|
|
4
|
+
return text.replace(/\{\{([^}]+)\}\}/g, (_match, path) => {
|
|
5
|
+
const parts = path.trim().split(".");
|
|
6
|
+
if (parts[0] === "entity") {
|
|
7
|
+
if (parts[1] === "name" && ctx.entityName)
|
|
8
|
+
return ctx.entityName;
|
|
9
|
+
}
|
|
10
|
+
if (parts[0] === "scene") {
|
|
11
|
+
if (parts[1] === "current" && ctx.sceneCurrent)
|
|
12
|
+
return ctx.sceneCurrent;
|
|
13
|
+
if (parts[1] === "type" && ctx.sceneType)
|
|
14
|
+
return ctx.sceneType;
|
|
15
|
+
if (parts[1] === "location" && ctx.sceneLocation)
|
|
16
|
+
return ctx.sceneLocation;
|
|
17
|
+
if (parts[1] === "time_of_day" && ctx.sceneTimeOfDay)
|
|
18
|
+
return ctx.sceneTimeOfDay;
|
|
19
|
+
if (parts[1] === "atmosphere" && ctx.sceneAtmosphere)
|
|
20
|
+
return ctx.sceneAtmosphere;
|
|
21
|
+
}
|
|
22
|
+
if (parts[0] === "countdown") {
|
|
23
|
+
const name = parts[1];
|
|
24
|
+
if (name && ctx.countdowns?.[name]) {
|
|
25
|
+
if (parts[2] === "remaining")
|
|
26
|
+
return String(ctx.countdowns[name].remaining);
|
|
27
|
+
if (parts[2] === "total")
|
|
28
|
+
return String(ctx.countdowns[name].total);
|
|
29
|
+
if (parts[2] === "scope" && ctx.countdowns[name].scope)
|
|
30
|
+
return ctx.countdowns[name].scope;
|
|
31
|
+
if (parts[2] === "direction" && ctx.countdowns[name].direction)
|
|
32
|
+
return ctx.countdowns[name].direction;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (parts[0] === "novel" && parts[1] === "slug" && ctx.novelSlug)
|
|
36
|
+
return ctx.novelSlug;
|
|
37
|
+
if (parts[0] === "badge" && parts[1] === "active" && ctx.badgeActive)
|
|
38
|
+
return ctx.badgeActive;
|
|
39
|
+
if (parts[0] === "party" && parts[1] === "size" && ctx.partySize !== undefined)
|
|
40
|
+
return String(ctx.partySize);
|
|
41
|
+
if (parts[0] === "world" && parts[1] === "room" && ctx.currentRoom)
|
|
42
|
+
return ctx.currentRoom;
|
|
43
|
+
if (parts[0] === "world" && parts[1] === "room_count" && ctx.worldRoomCount !== undefined)
|
|
44
|
+
return String(ctx.worldRoomCount);
|
|
45
|
+
if (parts[0] === "world" && parts[1] === "thing_count" && ctx.worldThingCount !== undefined)
|
|
46
|
+
return String(ctx.worldThingCount);
|
|
47
|
+
return `{{${path}}}`;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=macros.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"macros.js","sourceRoot":"","sources":["../../src/core/macros.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface Rng {
|
|
2
|
+
nextState(): number;
|
|
3
|
+
roll(faces: number): number;
|
|
4
|
+
}
|
|
5
|
+
export declare function createRng(seed: number | string): Rng;
|
|
6
|
+
export declare function sessionRoll(faces: number): number;
|
|
7
|
+
export interface DiceRoll {
|
|
8
|
+
dice: number[];
|
|
9
|
+
total: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function rollWithRng(rng: Rng, count: number, sides: number, dropLowest?: number): DiceRoll;
|
|
12
|
+
export declare function rollNotationWithRng(rng: Rng, notation: string): {
|
|
13
|
+
dice: number[];
|
|
14
|
+
total: number;
|
|
15
|
+
modifier: number;
|
|
16
|
+
};
|
|
17
|
+
export declare function roll4d6DropLowest(seed: string): number[];
|
package/dist/core/rng.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Deterministic seeded PRNG (REQ-050).
|
|
2
|
+
//
|
|
3
|
+
// Reference randomizer contract (Appendix B.4):
|
|
4
|
+
// 32-bit linear congruential generator
|
|
5
|
+
// state <- (state * 1664525 + 1013904223) mod 2^32
|
|
6
|
+
// initial state = parseInt(seed, 10)
|
|
7
|
+
// draw N faces = floor(next * N) + 1 where next = state / 2^32
|
|
8
|
+
//
|
|
9
|
+
// The session PRNG is seeded once at startup from TTRPG_SEED (default 0).
|
|
10
|
+
// Per-call seeds use an ISOLATED draw: they never advance the session PRNG
|
|
11
|
+
// position (REQ-050b).
|
|
12
|
+
export function createRng(seed) {
|
|
13
|
+
let state = typeof seed === "number" ? seed : parseInt(String(seed), 10);
|
|
14
|
+
if (!Number.isFinite(state) || Number.isNaN(state))
|
|
15
|
+
state = 0;
|
|
16
|
+
state = state >>> 0; // keep as 32-bit unsigned
|
|
17
|
+
return {
|
|
18
|
+
nextState() {
|
|
19
|
+
// state <- (state * 1664525 + 1013904223) mod 2^32, computed in 64-bit
|
|
20
|
+
state = (Math.imul(state, 1664525) + 1013904223) >>> 0;
|
|
21
|
+
return state;
|
|
22
|
+
},
|
|
23
|
+
roll(faces) {
|
|
24
|
+
const s = this.nextState();
|
|
25
|
+
return Math.floor((s / 0x100000000) * faces) + 1;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// Session PRNG — seeded once from TTRPG_SEED (default 0). Advances with each
|
|
30
|
+
// session-seeded draw. Per-call seeds construct a fresh isolated Rng instead.
|
|
31
|
+
const SESSION_SEED = process.env.TTRPG_SEED ?? "0";
|
|
32
|
+
const sessionRng = createRng(SESSION_SEED);
|
|
33
|
+
// Log the active seed at startup (REQ-050b) so operators can verify determinism.
|
|
34
|
+
console.error(`[info] PRNG seed: ${SESSION_SEED} (source: ${process.env.TTRPG_SEED ? "env" : "default"})`);
|
|
35
|
+
// Session draw — advances the session PRNG position.
|
|
36
|
+
export function sessionRoll(faces) {
|
|
37
|
+
return sessionRng.roll(faces);
|
|
38
|
+
}
|
|
39
|
+
export function rollWithRng(rng, count, sides, dropLowest = 0) {
|
|
40
|
+
const dice = [];
|
|
41
|
+
for (let i = 0; i < count; i++)
|
|
42
|
+
dice.push(rng.roll(sides));
|
|
43
|
+
if (dropLowest > 0 && dice.length >= 1) {
|
|
44
|
+
const sorted = [...dice].sort((a, b) => a - b);
|
|
45
|
+
for (let i = 0; i < dropLowest; i++) {
|
|
46
|
+
const idx = dice.indexOf(sorted[i]);
|
|
47
|
+
if (idx >= 0)
|
|
48
|
+
dice.splice(idx, 1);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { dice, total: dice.reduce((a, b) => a + b, 0) };
|
|
52
|
+
}
|
|
53
|
+
// Roll a dice notation string like "4d6" or "3d6+2" against a given Rng.
|
|
54
|
+
export function rollNotationWithRng(rng, notation) {
|
|
55
|
+
const m = String(notation).trim().match(/^(\d+)d(\d+)(?:([+-])(\d+))?$/i);
|
|
56
|
+
if (!m)
|
|
57
|
+
throw new Error(`Invalid dice notation '${notation}'.`);
|
|
58
|
+
const count = parseInt(m[1], 10);
|
|
59
|
+
const sides = parseInt(m[2], 10);
|
|
60
|
+
const res = rollWithRng(rng, count, sides);
|
|
61
|
+
const sign = m[3] === "-" ? -1 : 1;
|
|
62
|
+
const modifier = m[4] ? sign * parseInt(m[4], 10) : 0;
|
|
63
|
+
return { dice: res.dice, total: res.total + modifier, modifier };
|
|
64
|
+
}
|
|
65
|
+
// Deterministic 4d6 drop-lowest ability roll for stat generation (REQ-104c).
|
|
66
|
+
// Uses an ISOLATED Rng so it does not advance the session PRNG position.
|
|
67
|
+
export function roll4d6DropLowest(seed) {
|
|
68
|
+
const rng = createRng(seed);
|
|
69
|
+
const res = rollWithRng(rng, 4, 6, 1);
|
|
70
|
+
return res.dice;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=rng.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rng.js","sourceRoot":"","sources":["../../src/core/rng.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { StateManager, Badge, NovelState } from "./state.js";
|
|
2
|
+
export type ToolCtx = {
|
|
3
|
+
badge: Badge;
|
|
4
|
+
};
|
|
5
|
+
export type ToolHandler = (args: any, ctx: ToolCtx) => Promise<{
|
|
6
|
+
content: {
|
|
7
|
+
type: "text";
|
|
8
|
+
text: string;
|
|
9
|
+
}[];
|
|
10
|
+
}>;
|
|
11
|
+
export declare function initServer(state: StateManager): void;
|
|
12
|
+
export declare function getBadge(): Badge;
|
|
13
|
+
export declare function requireGM(): void;
|
|
14
|
+
export declare function requirePlayer(): void;
|
|
15
|
+
export declare function requireNotObserver(): void;
|
|
16
|
+
export declare function requireNovel(): NovelState;
|
|
17
|
+
export declare function novelSnapshot(): void;
|
|
18
|
+
export declare function withForbiddenAudit(handler: ToolHandler, toolName: string): ToolHandler;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Core server helpers — shared MCP boilerplate for all Holonovel servers
|
|
2
|
+
// Provides badge gating helpers, error prefixing, and novel/snapshot utilities.
|
|
3
|
+
let _state;
|
|
4
|
+
export function initServer(state) {
|
|
5
|
+
_state = state;
|
|
6
|
+
}
|
|
7
|
+
export function getBadge() {
|
|
8
|
+
return _state.activeNovel?.badge ?? "none";
|
|
9
|
+
}
|
|
10
|
+
export function requireGM() {
|
|
11
|
+
_state.requireGM(getBadge());
|
|
12
|
+
}
|
|
13
|
+
export function requirePlayer() {
|
|
14
|
+
_state.requirePlayer(getBadge());
|
|
15
|
+
}
|
|
16
|
+
export function requireNotObserver() {
|
|
17
|
+
_state.requireNotObserver(getBadge());
|
|
18
|
+
}
|
|
19
|
+
export function requireNovel() {
|
|
20
|
+
return _state.requireNovel();
|
|
21
|
+
}
|
|
22
|
+
export function novelSnapshot() {
|
|
23
|
+
const novel = _state.activeNovel;
|
|
24
|
+
if (novel)
|
|
25
|
+
_state.snapshot(novel, getBadge());
|
|
26
|
+
}
|
|
27
|
+
export function withForbiddenAudit(handler, toolName) {
|
|
28
|
+
return async (args, ctx) => {
|
|
29
|
+
try {
|
|
30
|
+
return await handler(args, ctx);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
if (e.message?.startsWith("[FORBIDDEN]")) {
|
|
34
|
+
_state.auditForbidden(ctx.badge, toolName, args);
|
|
35
|
+
}
|
|
36
|
+
throw e;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/core/server.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,gFAAgF;AAUhF,IAAI,MAAoB,CAAC;AAEzB,MAAM,UAAU,UAAU,CAAC,KAAmB;IAC5C,MAAM,GAAG,KAAK,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,QAAQ;IACtB,OAAO,MAAM,CAAC,WAAW,EAAE,KAAK,IAAI,MAAM,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,MAAM,CAAC,aAAa,CAAC,QAAQ,EAAE,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,MAAM,CAAC,kBAAkB,CAAC,QAAQ,EAAE,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,MAAM,CAAC,YAAY,EAAE,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC;IACjC,IAAI,KAAK;QAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,OAAoB,EACpB,QAAgB;IAEhB,OAAO,KAAK,EAAE,IAAS,EAAE,GAAY,EAAE,EAAE;QACvC,IAAI,CAAC;YACH,OAAO,MAAM,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,IAAI,CAAC,CAAC,OAAO,EAAE,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;gBACzC,MAAM,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;YACnD,CAAC;YACD,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|