@its-not-rocket-science/ananke 0.1.68 → 0.5.1

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 (165) hide show
  1. package/CHANGELOG.md +274 -0
  2. package/README.md +150 -431
  3. package/STABLE_API.md +113 -490
  4. package/conformance/README.md +46 -44
  5. package/conformance/lockstep-sequence.json +395 -395
  6. package/conformance/phase-order.json +21 -0
  7. package/conformance/replay-parity.json +65 -65
  8. package/conformance/state-hash-regression.json +39 -0
  9. package/conformance/state-hash.json +23 -22
  10. package/dist/src/conformance.d.ts +8 -2
  11. package/dist/src/content/index.d.ts +3 -0
  12. package/dist/src/content/index.js +2 -0
  13. package/dist/src/content/injector.d.ts +19 -0
  14. package/dist/src/content/injector.js +61 -0
  15. package/dist/src/content/loader.d.ts +3 -0
  16. package/dist/src/content/loader.js +170 -0
  17. package/dist/src/content/types.d.ts +56 -0
  18. package/dist/src/content/types.js +1 -0
  19. package/dist/src/content/validator.d.ts +6 -0
  20. package/dist/src/content/validator.js +14 -0
  21. package/dist/src/content-pack.d.ts +3 -2
  22. package/dist/src/content-pack.js +205 -10
  23. package/dist/src/data-governance.d.ts +73 -0
  24. package/dist/src/data-governance.js +188 -0
  25. package/dist/src/debug.d.ts +6 -0
  26. package/dist/src/debug.js +9 -0
  27. package/dist/src/determinism.d.ts +12 -0
  28. package/dist/src/determinism.js +93 -0
  29. package/dist/src/dialogue.d.ts +41 -0
  30. package/dist/src/dialogue.js +113 -2
  31. package/dist/src/economy.d.ts +97 -3
  32. package/dist/src/economy.js +191 -37
  33. package/dist/src/history/autosave.d.ts +19 -0
  34. package/dist/src/history/autosave.js +98 -0
  35. package/dist/src/history/timetravel.d.ts +28 -0
  36. package/dist/src/history/timetravel.js +61 -0
  37. package/dist/src/index.d.ts +19 -31
  38. package/dist/src/index.js +20 -38
  39. package/dist/src/model3d.js +63 -2
  40. package/dist/src/narrative/combat-logger.d.ts +25 -0
  41. package/dist/src/narrative/combat-logger.js +97 -0
  42. package/dist/src/narrative/plausibility.d.ts +30 -0
  43. package/dist/src/narrative/plausibility.js +70 -0
  44. package/dist/src/narrative-layer.d.ts +2 -0
  45. package/dist/src/narrative-layer.js +2 -0
  46. package/dist/src/navigation/causal-chain.d.ts +26 -0
  47. package/dist/src/navigation/causal-chain.js +74 -0
  48. package/dist/src/netcode.d.ts +1 -1
  49. package/dist/src/netcode.js +1 -1
  50. package/dist/src/performance/adaptive-tick.d.ts +23 -0
  51. package/dist/src/performance/adaptive-tick.js +50 -0
  52. package/dist/src/plugins/loader.d.ts +23 -0
  53. package/dist/src/plugins/loader.js +86 -0
  54. package/dist/src/plugins/registry.d.ts +11 -0
  55. package/dist/src/plugins/registry.js +28 -0
  56. package/dist/src/plugins/types.d.ts +31 -0
  57. package/dist/src/plugins/types.js +7 -0
  58. package/dist/src/replay.js +5 -2
  59. package/dist/src/scenario.js +2 -1
  60. package/dist/src/serialization/binary.d.ts +5 -0
  61. package/dist/src/serialization/binary.js +193 -0
  62. package/dist/src/session.d.ts +115 -0
  63. package/dist/src/session.js +228 -0
  64. package/dist/src/sim/ai/decide.js +1 -1
  65. package/dist/src/sim/ai/personality.js +2 -2
  66. package/dist/src/sim/bodyplan.d.ts +30 -0
  67. package/dist/src/sim/context.d.ts +2 -0
  68. package/dist/src/sim/kernel.js +80 -1294
  69. package/dist/src/sim/normalization.d.ts +12 -0
  70. package/dist/src/sim/normalization.js +92 -0
  71. package/dist/src/sim/resolvers/attack-resolver.d.ts +30 -0
  72. package/dist/src/sim/resolvers/attack-resolver.js +310 -0
  73. package/dist/src/sim/resolvers/capability-resolver.d.ts +15 -0
  74. package/dist/src/sim/resolvers/capability-resolver.js +270 -0
  75. package/dist/src/sim/resolvers/grapple-resolver.d.ts +28 -0
  76. package/dist/src/sim/resolvers/grapple-resolver.js +70 -0
  77. package/dist/src/sim/resolvers/shoot-resolver.d.ts +27 -0
  78. package/dist/src/sim/resolvers/shoot-resolver.js +212 -0
  79. package/dist/src/sim/resolvers/treat-resolver.d.ts +16 -0
  80. package/dist/src/sim/resolvers/treat-resolver.js +77 -0
  81. package/dist/src/sim/step/apply/intents.d.ts +6 -0
  82. package/dist/src/sim/step/apply/intents.js +83 -0
  83. package/dist/src/sim/step/kernel-pipeline.d.ts +9 -0
  84. package/dist/src/sim/step/kernel-pipeline.js +15 -0
  85. package/dist/src/sim/step/phases/capability-phase.d.ts +8 -0
  86. package/dist/src/sim/step/phases/capability-phase.js +65 -0
  87. package/dist/src/sim/step/phases/cooldowns-phase.d.ts +3 -0
  88. package/dist/src/sim/step/phases/cooldowns-phase.js +37 -0
  89. package/dist/src/sim/step/phases/prepare-phase.d.ts +10 -0
  90. package/dist/src/sim/step/phases/prepare-phase.js +33 -0
  91. package/dist/src/sim/step/pipeline.d.ts +2 -0
  92. package/dist/src/sim/step/pipeline.js +15 -0
  93. package/dist/src/sim/step/resolvers/impact-resolver.d.ts +9 -0
  94. package/dist/src/sim/step/resolvers/impact-resolver.js +55 -0
  95. package/dist/src/sim/step/world-phases.d.ts +7 -0
  96. package/dist/src/sim/step/world-phases.js +15 -0
  97. package/dist/src/sim/step/world-step-context.d.ts +23 -0
  98. package/dist/src/sim/step/world-step-context.js +1 -0
  99. package/dist/src/sim/systemic-toxicology.js +1 -1
  100. package/dist/src/sim/team.js +2 -2
  101. package/dist/src/sim/testing.js +4 -3
  102. package/dist/src/sim/toxicology.d.ts +1 -1
  103. package/dist/src/sim/toxicology.js +1 -1
  104. package/dist/src/sim/world.d.ts +17 -24
  105. package/dist/src/snapshot.js +3 -6
  106. package/dist/src/tier2.d.ts +19 -0
  107. package/dist/src/tier2.js +21 -0
  108. package/dist/src/tier3.d.ts +10 -0
  109. package/dist/src/tier3.js +12 -0
  110. package/dist/src/version.d.ts +6 -0
  111. package/dist/src/version.js +8 -0
  112. package/dist/src/wasm/bridge.d.ts +14 -0
  113. package/dist/src/wasm/bridge.js +84 -0
  114. package/dist/src/wasm-kernel.d.ts +2 -0
  115. package/dist/src/wasm-kernel.js +1 -0
  116. package/dist/src/world-evolution-backend/engine.d.ts +4 -0
  117. package/dist/src/world-evolution-backend/engine.js +347 -0
  118. package/dist/src/world-evolution-backend/host-schema.d.ts +145 -0
  119. package/dist/src/world-evolution-backend/host-schema.js +375 -0
  120. package/dist/src/world-evolution-backend/index.d.ts +1 -0
  121. package/dist/src/world-evolution-backend/index.js +1 -0
  122. package/dist/src/world-evolution-backend/open-world-host-adapter.d.ts +96 -0
  123. package/dist/src/world-evolution-backend/open-world-host-adapter.js +208 -0
  124. package/dist/src/world-evolution-backend/profiles.d.ts +4 -0
  125. package/dist/src/world-evolution-backend/profiles.js +136 -0
  126. package/dist/src/world-evolution-backend/public.d.ts +11 -0
  127. package/dist/src/world-evolution-backend/public.js +7 -0
  128. package/dist/src/world-evolution-backend/reproducibility.d.ts +8 -0
  129. package/dist/src/world-evolution-backend/reproducibility.js +35 -0
  130. package/dist/src/world-evolution-backend/timeline.d.ts +6 -0
  131. package/dist/src/world-evolution-backend/timeline.js +337 -0
  132. package/dist/src/world-evolution-backend/types.d.ts +138 -0
  133. package/dist/src/world-evolution-backend/types.js +1 -0
  134. package/dist/src/world-evolution-host-backend.d.ts +51 -0
  135. package/dist/src/world-evolution-host-backend.js +97 -0
  136. package/dist/src/world-evolution.d.ts +201 -0
  137. package/dist/src/world-evolution.js +543 -0
  138. package/dist/src/world-factory.js +2 -1
  139. package/dist/tools/pack-cli.js +55 -2
  140. package/docs/bridge-contract.md +140 -332
  141. package/docs/completion-evidence-dashboard.md +63 -0
  142. package/docs/host-contract.md +86 -310
  143. package/docs/host-world-evolution-schema.md +155 -0
  144. package/docs/integration-primer.md +29 -22
  145. package/docs/migration-monolith-to-modular.md +102 -120
  146. package/docs/package-architecture.md +116 -323
  147. package/docs/plugins/README.md +42 -0
  148. package/docs/plugins/hooks.md +22 -0
  149. package/docs/plugins/publishing.md +29 -0
  150. package/docs/plugins/security-model.md +273 -0
  151. package/docs/project-overview.md +30 -30
  152. package/docs/release-dashboard.md +47 -0
  153. package/docs/session-api.md +127 -0
  154. package/docs/stable-api-manifest.json +52 -0
  155. package/docs/trust-dashboard.md +37 -0
  156. package/docs/versioning.md +256 -245
  157. package/docs/wire-protocol.md +140 -209
  158. package/docs/world-evolution-backend.md +200 -0
  159. package/docs/world-evolution-engine.md +150 -0
  160. package/docs/world-evolution-host-backend.md +70 -0
  161. package/docs/world-evolution-orchestration.md +248 -0
  162. package/package.json +125 -6
  163. package/schema/content-pack.schema.json +85 -0
  164. package/schema/plugin.schema.json +57 -0
  165. package/schema/world-evolution-input.schema.json +196 -0
