@its-not-rocket-science/ananke 0.1.69 → 0.5.4
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/CHANGELOG.md +288 -0
- package/README.md +150 -432
- package/STABLE_API.md +113 -490
- package/conformance/README.md +46 -44
- package/conformance/lockstep-sequence.json +395 -395
- package/conformance/phase-order.json +21 -0
- package/conformance/replay-parity.json +65 -65
- package/conformance/state-hash-regression.json +39 -0
- package/conformance/state-hash.json +23 -22
- package/dist/src/conformance.d.ts +8 -2
- package/dist/src/content/index.d.ts +3 -0
- package/dist/src/content/index.js +2 -0
- package/dist/src/content/injector.d.ts +19 -0
- package/dist/src/content/injector.js +61 -0
- package/dist/src/content/loader.d.ts +3 -0
- package/dist/src/content/loader.js +170 -0
- package/dist/src/content/types.d.ts +56 -0
- package/dist/src/content/types.js +1 -0
- package/dist/src/content/validator.d.ts +6 -0
- package/dist/src/content/validator.js +14 -0
- package/dist/src/content-pack.d.ts +3 -2
- package/dist/src/content-pack.js +205 -10
- package/dist/src/data-governance.d.ts +73 -0
- package/dist/src/data-governance.js +188 -0
- package/dist/src/debug.d.ts +6 -0
- package/dist/src/debug.js +9 -0
- package/dist/src/determinism.d.ts +12 -0
- package/dist/src/determinism.js +93 -0
- package/dist/src/dialogue.d.ts +41 -0
- package/dist/src/dialogue.js +113 -2
- package/dist/src/economy.d.ts +97 -3
- package/dist/src/economy.js +191 -37
- package/dist/src/history/autosave.d.ts +19 -0
- package/dist/src/history/autosave.js +98 -0
- package/dist/src/history/timetravel.d.ts +28 -0
- package/dist/src/history/timetravel.js +61 -0
- package/dist/src/index.d.ts +19 -31
- package/dist/src/index.js +20 -38
- package/dist/src/model3d.js +63 -2
- package/dist/src/narrative/combat-logger.d.ts +25 -0
- package/dist/src/narrative/combat-logger.js +97 -0
- package/dist/src/narrative/plausibility.d.ts +30 -0
- package/dist/src/narrative/plausibility.js +70 -0
- package/dist/src/narrative-layer.d.ts +2 -0
- package/dist/src/narrative-layer.js +2 -0
- package/dist/src/navigation/causal-chain.d.ts +26 -0
- package/dist/src/navigation/causal-chain.js +74 -0
- package/dist/src/netcode.d.ts +1 -1
- package/dist/src/netcode.js +1 -1
- package/dist/src/performance/adaptive-tick.d.ts +23 -0
- package/dist/src/performance/adaptive-tick.js +50 -0
- package/dist/src/plugins/loader.d.ts +23 -0
- package/dist/src/plugins/loader.js +86 -0
- package/dist/src/plugins/registry.d.ts +11 -0
- package/dist/src/plugins/registry.js +28 -0
- package/dist/src/plugins/types.d.ts +31 -0
- package/dist/src/plugins/types.js +7 -0
- package/dist/src/replay.js +5 -2
- package/dist/src/scenario.js +2 -1
- package/dist/src/serialization/binary.d.ts +5 -0
- package/dist/src/serialization/binary.js +193 -0
- package/dist/src/session.d.ts +115 -0
- package/dist/src/session.js +228 -0
- package/dist/src/sim/ai/decide.js +1 -1
- package/dist/src/sim/ai/personality.js +2 -2
- package/dist/src/sim/bodyplan.d.ts +30 -0
- package/dist/src/sim/context.d.ts +2 -0
- package/dist/src/sim/kernel.js +80 -1294
- package/dist/src/sim/normalization.d.ts +12 -0
- package/dist/src/sim/normalization.js +92 -0
- package/dist/src/sim/resolvers/attack-resolver.d.ts +30 -0
- package/dist/src/sim/resolvers/attack-resolver.js +310 -0
- package/dist/src/sim/resolvers/capability-resolver.d.ts +15 -0
- package/dist/src/sim/resolvers/capability-resolver.js +270 -0
- package/dist/src/sim/resolvers/grapple-resolver.d.ts +28 -0
- package/dist/src/sim/resolvers/grapple-resolver.js +70 -0
- package/dist/src/sim/resolvers/shoot-resolver.d.ts +27 -0
- package/dist/src/sim/resolvers/shoot-resolver.js +212 -0
- package/dist/src/sim/resolvers/treat-resolver.d.ts +16 -0
- package/dist/src/sim/resolvers/treat-resolver.js +77 -0
- package/dist/src/sim/step/apply/intents.d.ts +6 -0
- package/dist/src/sim/step/apply/intents.js +83 -0
- package/dist/src/sim/step/kernel-pipeline.d.ts +9 -0
- package/dist/src/sim/step/kernel-pipeline.js +15 -0
- package/dist/src/sim/step/phases/capability-phase.d.ts +8 -0
- package/dist/src/sim/step/phases/capability-phase.js +65 -0
- package/dist/src/sim/step/phases/cooldowns-phase.d.ts +3 -0
- package/dist/src/sim/step/phases/cooldowns-phase.js +37 -0
- package/dist/src/sim/step/phases/prepare-phase.d.ts +10 -0
- package/dist/src/sim/step/phases/prepare-phase.js +33 -0
- package/dist/src/sim/step/pipeline.d.ts +2 -0
- package/dist/src/sim/step/pipeline.js +15 -0
- package/dist/src/sim/step/resolvers/impact-resolver.d.ts +9 -0
- package/dist/src/sim/step/resolvers/impact-resolver.js +55 -0
- package/dist/src/sim/step/world-phases.d.ts +7 -0
- package/dist/src/sim/step/world-phases.js +15 -0
- package/dist/src/sim/step/world-step-context.d.ts +23 -0
- package/dist/src/sim/step/world-step-context.js +1 -0
- package/dist/src/sim/systemic-toxicology.js +1 -1
- package/dist/src/sim/team.js +2 -2
- package/dist/src/sim/testing.js +4 -3
- package/dist/src/sim/toxicology.d.ts +1 -1
- package/dist/src/sim/toxicology.js +1 -1
- package/dist/src/sim/world.d.ts +17 -24
- package/dist/src/snapshot.js +3 -6
- package/dist/src/tier2.d.ts +19 -0
- package/dist/src/tier2.js +21 -0
- package/dist/src/tier3.d.ts +10 -0
- package/dist/src/tier3.js +12 -0
- package/dist/src/version.d.ts +6 -0
- package/dist/src/version.js +8 -0
- package/dist/src/wasm/bridge.d.ts +14 -0
- package/dist/src/wasm/bridge.js +84 -0
- package/dist/src/wasm-kernel.d.ts +2 -0
- package/dist/src/wasm-kernel.js +1 -0
- package/dist/src/world-evolution-backend/engine.d.ts +4 -0
- package/dist/src/world-evolution-backend/engine.js +347 -0
- package/dist/src/world-evolution-backend/host-schema.d.ts +145 -0
- package/dist/src/world-evolution-backend/host-schema.js +375 -0
- package/dist/src/world-evolution-backend/index.d.ts +1 -0
- package/dist/src/world-evolution-backend/index.js +1 -0
- package/dist/src/world-evolution-backend/open-world-host-adapter.d.ts +96 -0
- package/dist/src/world-evolution-backend/open-world-host-adapter.js +208 -0
- package/dist/src/world-evolution-backend/profiles.d.ts +4 -0
- package/dist/src/world-evolution-backend/profiles.js +136 -0
- package/dist/src/world-evolution-backend/public.d.ts +11 -0
- package/dist/src/world-evolution-backend/public.js +7 -0
- package/dist/src/world-evolution-backend/reproducibility.d.ts +8 -0
- package/dist/src/world-evolution-backend/reproducibility.js +35 -0
- package/dist/src/world-evolution-backend/timeline.d.ts +6 -0
- package/dist/src/world-evolution-backend/timeline.js +337 -0
- package/dist/src/world-evolution-backend/types.d.ts +138 -0
- package/dist/src/world-evolution-backend/types.js +1 -0
- package/dist/src/world-evolution-host-backend.d.ts +51 -0
- package/dist/src/world-evolution-host-backend.js +97 -0
- package/dist/src/world-evolution.d.ts +201 -0
- package/dist/src/world-evolution.js +543 -0
- package/dist/src/world-factory.js +2 -1
- package/dist/tools/pack-cli.js +55 -2
- package/docs/bridge-contract.md +140 -332
- package/docs/completion-evidence-dashboard.md +63 -0
- package/docs/host-contract.md +86 -310
- package/docs/host-world-evolution-schema.md +155 -0
- package/docs/integration-primer.md +29 -22
- package/docs/migration-monolith-to-modular.md +102 -120
- package/docs/package-architecture.md +116 -323
- package/docs/plugins/README.md +42 -0
- package/docs/plugins/hooks.md +22 -0
- package/docs/plugins/publishing.md +29 -0
- package/docs/plugins/security-model.md +273 -0
- package/docs/project-overview.md +30 -30
- package/docs/release-dashboard.md +47 -0
- package/docs/session-api.md +127 -0
- package/docs/stable-api-manifest.json +52 -0
- package/docs/trust-dashboard.md +37 -0
- package/docs/versioning.md +256 -245
- package/docs/wire-protocol.md +140 -209
- package/docs/world-evolution-backend.md +200 -0
- package/docs/world-evolution-engine.md +150 -0
- package/docs/world-evolution-host-backend.md +70 -0
- package/docs/world-evolution-orchestration.md +248 -0
- package/package.json +125 -6
- package/schema/content-pack.schema.json +85 -0
- package/schema/plugin.schema.json +57 -0
- package/schema/world-evolution-input.schema.json +196 -0
package/dist/src/debug.d.ts
CHANGED
|
@@ -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
|
+
}
|
package/dist/src/dialogue.d.ts
CHANGED
|
@@ -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
|
*
|
package/dist/src/dialogue.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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) {
|
package/dist/src/economy.d.ts
CHANGED
|
@@ -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
|
|
122
|
+
* Convert armour state to condition fraction.
|
|
69
123
|
*
|
|
70
124
|
* ```
|
|
71
|
-
*
|
|
125
|
+
* condition = resistRemaining_J / resist_J
|
|
72
126
|
* ```
|
|
73
|
-
*
|
|
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
|
*/
|