feat(creatures): add creature types, data, ECS components, and core systems

Phase 5.1-5.5: Creatures & Ecology foundation
- 3 species data (Crystallid, Acidophile, Reagent) with real chemistry
- ECS components: Creature, AI, Metabolism, LifeCycle
- AI FSM system: idle → wander → feed → flee → attack
- Metabolism: energy drain, feeding from resources, starvation damage
- Life cycle: egg → youth → mature → aging → natural death
- Population dynamics: counting, reproduction, initial spawning
- Species registry with numeric/string ID lookup
- 51 tests passing (272 total)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Денис Шкабатур
2026-02-12 14:13:26 +03:00
parent 7e46d1ed1d
commit 324be5e643
10 changed files with 1965 additions and 0 deletions

126
src/creatures/types.ts Normal file
View File

@@ -0,0 +1,126 @@
/**
* Creature Types — data definitions for the creature/ecology system
*
* Three species in Catalytic Wastes:
* - Crystallids: slow, sturdy, eat minerals, excrete silicon
* - Acidophiles: medium speed, eat minerals, excrete acid
* - Reagents: fast, paired, predatory, explosive
*/
/** Species identifier (stored as numeric ID in ECS components) */
export enum SpeciesId {
Crystallid = 0,
Acidophile = 1,
Reagent = 2,
}
/** AI behavior state (FSM) */
export enum AIState {
Idle = 0,
Wander = 1,
Feed = 2,
Flee = 3,
Attack = 4,
}
/** Life cycle stage */
export enum LifeStage {
Egg = 0,
Youth = 1,
Mature = 2,
Aging = 3,
}
/** Diet type — what a creature can feed on */
export type DietType = 'mineral' | 'acid' | 'creature';
/** Species definition loaded from creatures.json */
export interface SpeciesData {
id: string;
name: string;
nameRu: string;
speciesId: SpeciesId;
description: string;
descriptionRu: string;
/** Visual */
color: string; // hex color string
radius: number; // base radius in pixels
radiusYouth: number; // smaller when young
/** Stats */
health: number;
speed: number; // pixels per second
damage: number; // damage per attack
armor: number; // damage reduction (0-1)
/** Metabolism */
diet: DietType;
dietTiles: string[]; // tile names this species feeds on
excretionElement: number; // atomic number of excreted element (0 = none)
energyMax: number; // max energy capacity
energyPerFeed: number; // energy gained per feeding
energyDrainPerSecond: number; // passive energy loss rate
hungerThreshold: number; // below this → seek food (0-1 fraction of max)
/** Behavior */
aggressionRadius: number; // distance to detect threats
fleeRadius: number; // distance to start fleeing
wanderRadius: number; // max wander distance from home
attackRange: number; // melee attack range
attackCooldown: number; // ms between attacks
/** Life cycle durations (ms) */
eggDuration: number;
youthDuration: number;
matureDuration: number;
agingDuration: number;
/** Reproduction */
reproductionEnergy: number; // energy cost to reproduce
offspringCount: number; // eggs per reproduction event
/** Population */
maxPopulation: number; // per-species cap
spawnWeight: number; // relative spawn density (0-1)
preferredTiles: string[]; // tiles where this species spawns
}
/** Runtime creature state (non-ECS, for string data lookup) */
export interface CreatureInfo {
speciesId: SpeciesId;
speciesDataId: string; // key into species registry
}
/** Creature species registry */
export class SpeciesRegistry {
private byId = new Map<string, SpeciesData>();
private byNumericId = new Map<SpeciesId, SpeciesData>();
constructor(data: SpeciesData[]) {
for (const species of data) {
this.byId.set(species.id, species);
this.byNumericId.set(species.speciesId, species);
}
}
/** Get species by string ID */
get(id: string): SpeciesData | undefined {
return this.byId.get(id);
}
/** Get species by numeric ID (from ECS component) */
getByNumericId(id: SpeciesId): SpeciesData | undefined {
return this.byNumericId.get(id);
}
/** Get all species */
getAll(): SpeciesData[] {
return [...this.byId.values()];
}
/** Get species count */
get count(): number {
return this.byId.size;
}
}