@ai-rpg-engine/modules 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/dist/belief-provenance.d.ts +41 -0
  2. package/dist/belief-provenance.d.ts.map +1 -0
  3. package/dist/belief-provenance.js +254 -0
  4. package/dist/belief-provenance.js.map +1 -0
  5. package/dist/belief-provenance.test.d.ts +2 -0
  6. package/dist/belief-provenance.test.d.ts.map +1 -0
  7. package/dist/belief-provenance.test.js +139 -0
  8. package/dist/belief-provenance.test.js.map +1 -0
  9. package/dist/cognition-core.d.ts +89 -0
  10. package/dist/cognition-core.d.ts.map +1 -0
  11. package/dist/cognition-core.js +370 -0
  12. package/dist/cognition-core.js.map +1 -0
  13. package/dist/cognition-core.test.d.ts +2 -0
  14. package/dist/cognition-core.test.d.ts.map +1 -0
  15. package/dist/cognition-core.test.js +215 -0
  16. package/dist/cognition-core.test.js.map +1 -0
  17. package/dist/combat-core.d.ts +11 -0
  18. package/dist/combat-core.d.ts.map +1 -0
  19. package/dist/combat-core.js +152 -0
  20. package/dist/combat-core.js.map +1 -0
  21. package/dist/dialogue-core.d.ts +10 -0
  22. package/dist/dialogue-core.d.ts.map +1 -0
  23. package/dist/dialogue-core.js +172 -0
  24. package/dist/dialogue-core.js.map +1 -0
  25. package/dist/district-core.d.ts +59 -0
  26. package/dist/district-core.d.ts.map +1 -0
  27. package/dist/district-core.js +224 -0
  28. package/dist/district-core.js.map +1 -0
  29. package/dist/district-core.test.d.ts +2 -0
  30. package/dist/district-core.test.d.ts.map +1 -0
  31. package/dist/district-core.test.js +157 -0
  32. package/dist/district-core.test.js.map +1 -0
  33. package/dist/environment-core.d.ts +78 -0
  34. package/dist/environment-core.d.ts.map +1 -0
  35. package/dist/environment-core.js +229 -0
  36. package/dist/environment-core.js.map +1 -0
  37. package/dist/environment-core.test.d.ts +2 -0
  38. package/dist/environment-core.test.d.ts.map +1 -0
  39. package/dist/environment-core.test.js +252 -0
  40. package/dist/environment-core.test.js.map +1 -0
  41. package/dist/faction-cognition.d.ts +39 -0
  42. package/dist/faction-cognition.d.ts.map +1 -0
  43. package/dist/faction-cognition.js +139 -0
  44. package/dist/faction-cognition.js.map +1 -0
  45. package/dist/faction-cognition.test.d.ts +2 -0
  46. package/dist/faction-cognition.test.d.ts.map +1 -0
  47. package/dist/faction-cognition.test.js +166 -0
  48. package/dist/faction-cognition.test.js.map +1 -0
  49. package/dist/index.d.ts +31 -0
  50. package/dist/index.d.ts.map +1 -0
  51. package/dist/index.js +18 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/inventory-core.d.ts +10 -0
  54. package/dist/inventory-core.d.ts.map +1 -0
  55. package/dist/inventory-core.js +69 -0
  56. package/dist/inventory-core.js.map +1 -0
  57. package/dist/knowledge-decay.test.d.ts +2 -0
  58. package/dist/knowledge-decay.test.d.ts.map +1 -0
  59. package/dist/knowledge-decay.test.js +143 -0
  60. package/dist/knowledge-decay.test.js.map +1 -0
  61. package/dist/narrative-authority.d.ts +40 -0
  62. package/dist/narrative-authority.d.ts.map +1 -0
  63. package/dist/narrative-authority.js +89 -0
  64. package/dist/narrative-authority.js.map +1 -0
  65. package/dist/narrative-authority.test.d.ts +2 -0
  66. package/dist/narrative-authority.test.d.ts.map +1 -0
  67. package/dist/narrative-authority.test.js +237 -0
  68. package/dist/narrative-authority.test.js.map +1 -0
  69. package/dist/observer-presentation.d.ts +74 -0
  70. package/dist/observer-presentation.d.ts.map +1 -0
  71. package/dist/observer-presentation.js +277 -0
  72. package/dist/observer-presentation.js.map +1 -0
  73. package/dist/observer-presentation.test.d.ts +2 -0
  74. package/dist/observer-presentation.test.d.ts.map +1 -0
  75. package/dist/observer-presentation.test.js +156 -0
  76. package/dist/observer-presentation.test.js.map +1 -0
  77. package/dist/perception-filter.d.ts +46 -0
  78. package/dist/perception-filter.d.ts.map +1 -0
  79. package/dist/perception-filter.js +281 -0
  80. package/dist/perception-filter.js.map +1 -0
  81. package/dist/perception-filter.test.d.ts +2 -0
  82. package/dist/perception-filter.test.d.ts.map +1 -0
  83. package/dist/perception-filter.test.js +308 -0
  84. package/dist/perception-filter.test.js.map +1 -0
  85. package/dist/progression-core.d.ts +63 -0
  86. package/dist/progression-core.d.ts.map +1 -0
  87. package/dist/progression-core.js +232 -0
  88. package/dist/progression-core.js.map +1 -0
  89. package/dist/progression-core.test.d.ts +2 -0
  90. package/dist/progression-core.test.d.ts.map +1 -0
  91. package/dist/progression-core.test.js +328 -0
  92. package/dist/progression-core.test.js.map +1 -0
  93. package/dist/rumor-propagation.d.ts +31 -0
  94. package/dist/rumor-propagation.d.ts.map +1 -0
  95. package/dist/rumor-propagation.js +180 -0
  96. package/dist/rumor-propagation.js.map +1 -0
  97. package/dist/rumor-propagation.test.d.ts +2 -0
  98. package/dist/rumor-propagation.test.d.ts.map +1 -0
  99. package/dist/rumor-propagation.test.js +176 -0
  100. package/dist/rumor-propagation.test.js.map +1 -0
  101. package/dist/simulation-inspector.d.ts +78 -0
  102. package/dist/simulation-inspector.d.ts.map +1 -0
  103. package/dist/simulation-inspector.js +263 -0
  104. package/dist/simulation-inspector.js.map +1 -0
  105. package/dist/simulation-inspector.test.d.ts +2 -0
  106. package/dist/simulation-inspector.test.d.ts.map +1 -0
  107. package/dist/simulation-inspector.test.js +152 -0
  108. package/dist/simulation-inspector.test.js.map +1 -0
  109. package/dist/status-core.d.ts +17 -0
  110. package/dist/status-core.d.ts.map +1 -0
  111. package/dist/status-core.js +106 -0
  112. package/dist/status-core.js.map +1 -0
  113. package/dist/traversal-core.d.ts +3 -0
  114. package/dist/traversal-core.d.ts.map +1 -0
  115. package/dist/traversal-core.js +93 -0
  116. package/dist/traversal-core.js.map +1 -0
  117. package/package.json +34 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combat-core.d.ts","sourceRoot":"","sources":["../src/combat-core.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,YAAY,EAEZ,UAAU,EAEV,WAAW,EACZ,MAAM,qBAAqB,CAAC;AAG7B,MAAM,MAAM,cAAc,GAAG;IAC3B,8FAA8F;IAC9F,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,KAAK,MAAM,CAAC;IACtF,+DAA+D;IAC/D,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,KAAK,MAAM,CAAC;CACpF,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,QAAQ,CAAC,EAAE,cAAc,GAAG,YAAY,CAcxE;AAED,gDAAgD;AAChD,eAAO,MAAM,UAAU,EAAE,YAAiC,CAAC"}
