@its-not-rocket-science/ananke 0.1.1 → 0.1.3
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 +27 -0
- package/dist/src/index.d.ts +3 -26
- package/dist/src/index.js +16 -42
- package/dist/src/scenario.d.ts +37 -0
- package/dist/src/scenario.js +109 -0
- package/dist/src/world-factory.d.ts +33 -0
- package/dist/src/world-factory.js +132 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,33 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## [0.1.3] — 2026-03-20
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- `src/index.ts` (CE-4) now exports only the Tier 1 stable surface defined in `STABLE_API.md`.
|
|
18
|
+
Tier 2 (experimental) and Tier 3 (internal) exports have been removed from the root barrel
|
|
19
|
+
and are accessible via direct module paths (e.g. `dist/src/sim/aging.js`).
|
|
20
|
+
- `createWorld`, `loadScenario`, `validateScenario`, `ARCHETYPE_MAP`, `ITEM_MAP` promoted to
|
|
21
|
+
Tier 1 (were incorrectly placed under Tier 3 in 0.1.2).
|
|
22
|
+
- `describeCharacter`, `formatCharacterSheet`, `formatOneLine` added to root barrel (were
|
|
23
|
+
listed as Tier 1 in `STABLE_API.md` but missing from the 0.1.2 export).
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## [0.1.2] — 2026-03-19
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- `createWorld(seed, entities)` — Tier-1 convenience factory; builds a `WorldState` from
|
|
32
|
+
`EntitySpec[]` (archetype, weapon, armour string IDs) without manual entity construction
|
|
33
|
+
- `loadScenario(json)` / `validateScenario(json)` — JSON-driven world creation for
|
|
34
|
+
non-TypeScript consumers (Godot GDScript, Unity C#, scenario files)
|
|
35
|
+
- `ARCHETYPE_MAP` — `ReadonlyMap` of all 21 built-in archetypes (7 base + 14 species)
|
|
36
|
+
- `ITEM_MAP` — `ReadonlyMap` of all historical and starter weapons/armour
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
13
40
|
## [0.1.1] — 2026-03-19
|
|
14
41
|
|
|
15
42
|
### Documentation
|
package/dist/src/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./types.js";
|
|
|
3
3
|
export * from "./archetypes.js";
|
|
4
4
|
export * from "./generate.js";
|
|
5
5
|
export * from "./equipment.js";
|
|
6
|
+
export * from "./describe.js";
|
|
6
7
|
export * from "./sim/vec3.js";
|
|
7
8
|
export * from "./sim/condition.js";
|
|
8
9
|
export * from "./sim/injury.js";
|
|
@@ -14,29 +15,5 @@ export * from "./sim/world.js";
|
|
|
14
15
|
export * from "./model3d.js";
|
|
15
16
|
export * from "./replay.js";
|
|
16
17
|
export * from "./bridge/index.js";
|
|
17
|
-
export * from "./
|
|
18
|
-
export * from "./
|
|
19
|
-
export * from "./derive.js";
|
|
20
|
-
export * from "./sim/intent.js";
|
|
21
|
-
export * from "./sim/action.js";
|
|
22
|
-
export * from "./sim/combat.js";
|
|
23
|
-
export * from "./quest.js";
|
|
24
|
-
export * from "./quest-generators.js";
|
|
25
|
-
export * from "./relationships.js";
|
|
26
|
-
export * from "./relationships-effects.js";
|
|
27
|
-
export * from "./inventory.js";
|
|
28
|
-
export * from "./item-durability.js";
|
|
29
|
-
export * from "./settlement.js";
|
|
30
|
-
export * from "./settlement-services.js";
|
|
31
|
-
export * from "./chronicle.js";
|
|
32
|
-
export * from "./story-arcs.js";
|
|
33
|
-
export * from "./narrative-render.js";
|
|
34
|
-
export * from "./world-generation.js";
|
|
35
|
-
export * from "./sim/trace.js";
|
|
36
|
-
export * from "./rng.js";
|
|
37
|
-
export * from "./dist.js";
|
|
38
|
-
export * from "./lod.js";
|
|
39
|
-
export * from "./sim/impairment.js";
|
|
40
|
-
export * from "./sim/indexing.js";
|
|
41
|
-
export * from "./sim/tuning.js";
|
|
42
|
-
export * from "./sim/testing.js";
|
|
18
|
+
export * from "./world-factory.js";
|
|
19
|
+
export * from "./scenario.js";
|
package/dist/src/index.js
CHANGED
|
@@ -1,54 +1,28 @@
|
|
|
1
1
|
// ── Tier 1 — Stable host API ─────────────────────────────────────────────────
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
2
|
+
// This is the only import path companion projects and hosts should use:
|
|
3
|
+
// import { stepWorld, createWorld, q, SCALE } from "@its-not-rocket-science/ananke"
|
|
4
|
+
//
|
|
5
|
+
// Breaking changes to any export here require a major semver bump (x.0.0) and
|
|
6
|
+
// a migration guide in CHANGELOG.md. See STABLE_API.md for the full contract.
|
|
7
|
+
//
|
|
8
|
+
// Tier 2 (experimental) and Tier 3 (internal) exports are accessible via direct
|
|
9
|
+
// module imports, e.g. import { stepAging } from ".../dist/src/sim/aging.js"
|
|
10
|
+
export * from "./units.js"; // q(), SCALE, qMul, qDiv, clampQ, mulDiv, to, from, sqrtQ
|
|
6
11
|
export * from "./types.js"; // IndividualAttributes, core scalar types
|
|
7
12
|
export * from "./archetypes.js"; // Archetype, BodyPlan, built-in species presets
|
|
8
13
|
export * from "./generate.js"; // generateIndividual()
|
|
9
14
|
export * from "./equipment.js"; // WEAPONS database, EquipmentCatalogue
|
|
10
|
-
export * from "./
|
|
15
|
+
export * from "./describe.js"; // describeCharacter(), formatCharacterSheet(), formatOneLine()
|
|
16
|
+
export * from "./sim/vec3.js"; // Vec3, lerpVec3, addVec3
|
|
11
17
|
export * from "./sim/condition.js"; // ConditionSnapshot, condition constants
|
|
12
18
|
export * from "./sim/injury.js"; // InjuryRegion, BodyRegion, injury constants
|
|
13
19
|
export * from "./sim/entity.js"; // Entity (stable fields: id, pos, mass_kg, attributes…)
|
|
14
20
|
export * from "./sim/commands.js"; // CommandMap, EntityCommand, action verbs
|
|
15
21
|
export * from "./sim/kernel.js"; // stepWorld(), applyImpactToInjury(), applyExplosion()
|
|
16
22
|
export * from "./sim/body.js"; // BodyPlan, BodySegment, humanoid / quadruped plans
|
|
17
|
-
export * from "./sim/world.js"; // WorldState, KernelContext
|
|
18
|
-
export * from "./model3d.js"; // extractRigSnapshots(), deriveAnimationHints(), RigSnapshot, AnimationHints
|
|
23
|
+
export * from "./sim/world.js"; // WorldState, KernelContext
|
|
24
|
+
export * from "./model3d.js"; // extractRigSnapshots(), deriveAnimationHints(), RigSnapshot, AnimationHints
|
|
19
25
|
export * from "./replay.js"; // ReplayRecorder, replayTo(), serializeReplay(), deserializeReplay()
|
|
20
|
-
export * from "./bridge/index.js"; // BridgeEngine, InterpolatedState, BridgeConfig
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// minor versions (0.x.0); CHANGELOG.md will document any breaking change.
|
|
24
|
-
// Reference STABLE_API.md §Tier 2 for the full export list per module.
|
|
25
|
-
export * from "./channels.js"; // damage channel constants (BLUNT, SLASH, …)
|
|
26
|
-
export * from "./traits.js"; // trait descriptors
|
|
27
|
-
export * from "./derive.js"; // derived attribute helpers
|
|
28
|
-
export * from "./sim/intent.js"; // IntentMap, buildIntent()
|
|
29
|
-
export * from "./sim/action.js"; // ActionResult, resolveAction()
|
|
30
|
-
export * from "./sim/combat.js"; // resolveHit(), resolveParry(), applyCombat()
|
|
31
|
-
export * from "./quest.js"; // Quest, QuestObjective, questStep()
|
|
32
|
-
export * from "./quest-generators.js"; // generateQuest(), generateQuestChain()
|
|
33
|
-
export * from "./relationships.js"; // RelationshipMap, updateRelationship()
|
|
34
|
-
export * from "./relationships-effects.js"; // applyRelationshipEffect()
|
|
35
|
-
export * from "./inventory.js"; // Inventory, equipItem(), addItemToInventory()
|
|
36
|
-
export * from "./item-durability.js"; // durability helpers, resolveRepair()
|
|
37
|
-
export * from "./settlement.js"; // Settlement, stepSettlement()
|
|
38
|
-
export * from "./settlement-services.js"; // service resolution helpers
|
|
39
|
-
export * from "./chronicle.js"; // ChronicleEntry, addChronicleEntry()
|
|
40
|
-
export * from "./story-arcs.js"; // StoryArc, detectArcs()
|
|
41
|
-
export * from "./narrative-render.js"; // renderEntry(), renderChronicle(), generateNarrative()
|
|
42
|
-
export * from "./world-generation.js"; // WorldTemplate, generateWorld()
|
|
43
|
-
export * from "./sim/trace.js"; // SimTrace, traceStep() — debugging / profiling
|
|
44
|
-
// ── Tier 3 — Internal / kernel API ───────────────────────────────────────────
|
|
45
|
-
// Exported for power users and diagnostic tooling. Not stability-guaranteed;
|
|
46
|
-
// may change at any time without a changelog entry. Prefer Tier 1/2 surfaces
|
|
47
|
-
// in production host code. See STABLE_API.md §Tier 3 for rationale.
|
|
48
|
-
export * from "./rng.js"; // makeRng(), eventSeed() — RNG internals
|
|
49
|
-
export * from "./dist.js"; // distribution primitives
|
|
50
|
-
export * from "./lod.js"; // level-of-detail helpers
|
|
51
|
-
export * from "./sim/impairment.js"; // low-level impairment accumulators
|
|
52
|
-
export * from "./sim/indexing.js"; // SpatialIndex internals
|
|
53
|
-
export * from "./sim/tuning.js"; // kernel tuning constants (may be adjusted)
|
|
54
|
-
export * from "./sim/testing.js"; // mkHumanoidEntity() and other test helpers
|
|
26
|
+
export * from "./bridge/index.js"; // BridgeEngine, InterpolatedState, BridgeConfig
|
|
27
|
+
export * from "./world-factory.js"; // createWorld(), EntitySpec, ARCHETYPE_MAP, ITEM_MAP
|
|
28
|
+
export * from "./scenario.js"; // loadScenario(), validateScenario(), AnankeScenario
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CE-3: JSON scenario loader.
|
|
3
|
+
*
|
|
4
|
+
* Provides typed AnankeScenario interface, structural validation, and
|
|
5
|
+
* a loadScenario() function that converts validated JSON into a WorldState.
|
|
6
|
+
*/
|
|
7
|
+
import type { WorldState } from "./sim/world.js";
|
|
8
|
+
export interface AnankeScenarioEntity {
|
|
9
|
+
id: number;
|
|
10
|
+
teamId: number;
|
|
11
|
+
archetype: string;
|
|
12
|
+
weapon: string;
|
|
13
|
+
armour?: string;
|
|
14
|
+
x_m?: number;
|
|
15
|
+
y_m?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface AnankeScenario {
|
|
18
|
+
$schema?: string;
|
|
19
|
+
id: string;
|
|
20
|
+
seed: number;
|
|
21
|
+
maxTicks: number;
|
|
22
|
+
tractionCoeff?: number;
|
|
23
|
+
entities: AnankeScenarioEntity[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Validate structural correctness of a JSON scenario object.
|
|
27
|
+
* Returns an array of error strings — empty array means valid.
|
|
28
|
+
* Does NOT perform simulation-level lookups (e.g. archetype/weapon existence).
|
|
29
|
+
*/
|
|
30
|
+
export declare function validateScenario(json: unknown): string[];
|
|
31
|
+
/**
|
|
32
|
+
* Parse and load a scenario from JSON, returning a WorldState ready for stepWorld().
|
|
33
|
+
*
|
|
34
|
+
* Calls validateScenario first — throws an Error with all validation messages if invalid.
|
|
35
|
+
* Maps AnankeScenarioEntity.id as the entity seed.
|
|
36
|
+
*/
|
|
37
|
+
export declare function loadScenario(json: unknown): WorldState;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CE-3: JSON scenario loader.
|
|
3
|
+
*
|
|
4
|
+
* Provides typed AnankeScenario interface, structural validation, and
|
|
5
|
+
* a loadScenario() function that converts validated JSON into a WorldState.
|
|
6
|
+
*/
|
|
7
|
+
import { createWorld } from "./world-factory.js";
|
|
8
|
+
// ── Validation ────────────────────────────────────────────────────────────────
|
|
9
|
+
/**
|
|
10
|
+
* Validate structural correctness of a JSON scenario object.
|
|
11
|
+
* Returns an array of error strings — empty array means valid.
|
|
12
|
+
* Does NOT perform simulation-level lookups (e.g. archetype/weapon existence).
|
|
13
|
+
*/
|
|
14
|
+
export function validateScenario(json) {
|
|
15
|
+
const errors = [];
|
|
16
|
+
// Must be a plain object
|
|
17
|
+
if (json === null || typeof json !== "object" || Array.isArray(json)) {
|
|
18
|
+
errors.push("scenario must be a plain object");
|
|
19
|
+
return errors; // can't continue checking fields
|
|
20
|
+
}
|
|
21
|
+
const obj = json;
|
|
22
|
+
// id — non-empty string
|
|
23
|
+
if (typeof obj["id"] !== "string" || obj["id"].length === 0) {
|
|
24
|
+
errors.push("scenario.id must be a non-empty string");
|
|
25
|
+
}
|
|
26
|
+
// seed — positive integer
|
|
27
|
+
if (typeof obj["seed"] !== "number" ||
|
|
28
|
+
!Number.isInteger(obj["seed"]) ||
|
|
29
|
+
obj["seed"] <= 0) {
|
|
30
|
+
errors.push("scenario.seed must be a positive integer");
|
|
31
|
+
}
|
|
32
|
+
// maxTicks — positive integer
|
|
33
|
+
if (typeof obj["maxTicks"] !== "number" ||
|
|
34
|
+
!Number.isInteger(obj["maxTicks"]) ||
|
|
35
|
+
obj["maxTicks"] <= 0) {
|
|
36
|
+
errors.push("scenario.maxTicks must be a positive integer");
|
|
37
|
+
}
|
|
38
|
+
// entities — non-empty array
|
|
39
|
+
if (!Array.isArray(obj["entities"])) {
|
|
40
|
+
errors.push("scenario.entities must be an array");
|
|
41
|
+
return errors; // can't check entity elements
|
|
42
|
+
}
|
|
43
|
+
const rawEntities = obj["entities"];
|
|
44
|
+
if (rawEntities.length === 0) {
|
|
45
|
+
errors.push("scenario.entities must not be empty");
|
|
46
|
+
return errors;
|
|
47
|
+
}
|
|
48
|
+
// Validate each entity element
|
|
49
|
+
const seenIds = new Set();
|
|
50
|
+
for (let i = 0; i < rawEntities.length; i++) {
|
|
51
|
+
const ent = rawEntities[i];
|
|
52
|
+
if (ent === null || typeof ent !== "object" || Array.isArray(ent)) {
|
|
53
|
+
errors.push(`scenario.entities[${i}] must be a plain object`);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const e = ent;
|
|
57
|
+
if (typeof e["id"] !== "number") {
|
|
58
|
+
errors.push(`scenario.entities[${i}].id must be a number`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const eid = e["id"];
|
|
62
|
+
if (seenIds.has(eid)) {
|
|
63
|
+
errors.push(`scenario.entities[${i}].id ${eid} is a duplicate`);
|
|
64
|
+
}
|
|
65
|
+
seenIds.add(eid);
|
|
66
|
+
}
|
|
67
|
+
if (typeof e["teamId"] !== "number") {
|
|
68
|
+
errors.push(`scenario.entities[${i}].teamId must be a number`);
|
|
69
|
+
}
|
|
70
|
+
if (typeof e["archetype"] !== "string") {
|
|
71
|
+
errors.push(`scenario.entities[${i}].archetype must be a string`);
|
|
72
|
+
}
|
|
73
|
+
if (typeof e["weapon"] !== "string") {
|
|
74
|
+
errors.push(`scenario.entities[${i}].weapon must be a string`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return errors;
|
|
78
|
+
}
|
|
79
|
+
// ── Loader ────────────────────────────────────────────────────────────────────
|
|
80
|
+
/**
|
|
81
|
+
* Parse and load a scenario from JSON, returning a WorldState ready for stepWorld().
|
|
82
|
+
*
|
|
83
|
+
* Calls validateScenario first — throws an Error with all validation messages if invalid.
|
|
84
|
+
* Maps AnankeScenarioEntity.id as the entity seed.
|
|
85
|
+
*/
|
|
86
|
+
export function loadScenario(json) {
|
|
87
|
+
const errors = validateScenario(json);
|
|
88
|
+
if (errors.length > 0) {
|
|
89
|
+
throw new Error(`loadScenario: invalid scenario:\n ${errors.join("\n ")}`);
|
|
90
|
+
}
|
|
91
|
+
const scenario = json;
|
|
92
|
+
const specs = scenario.entities.map(ent => {
|
|
93
|
+
const spec = {
|
|
94
|
+
id: ent.id,
|
|
95
|
+
teamId: ent.teamId,
|
|
96
|
+
seed: ent.id, // use entity id as deterministic seed
|
|
97
|
+
archetype: ent.archetype,
|
|
98
|
+
weaponId: ent.weapon,
|
|
99
|
+
};
|
|
100
|
+
if (ent.armour !== undefined)
|
|
101
|
+
spec.armourId = ent.armour;
|
|
102
|
+
if (ent.x_m !== undefined)
|
|
103
|
+
spec.x_m = ent.x_m;
|
|
104
|
+
if (ent.y_m !== undefined)
|
|
105
|
+
spec.y_m = ent.y_m;
|
|
106
|
+
return spec;
|
|
107
|
+
});
|
|
108
|
+
return createWorld(scenario.seed, specs);
|
|
109
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CE-2: createWorld convenience factory.
|
|
3
|
+
*
|
|
4
|
+
* Builds a deterministic WorldState from a simple declarative entity spec.
|
|
5
|
+
* No Math.random() — all randomness flows through generateIndividual(spec.seed, archetype).
|
|
6
|
+
* All position coordinates are fixed-point (SCALE.m multiplier + Math.round).
|
|
7
|
+
*/
|
|
8
|
+
import type { Archetype } from "./archetypes.js";
|
|
9
|
+
import type { Item } from "./equipment.js";
|
|
10
|
+
import type { WorldState } from "./sim/world.js";
|
|
11
|
+
/** Map of string keys to Archetype objects for use with createWorld(). */
|
|
12
|
+
export declare const ARCHETYPE_MAP: ReadonlyMap<string, Archetype>;
|
|
13
|
+
/** Map of item id → Item for weapons and armour usable with createWorld(). */
|
|
14
|
+
export declare const ITEM_MAP: ReadonlyMap<string, Item>;
|
|
15
|
+
export interface EntitySpec {
|
|
16
|
+
id: number;
|
|
17
|
+
teamId: number;
|
|
18
|
+
seed: number;
|
|
19
|
+
archetype: string;
|
|
20
|
+
weaponId: string;
|
|
21
|
+
armourId?: string;
|
|
22
|
+
x_m?: number;
|
|
23
|
+
y_m?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Build a deterministic WorldState from a declarative entity spec list.
|
|
27
|
+
*
|
|
28
|
+
* - Uses spec.seed for generateIndividual() — no Math.random().
|
|
29
|
+
* - Position coordinates are fixed-point: Math.round(metres * SCALE.m).
|
|
30
|
+
* - Throws on unknown archetype, weaponId, or armourId.
|
|
31
|
+
* - Throws on duplicate entity ids.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createWorld(seed: number, entities: EntitySpec[]): WorldState;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CE-2: createWorld convenience factory.
|
|
3
|
+
*
|
|
4
|
+
* Builds a deterministic WorldState from a simple declarative entity spec.
|
|
5
|
+
* No Math.random() — all randomness flows through generateIndividual(spec.seed, archetype).
|
|
6
|
+
* All position coordinates are fixed-point (SCALE.m multiplier + Math.round).
|
|
7
|
+
*/
|
|
8
|
+
import { q, SCALE } from "./units.js";
|
|
9
|
+
import { generateIndividual } from "./generate.js";
|
|
10
|
+
import { HUMAN_BASE, AMATEUR_BOXER, PRO_BOXER, GRECO_WRESTLER, KNIGHT_INFANTRY, LARGE_PACIFIC_OCTOPUS, SERVICE_ROBOT, } from "./archetypes.js";
|
|
11
|
+
import { ELF_SPECIES, DWARF_SPECIES, HALFLING_SPECIES, ORC_SPECIES, OGRE_SPECIES, GOBLIN_SPECIES, TROLL_SPECIES, VULCAN_SPECIES, KLINGON_SPECIES, ROMULAN_SPECIES, DRAGON_SPECIES, CENTAUR_SPECIES, SATYR_SPECIES, HEECHEE_SPECIES, } from "./species.js";
|
|
12
|
+
import { ALL_HISTORICAL_MELEE, ALL_HISTORICAL_RANGED } from "./weapons.js";
|
|
13
|
+
import { STARTER_WEAPONS, STARTER_ARMOUR, STARTER_ARMOUR_11C } from "./equipment.js";
|
|
14
|
+
import { v3 } from "./sim/vec3.js";
|
|
15
|
+
import { defaultIntent } from "./sim/intent.js";
|
|
16
|
+
import { defaultAction } from "./sim/action.js";
|
|
17
|
+
import { defaultCondition } from "./sim/condition.js";
|
|
18
|
+
import { defaultInjury } from "./sim/injury.js";
|
|
19
|
+
// ── Static archetype map ──────────────────────────────────────────────────────
|
|
20
|
+
/** Map of string keys to Archetype objects for use with createWorld(). */
|
|
21
|
+
export const ARCHETYPE_MAP = new Map([
|
|
22
|
+
// Direct archetypes from archetypes.ts
|
|
23
|
+
["HUMAN_BASE", HUMAN_BASE],
|
|
24
|
+
["AMATEUR_BOXER", AMATEUR_BOXER],
|
|
25
|
+
["PRO_BOXER", PRO_BOXER],
|
|
26
|
+
["GRECO_WRESTLER", GRECO_WRESTLER],
|
|
27
|
+
["KNIGHT_INFANTRY", KNIGHT_INFANTRY],
|
|
28
|
+
["LARGE_PACIFIC_OCTOPUS", LARGE_PACIFIC_OCTOPUS],
|
|
29
|
+
["SERVICE_ROBOT", SERVICE_ROBOT],
|
|
30
|
+
// Species archetypes from species.ts
|
|
31
|
+
["ELF", ELF_SPECIES.archetype],
|
|
32
|
+
["DWARF", DWARF_SPECIES.archetype],
|
|
33
|
+
["HALFLING", HALFLING_SPECIES.archetype],
|
|
34
|
+
["ORC", ORC_SPECIES.archetype],
|
|
35
|
+
["OGRE", OGRE_SPECIES.archetype],
|
|
36
|
+
["GOBLIN", GOBLIN_SPECIES.archetype],
|
|
37
|
+
["TROLL", TROLL_SPECIES.archetype],
|
|
38
|
+
["VULCAN", VULCAN_SPECIES.archetype],
|
|
39
|
+
["KLINGON", KLINGON_SPECIES.archetype],
|
|
40
|
+
["ROMULAN", ROMULAN_SPECIES.archetype],
|
|
41
|
+
["DRAGON", DRAGON_SPECIES.archetype],
|
|
42
|
+
["CENTAUR", CENTAUR_SPECIES.archetype],
|
|
43
|
+
["SATYR", SATYR_SPECIES.archetype],
|
|
44
|
+
["HEECHEE", HEECHEE_SPECIES.archetype],
|
|
45
|
+
]);
|
|
46
|
+
// ── Static item map ───────────────────────────────────────────────────────────
|
|
47
|
+
function buildItemMap() {
|
|
48
|
+
const map = new Map();
|
|
49
|
+
const allItems = [
|
|
50
|
+
...ALL_HISTORICAL_MELEE,
|
|
51
|
+
...ALL_HISTORICAL_RANGED,
|
|
52
|
+
...STARTER_WEAPONS,
|
|
53
|
+
...STARTER_ARMOUR,
|
|
54
|
+
...STARTER_ARMOUR_11C,
|
|
55
|
+
];
|
|
56
|
+
for (const item of allItems) {
|
|
57
|
+
map.set(item.id, item);
|
|
58
|
+
}
|
|
59
|
+
return map;
|
|
60
|
+
}
|
|
61
|
+
/** Map of item id → Item for weapons and armour usable with createWorld(). */
|
|
62
|
+
export const ITEM_MAP = buildItemMap();
|
|
63
|
+
// ── createWorld ───────────────────────────────────────────────────────────────
|
|
64
|
+
/**
|
|
65
|
+
* Build a deterministic WorldState from a declarative entity spec list.
|
|
66
|
+
*
|
|
67
|
+
* - Uses spec.seed for generateIndividual() — no Math.random().
|
|
68
|
+
* - Position coordinates are fixed-point: Math.round(metres * SCALE.m).
|
|
69
|
+
* - Throws on unknown archetype, weaponId, or armourId.
|
|
70
|
+
* - Throws on duplicate entity ids.
|
|
71
|
+
*/
|
|
72
|
+
export function createWorld(seed, entities) {
|
|
73
|
+
const built = [];
|
|
74
|
+
for (const spec of entities) {
|
|
75
|
+
// ── Archetype lookup ──────────────────────────────────────────────────────
|
|
76
|
+
const archetype = ARCHETYPE_MAP.get(spec.archetype);
|
|
77
|
+
if (archetype === undefined) {
|
|
78
|
+
throw new Error(`createWorld: unknown archetype "${spec.archetype}". ` +
|
|
79
|
+
`Valid keys: ${[...ARCHETYPE_MAP.keys()].join(", ")}`);
|
|
80
|
+
}
|
|
81
|
+
// ── Weapon lookup ─────────────────────────────────────────────────────────
|
|
82
|
+
const weapon = ITEM_MAP.get(spec.weaponId);
|
|
83
|
+
if (weapon === undefined) {
|
|
84
|
+
throw new Error(`createWorld: unknown weaponId "${spec.weaponId}"`);
|
|
85
|
+
}
|
|
86
|
+
// ── Optional armour lookup ────────────────────────────────────────────────
|
|
87
|
+
let armour;
|
|
88
|
+
if (spec.armourId !== undefined) {
|
|
89
|
+
armour = ITEM_MAP.get(spec.armourId);
|
|
90
|
+
if (armour === undefined) {
|
|
91
|
+
throw new Error(`createWorld: unknown armourId "${spec.armourId}"`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// ── Generate individual attributes ────────────────────────────────────────
|
|
95
|
+
const attrs = generateIndividual(spec.seed, archetype);
|
|
96
|
+
// ── Default position ──────────────────────────────────────────────────────
|
|
97
|
+
// Team 1 defaults to x=0; all others default to x=0.6m.
|
|
98
|
+
const defaultX = spec.teamId === 1 ? 0 : 0.6;
|
|
99
|
+
const x_fixed = Math.round((spec.x_m ?? defaultX) * SCALE.m);
|
|
100
|
+
const y_fixed = Math.round((spec.y_m ?? 0) * SCALE.m);
|
|
101
|
+
// ── Build loadout ─────────────────────────────────────────────────────────
|
|
102
|
+
const items = [weapon];
|
|
103
|
+
if (armour !== undefined)
|
|
104
|
+
items.push(armour);
|
|
105
|
+
// ── Assemble entity ───────────────────────────────────────────────────────
|
|
106
|
+
const entity = {
|
|
107
|
+
id: spec.id,
|
|
108
|
+
teamId: spec.teamId,
|
|
109
|
+
attributes: attrs,
|
|
110
|
+
energy: { reserveEnergy_J: attrs.performance.reserveEnergy_J, fatigue: q(0) },
|
|
111
|
+
loadout: { items },
|
|
112
|
+
traits: [],
|
|
113
|
+
position_m: v3(x_fixed, y_fixed, 0),
|
|
114
|
+
velocity_mps: v3(0, 0, 0),
|
|
115
|
+
intent: defaultIntent(),
|
|
116
|
+
action: defaultAction(),
|
|
117
|
+
condition: defaultCondition(),
|
|
118
|
+
injury: defaultInjury(),
|
|
119
|
+
grapple: { holdingTargetId: 0, heldByIds: [], gripQ: q(0), position: "standing" },
|
|
120
|
+
};
|
|
121
|
+
built.push(entity);
|
|
122
|
+
}
|
|
123
|
+
// ── Sort by id ────────────────────────────────────────────────────────────
|
|
124
|
+
built.sort((a, b) => a.id - b.id);
|
|
125
|
+
// ── Duplicate id check ────────────────────────────────────────────────────
|
|
126
|
+
const ids = built.map(e => e.id);
|
|
127
|
+
const dupes = ids.filter((id, i) => ids.indexOf(id) !== i);
|
|
128
|
+
if (dupes.length > 0) {
|
|
129
|
+
throw new Error(`createWorld: duplicate entity IDs detected: ${[...new Set(dupes)].join(", ")}`);
|
|
130
|
+
}
|
|
131
|
+
return { tick: 0, seed, entities: built };
|
|
132
|
+
}
|