@@ -2,6 +2,7 @@ import type { WorldState } from "./sim/world.js";
2
2
  import type { Vec3 } from "./sim/vec3.js";
3
3
  import type { Q } from "./units.js";
4
4
  import type { TraceEvent } from "./sim/trace.js";
5
+ import { type Explanation, type ExplanationContext, type TickRange } from "./navigation/causal-chain.js";
5
6
  /**
6
7
  * Per-entity motion state — position, velocity, and facing direction.
7
8
  * Suitable for overlaying movement arrows in a host renderer.
@@ -84,3 +85,8 @@ export interface ConditionSample {
84
85
  * const samples = extractConditionSamples(past);
85
86
  */
86
87
  export declare function extractConditionSamples(world: WorldState): ConditionSample[];
88
+ /**
89
+ * Convenience adapter for debugger UIs that need a reasoned explanation
90
+ * for an entity outcome over a tick range.
91
+ */
92
+ export declare function explainOutcomeFromTrace(entityId: number, tickRange: TickRange, context: ExplanationContext): Explanation;
package/dist/src/debug.js CHANGED
@@ -6,6 +6,7 @@
6
6
  // Each function transforms WorldState or TraceEvent[] into a structured
7
7
  // snapshot that a host renderer can consume directly.
8
8
  import { TraceKinds } from "./sim/kinds.js";