@@ -0,0 +1,152 @@
1
+ // combat-core — attack, damage, defeat
2
+ import { nextId } from '@ai-rpg-engine/core';
3
+ export function createCombatCore(formulas) {
4
+ return {
5
+ id: 'combat-core',
6
+ version: '0.1.0',
7
+ register(ctx) {
8
+ ctx.actions.registerVerb('attack', (action, world) => attackHandler(action, world, formulas));
9
+ ctx.persistence.registerNamespace('combat-core', {
10
+ inCombat: false,
11
+ combatants: [],
12
+ });
13
+ },
14
+ };
15
+ }
16
+ /** Default combat module with basic formulas */
17
+ export const combatCore = createCombatCore();
18
+ function attackHandler(action, world, formulas) {
19
+ const events = [];
20
+ const attacker = world.entities[action.actorId];
21
+ const targetId = action.targetIds?.[0];
22
+ if (!attacker) {
23
+ return [makeEvent(action, 'action.rejected', { reason: 'attacker not found' })];
24
+ }
25
+ if (!targetId) {
26
+ return [makeEvent(action, 'action.rejected', { reason: 'no target specified' })];
27
+ }
28
+ const target = world.entities[targetId];
29
+ if (!target) {
30
+ return [makeEvent(action, 'action.rejected', { reason: `target ${targetId} not found` })];
31
+ }
32
+ // Check same zone
33
+ if (attacker.zoneId !== target.zoneId) {
34
+ return [makeEvent(action, 'action.rejected', { reason: 'target not in same zone' })];
35
+ }
36
+ // Check target is alive
37
+ if ((target.resources.hp ?? 0) <= 0) {
38
+ return [makeEvent(action, 'action.rejected', { reason: 'target is already defeated' })];
39
+ }
40
+ // Stamina cost
41
+ const staminaCost = 1;
42
+ const currentStamina = attacker.resources.stamina ?? 0;
43
+ if (currentStamina < staminaCost) {
44
+ return [makeEvent(action, 'action.rejected', { reason: 'not enough stamina' })];
45
+ }
46
+ attacker.resources.stamina = currentStamina - staminaCost;
47
+ events.push(makeEvent(action, 'resource.changed', {
48
+ entityId: attacker.id,
49
+ resource: 'stamina',
50
+ previous: currentStamina,
51
+ current: attacker.resources.stamina,
52
+ delta: -staminaCost,
53
+ }));
54
+ // Hit check
55
+ const hitChance = formulas?.hitChance
56
+ ? formulas.hitChance(attacker, target, world)
57
+ : defaultHitChance(attacker, target);
58
+ // Use a simple deterministic roll based on tick + entity IDs
59
+ const roll = simpleRoll(world.meta.tick, attacker.id, target.id);
60
+ if (roll > hitChance) {
61
+ events.push(makeEvent(action, 'combat.contact.miss', {
62
+ attackerId: attacker.id,
63
+ targetId: target.id,
64
+ roll,
65
+ hitChance,
66
+ }, {
67
+ targetIds: [target.id],
68
+ presentation: { channels: ['objective'], priority: 'normal' },
69
+ }));
70
+ return events;
71
+ }
72
+ // Damage
73
+ const damage = formulas?.damage
74
+ ? formulas.damage(attacker, target, world)
75
+ : defaultDamage(attacker);
76
+ const previousHp = target.resources.hp ?? 0;
77
+ target.resources.hp = Math.max(0, previousHp - damage);
78
+ events.push(makeEvent(action, 'combat.contact.hit', {
79
+ attackerId: attacker.id,
80
+ targetId: target.id,
81
+ roll,
82
+ hitChance,
83
+ }, {
84
+ targetIds: [target.id],
85
+ presentation: { channels: ['objective'], priority: 'normal' },
86
+ }));
87
+ events.push(makeEvent(action, 'combat.damage.applied', {
88
+ attackerId: attacker.id,
89
+ targetId: target.id,
90
+ damage,
91
+ previousHp,
92
+ currentHp: target.resources.hp,
93
+ }, {
94
+ targetIds: [target.id],
95
+ presentation: {
96
+ channels: ['objective'],
97
+ priority: 'high',
98
+ soundCues: ['combat.hit'],
99
+ },
100
+ }));
101
+ events.push(makeEvent(action, 'resource.changed', {
102
+ entityId: target.id,
103
+ resource: 'hp',
104
+ previous: previousHp,
105
+ current: target.resources.hp,
106
+ delta: -damage,
107
+ }));
108
+ // Defeat check
109
+ if (target.resources.hp <= 0) {
110
+ events.push(makeEvent(action, 'combat.entity.defeated', {
111
+ entityId: target.id,
112
+ entityName: target.name,
113
+ defeatedBy: attacker.id,
114
+ }, {
115
+ targetIds: [target.id],
116
+ presentation: {
117
+ channels: ['objective', 'narrator'],
118
+ priority: 'critical',
119
+ soundCues: ['combat.defeat'],
120
+ },
121
+ }));
122
+ }
123
+ return events;
124
+ }
125
+ function defaultHitChance(attacker, target) {
126
+ const attackerInstinct = attacker.stats.instinct ?? 5;
127
+ const targetInstinct = target.stats.instinct ?? 5;
128
+ return Math.min(95, Math.max(5, 50 + attackerInstinct * 5 - targetInstinct * 3));
129
+ }
130
+ function defaultDamage(attacker) {
131
+ const vigor = attacker.stats.vigor ?? 3;
132
+ return Math.max(1, vigor);
133
+ }
134
+ /** Simple deterministic roll 1-100 based on tick and IDs */
135
+ function simpleRoll(tick, attackerId, targetId) {
136
+ let hash = tick * 2654435761;
137
+ for (const char of attackerId + targetId) {
138
+ hash = ((hash << 5) - hash + char.charCodeAt(0)) | 0;
139
+ }
140
+ return (Math.abs(hash) % 100) + 1;
141
+ }
142
+ function makeEvent(action, type, payload, extra) {
143
+ return {
144
+ id: nextId('evt'),
145
+ tick: action.issuedAtTick,
146
+ type,
147
+ actorId: action.actorId,
148
+ payload,
149
+ ...extra,
150
+ };
151
+ }
152
+ //# sourceMappingURL=combat-core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combat-core.js","sourceRoot":"","sources":["../src/combat-core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { EngineModule } from '@ai-rpg-engine/core';
2
+ import type { DialogueDefinition } from '@ai-rpg-engine/content-schema';
3
+ export type DialogueState = {
4
+ activeDialogue: string | null;
5
+ activeNodeId: string | null;
6
+ speakerId: string | null;
7
+ };
8
+ export type DialogueRegistry = Map<string, DialogueDefinition>;
9
+ export declare function createDialogueCore(dialogues: DialogueDefinition[]): EngineModule;
10
+ //# sourceMappingURL=dialogue-core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialogue-core.d.ts","sourceRoot":"","sources":["../src/dialogue-core.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,YAAY,EAKb,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,kBAAkB,EAAkC,MAAM,+BAA+B,CAAC;AAExG,MAAM,MAAM,aAAa,GAAG;IAC1B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;AAE/D,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,kBAAkB,EAAE,GAAG,YAAY,CAqBhF"}
@@ -0,0 +1,172 @@
1
+ // dialogue-core — NPC dialogue trees with choices and conditions
2
+ import { nextId } from '@ai-rpg-engine/core';
3
+ export function createDialogueCore(dialogues) {
4
+ const registry = new Map();
5
+ for (const d of dialogues) {
6
+ registry.set(d.id, d);
7
+ }
8
+ return {
9
+ id: 'dialogue-core',
10
+ version: '0.1.0',
11
+ register(ctx) {
12
+ ctx.actions.registerVerb('speak', (action, world) => speakHandler(action, world, registry));
13
+ ctx.actions.registerVerb('choose', (action, world) => chooseHandler(action, world, registry));
14
+ ctx.persistence.registerNamespace('dialogue-core', {
15
+ activeDialogue: null,
16
+ activeNodeId: null,
17
+ speakerId: null,
18
+ });
19
+ },
20
+ };
21
+ }
22
+ function speakHandler(action, world, registry) {
23
+ const targetId = action.targetIds?.[0];
24
+ if (!targetId) {
25
+ return [makeEvent(action, 'action.rejected', { reason: 'no one to speak to' })];
26
+ }
27
+ const target = world.entities[targetId];
28
+ if (!target) {
29
+ return [makeEvent(action, 'action.rejected', { reason: `${targetId} not found` })];
30
+ }
31
+ // Find dialogue for this NPC
32
+ const dialogueId = action.parameters?.dialogueId;
33
+ let dialogue;
34
+ if (dialogueId) {
35
+ dialogue = registry.get(dialogueId);
36
+ }
37
+ else {
38
+ // Find first dialogue that includes this speaker
39
+ for (const d of registry.values()) {
40
+ if (d.speakers.includes(targetId)) {
41
+ dialogue = d;
42
+ break;
43
+ }
44
+ }
45
+ }
46
+ if (!dialogue) {
47
+ return [makeEvent(action, 'action.rejected', { reason: `${target.name} has nothing to say` })];
48
+ }
49
+ const entryNode = dialogue.nodes[dialogue.entryNodeId];
50
+ if (!entryNode) {
51
+ return [makeEvent(action, 'action.rejected', { reason: 'dialogue has no entry node' })];
52
+ }
53
+ // Set dialogue state
54
+ const dState = {
55
+ activeDialogue: dialogue.id,
56
+ activeNodeId: dialogue.entryNodeId,
57
+ speakerId: targetId,
58
+ };
59
+ world.modules['dialogue-core'] = dState;
60
+ const events = [
61
+ makeEvent(action, 'dialogue.started', {
62
+ dialogueId: dialogue.id,
63
+ speakerId: targetId,
64
+ speakerName: target.name,
65
+ }),
66
+ ];
67
+ events.push(...renderNode(action, entryNode, world));
68
+ return events;
69
+ }
70
+ function chooseHandler(action, world, registry) {
71
+ const dState = world.modules['dialogue-core'];
72
+ if (!dState?.activeDialogue || !dState.activeNodeId) {
73
+ return [makeEvent(action, 'action.rejected', { reason: 'no active dialogue' })];
74
+ }
75
+ const dialogue = registry.get(dState.activeDialogue);
76
+ if (!dialogue) {
77
+ return [makeEvent(action, 'action.rejected', { reason: 'dialogue not found' })];
78
+ }
79
+ const currentNode = dialogue.nodes[dState.activeNodeId];
80
+ if (!currentNode?.choices) {
81
+ return [makeEvent(action, 'action.rejected', { reason: 'no choices available' })];
82
+ }
83
+ const choiceId = action.parameters?.choiceId;
84
+ const choiceIndex = action.parameters?.choiceIndex;
85
+ let choice;
86
+ if (choiceId) {
87
+ choice = currentNode.choices.find(c => c.id === choiceId);
88
+ }
89
+ else if (choiceIndex !== undefined) {
90
+ const available = currentNode.choices.filter(c => !c.condition || evaluateCondition(c.condition, world));
91
+ choice = available[choiceIndex];
92
+ }
93
+ if (!choice) {
94
+ return [makeEvent(action, 'action.rejected', { reason: 'invalid choice' })];
95
+ }
96
+ const events = [
97
+ makeEvent(action, 'dialogue.choice.selected', {
98
+ dialogueId: dialogue.id,
99
+ choiceId: choice.id,
100
+ choiceText: choice.text,
101
+ }),
102
+ ];
103
+ // Apply choice effects
104
+ if (choice.effects) {
105
+ for (const effect of choice.effects) {
106
+ events.push(...applyDialogueEffect(action, effect, world));
107
+ }
108
+ }
109
+ // Navigate to next node
110
+ const nextNode = dialogue.nodes[choice.nextNodeId];
111
+ if (nextNode) {
112
+ dState.activeNodeId = choice.nextNodeId;
113
+ events.push(...renderNode(action, nextNode, world));
114
+ }
115
+ else {
116
+ // End dialogue
117
+ dState.activeDialogue = null;
118
+ dState.activeNodeId = null;
119
+ dState.speakerId = null;
120
+ events.push(makeEvent(action, 'dialogue.ended', { dialogueId: dialogue.id }));
121
+ }
122
+ return events;
123
+ }
124
+ function renderNode(action, node, world) {
125
+ const text = typeof node.text === 'string' ? node.text : node.text[0]?.text ?? '';
126
+ const availableChoices = node.choices
127
+ ?.filter(c => !c.condition || evaluateCondition(c.condition, world))
128
+ .map((c, i) => ({ id: c.id, text: c.text, index: i }));
129
+ return [
130
+ makeEvent(action, 'dialogue.node.entered', {
131
+ nodeId: node.id,
132
+ speaker: node.speaker,
133
+ text,
134
+ choices: availableChoices ?? [],
135
+ hasChoices: (availableChoices?.length ?? 0) > 0,
136
+ }, {
137
+ presentation: {
138
+ channels: ['dialogue'],
139
+ priority: 'high',
140
+ },
141
+ }),
142
+ ];
143
+ }
144
+ function applyDialogueEffect(action, effect, world) {
145
+ if (effect.type === 'set-global') {
146
+ const key = effect.params.key;
147
+ const value = effect.params.value;
148
+ world.globals[key] = value;
149
+ return [makeEvent(action, 'world.flag.changed', { key, value })];
150
+ }
151
+ return [];
152
+ }
153
+ function evaluateCondition(condition, world) {
154
+ if (condition.type === 'global-equals') {
155
+ return world.globals[condition.params.key] === condition.params.value;
156
+ }
157
+ if (condition.type === 'global-set') {
158
+ return world.globals[condition.params.key] !== undefined;
159
+ }
160
+ return true;
161
+ }
162
+ function makeEvent(action, type, payload, extra) {
163
+ return {
164
+ id: nextId('evt'),
165
+ tick: action.issuedAtTick,
166
+ type,
167
+ actorId: action.actorId,
168
+ payload,
169
+ ...extra,
170
+ };
171
+ }
172
+ //# sourceMappingURL=dialogue-core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialogue-core.js","sourceRoot":"","sources":["../src/dialogue-core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ import type { EngineModule, WorldState } from '@ai-rpg-engine/core';
2
+ export type DistrictMetrics = {
3
+ /** Rises from combat and hostile events, decays over time */
4
+ alertPressure: number;
5
+ /** Rises from rumor propagation events */
6
+ rumorDensity: number;
7
+ /** Rises from non-faction entity sightings in the district */
8
+ intruderLikelihood: number;
9
+ /** Faction presence strength — higher means more watchful */
10
+ surveillance: number;
11
+ /** Aggregated environmental stability from constituent zones */
12
+ stability: number;
13
+ };
14
+ export type DistrictDefinition = {
15
+ id: string;
16
+ name: string;
17
+ zoneIds: string[];
18
+ tags: string[];
19
+ controllingFaction?: string;
20
+ baseMetrics?: Partial<DistrictMetrics>;
21
+ };
22
+ export type DistrictState = DistrictMetrics & {
23
+ lastUpdateTick: number;
24
+ eventCount: number;
25
+ };
26
+ export type DistrictDecayConfig = {
27
+ /** Base decay rate per tick (default: 1) */
28
+ decayRate: number;
29
+ /** Minimum metric value (default: 0) */
30
+ floor: number;
31
+ };
32
+ export type DistrictCoreConfig = {
33
+ districts: DistrictDefinition[];
34
+ decay?: Partial<DistrictDecayConfig>;
35
+ };
36
+ export declare function createDistrictCore(config: DistrictCoreConfig): EngineModule;
37
+ /** Get the district a zone belongs to */
38
+ export declare function getDistrictForZone(world: WorldState, zoneId: string): string | undefined;
39
+ /** Get district state */
40
+ export declare function getDistrictState(world: WorldState, districtId: string): DistrictState | undefined;
41
+ /** Get district definition */
42
+ export declare function getDistrictDefinition(world: WorldState, districtId: string): DistrictDefinition | undefined;
43
+ /** Get all district IDs */
44
+ export declare function getAllDistrictIds(world: WorldState): string[];
45
+ /** Get a specific district metric */
46
+ export declare function getDistrictMetric(world: WorldState, districtId: string, metric: keyof DistrictMetrics): number;
47
+ /** Manually modify a district metric */
48
+ export declare function modifyDistrictMetric(world: WorldState, districtId: string, metric: keyof DistrictMetrics, delta: number): void;
49
+ /**
50
+ * Check if a district is on high alert.
51
+ * Useful for AI intent selection — patrol tendency increases in alert districts.
52
+ */
53
+ export declare function isDistrictOnAlert(world: WorldState, districtId: string): boolean;
54
+ /**
55
+ * Get the combined threat level of a district.
56
+ * Factors: alert pressure, intruder likelihood, rumor density.
57
+ */
58
+ export declare function getDistrictThreatLevel(world: WorldState, districtId: string): number;
59
+ //# sourceMappingURL=district-core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"district-core.d.ts","sourceRoot":"","sources":["../src/district-core.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,YAAY,EACZ,UAAU,EAGX,MAAM,qBAAqB,CAAC;AAM7B,MAAM,MAAM,eAAe,GAAG;IAC5B,6DAA6D;IAC7D,aAAa,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,6DAA6D;IAC7D,YAAY,EAAE,MAAM,CAAC;IACrB,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,eAAe,GAAG;IAC5C,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,kBAAkB,EAAE,CAAC;IAChC,KAAK,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;CACtC,CAAC;AAuBF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,YAAY,CA6F3E;AAYD,yCAAyC;AACzC,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAExF;AAED,yBAAyB;AACzB,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAEjG;AAED,8BAA8B;AAC9B,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAE3G;AAED,2BAA2B;AAC3B,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,EAAE,CAE7D;AAED,qCAAqC;AACrC,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,UAAU,EACjB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,eAAe,GAC5B,MAAM,CAGR;AAED,wCAAwC;AACxC,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,UAAU,EACjB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,eAAe,EAC7B,KAAK,EAAE,MAAM,GACZ,IAAI,CAIN;AAID;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAGhF;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAQpF"}
@@ -0,0 +1,224 @@
1
+ // district-core — spatial memory layer
2
+ // Districts aggregate zone-level signals into persistent informational state.
3
+ // Alert pressure, rumor density, intruder likelihood, and surveillance
4
+ // rise from events and decay over time, giving the world spatial memory.
5
+ import { getZoneProperty } from './environment-core.js';
6
+ import { getEntityFaction, getFactionCognition } from './faction-cognition.js';
7
+ const DEFAULT_METRICS = {
8
+ alertPressure: 0,
9
+ rumorDensity: 0,
10
+ intruderLikelihood: 0,
11
+ surveillance: 0,
12
+ stability: 5,
13
+ };
14
+ const DEFAULT_DECAY = {
15
+ decayRate: 1,
16
+ floor: 0,
17
+ };
18
+ // --- Module ---
19
+ export function createDistrictCore(config) {
20
+ const decayConfig = { ...DEFAULT_DECAY, ...config.decay };
21
+ const initialState = {
22
+ districts: {},
23
+ zoneToDistrict: {},
24
+ definitions: {},
25
+ };
26
+ for (const def of config.districts) {
27
+ initialState.definitions[def.id] = def;
28
+ initialState.districts[def.id] = {
29
+ ...DEFAULT_METRICS,
30
+ ...def.baseMetrics,
31
+ lastUpdateTick: 0,
32
+ eventCount: 0,
33
+ };
34
+ for (const zoneId of def.zoneIds) {
35
+ initialState.zoneToDistrict[zoneId] = def.id;
36
+ }
37
+ }
38
+ return {
39
+ id: 'district-core',
40
+ version: '0.1.0',
41
+ dependsOn: ['environment-core'],
42
+ register(ctx) {
43
+ ctx.persistence.registerNamespace('district-core', initialState);
44
+ // Combat raises alert pressure in the district
45
+ ctx.events.on('combat.*', (event, world) => {
46
+ const districtId = getEventDistrictId(event, world);
47
+ if (!districtId)
48
+ return;
49
+ const state = getModuleState(world);
50
+ const district = state.districts[districtId];
51
+ if (!district)
52
+ return;
53
+ const delta = event.type === 'combat.entity.defeated' ? 8 : 4;
54
+ district.alertPressure = Math.min(100, district.alertPressure + delta);
55
+ district.eventCount++;
56
+ district.lastUpdateTick = event.tick;
57
+ });
58
+ // Zone entry raises intruder likelihood if entity is not from controlling faction
59
+ ctx.events.on('world.zone.entered', (event, world) => {
60
+ const zoneId = event.payload.zoneId;
61
+ const districtId = getDistrictForZone(world, zoneId);
62
+ if (!districtId)
63
+ return;
64
+ const state = getModuleState(world);
65
+ const district = state.districts[districtId];
66
+ const def = state.definitions[districtId];
67
+ if (!district || !def)
68
+ return;
69
+ const actorId = event.actorId;
70
+ if (!actorId)
71
+ return;
72
+ // Non-faction members raise intruder likelihood
73
+ if (def.controllingFaction) {
74
+ const actorFaction = getEntityFaction(world, actorId);
75
+ if (actorFaction !== def.controllingFaction) {
76
+ district.intruderLikelihood = Math.min(100, district.intruderLikelihood + 10);
77
+ }
78
+ }
79
+ district.eventCount++;
80
+ district.lastUpdateTick = event.tick;
81
+ });
82
+ // Rumor events raise rumor density
83
+ ctx.events.on('rumor.belief.propagated', (event, world) => {
84
+ const factionId = event.payload.factionId;
85
+ // Find districts controlled by this faction
86
+ const state = getModuleState(world);
87
+ for (const [dId, def] of Object.entries(state.definitions)) {
88
+ if (def.controllingFaction === factionId) {
89
+ const district = state.districts[dId];
90
+ if (district) {
91
+ district.rumorDensity = Math.min(100, district.rumorDensity + 5);
92
+ district.lastUpdateTick = event.tick;
93
+ }
94
+ }
95
+ }
96
+ });
97
+ // District tick: decay metrics, update surveillance, sync stability
98
+ ctx.actions.registerVerb('district-tick', (_action, world) => {
99
+ processDistrictTick(world, decayConfig);
100
+ return [];
101
+ });
102
+ },
103
+ };
104
+ }
105
+ // --- State Access ---
106
+ function getModuleState(world) {
107
+ return (world.modules['district-core'] ?? {
108
+ districts: {},
109
+ zoneToDistrict: {},
110
+ definitions: {},
111
+ });
112
+ }
113
+ /** Get the district a zone belongs to */
114
+ export function getDistrictForZone(world, zoneId) {
115
+ return getModuleState(world).zoneToDistrict[zoneId];
116
+ }
117
+ /** Get district state */
118
+ export function getDistrictState(world, districtId) {
119
+ return getModuleState(world).districts[districtId];
120
+ }
121
+ /** Get district definition */
122
+ export function getDistrictDefinition(world, districtId) {
123
+ return getModuleState(world).definitions[districtId];
124
+ }
125
+ /** Get all district IDs */
126
+ export function getAllDistrictIds(world) {
127
+ return Object.keys(getModuleState(world).districts);
128
+ }
129
+ /** Get a specific district metric */
130
+ export function getDistrictMetric(world, districtId, metric) {
131
+ const state = getModuleState(world).districts[districtId];
132
+ return state?.[metric] ?? 0;
133
+ }
134
+ /** Manually modify a district metric */
135
+ export function modifyDistrictMetric(world, districtId, metric, delta) {
136
+ const state = getModuleState(world).districts[districtId];
137
+ if (!state)
138
+ return;
139
+ state[metric] = Math.max(0, Math.min(100, state[metric] + delta));
140
+ }
141
+ // --- District-Aware Hooks (A4) ---
142
+ /**
143
+ * Check if a district is on high alert.
144
+ * Useful for AI intent selection — patrol tendency increases in alert districts.
145
+ */
146
+ export function isDistrictOnAlert(world, districtId) {
147
+ const state = getModuleState(world).districts[districtId];
148
+ return (state?.alertPressure ?? 0) > 30;
149
+ }
150
+ /**
151
+ * Get the combined threat level of a district.
152
+ * Factors: alert pressure, intruder likelihood, rumor density.
153
+ */
154
+ export function getDistrictThreatLevel(world, districtId) {
155
+ const state = getModuleState(world).districts[districtId];
156
+ if (!state)
157
+ return 0;
158
+ return Math.min(100, Math.round(state.alertPressure * 0.4 +
159
+ state.intruderLikelihood * 0.35 +
160
+ state.rumorDensity * 0.25));
161
+ }
162
+ // --- Tick Processing ---
163
+ function processDistrictTick(world, decayConfig) {
164
+ const state = getModuleState(world);
165
+ for (const [districtId, district] of Object.entries(state.districts)) {
166
+ const def = state.definitions[districtId];
167
+ if (!def)
168
+ continue;
169
+ // Decay metrics toward floor
170
+ district.alertPressure = Math.max(decayConfig.floor, district.alertPressure - decayConfig.decayRate);
171
+ district.rumorDensity = Math.max(decayConfig.floor, district.rumorDensity - decayConfig.decayRate * 0.5);
172
+ district.intruderLikelihood = Math.max(decayConfig.floor, district.intruderLikelihood - decayConfig.decayRate * 0.8);
173
+ // Surveillance: count faction members present in district zones
174
+ if (def.controllingFaction) {
175
+ let factionPresence = 0;
176
+ for (const zoneId of def.zoneIds) {
177
+ for (const entity of Object.values(world.entities)) {
178
+ if (entity.zoneId === zoneId && entity.ai) {
179
+ const faction = getEntityFaction(world, entity.id);
180
+ if (faction === def.controllingFaction) {
181
+ factionPresence++;
182
+ }
183
+ }
184
+ }
185
+ }
186
+ district.surveillance = factionPresence * 15;
187
+ }
188
+ // Stability: average stability of constituent zones
189
+ let totalStability = 0;
190
+ let zoneCount = 0;
191
+ for (const zoneId of def.zoneIds) {
192
+ if (world.zones[zoneId]) {
193
+ totalStability += getZoneProperty(world, zoneId, 'stability') || 5;
194
+ zoneCount++;
195
+ }
196
+ }
197
+ if (zoneCount > 0) {
198
+ district.stability = totalStability / zoneCount;
199
+ }
200
+ // District-aware faction hook: high intruder likelihood boosts faction alert
201
+ if (def.controllingFaction && district.intruderLikelihood > 20) {
202
+ const factionCog = getFactionCognition(world, def.controllingFaction);
203
+ const boost = Math.round(district.intruderLikelihood * 0.1);
204
+ factionCog.alertLevel = Math.min(100, factionCog.alertLevel + boost);
205
+ }
206
+ }
207
+ }
208
+ // --- Internal Helpers ---
209
+ function getEventDistrictId(event, world) {
210
+ const zoneId = getEventZoneId(event, world);
211
+ if (!zoneId)
212
+ return undefined;
213
+ return getDistrictForZone(world, zoneId);
214
+ }
215
+ function getEventZoneId(event, world) {
216
+ if (event.payload.zoneId)
217
+ return event.payload.zoneId;
218
+ if (event.actorId)
219
+ return world.entities[event.actorId]?.zoneId;
220
+ if (event.targetIds?.[0])
221
+ return world.entities[event.targetIds[0]]?.zoneId;
222
+ return undefined;
223
+ }
224
+ //# sourceMappingURL=district-core.js.map