@its-not-rocket-science/ananke 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +135 -0
- package/LICENSE +21 -0
- package/README.md +2199 -0
- package/STABLE_API.md +266 -0
- package/dist/src/anatomy/anatomy-compiler.d.ts +14 -0
- package/dist/src/anatomy/anatomy-compiler.js +277 -0
- package/dist/src/anatomy/anatomy-contracts.d.ts +94 -0
- package/dist/src/anatomy/anatomy-contracts.js +1 -0
- package/dist/src/anatomy/anatomy-helpers.d.ts +82 -0
- package/dist/src/anatomy/anatomy-helpers.js +233 -0
- package/dist/src/anatomy/anatomy-schema.d.ts +28 -0
- package/dist/src/anatomy/anatomy-schema.js +388 -0
- package/dist/src/anatomy/index.d.ts +4 -0
- package/dist/src/anatomy/index.js +4 -0
- package/dist/src/archetypes.d.ts +87 -0
- package/dist/src/archetypes.js +285 -0
- package/dist/src/arena.d.ts +173 -0
- package/dist/src/arena.js +695 -0
- package/dist/src/bridge/bridge-engine.d.ts +46 -0
- package/dist/src/bridge/bridge-engine.js +252 -0
- package/dist/src/bridge/index.d.ts +4 -0
- package/dist/src/bridge/index.js +5 -0
- package/dist/src/bridge/interpolation.d.ts +64 -0
- package/dist/src/bridge/interpolation.js +130 -0
- package/dist/src/bridge/mapping.d.ts +33 -0
- package/dist/src/bridge/mapping.js +54 -0
- package/dist/src/bridge/types.d.ts +94 -0
- package/dist/src/bridge/types.js +2 -0
- package/dist/src/campaign.d.ts +141 -0
- package/dist/src/campaign.js +235 -0
- package/dist/src/channels.d.ts +15 -0
- package/dist/src/channels.js +20 -0
- package/dist/src/chronicle.d.ts +124 -0
- package/dist/src/chronicle.js +232 -0
- package/dist/src/collective-activities.d.ts +154 -0
- package/dist/src/collective-activities.js +247 -0
- package/dist/src/competence/acoustic.d.ts +101 -0
- package/dist/src/competence/acoustic.js +242 -0
- package/dist/src/competence/catalogue.d.ts +30 -0
- package/dist/src/competence/catalogue.js +241 -0
- package/dist/src/competence/crafting.d.ts +35 -0
- package/dist/src/competence/crafting.js +88 -0
- package/dist/src/competence/engineering.d.ts +53 -0
- package/dist/src/competence/engineering.js +108 -0
- package/dist/src/competence/framework.d.ts +68 -0
- package/dist/src/competence/framework.js +694 -0
- package/dist/src/competence/index.d.ts +12 -0
- package/dist/src/competence/index.js +13 -0
- package/dist/src/competence/interspecies.d.ts +81 -0
- package/dist/src/competence/interspecies.js +108 -0
- package/dist/src/competence/language.d.ts +79 -0
- package/dist/src/competence/language.js +115 -0
- package/dist/src/competence/naturalist.d.ts +97 -0
- package/dist/src/competence/naturalist.js +187 -0
- package/dist/src/competence/navigation.d.ts +24 -0
- package/dist/src/competence/navigation.js +48 -0
- package/dist/src/competence/performance.d.ts +125 -0
- package/dist/src/competence/performance.js +210 -0
- package/dist/src/competence/teaching.d.ts +64 -0
- package/dist/src/competence/teaching.js +121 -0
- package/dist/src/competence/willpower.d.ts +74 -0
- package/dist/src/competence/willpower.js +114 -0
- package/dist/src/crafting/index.d.ts +55 -0
- package/dist/src/crafting/index.js +229 -0
- package/dist/src/crafting/manufacturing.d.ts +83 -0
- package/dist/src/crafting/manufacturing.js +165 -0
- package/dist/src/crafting/materials.d.ts +53 -0
- package/dist/src/crafting/materials.js +120 -0
- package/dist/src/crafting/recipes.d.ts +75 -0
- package/dist/src/crafting/recipes.js +233 -0
- package/dist/src/crafting/workshops.d.ts +61 -0
- package/dist/src/crafting/workshops.js +170 -0
- package/dist/src/debug.d.ts +86 -0
- package/dist/src/debug.js +76 -0
- package/dist/src/derive.d.ts +21 -0
- package/dist/src/derive.js +88 -0
- package/dist/src/describe.d.ts +29 -0
- package/dist/src/describe.js +276 -0
- package/dist/src/dialogue.d.ts +122 -0
- package/dist/src/dialogue.js +266 -0
- package/dist/src/dist.d.ts +20 -0
- package/dist/src/dist.js +39 -0
- package/dist/src/downtime.d.ts +89 -0
- package/dist/src/downtime.js +391 -0
- package/dist/src/economy.d.ts +116 -0
- package/dist/src/economy.js +182 -0
- package/dist/src/emotional-contagion.d.ts +142 -0
- package/dist/src/emotional-contagion.js +274 -0
- package/dist/src/equipment.d.ts +206 -0
- package/dist/src/equipment.js +598 -0
- package/dist/src/faction.d.ts +102 -0
- package/dist/src/faction.js +237 -0
- package/dist/src/generate.d.ts +35 -0
- package/dist/src/generate.js +166 -0
- package/dist/src/index.d.ts +42 -0
- package/dist/src/index.js +54 -0
- package/dist/src/inheritance.d.ts +69 -0
- package/dist/src/inheritance.js +136 -0
- package/dist/src/inventory.d.ts +194 -0
- package/dist/src/inventory.js +637 -0
- package/dist/src/item-durability.d.ts +69 -0
- package/dist/src/item-durability.js +308 -0
- package/dist/src/legend.d.ts +97 -0
- package/dist/src/legend.js +269 -0
- package/dist/src/lod.d.ts +9 -0
- package/dist/src/lod.js +84 -0
- package/dist/src/metrics.d.ts +51 -0
- package/dist/src/metrics.js +91 -0
- package/dist/src/model3d.d.ts +138 -0
- package/dist/src/model3d.js +214 -0
- package/dist/src/mythology.d.ts +101 -0
- package/dist/src/mythology.js +308 -0
- package/dist/src/narrative-render.d.ts +42 -0
- package/dist/src/narrative-render.js +194 -0
- package/dist/src/narrative-stress.d.ts +123 -0
- package/dist/src/narrative-stress.js +183 -0
- package/dist/src/narrative.d.ts +44 -0
- package/dist/src/narrative.js +257 -0
- package/dist/src/party.d.ts +70 -0
- package/dist/src/party.js +226 -0
- package/dist/src/polity.d.ts +262 -0
- package/dist/src/polity.js +398 -0
- package/dist/src/presets.d.ts +42 -0
- package/dist/src/presets.js +170 -0
- package/dist/src/progression.d.ts +170 -0
- package/dist/src/progression.js +256 -0
- package/dist/src/quest-generators.d.ts +76 -0
- package/dist/src/quest-generators.js +534 -0
- package/dist/src/quest.d.ts +239 -0
- package/dist/src/quest.js +520 -0
- package/dist/src/relationships-effects.d.ts +75 -0
- package/dist/src/relationships-effects.js +219 -0
- package/dist/src/relationships.d.ts +104 -0
- package/dist/src/relationships.js +347 -0
- package/dist/src/replay.d.ts +47 -0
- package/dist/src/replay.js +82 -0
- package/dist/src/rng.d.ts +9 -0
- package/dist/src/rng.js +37 -0
- package/dist/src/settlement-services.d.ts +67 -0
- package/dist/src/settlement-services.js +267 -0
- package/dist/src/settlement.d.ts +143 -0
- package/dist/src/settlement.js +419 -0
- package/dist/src/sim/action.d.ts +28 -0
- package/dist/src/sim/action.js +12 -0
- package/dist/src/sim/aging.d.ts +95 -0
- package/dist/src/sim/aging.js +243 -0
- package/dist/src/sim/ai/decide.d.ts +10 -0
- package/dist/src/sim/ai/decide.js +267 -0
- package/dist/src/sim/ai/perception.d.ts +12 -0
- package/dist/src/sim/ai/perception.js +54 -0
- package/dist/src/sim/ai/personality.d.ts +54 -0
- package/dist/src/sim/ai/personality.js +202 -0
- package/dist/src/sim/ai/presets.d.ts +2 -0
- package/dist/src/sim/ai/presets.js +28 -0
- package/dist/src/sim/ai/system.d.ts +6 -0
- package/dist/src/sim/ai/system.js +13 -0
- package/dist/src/sim/ai/targeting.d.ts +8 -0
- package/dist/src/sim/ai/targeting.js +42 -0
- package/dist/src/sim/ai/types.d.ts +14 -0
- package/dist/src/sim/ai/types.js +1 -0
- package/dist/src/sim/body.d.ts +9 -0
- package/dist/src/sim/body.js +32 -0
- package/dist/src/sim/bodyplan.d.ts +161 -0
- package/dist/src/sim/bodyplan.js +677 -0
- package/dist/src/sim/capability.d.ts +135 -0
- package/dist/src/sim/capability.js +8 -0
- package/dist/src/sim/combat.d.ts +21 -0
- package/dist/src/sim/combat.js +77 -0
- package/dist/src/sim/commandBuilders.d.ts +11 -0
- package/dist/src/sim/commandBuilders.js +39 -0
- package/dist/src/sim/commands.d.ts +71 -0
- package/dist/src/sim/commands.js +8 -0
- package/dist/src/sim/condition.d.ts +35 -0
- package/dist/src/sim/condition.js +21 -0
- package/dist/src/sim/cone.d.ts +40 -0
- package/dist/src/sim/cone.js +44 -0
- package/dist/src/sim/context.d.ts +68 -0
- package/dist/src/sim/context.js +1 -0
- package/dist/src/sim/density.d.ts +14 -0
- package/dist/src/sim/density.js +33 -0
- package/dist/src/sim/disease.d.ts +141 -0
- package/dist/src/sim/disease.js +353 -0
- package/dist/src/sim/entity.d.ts +251 -0
- package/dist/src/sim/entity.js +19 -0
- package/dist/src/sim/events.d.ts +25 -0
- package/dist/src/sim/events.js +5 -0
- package/dist/src/sim/explosion.d.ts +40 -0
- package/dist/src/sim/explosion.js +40 -0
- package/dist/src/sim/formation-unit.d.ts +138 -0
- package/dist/src/sim/formation-unit.js +197 -0
- package/dist/src/sim/formation.d.ts +12 -0
- package/dist/src/sim/formation.js +54 -0
- package/dist/src/sim/frontage.d.ts +30 -0
- package/dist/src/sim/frontage.js +84 -0
- package/dist/src/sim/grapple.d.ts +100 -0
- package/dist/src/sim/grapple.js +480 -0
- package/dist/src/sim/hazard.d.ts +104 -0
- package/dist/src/sim/hazard.js +201 -0
- package/dist/src/sim/hydrostatic.d.ts +58 -0
- package/dist/src/sim/hydrostatic.js +117 -0
- package/dist/src/sim/impairment.d.ts +20 -0
- package/dist/src/sim/impairment.js +162 -0
- package/dist/src/sim/indexing.d.ts +7 -0
- package/dist/src/sim/indexing.js +7 -0
- package/dist/src/sim/injury.d.ts +54 -0
- package/dist/src/sim/injury.js +66 -0
- package/dist/src/sim/intent.d.ts +26 -0
- package/dist/src/sim/intent.js +7 -0
- package/dist/src/sim/kernel.d.ts +45 -0
- package/dist/src/sim/kernel.js +1992 -0
- package/dist/src/sim/kinds.d.ts +64 -0
- package/dist/src/sim/kinds.js +56 -0
- package/dist/src/sim/knockback.d.ts +50 -0
- package/dist/src/sim/knockback.js +82 -0
- package/dist/src/sim/limb.d.ts +48 -0
- package/dist/src/sim/limb.js +78 -0
- package/dist/src/sim/medical.d.ts +32 -0
- package/dist/src/sim/medical.js +33 -0
- package/dist/src/sim/morale.d.ts +69 -0
- package/dist/src/sim/morale.js +92 -0
- package/dist/src/sim/mount.d.ts +150 -0
- package/dist/src/sim/mount.js +225 -0
- package/dist/src/sim/nutrition.d.ts +74 -0
- package/dist/src/sim/nutrition.js +168 -0
- package/dist/src/sim/occlusion.d.ts +8 -0
- package/dist/src/sim/occlusion.js +71 -0
- package/dist/src/sim/push.d.ts +11 -0
- package/dist/src/sim/push.js +79 -0
- package/dist/src/sim/ranged.d.ts +44 -0
- package/dist/src/sim/ranged.js +69 -0
- package/dist/src/sim/seeds.d.ts +3 -0
- package/dist/src/sim/seeds.js +16 -0
- package/dist/src/sim/sensory-extended.d.ts +103 -0
- package/dist/src/sim/sensory-extended.js +181 -0
- package/dist/src/sim/sensory.d.ts +38 -0
- package/dist/src/sim/sensory.js +109 -0
- package/dist/src/sim/skills.d.ts +70 -0
- package/dist/src/sim/skills.js +69 -0
- package/dist/src/sim/sleep.d.ts +107 -0
- package/dist/src/sim/sleep.js +215 -0
- package/dist/src/sim/spatial.d.ts +8 -0
- package/dist/src/sim/spatial.js +59 -0
- package/dist/src/sim/step/capability.d.ts +8 -0
- package/dist/src/sim/step/capability.js +77 -0
- package/dist/src/sim/step/concentration.d.ts +9 -0
- package/dist/src/sim/step/concentration.js +25 -0
- package/dist/src/sim/step/effects.d.ts +17 -0
- package/dist/src/sim/step/effects.js +96 -0
- package/dist/src/sim/step/energy.d.ts +3 -0
- package/dist/src/sim/step/energy.js +31 -0
- package/dist/src/sim/step/hazards.d.ts +4 -0
- package/dist/src/sim/step/hazards.js +19 -0
- package/dist/src/sim/step/injury.d.ts +10 -0
- package/dist/src/sim/step/injury.js +353 -0
- package/dist/src/sim/step/morale.d.ts +11 -0
- package/dist/src/sim/step/morale.js +130 -0
- package/dist/src/sim/step/movement.d.ts +5 -0
- package/dist/src/sim/step/movement.js +172 -0
- package/dist/src/sim/step/push.d.ts +11 -0
- package/dist/src/sim/step/push.js +79 -0
- package/dist/src/sim/step/substances.d.ts +3 -0
- package/dist/src/sim/step/substances.js +75 -0
- package/dist/src/sim/substance.d.ts +38 -0
- package/dist/src/sim/substance.js +57 -0
- package/dist/src/sim/systemic-toxicology.d.ts +109 -0
- package/dist/src/sim/systemic-toxicology.js +263 -0
- package/dist/src/sim/team.d.ts +9 -0
- package/dist/src/sim/team.js +37 -0
- package/dist/src/sim/tech.d.ts +36 -0
- package/dist/src/sim/tech.js +46 -0
- package/dist/src/sim/terrain.d.ts +121 -0
- package/dist/src/sim/terrain.js +141 -0
- package/dist/src/sim/testing.d.ts +13 -0
- package/dist/src/sim/testing.js +100 -0
- package/dist/src/sim/thermoregulation.d.ts +77 -0
- package/dist/src/sim/thermoregulation.js +161 -0
- package/dist/src/sim/tick.d.ts +3 -0
- package/dist/src/sim/tick.js +3 -0
- package/dist/src/sim/toxicology.d.ts +52 -0
- package/dist/src/sim/toxicology.js +104 -0
- package/dist/src/sim/trace.d.ts +141 -0
- package/dist/src/sim/trace.js +1 -0
- package/dist/src/sim/tuning.d.ts +16 -0
- package/dist/src/sim/tuning.js +42 -0
- package/dist/src/sim/vec3.d.ts +14 -0
- package/dist/src/sim/vec3.js +31 -0
- package/dist/src/sim/weapon_dynamics.d.ts +102 -0
- package/dist/src/sim/weapon_dynamics.js +142 -0
- package/dist/src/sim/weather.d.ts +95 -0
- package/dist/src/sim/weather.js +105 -0
- package/dist/src/sim/world.d.ts +52 -0
- package/dist/src/sim/world.js +1 -0
- package/dist/src/sim/wound-aging.d.ts +120 -0
- package/dist/src/sim/wound-aging.js +223 -0
- package/dist/src/species.d.ts +106 -0
- package/dist/src/species.js +664 -0
- package/dist/src/story-arcs.d.ts +17 -0
- package/dist/src/story-arcs.js +276 -0
- package/dist/src/tech-diffusion.d.ts +80 -0
- package/dist/src/tech-diffusion.js +185 -0
- package/dist/src/traits.d.ts +25 -0
- package/dist/src/traits.js +178 -0
- package/dist/src/types.d.ts +117 -0
- package/dist/src/types.js +1 -0
- package/dist/src/units.d.ts +41 -0
- package/dist/src/units.js +64 -0
- package/dist/src/weapons.d.ts +20 -0
- package/dist/src/weapons.js +824 -0
- package/dist/src/world-generation.d.ts +52 -0
- package/dist/src/world-generation.js +301 -0
- package/package.json +74 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { Q } from "../units.js";
|
|
2
|
+
import type { Recipe } from "./recipes.js";
|
|
3
|
+
/** Facility level tiers with associated bonuses. */
|
|
4
|
+
export type FacilityLevel = "crude" | "basic" | "advanced" | "master";
|
|
5
|
+
/** Workshop type definition. */
|
|
6
|
+
export interface WorkshopType {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
requiredFacilityLevel: FacilityLevel;
|
|
10
|
+
toolBonus_Q: Q;
|
|
11
|
+
timeReduction_Q: Q;
|
|
12
|
+
qualityBonus_Q: Q;
|
|
13
|
+
}
|
|
14
|
+
/** Workshop instance at a specific location. */
|
|
15
|
+
export interface WorkshopInstance {
|
|
16
|
+
typeId: string;
|
|
17
|
+
locationId: string;
|
|
18
|
+
facilityLevel: FacilityLevel;
|
|
19
|
+
availableTools: Map<string, Q>;
|
|
20
|
+
}
|
|
21
|
+
/** Combined workshop bonuses for a specific recipe. */
|
|
22
|
+
export interface WorkshopBonus {
|
|
23
|
+
toolBonus_Q: Q;
|
|
24
|
+
timeReduction_Q: Q;
|
|
25
|
+
qualityBonus_Q: Q;
|
|
26
|
+
}
|
|
27
|
+
/** Facility level definitions with numeric tier. */
|
|
28
|
+
export declare const FACILITY_LEVELS: Record<FacilityLevel, {
|
|
29
|
+
tier: number;
|
|
30
|
+
name: string;
|
|
31
|
+
}>;
|
|
32
|
+
export declare const WORKSHOP_TYPES: WorkshopType[];
|
|
33
|
+
/**
|
|
34
|
+
* Get combined workshop bonuses for a specific recipe.
|
|
35
|
+
* Takes into account workshop type, facility level, and available tools.
|
|
36
|
+
*/
|
|
37
|
+
export declare function getWorkshopBonus(workshop: WorkshopInstance, recipe: Recipe): WorkshopBonus;
|
|
38
|
+
/**
|
|
39
|
+
* Validate workshop requirements for a recipe.
|
|
40
|
+
* Returns list of missing tool categories and facility level insufficiency.
|
|
41
|
+
*/
|
|
42
|
+
export declare function validateWorkshopRequirements(workshop: WorkshopInstance, recipe: Recipe): {
|
|
43
|
+
missingTools: string[];
|
|
44
|
+
facilityLevelInsufficient: boolean;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Upgrade workshop facility level if resources are available.
|
|
48
|
+
* Returns success and new workshop instance (or same if failed).
|
|
49
|
+
*/
|
|
50
|
+
export declare function upgradeWorkshop(workshop: WorkshopInstance, resources: Map<string, number>, // resource itemId -> quantity consumed
|
|
51
|
+
targetLevel: FacilityLevel): {
|
|
52
|
+
success: boolean;
|
|
53
|
+
upgradedWorkshop: WorkshopInstance;
|
|
54
|
+
consumedResources: Map<string, number>;
|
|
55
|
+
};
|
|
56
|
+
/** Create a new workshop instance with default tools. */
|
|
57
|
+
export declare function createWorkshop(typeId: string, locationId: string, facilityLevel?: FacilityLevel, initialTools?: Map<string, Q>): WorkshopInstance | undefined;
|
|
58
|
+
/** Get workshop type by ID. */
|
|
59
|
+
export declare function getWorkshopTypeById(id: string): WorkshopType | undefined;
|
|
60
|
+
/** Get all workshop types that can be used at a given facility level. */
|
|
61
|
+
export declare function getWorkshopTypesForLevel(facilityLevel: FacilityLevel): WorkshopType[];
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// src/crafting/workshops.ts — Phase 61: Workshop System
|
|
2
|
+
//
|
|
3
|
+
// Workshop types with tiered bonuses, facility levels, and tool requirements.
|
|
4
|
+
// Deterministic bonuses applied to crafting resolution.
|
|
5
|
+
import { SCALE, q, clampQ } from "../units.js";
|
|
6
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
7
|
+
/** Facility level definitions with numeric tier. */
|
|
8
|
+
export const FACILITY_LEVELS = {
|
|
9
|
+
crude: { tier: 1, name: "Crude" },
|
|
10
|
+
basic: { tier: 2, name: "Basic" },
|
|
11
|
+
advanced: { tier: 3, name: "Advanced" },
|
|
12
|
+
master: { tier: 4, name: "Master" },
|
|
13
|
+
};
|
|
14
|
+
/** Default tool quality for missing tools. */
|
|
15
|
+
const DEFAULT_TOOL_QUALITY_Q = q(0.30);
|
|
16
|
+
// ── Workshop Type Catalogue ───────────────────────────────────────────────────
|
|
17
|
+
export const WORKSHOP_TYPES = [
|
|
18
|
+
{
|
|
19
|
+
id: "forge",
|
|
20
|
+
name: "Forge",
|
|
21
|
+
requiredFacilityLevel: "crude",
|
|
22
|
+
toolBonus_Q: q(0.20),
|
|
23
|
+
timeReduction_Q: q(0.95),
|
|
24
|
+
qualityBonus_Q: q(1.05),
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "carpentry",
|
|
28
|
+
name: "Carpentry Workshop",
|
|
29
|
+
requiredFacilityLevel: "basic",
|
|
30
|
+
toolBonus_Q: q(0.15),
|
|
31
|
+
timeReduction_Q: q(0.90),
|
|
32
|
+
qualityBonus_Q: q(1.03),
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "tailor",
|
|
36
|
+
name: "Tailor's Workshop",
|
|
37
|
+
requiredFacilityLevel: "basic",
|
|
38
|
+
toolBonus_Q: q(0.10),
|
|
39
|
+
timeReduction_Q: q(0.92),
|
|
40
|
+
qualityBonus_Q: q(1.02),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "smithy",
|
|
44
|
+
name: "Smithy",
|
|
45
|
+
requiredFacilityLevel: "advanced",
|
|
46
|
+
toolBonus_Q: q(0.30),
|
|
47
|
+
timeReduction_Q: q(0.85),
|
|
48
|
+
qualityBonus_Q: q(1.10),
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "alchemist",
|
|
52
|
+
name: "Alchemist's Laboratory",
|
|
53
|
+
requiredFacilityLevel: "advanced",
|
|
54
|
+
toolBonus_Q: q(0.25),
|
|
55
|
+
timeReduction_Q: q(0.88),
|
|
56
|
+
qualityBonus_Q: q(1.08),
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "artificer",
|
|
60
|
+
name: "Artificer's Workshop",
|
|
61
|
+
requiredFacilityLevel: "master",
|
|
62
|
+
toolBonus_Q: q(0.40),
|
|
63
|
+
timeReduction_Q: q(0.80),
|
|
64
|
+
qualityBonus_Q: q(1.20),
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
// ── Workshop Operations ───────────────────────────────────────────────────────
|
|
68
|
+
/**
|
|
69
|
+
* Get combined workshop bonuses for a specific recipe.
|
|
70
|
+
* Takes into account workshop type, facility level, and available tools.
|
|
71
|
+
*/
|
|
72
|
+
export function getWorkshopBonus(workshop, recipe) {
|
|
73
|
+
const workshopType = WORKSHOP_TYPES.find(wt => wt.id === workshop.typeId);
|
|
74
|
+
if (!workshopType) {
|
|
75
|
+
// No bonus if workshop type unknown
|
|
76
|
+
return { toolBonus_Q: q(0), timeReduction_Q: q(1.0), qualityBonus_Q: q(1.0) };
|
|
77
|
+
}
|
|
78
|
+
// Facility level multiplier: higher tier amplifies bonuses
|
|
79
|
+
const tier = FACILITY_LEVELS[workshop.facilityLevel].tier;
|
|
80
|
+
const facilityMul = 1.0 + (tier - 1) * 0.1; // 10% per tier
|
|
81
|
+
// Tool bonus: average quality of tools required by recipe
|
|
82
|
+
let totalToolQuality = 0;
|
|
83
|
+
let toolCount = 0;
|
|
84
|
+
for (const toolReq of recipe.toolRequirements) {
|
|
85
|
+
const toolQ = workshop.availableTools.get(toolReq.toolCategory) ?? DEFAULT_TOOL_QUALITY_Q;
|
|
86
|
+
totalToolQuality += toolQ;
|
|
87
|
+
toolCount++;
|
|
88
|
+
}
|
|
89
|
+
const avgToolQuality = toolCount > 0 ? totalToolQuality / toolCount : DEFAULT_TOOL_QUALITY_Q;
|
|
90
|
+
const toolBonus_Q = clampQ(Math.round(workshopType.toolBonus_Q * avgToolQuality / SCALE.Q * facilityMul), q(0), q(0.50));
|
|
91
|
+
// Time reduction: facility level reduces time further
|
|
92
|
+
const timeReduction_Q = clampQ(Math.round(workshopType.timeReduction_Q * SCALE.Q / facilityMul), q(0.50), q(1.0));
|
|
93
|
+
// Quality bonus: facility level increases quality
|
|
94
|
+
const qualityBonus_Q = clampQ(Math.round(workshopType.qualityBonus_Q * facilityMul), q(1.0), q(1.30));
|
|
95
|
+
return { toolBonus_Q, timeReduction_Q, qualityBonus_Q };
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Validate workshop requirements for a recipe.
|
|
99
|
+
* Returns list of missing tool categories and facility level insufficiency.
|
|
100
|
+
*/
|
|
101
|
+
export function validateWorkshopRequirements(workshop, recipe) {
|
|
102
|
+
const workshopType = WORKSHOP_TYPES.find(wt => wt.id === workshop.typeId);
|
|
103
|
+
const missingTools = [];
|
|
104
|
+
const facilityLevelInsufficient = workshopType
|
|
105
|
+
? FACILITY_LEVELS[workshop.facilityLevel].tier < FACILITY_LEVELS[workshopType.requiredFacilityLevel].tier
|
|
106
|
+
: true;
|
|
107
|
+
// Check each required tool category exists in workshop with sufficient quality
|
|
108
|
+
for (const toolReq of recipe.toolRequirements) {
|
|
109
|
+
const toolQ = workshop.availableTools.get(toolReq.toolCategory) ?? q(0);
|
|
110
|
+
const requiredQ = toolReq.minQuality_Q ?? DEFAULT_TOOL_QUALITY_Q;
|
|
111
|
+
if (toolQ < requiredQ) {
|
|
112
|
+
missingTools.push(toolReq.toolCategory);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return { missingTools, facilityLevelInsufficient };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Upgrade workshop facility level if resources are available.
|
|
119
|
+
* Returns success and new workshop instance (or same if failed).
|
|
120
|
+
*/
|
|
121
|
+
export function upgradeWorkshop(workshop, resources, // resource itemId -> quantity consumed
|
|
122
|
+
targetLevel) {
|
|
123
|
+
const currentTier = FACILITY_LEVELS[workshop.facilityLevel].tier;
|
|
124
|
+
const targetTier = FACILITY_LEVELS[targetLevel].tier;
|
|
125
|
+
if (targetTier <= currentTier) {
|
|
126
|
+
return { success: false, upgradedWorkshop: workshop, consumedResources: new Map() };
|
|
127
|
+
}
|
|
128
|
+
// TODO: check resource requirements based on workshop type and tier difference
|
|
129
|
+
// For now, assume upgrade always succeeds
|
|
130
|
+
const upgradedWorkshop = {
|
|
131
|
+
...workshop,
|
|
132
|
+
facilityLevel: targetLevel,
|
|
133
|
+
};
|
|
134
|
+
// Consume resources (placeholder)
|
|
135
|
+
const consumedResources = new Map();
|
|
136
|
+
// Example: consume 10 units of "material_wood" per tier step
|
|
137
|
+
const tierSteps = targetTier - currentTier;
|
|
138
|
+
consumedResources.set("material_wood", 10 * tierSteps);
|
|
139
|
+
return { success: true, upgradedWorkshop, consumedResources };
|
|
140
|
+
}
|
|
141
|
+
// ── Workshop Creation ─────────────────────────────────────────────────────────
|
|
142
|
+
/** Create a new workshop instance with default tools. */
|
|
143
|
+
export function createWorkshop(typeId, locationId, facilityLevel = "crude", initialTools) {
|
|
144
|
+
const workshopType = WORKSHOP_TYPES.find(wt => wt.id === typeId);
|
|
145
|
+
if (!workshopType)
|
|
146
|
+
return undefined;
|
|
147
|
+
// Ensure facility level meets minimum requirement
|
|
148
|
+
const requiredTier = FACILITY_LEVELS[workshopType.requiredFacilityLevel].tier;
|
|
149
|
+
const actualTier = FACILITY_LEVELS[facilityLevel].tier;
|
|
150
|
+
if (actualTier < requiredTier) {
|
|
151
|
+
// Downgrade to required level
|
|
152
|
+
facilityLevel = workshopType.requiredFacilityLevel;
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
typeId,
|
|
156
|
+
locationId,
|
|
157
|
+
facilityLevel,
|
|
158
|
+
availableTools: initialTools ?? new Map(),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
// ── Utility Functions ────────────────────────────────────────────────────────
|
|
162
|
+
/** Get workshop type by ID. */
|
|
163
|
+
export function getWorkshopTypeById(id) {
|
|
164
|
+
return WORKSHOP_TYPES.find(wt => wt.id === id);
|
|
165
|
+
}
|
|
166
|
+
/** Get all workshop types that can be used at a given facility level. */
|
|
167
|
+
export function getWorkshopTypesForLevel(facilityLevel) {
|
|
168
|
+
const tier = FACILITY_LEVELS[facilityLevel].tier;
|
|
169
|
+
return WORKSHOP_TYPES.filter(wt => FACILITY_LEVELS[wt.requiredFacilityLevel].tier <= tier);
|
|
170
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { WorldState } from "./sim/world.js";
|
|
2
|
+
import type { Vec3 } from "./sim/vec3.js";
|
|
3
|
+
import type { Q } from "./units.js";
|
|
4
|
+
import type { TraceEvent } from "./sim/trace.js";
|
|
5
|
+
/**
|
|
6
|
+
* Per-entity motion state — position, velocity, and facing direction.
|
|
7
|
+
* Suitable for overlaying movement arrows in a host renderer.
|
|
8
|
+
* Includes dead entities (last-known position before death).
|
|
9
|
+
*/
|
|
10
|
+
export interface MotionVector {
|
|
11
|
+
entityId: number;
|
|
12
|
+
teamId: number;
|
|
13
|
+
position_m: Vec3;
|
|
14
|
+
velocity_mps: Vec3;
|
|
15
|
+
/** Facing direction from ActionState.facingDirQ (fixed-point unit vector). */
|
|
16
|
+
facing: Vec3;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Extract per-entity motion vectors from the current world state.
|
|
20
|
+
* Call once per tick after stepWorld to get the latest motion snapshot.
|
|
21
|
+
*/
|
|
22
|
+
export declare function extractMotionVectors(world: WorldState): MotionVector[];
|
|
23
|
+
/**
|
|
24
|
+
* A resolved melee hit — energy delivered to a specific body region.
|
|
25
|
+
* Derived from TraceKinds.Attack events.
|
|
26
|
+
*/
|
|
27
|
+
export interface HitTraceEntry {
|
|
28
|
+
tick: number;
|
|
29
|
+
attackerId: number;
|
|
30
|
+
targetId: number;
|
|
31
|
+
region: string;
|
|
32
|
+
energy_J: number;
|
|
33
|
+
blocked: boolean;
|
|
34
|
+
parried: boolean;
|
|
35
|
+
shieldBlocked: boolean;
|
|
36
|
+
armoured: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A resolved projectile hit — energy at impact for a specific body region.
|
|
40
|
+
* Derived from TraceKinds.ProjectileHit events where hit === true.
|
|
41
|
+
*/
|
|
42
|
+
export interface ProjectileHitEntry {
|
|
43
|
+
tick: number;
|
|
44
|
+
shooterId: number;
|
|
45
|
+
targetId: number;
|
|
46
|
+
region: string;
|
|
47
|
+
distance_m: number;
|
|
48
|
+
energyAtImpact_J: number;
|
|
49
|
+
}
|
|
50
|
+
export interface HitTraceResult {
|
|
51
|
+
meleeHits: HitTraceEntry[];
|
|
52
|
+
projectileHits: ProjectileHitEntry[];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Extract melee and projectile hit data from a flat trace event array.
|
|
56
|
+
* Pass events accumulated by CollectingTrace for a span of ticks.
|
|
57
|
+
* Only confirmed hits (blocked=false or energy>0 for melee; hit=true for projectiles)
|
|
58
|
+
* are included — missed shots and full blocks are omitted.
|
|
59
|
+
*/
|
|
60
|
+
export declare function extractHitTraces(events: TraceEvent[]): HitTraceResult;
|
|
61
|
+
/**
|
|
62
|
+
* Per-entity condition snapshot for heatmap visualisation.
|
|
63
|
+
* All values are fixed-point (Q range 0–10000 unless noted).
|
|
64
|
+
*/
|
|
65
|
+
export interface ConditionSample {
|
|
66
|
+
entityId: number;
|
|
67
|
+
teamId: number;
|
|
68
|
+
position_m: Vec3;
|
|
69
|
+
/** Psychological fear level (0 = calm, q(1.0) = maximum fear). */
|
|
70
|
+
fearQ: Q;
|
|
71
|
+
/** Physiological shock (0 = none, q(1.0) = catastrophic). */
|
|
72
|
+
shock: Q;
|
|
73
|
+
/** Consciousness level (q(1.0) = fully alert, q(0) = unconscious). */
|
|
74
|
+
consciousness: Q;
|
|
75
|
+
/** Cumulative fluid loss (0 = none, q(1.0) = fatal). */
|
|
76
|
+
fluidLoss: Q;
|
|
77
|
+
dead: boolean;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Extract per-entity condition state from the current world state.
|
|
81
|
+
* Includes dead entities (position = last known position before death).
|
|
82
|
+
* To sample condition at any past tick, call replayTo(tick) first:
|
|
83
|
+
* const past = replayTo(replay, tick, ctx);
|
|
84
|
+
* const samples = extractConditionSamples(past);
|
|
85
|
+
*/
|
|
86
|
+
export declare function extractConditionSamples(world: WorldState): ConditionSample[];
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// src/debug.ts
|
|
2
|
+
//
|
|
3
|
+
// Phase 13 — Visual Debug Layer
|
|
4
|
+
//
|
|
5
|
+
// Pure data-extraction functions. No rendering, no kernel changes.
|
|
6
|
+
// Each function transforms WorldState or TraceEvent[] into a structured
|
|
7
|
+
// snapshot that a host renderer can consume directly.
|
|
8
|
+
import { TraceKinds } from "./sim/kinds.js";
|
|
9
|
+
/**
|
|
10
|
+
* Extract per-entity motion vectors from the current world state.
|
|
11
|
+
* Call once per tick after stepWorld to get the latest motion snapshot.
|
|
12
|
+
*/
|
|
13
|
+
export function extractMotionVectors(world) {
|
|
14
|
+
return world.entities.map(e => ({
|
|
15
|
+
entityId: e.id,
|
|
16
|
+
teamId: e.teamId,
|
|
17
|
+
position_m: e.position_m,
|
|
18
|
+
velocity_mps: e.velocity_mps,
|
|
19
|
+
facing: e.action.facingDirQ,
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Extract melee and projectile hit data from a flat trace event array.
|
|
24
|
+
* Pass events accumulated by CollectingTrace for a span of ticks.
|
|
25
|
+
* Only confirmed hits (blocked=false or energy>0 for melee; hit=true for projectiles)
|
|
26
|
+
* are included — missed shots and full blocks are omitted.
|
|
27
|
+
*/
|
|
28
|
+
export function extractHitTraces(events) {
|
|
29
|
+
const meleeHits = [];
|
|
30
|
+
const projectileHits = [];
|
|
31
|
+
for (const ev of events) {
|
|
32
|
+
if (ev.kind === TraceKinds.Attack) {
|
|
33
|
+
meleeHits.push({
|
|
34
|
+
tick: ev.tick,
|
|
35
|
+
attackerId: ev.attackerId,
|
|
36
|
+
targetId: ev.targetId,
|
|
37
|
+
region: ev.region,
|
|
38
|
+
energy_J: ev.energy_J,
|
|
39
|
+
blocked: ev.blocked,
|
|
40
|
+
parried: ev.parried,
|
|
41
|
+
shieldBlocked: ev.shieldBlocked,
|
|
42
|
+
armoured: ev.armoured,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
else if (ev.kind === TraceKinds.ProjectileHit && ev.hit && ev.region !== undefined) {
|
|
46
|
+
projectileHits.push({
|
|
47
|
+
tick: ev.tick,
|
|
48
|
+
shooterId: ev.shooterId,
|
|
49
|
+
targetId: ev.targetId,
|
|
50
|
+
region: ev.region,
|
|
51
|
+
distance_m: ev.distance_m,
|
|
52
|
+
energyAtImpact_J: ev.energyAtImpact_J,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { meleeHits, projectileHits };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Extract per-entity condition state from the current world state.
|
|
60
|
+
* Includes dead entities (position = last known position before death).
|
|
61
|
+
* To sample condition at any past tick, call replayTo(tick) first:
|
|
62
|
+
* const past = replayTo(replay, tick, ctx);
|
|
63
|
+
* const samples = extractConditionSamples(past);
|
|
64
|
+
*/
|
|
65
|
+
export function extractConditionSamples(world) {
|
|
66
|
+
return world.entities.map(e => ({
|
|
67
|
+
entityId: e.id,
|
|
68
|
+
teamId: e.teamId,
|
|
69
|
+
position_m: e.position_m,
|
|
70
|
+
fearQ: e.condition.fearQ ?? 0,
|
|
71
|
+
shock: e.injury.shock,
|
|
72
|
+
consciousness: e.injury.consciousness,
|
|
73
|
+
fluidLoss: e.injury.fluidLoss,
|
|
74
|
+
dead: e.injury.dead,
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Q } from "./units.js";
|
|
2
|
+
import type { IndividualAttributes, EnergyState } from "./types.js";
|
|
3
|
+
import type { Loadout } from "./equipment.js";
|
|
4
|
+
import { type CarryRules } from "./equipment.js";
|
|
5
|
+
export interface MovementCaps {
|
|
6
|
+
maxSprintSpeed_mps: number;
|
|
7
|
+
maxAcceleration_mps2: number;
|
|
8
|
+
jumpHeight_m: number;
|
|
9
|
+
}
|
|
10
|
+
export interface DeriveContext {
|
|
11
|
+
tractionCoeff: Q;
|
|
12
|
+
carryRules?: CarryRules;
|
|
13
|
+
}
|
|
14
|
+
/** Fraction of reserve energy that can be spent on a single jump (~0.0283). */
|
|
15
|
+
export declare const JUMP_ENERGY_FRACTION: number;
|
|
16
|
+
export declare function derivePeakForceEff_N(a: IndividualAttributes): number;
|
|
17
|
+
export declare function deriveMaxAcceleration_mps2(a: IndividualAttributes, tractionCoeff: Q): number;
|
|
18
|
+
export declare function deriveMaxSprintSpeed_mps(a: IndividualAttributes): number;
|
|
19
|
+
export declare function deriveJumpHeight_m(a: IndividualAttributes, reserveSpend_J: number): number;
|
|
20
|
+
export declare function deriveMovementCaps(a: IndividualAttributes, loadout: Loadout, ctx: DeriveContext): MovementCaps;
|
|
21
|
+
export declare function stepEnergyAndFatigue(a: IndividualAttributes, state: EnergyState, loadout: Loadout, demandedPower_W: number, dt_s: number, ctx: DeriveContext): void;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { SCALE, q, clampQ, qMul, mulDiv, cbrtQ, sqrtQ, G_mps2 } from "./units.js";
|
|
2
|
+
import { computeEncumbrance, deriveArmourProfile, DEFAULT_CARRY_RULES } from "./equipment.js";
|
|
3
|
+
/** Fraction of reserve energy that can be spent on a single jump (~0.0283). */
|
|
4
|
+
export const JUMP_ENERGY_FRACTION = q(0.0283);
|
|
5
|
+
export function derivePeakForceEff_N(a) {
|
|
6
|
+
const F0 = a.performance.peakForce_N;
|
|
7
|
+
const controlFactor = q(0.7) + qMul(q(0.3), a.control.controlQuality);
|
|
8
|
+
const combined = qMul(a.morphology.actuatorScale, controlFactor);
|
|
9
|
+
return mulDiv(F0, combined, SCALE.Q);
|
|
10
|
+
}
|
|
11
|
+
export function deriveMaxAcceleration_mps2(a, tractionCoeff) {
|
|
12
|
+
const m = Math.max(1, a.morphology.mass_kg);
|
|
13
|
+
// normalForce ~ m*g. Use g~9.81 and keep deterministic:
|
|
14
|
+
const normalForce_N_scaled = mulDiv(m, G_mps2 * SCALE.N, SCALE.mps2); // scaled N
|
|
15
|
+
const tractionLimit_N = mulDiv(normalForce_N_scaled, tractionCoeff, SCALE.Q);
|
|
16
|
+
const F_eff = derivePeakForceEff_N(a);
|
|
17
|
+
const usable_N = Math.min(F_eff, tractionLimit_N);
|
|
18
|
+
// a = F/m
|
|
19
|
+
return mulDiv(usable_N, SCALE.kg * SCALE.mps2, m * SCALE.N);
|
|
20
|
+
}
|
|
21
|
+
export function deriveMaxSprintSpeed_mps(a) {
|
|
22
|
+
const m = Math.max(1, a.morphology.mass_kg);
|
|
23
|
+
const P = a.performance.peakPower_W;
|
|
24
|
+
const p2m_Q = mulDiv(P * SCALE.Q, SCALE.kg, m); // Q
|
|
25
|
+
const c = cbrtQ(Math.max(1, p2m_Q));
|
|
26
|
+
const reachSqrt = sqrtQ(a.morphology.reachScale);
|
|
27
|
+
const controlFactor = q(0.6) + qMul(q(0.4), a.control.controlQuality);
|
|
28
|
+
const K = q(2.86);
|
|
29
|
+
const mult = qMul(qMul(qMul(qMul(K, c), reachSqrt), controlFactor), a.performance.conversionEfficiency);
|
|
30
|
+
return mulDiv(mult, SCALE.mps, SCALE.Q);
|
|
31
|
+
}
|
|
32
|
+
export function deriveJumpHeight_m(a, reserveSpend_J) {
|
|
33
|
+
const m = Math.max(1, a.morphology.mass_kg);
|
|
34
|
+
const Euse = Math.min(a.performance.reserveEnergy_J, reserveSpend_J);
|
|
35
|
+
const controlFactor = q(0.7) + qMul(q(0.3), a.control.controlQuality);
|
|
36
|
+
const Eeff = mulDiv(mulDiv(Euse, a.performance.conversionEfficiency, SCALE.Q), controlFactor, SCALE.Q);
|
|
37
|
+
// h = E/(m*g)
|
|
38
|
+
// force_real = m * g where m = mass_real (kg), g = G_mps2 / SCALE.mps2
|
|
39
|
+
const force_real = mulDiv(m, G_mps2, SCALE.mps2 * SCALE.kg); // integer Newtons
|
|
40
|
+
return mulDiv(Eeff, SCALE.m, Math.max(1, force_real));
|
|
41
|
+
}
|
|
42
|
+
export function deriveMovementCaps(a, loadout, ctx) {
|
|
43
|
+
const carryRules = ctx.carryRules ?? DEFAULT_CARRY_RULES;
|
|
44
|
+
const { penalties } = computeEncumbrance(a, loadout, carryRules);
|
|
45
|
+
const armour = deriveArmourProfile(loadout);
|
|
46
|
+
const speedMul = qMul(penalties.speedMul, armour.mobilityMul);
|
|
47
|
+
const accelMul = qMul(penalties.accelMul, armour.mobilityMul);
|
|
48
|
+
const jumpMul = qMul(penalties.jumpMul, armour.mobilityMul);
|
|
49
|
+
const baseV = deriveMaxSprintSpeed_mps(a);
|
|
50
|
+
const baseA = deriveMaxAcceleration_mps2(a, ctx.tractionCoeff);
|
|
51
|
+
const baseH = deriveJumpHeight_m(a, Math.trunc(a.performance.reserveEnergy_J * JUMP_ENERGY_FRACTION / SCALE.Q));
|
|
52
|
+
return {
|
|
53
|
+
maxSprintSpeed_mps: mulDiv(baseV, speedMul, SCALE.Q),
|
|
54
|
+
maxAcceleration_mps2: mulDiv(baseA, accelMul, SCALE.Q),
|
|
55
|
+
jumpHeight_m: mulDiv(baseH, jumpMul, SCALE.Q),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function stepEnergyAndFatigue(a, state, loadout, demandedPower_W, dt_s, ctx) {
|
|
59
|
+
const carryRules = ctx.carryRules ?? DEFAULT_CARRY_RULES;
|
|
60
|
+
const { penalties } = computeEncumbrance(a, loadout, carryRules);
|
|
61
|
+
const armour = deriveArmourProfile(loadout);
|
|
62
|
+
const demandMul = qMul(penalties.energyDemandMul, armour.fatigueMul);
|
|
63
|
+
const P = mulDiv(demandedPower_W, demandMul, SCALE.Q);
|
|
64
|
+
const dt = dt_s;
|
|
65
|
+
const E_need = mulDiv(P, dt, SCALE.s);
|
|
66
|
+
const cont = a.performance.continuousPower_W;
|
|
67
|
+
const P_sustain = Math.min(P, cont);
|
|
68
|
+
const E_sustain = mulDiv(P_sustain, dt, SCALE.s);
|
|
69
|
+
const E_excess = Math.max(0, E_need - E_sustain);
|
|
70
|
+
const eff = Math.max(1, a.performance.conversionEfficiency);
|
|
71
|
+
const E_reserveDrain = mulDiv(E_excess * SCALE.Q, 1, eff);
|
|
72
|
+
state.reserveEnergy_J = Math.max(0, state.reserveEnergy_J - E_reserveDrain);
|
|
73
|
+
// Phase 2B: regen — surplus continuous power replenishes reserve.
|
|
74
|
+
// Rate = surplus_W × dt × recoveryRate × 0.40 (40 % aerobic-to-reserve conversion).
|
|
75
|
+
// Calibration: at idle (80 W demand, 200 W cont) → 120 W surplus →
|
|
76
|
+
// 120 × 0.05 s × 1.0 × 0.40 = 2.4 J/tick ≈ 2 J/tick (integer floor).
|
|
77
|
+
const surplus_W = Math.max(0, cont - P);
|
|
78
|
+
if (surplus_W > 0) {
|
|
79
|
+
const regenBase_J = mulDiv(surplus_W, dt, SCALE.s);
|
|
80
|
+
const regen_J = mulDiv(mulDiv(regenBase_J, a.resilience.recoveryRate, SCALE.Q), q(0.40), SCALE.Q);
|
|
81
|
+
state.reserveEnergy_J = Math.min(a.performance.reserveEnergy_J, state.reserveEnergy_J + regen_J);
|
|
82
|
+
}
|
|
83
|
+
const cap = Math.max(1, a.performance.reserveEnergy_J);
|
|
84
|
+
const stepFrac_Q = mulDiv(E_reserveDrain * SCALE.Q, 1, cap);
|
|
85
|
+
const k = q(0.15);
|
|
86
|
+
const delta = qMul(qMul(k, stepFrac_Q), a.resilience.fatigueRate);
|
|
87
|
+
state.fatigue = clampQ((state.fatigue + delta), 0, SCALE.Q);
|
|
88
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { IndividualAttributes } from "./types.js";
|
|
2
|
+
export type Tier = 1 | 2 | 3 | 4 | 5 | 6;
|
|
3
|
+
export interface AttributeRating {
|
|
4
|
+
tier: Tier;
|
|
5
|
+
label: string;
|
|
6
|
+
comparison: string;
|
|
7
|
+
value: string;
|
|
8
|
+
}
|
|
9
|
+
export interface CharacterDescription {
|
|
10
|
+
stature: string;
|
|
11
|
+
mass: string;
|
|
12
|
+
strength: AttributeRating;
|
|
13
|
+
explosivePower: AttributeRating;
|
|
14
|
+
endurance: AttributeRating;
|
|
15
|
+
stamina: AttributeRating;
|
|
16
|
+
reactionTime: AttributeRating;
|
|
17
|
+
coordination: AttributeRating;
|
|
18
|
+
balance: AttributeRating;
|
|
19
|
+
precision: AttributeRating;
|
|
20
|
+
painTolerance: AttributeRating;
|
|
21
|
+
toughness: AttributeRating;
|
|
22
|
+
concussionResistance: AttributeRating;
|
|
23
|
+
visionRange: string;
|
|
24
|
+
hearingRange: string;
|
|
25
|
+
decisionSpeed: AttributeRating;
|
|
26
|
+
}
|
|
27
|
+
export declare function describeCharacter(attrs: IndividualAttributes): CharacterDescription;
|
|
28
|
+
export declare function formatCharacterSheet(desc: CharacterDescription): string;
|
|
29
|
+
export declare function formatOneLine(desc: CharacterDescription): string;
|