9
+ import { explainOutcome } from "./navigation/causal-chain.js";
9
10
  /**
10
11
  * Extract per-entity motion vectors from the current world state.
11
12
  * Call once per tick after stepWorld to get the latest motion snapshot.
@@ -74,3 +75,11 @@ export function extractConditionSamples(world) {
74
75
  dead: e.injury.dead,
75
76
  }));
76
77
  }
78
+ // ─── Causal chain explanations ───────────────────────────────────────────────
79
+ /**
80
+ * Convenience adapter for debugger UIs that need a reasoned explanation
81
+ * for an entity outcome over a tick range.
82
+ */
83
+ export function explainOutcomeFromTrace(entityId, tickRange, context) {
84
+ return explainOutcome(entityId, tickRange, context);
85
+ }
@@ -0,0 +1,12 @@
1
+ import type { WorldState } from "./sim/world.js";
2
+ export declare function assertNoFloatUsage(world: WorldState): void;
3
+ export declare function assertNoFloatUsageInProduction(world: WorldState): void;
4
+ /**
5
+ * Strict deterministic-world invariants used by CI fuzzing and replay checks.
6
+ *
7
+ * Covers:
8
+ * - no NaN/Infinity in core numeric state
9
+ * - deterministic ordering in entity/id lists
10
+ * - stable entity indexing assumptions used by kernel/index layers
11
+ */
12
+ export declare function assertDeterministicWorldLike(world: WorldState, stage?: string): void;
@@ -0,0 +1,93 @@
1
+ let asserted = false;
2
+ function assertInteger(name, value) {
3
+ if (!Number.isFinite(value) || !Number.isInteger(value)) {
4
+ throw new Error(`determinism assertion failed: ${name} must be a finite integer, received ${value}`);
5
+ }
6
+ }
7
+ export function assertNoFloatUsage(world) {
8
+ assertInteger("world.tick", world.tick);
9
+ assertInteger("world.seed", world.seed);
10
+ for (const e of world.entities) {
11
+ assertInteger(`entity:${e.id}.position.x`, e.position_m.x);
12
+ assertInteger(`entity:${e.id}.position.y`, e.position_m.y);
13
+ assertInteger(`entity:${e.id}.velocity.x`, e.velocity_mps.x);
14
+ assertInteger(`entity:${e.id}.velocity.y`, e.velocity_mps.y);
15
+ assertInteger(`entity:${e.id}.injury.fluidLoss`, e.injury.fluidLoss);
16
+ assertInteger(`entity:${e.id}.injury.shock`, e.injury.shock);
17
+ assertInteger(`entity:${e.id}.injury.consciousness`, e.injury.consciousness);
18
+ }
19
+ }
20
+ export function assertNoFloatUsageInProduction(world) {
21
+ if (asserted)
22
+ return;
23
+ asserted = true;
24
+ if (typeof process !== "undefined" && process.env.NODE_ENV === "production") {
25
+ assertNoFloatUsage(world);
26
+ }
27
+ }
28
+ function assertFiniteNumber(name, value) {
29
+ if (typeof value !== "number" || !Number.isFinite(value)) {
30
+ throw new Error(`determinism invariant failed: ${name} must be a finite number, received ${String(value)}`);
31
+ }
32
+ }
33
+ function assertSortedUniqueAscending(name, values) {
34
+ let prev = -Infinity;
35
+ for (let i = 0; i < values.length; i++) {
36
+ const value = values[i];
37
+ assertFiniteNumber(`${name}[${i}]`, value);
38
+ if (!Number.isInteger(value)) {
39
+ throw new Error(`determinism invariant failed: ${name}[${i}] must be an integer id, received ${String(value)}`);
40
+ }
41
+ if (value <= prev) {
42
+ throw new Error(`determinism invariant failed: ${name} must be strictly ascending (idx ${i - 1}=${prev}, idx ${i}=${value})`);
43
+ }
44
+ prev = value;
45
+ }
46
+ }
47
+ /**
48
+ * Strict deterministic-world invariants used by CI fuzzing and replay checks.
49
+ *
50
+ * Covers:
51
+ * - no NaN/Infinity in core numeric state
52
+ * - deterministic ordering in entity/id lists
53
+ * - stable entity indexing assumptions used by kernel/index layers
54
+ */
55
+ export function assertDeterministicWorldLike(world, stage = "unknown") {
56
+ assertFiniteNumber(`world.tick @${stage}`, world.tick);
57
+ assertFiniteNumber(`world.seed @${stage}`, world.seed);
58
+ if (!Number.isInteger(world.tick)) {
59
+ throw new Error(`determinism invariant failed: world.tick @${stage} must be an integer`);
60
+ }
61
+ if (!Number.isInteger(world.seed)) {
62
+ throw new Error(`determinism invariant failed: world.seed @${stage} must be an integer`);
63
+ }
64
+ const ids = world.entities.map((e) => e.id);
65
+ assertSortedUniqueAscending(`world.entities.id @${stage}`, ids);
66
+ const byId = new Map();
67
+ for (let i = 0; i < world.entities.length; i++) {
68
+ const e = world.entities[i];
69
+ byId.set(e.id, i);
70
+ assertFiniteNumber(`entity:${e.id}.id @${stage}`, e.id);
71
+ assertFiniteNumber(`entity:${e.id}.teamId @${stage}`, e.teamId);
72
+ assertFiniteNumber(`entity:${e.id}.position.x @${stage}`, e.position_m.x);
73
+ assertFiniteNumber(`entity:${e.id}.position.y @${stage}`, e.position_m.y);
74
+ assertFiniteNumber(`entity:${e.id}.position.z @${stage}`, e.position_m.z);
75
+ assertFiniteNumber(`entity:${e.id}.velocity.x @${stage}`, e.velocity_mps.x);
76
+ assertFiniteNumber(`entity:${e.id}.velocity.y @${stage}`, e.velocity_mps.y);
77
+ assertFiniteNumber(`entity:${e.id}.velocity.z @${stage}`, e.velocity_mps.z);
78
+ assertFiniteNumber(`entity:${e.id}.injury.fluidLoss @${stage}`, e.injury.fluidLoss);
79
+ assertFiniteNumber(`entity:${e.id}.injury.shock @${stage}`, e.injury.shock);
80
+ assertFiniteNumber(`entity:${e.id}.injury.consciousness @${stage}`, e.injury.consciousness);
81
+ assertFiniteNumber(`entity:${e.id}.grapple.holdingTargetId @${stage}`, e.grapple.holdingTargetId);
82
+ assertFiniteNumber(`entity:${e.id}.action.attackCooldownTicks @${stage}`, e.action.attackCooldownTicks);
83
+ assertFiniteNumber(`entity:${e.id}.action.defenceCooldownTicks @${stage}`, e.action.defenceCooldownTicks);
84
+ assertFiniteNumber(`entity:${e.id}.action.grappleCooldownTicks @${stage}`, e.action.grappleCooldownTicks);
85
+ assertSortedUniqueAscending(`entity:${e.id}.grapple.heldByIds @${stage}`, e.grapple.heldByIds);
86
+ }
87
+ for (const [id, idx] of byId) {
88
+ const entityAtIndex = world.entities[idx];
89
+ if (!entityAtIndex || entityAtIndex.id !== id) {
90
+ throw new Error(`determinism invariant failed: unstable entity indexing for id=${id} @${stage}`);
91
+ }
92
+ }
93
+ }
@@ -1,6 +1,9 @@
1
1
  import type { Q } from "./units.js";
2
2
  import type { Entity } from "./sim/entity.js";
3
3
  import type { NarrativeConfig } from "./narrative.js";
4
+ import type { FactionRegistry } from "./faction.js";
5
+ import type { RelationshipGraph, RelationshipEventType } from "./relationships.js";
6
+ import type { CampaignState } from "./campaign.js";
4
7
  /** A lightweight trade item for negotiation offers. Full item economy is Phase 25. */
5
8
  export interface TradeItem {
6
9
  id: string;
@@ -61,6 +64,18 @@ export interface DialogueContext {
61
64
  tick: number;
62
65
  sharedFaction?: boolean;
63
66
  priorFailedAttempts?: number;
67
+ /** Initiator standing with target's faction (q(0.5)=neutral). */
68
+ factionStanding_Q?: Q;
69
+ /** Existing interpersonal trust between initiator and target. */
70
+ relationshipTrust_Q?: Q;
71
+ /** Existing interpersonal affinity (-q(1.0)..q(1.0)). */
72
+ relationshipAffinity_Q?: Q;
73
+ /** Initiator fame axis from renown registry. */
74
+ initiatorRenown_Q?: Q;
75
+ /** Initiator dark reputation axis from renown registry. */
76
+ initiatorInfamy_Q?: Q;
77
+ /** Campaign pressure on target (siege, attrition, isolation). */
78
+ campaignPressure_Q?: Q;
64
79
  }
65
80
  /** Reduction to intimidation probability when target has the "leader" trait. */
66
81
  export declare const LEADER_INTIMIDATE_REDUCTION: Q;
@@ -79,6 +94,17 @@ export declare const SURRENDER_THRESHOLD: Q;
79
94
  * the target interprets the attempt as an insult and escalates.
80
95
  */
81
96
  export declare const ESCALATE_THRESHOLD: Q;
97
+ export declare const STANDING_NEUTRAL_Q: Q;
98
+ export declare const RELATIONSHIP_NEUTRAL_AFFINITY_Q: Q;
99
+ export declare const INTIMIDATE_REPUTATION_WEIGHT_Q: Q;
100
+ export declare const PERSUADE_STANDING_WEIGHT_Q: Q;
101
+ export declare const PERSUADE_TRUST_WEIGHT_Q: Q;
102
+ export declare const PERSUADE_AFFINITY_WEIGHT_Q: Q;
103
+ export declare const PERSUADE_RENOWN_WEIGHT_Q: Q;
104
+ export declare const SURRENDER_PRESSURE_WEIGHT_Q: Q;
105
+ export declare const NEGOTIATE_STANDING_WEIGHT_Q: Q;
106
+ export declare const NEGOTIATE_TRUST_WEIGHT_Q: Q;
107
+ export declare const NEGOTIATE_AFFINITY_WEIGHT_Q: Q;
82
108
  /**
83
109
  * Compute the success probability for a dialogue action without rolling RNG.
84
110
  *
@@ -107,6 +133,21 @@ export declare function resolveDialogue(action: DialogueAction, ctx: DialogueCon
107
133
  * No-op if `outcome.result !== "success"`.
108
134
  */
109
135
  export declare function applyDialogueOutcome(outcome: DialogueOutcome, target: Entity): void;
136
+ export interface DialogueStateAdapters {
137
+ factionRegistry?: FactionRegistry;
138
+ relationshipGraph?: RelationshipGraph;
139
+ campaign?: CampaignState;
140
+ }
141
+ export interface DialogueStateReport {
142
+ reputationDelta_Q: Q;
143
+ relationshipEvent?: RelationshipEventType;
144
+ campaignLogEntry?: string;
145
+ }
146
+ /**
147
+ * Apply dialogue outcome to broader campaign/faction/relationship state.
148
+ * All state holders are optional and explicitly injected by host code.
149
+ */
150
+ export declare function applyDialogueState(action: DialogueAction, outcome: DialogueOutcome, ctx: DialogueContext, state: DialogueStateAdapters): DialogueStateReport;
110
151
  /**
111
152
  * Produce a human-readable description of a dialogue action and its outcome.
112
153
  *
@@ -10,6 +10,7 @@ import { SCALE, q, clampQ, qMul, mulDiv, to } from "./units.js";
10
10
  import { eventSeed } from "./sim/seeds.js";
11
11
  import { makeRng } from "./rng.js";
12
12
  import { resolveSignal } from "./competence/interspecies.js";
13
+ import { recordRelationshipEvent } from "./relationships.js";
13
14
  // ── Constants ─────────────────────────────────────────────────────────────────
14
15
  /** Real-newton threshold where intimidation force factor reaches q(1.0). */
15
16
  const INTIMIDATE_FORCE_SCALE = to.N(4000);
@@ -30,6 +31,17 @@ export const SURRENDER_THRESHOLD = q(0.40);
30
31
  * the target interprets the attempt as an insult and escalates.
31
32
  */
32
33
  export const ESCALATE_THRESHOLD = q(0.20);
34
+ export const STANDING_NEUTRAL_Q = q(0.50);
35
+ export const RELATIONSHIP_NEUTRAL_AFFINITY_Q = q(0);
36
+ export const INTIMIDATE_REPUTATION_WEIGHT_Q = q(0.20);
37
+ export const PERSUADE_STANDING_WEIGHT_Q = q(0.20);
38
+ export const PERSUADE_TRUST_WEIGHT_Q = q(0.20);
39
+ export const PERSUADE_AFFINITY_WEIGHT_Q = q(0.10);
40
+ export const PERSUADE_RENOWN_WEIGHT_Q = q(0.15);
41
+ export const SURRENDER_PRESSURE_WEIGHT_Q = q(0.20);
42
+ export const NEGOTIATE_STANDING_WEIGHT_Q = q(0.30);
43
+ export const NEGOTIATE_TRUST_WEIGHT_Q = q(0.20);
44
+ export const NEGOTIATE_AFFINITY_WEIGHT_Q = q(0.10);
33
45
  // ── RNG salts ─────────────────────────────────────────────────────────────────
34
46
  const SALT_INTIMIDATE = 0xD1A100;
35
47
  const SALT_PERSUADE = 0xD1A101;
@@ -57,6 +69,31 @@ function attentionDepthQ(depth) {
57
69
  function learningBonus(attentionDepth) {
58
70
  return clampQ(Math.max(0, attentionDepth - 4) * 250, 0, q(0.20));
59
71
  }
72
+ function signedMulQ(value_Q, weight_Q) {
73
+ return Math.trunc((value_Q * weight_Q) / SCALE.Q);
74
+ }
75
+ function standingCenteredQ(standing_Q) {
76
+ return ((standing_Q ?? STANDING_NEUTRAL_Q) - STANDING_NEUTRAL_Q);
77
+ }
78
+ function persuasionSocialModifier(ctx) {
79
+ const standingBonus = signedMulQ(standingCenteredQ(ctx.factionStanding_Q), PERSUADE_STANDING_WEIGHT_Q);
80
+ const trustBonus = signedMulQ(((ctx.relationshipTrust_Q ?? STANDING_NEUTRAL_Q) - STANDING_NEUTRAL_Q), PERSUADE_TRUST_WEIGHT_Q);
81
+ const affinityBonus = signedMulQ((ctx.relationshipAffinity_Q ?? RELATIONSHIP_NEUTRAL_AFFINITY_Q), PERSUADE_AFFINITY_WEIGHT_Q);
82
+ const renownDelta = ((ctx.initiatorRenown_Q ?? q(0)) - (ctx.initiatorInfamy_Q ?? q(0)));
83
+ const renownBonus = signedMulQ(renownDelta, PERSUADE_RENOWN_WEIGHT_Q);
84
+ return (standingBonus + trustBonus + affinityBonus + renownBonus);
85
+ }
86
+ function intimidationReputationModifier(ctx) {
87
+ const profile = Math.max(ctx.initiatorRenown_Q ?? q(0), ctx.initiatorInfamy_Q ?? q(0));
88
+ return signedMulQ(profile, INTIMIDATE_REPUTATION_WEIGHT_Q);
89
+ }
90
+ function negotiationSocialBias(ctx) {
91
+ const standingBias = signedMulQ(standingCenteredQ(ctx.factionStanding_Q), NEGOTIATE_STANDING_WEIGHT_Q);
92
+ const trustCentered = ((ctx.relationshipTrust_Q ?? STANDING_NEUTRAL_Q) - STANDING_NEUTRAL_Q);
93
+ const trustBias = signedMulQ(trustCentered, NEGOTIATE_TRUST_WEIGHT_Q);
94
+ const affinityBias = signedMulQ((ctx.relationshipAffinity_Q ?? RELATIONSHIP_NEUTRAL_AFFINITY_Q), NEGOTIATE_AFFINITY_WEIGHT_Q);
95
+ return (standingBias + trustBias + affinityBias);
96
+ }
60
97
  // ── Probability computation ───────────────────────────────────────────────────
61
98
  /**
62
99
  * Compute the success probability for a dialogue action without rolling RNG.
@@ -74,6 +111,7 @@ export function dialogueProbability(action, ctx) {
74
111
  return clampQ(forceFrac
75
112
  + target.condition.fearQ
76
113
  - target.attributes.resilience.distressTolerance
114
+ + intimidationReputationModifier(ctx)
77
115
  - leaderRed, 0, SCALE.Q);
78
116
  }
79
117
  case "persuade": {
@@ -87,6 +125,7 @@ export function dialogueProbability(action, ctx) {
87
125
  return clampQ(dynamicBase
88
126
  + learningBonus(target.attributes.perception?.attentionDepth ?? 0)
89
127
  + (ctx.sharedFaction ? PERSUADE_FACTION_BONUS : 0)
128
+ + persuasionSocialModifier(ctx)
90
129
  - (failed * PERSUADE_FAILURE_PENALTY), 0, SCALE.Q);
91
130
  }
92
131
  case "deceive": {
@@ -100,11 +139,16 @@ export function dialogueProbability(action, ctx) {
100
139
  }
101
140
  case "surrender":
102
141
  // Returns non-zero only when target's fear exceeds the acceptance threshold.
103
- return clampQ(target.condition.fearQ - SURRENDER_THRESHOLD, 0, SCALE.Q);
142
+ return clampQ(target.condition.fearQ
143
+ + signedMulQ((ctx.campaignPressure_Q ?? q(0)), SURRENDER_PRESSURE_WEIGHT_Q)
144
+ - SURRENDER_THRESHOLD, 0, SCALE.Q);
104
145
  case "negotiate": {
105
146
  const given = action.offer.giving.reduce((s, i) => s + i.value, 0);
106
147
  const received = action.offer.receiving.reduce((s, i) => s + i.value, 0);
107
- return given > received ? SCALE.Q : 0;
148
+ const total = Math.max(1, given + received);
149
+ const baseUtility_Q = Math.trunc(((given - received) * SCALE.Q) / total);
150
+ const adjusted = (baseUtility_Q + negotiationSocialBias(ctx));
151
+ return adjusted > 0 ? SCALE.Q : 0;
108
152
  }
109
153
  case "signal":
110
154
  // Phase 36: signal probability is computed in resolveSignal; here we return a placeholder
@@ -194,6 +238,73 @@ export function applyDialogueOutcome(outcome, target) {
194
238
  target.condition.surrendered = true;
195
239
  }
196
240
  }
241
+ function applyFactionDelta(registry, actorId, factionId, delta_Q) {
242
+ if (!factionId || delta_Q === 0)
243
+ return;
244
+ let reps = registry.entityReputations.get(actorId);
245
+ if (!reps) {
246
+ reps = new Map();
247
+ registry.entityReputations.set(actorId, reps);
248
+ }
249
+ const current = reps.get(factionId) ?? STANDING_NEUTRAL_Q;
250
+ reps.set(factionId, clampQ((current + delta_Q), 0, SCALE.Q));
251
+ }
252
+ /**
253
+ * Apply dialogue outcome to broader campaign/faction/relationship state.
254
+ * All state holders are optional and explicitly injected by host code.
255
+ */
256
+ export function applyDialogueState(action, outcome, ctx, state) {
257
+ applyDialogueOutcome(outcome, ctx.target);
258
+ let reputationDelta_Q = q(0);
259
+ let relationshipEvent;
260
+ let campaignLogEntry;
261
+ if (outcome.result === "success") {
262
+ if (action.kind === "surrender") {
263
+ reputationDelta_Q = q(0.05);
264
+ relationshipEvent = "saved";
265
+ campaignLogEntry = `Entity ${ctx.target.id} surrendered to entity ${ctx.initiator.id}.`;
266
+ }
267
+ else if (action.kind === "negotiate") {
268
+ reputationDelta_Q = q(0.04);
269
+ relationshipEvent = "gift_given";
270
+ }
271
+ else if (action.kind === "persuade") {
272
+ reputationDelta_Q = q(0.02);
273
+ relationshipEvent = "bonded";
274
+ }
275
+ else if (action.kind === "intimidate") {
276
+ reputationDelta_Q = q(-0.03);
277
+ relationshipEvent = "insult";
278
+ }
279
+ }
280
+ else if (outcome.result === "failure" || outcome.result === "escalate") {
281
+ if (action.kind === "negotiate" || action.kind === "persuade") {
282
+ relationshipEvent = "insult";
283
+ }
284
+ }
285
+ if (state.factionRegistry) {
286
+ applyFactionDelta(state.factionRegistry, ctx.initiator.id, ctx.target.faction, reputationDelta_Q);
287
+ }
288
+ if (state.relationshipGraph && relationshipEvent) {
289
+ recordRelationshipEvent(state.relationshipGraph, ctx.initiator.id, ctx.target.id, {
290
+ tick: ctx.tick,
291
+ type: relationshipEvent,
292
+ magnitude_Q: outcome.result === "success" ? q(0.20) : q(0.10),
293
+ });
294
+ }
295
+ if (state.campaign && campaignLogEntry) {
296
+ state.campaign.log.push({
297
+ worldTime_s: state.campaign.worldTime_s,
298
+ text: campaignLogEntry,
299
+ });
300
+ }
301
+ const report = { reputationDelta_Q };
302
+ if (relationshipEvent !== undefined)
303
+ report.relationshipEvent = relationshipEvent;
304
+ if (campaignLogEntry !== undefined)
305
+ report.campaignLogEntry = campaignLogEntry;
306
+ return report;
307
+ }
197
308
  // ── Narrative ─────────────────────────────────────────────────────────────────
198
309
  /** Short display label per action kind. */
199
310
  function actionLabel(action) {
@@ -33,6 +33,60 @@ export interface TradeEvaluation {
33
33
  /** True when the accepting party has all "want" items in sufficient quantity. */
34
34
  feasible: boolean;
35
35
  }
36
+ export interface DurabilityValuePolicy {
37
+ /**
38
+ * Minimum retained value fraction even at zero condition.
39
+ * Useful for salvage economies.
40
+ */
41
+ salvageFloorFraction?: number;
42
+ /**
43
+ * Curvature applied to condition in [0, 1].
44
+ * - >1 discounts damaged gear harder.
45
+ * - <1 is more forgiving to worn gear.
46
+ */
47
+ conditionExponent?: number;
48
+ }
49
+ export interface ItemizedCount {
50
+ itemId: string;
51
+ count: number;
52
+ }
53
+ export interface DropResolution {
54
+ dropped: string[];
55
+ guaranteed: string[];
56
+ probabilistic: string[];
57
+ preventedByState: boolean;
58
+ source: "dead" | "incapacitated" | "none";
59
+ }
60
+ export interface TradeInventorySnapshot {
61
+ ownedCount: number;
62
+ shortageCount: number;
63
+ entryUnitValue: number;
64
+ offerUnitValue: number;
65
+ }
66
+ export interface TradeEvaluationDetailed extends TradeEvaluation {
67
+ giveValue: number;
68
+ wantValue: number;
69
+ shortages: Array<{
70
+ itemId: string;
71
+ missingCount: number;
72
+ }>;
73
+ snapshots: Map<string, TradeInventorySnapshot>;
74
+ }
75
+ export interface EconomyHostReport {
76
+ label: string;
77
+ totals: {
78
+ grossValue: number;
79
+ netValue: number;
80
+ lineCount: number;
81
+ };
82
+ lines: Array<{
83
+ itemId: string;
84
+ count: number;
85
+ unitValue: number;
86
+ totalValue: number;
87
+ }>;
88
+ tags: string[];
89
+ }
36
90
  /**
37
91
  * Campaign-level item inventory: maps itemId → { count, unitValue }.
38
92
  * `unitValue` is in cost units; used by totalInventoryValue.
@@ -65,14 +119,28 @@ export declare const WEAR_FUMBLE_THRESHOLD: Q;
65
119
  */
66
120
  export declare function computeItemValue(item: Item | MedicalResource, wear_Q?: Q): ItemValue;
67
121
  /**
68
- * Convert armour degradation state to a wear_Q fraction.
122
+ * Convert armour state to condition fraction.
69
123
  *
70
124
  * ```
71
- * wear = q(1.0) − resistRemaining_J / resist_J
125
+ * condition = resistRemaining_J / resist_J
72
126
  * ```
73
- * Used to derive `condition_Q` for ablative armour via `computeItemValue`.
127
+ *
128
+ * The result can be converted to wear via `conditionToWearQ`.
74
129
  */
75
130
  export declare function armourConditionQ(resist_J: number, resistRemaining_J: number): Q;
131
+ /** Convert condition to wear (`wear = 1 - condition`) in fixed-point space. */
132
+ export declare function conditionToWearQ(condition_Q: Q): Q;
133
+ /**
134
+ * Convert an ItemValue into market-usable unit values.
135
+ *
136
+ * This keeps `computeItemValue` stable and lets hosts pick stricter/looser
137
+ * durability discounting.
138
+ */
139
+ export declare function toMarketValue(itemValue: ItemValue, policy?: DurabilityValuePolicy): {
140
+ buyValue: number;
141
+ sellValue: number;
142
+ conditionMultiplier: number;
143
+ };
76
144
  /**
77
145
  * Apply one strike's worth of use-wear to a melee weapon.
78
146
  *
@@ -86,6 +154,12 @@ export declare function armourConditionQ(resist_J: number, resistRemaining_J: nu
86
154
  * The returned `wear_Q` should be written back to `weapon.wear_Q` by the caller.
87
155
  */
88
156
  export declare function applyWear(weapon: Weapon, actionIntensity_Q: Q, seed?: number): WearResult;
157
+ /**
158
+ * Structured drop resolution for host-facing UI and telemetry.
159
+ */
160
+ export declare function resolveDropsDetailed(entity: Entity, seed: number, extra?: DropTable, config?: {
161
+ dropOnIncapacitated?: boolean;
162
+ }): DropResolution;
89
163
  /**
90
164
  * Compute the list of item IDs dropped by an entity on death or incapacitation.
91
165
  *
@@ -110,6 +184,26 @@ export declare function resolveDrops(entity: Entity, seed: number, extra?: DropT
110
184
  * `feasible` — the accepting party has all `want` items in sufficient quantities.
111
185
  */
112
186
  export declare function evaluateTradeOffer(offer: TradeOffer, inventory: ItemInventory): TradeEvaluation;
187
+ /**
188
+ * Inventory-aware trade evaluation that surfaces shortages and value breakdown.
189
+ */
190
+ export declare function evaluateTradeOfferDetailed(offer: TradeOffer, inventory: ItemInventory): TradeEvaluationDetailed;
191
+ /** Merge simple itemized counts into an ItemInventory map. */
192
+ export declare function mergeIntoInventory(inventory: ItemInventory, items: ItemizedCount[], resolver: (itemId: string) => number): ItemInventory;
193
+ /** Build a host-facing value report for any itemized list. */
194
+ export declare function createEconomyReport(label: string, entries: ItemizedCount[], resolver: (itemId: string) => number, tags?: string[]): EconomyHostReport;
195
+ /**
196
+ * Example flow: post-battle loot screen.
197
+ */
198
+ export declare function examplePostBattleLootFlow(entity: Entity, seed: number, resolver: (itemId: string) => number): EconomyHostReport;
199
+ /**
200
+ * Example flow: repair/reuse loop that compares pre-repair vs post-repair resale.
201
+ */
202
+ export declare function exampleRepairReuseLoop(item: Item, startingWear_Q: Q, repairedWear_Q: Q): EconomyHostReport;
203
+ /**
204
+ * Example flow: settlement/shop interaction, including affordability and shortages.
205
+ */
206
+ export declare function exampleSettlementTradeFlow(offer: TradeOffer, inventory: ItemInventory): TradeEvaluationDetailed;
113
207
  /**
114
208
  * Sum the total value of all items in an inventory (count × unitValue for each entry).
115
209
  */