5da03ebb69
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
384 lines
11 KiB
TypeScript
384 lines
11 KiB
TypeScript
import type { AccessorySlot, PortraitSlot } from './constants.js';
|
|
import type { NarrationEvent } from './narration.js';
|
|
|
|
// Entity is just a numeric ID
|
|
export type EntityId = number;
|
|
|
|
// Event memory types
|
|
export type MemoryEventType =
|
|
| 'social_positive' | 'social_negative'
|
|
| 'proposal_accepted' | 'proposal_rejected'
|
|
| 'tier_change'
|
|
| 'need_crisis' | 'need_recovery'
|
|
| 'goal_change'
|
|
| 'bond_formed' | 'bond_dissolved'
|
|
| 'spawned'
|
|
| 'invention'
|
|
| 'desire_fulfilled' | 'desire_added';
|
|
|
|
export interface MemoryEvent {
|
|
id: number;
|
|
type: MemoryEventType;
|
|
tick: number;
|
|
otherEntityId?: EntityId;
|
|
otherName?: string;
|
|
detail: string;
|
|
oldTier?: string;
|
|
newTier?: string;
|
|
need?: 'hunger' | 'energy' | 'productivity';
|
|
oldGoal?: string;
|
|
newGoal?: string;
|
|
}
|
|
|
|
// Components
|
|
export interface Position {
|
|
x: number;
|
|
y: number;
|
|
}
|
|
|
|
export interface Velocity {
|
|
dx: number;
|
|
dy: number;
|
|
}
|
|
|
|
export interface Appearance {
|
|
skinId: string; // e.g. "shape00_skin02"
|
|
accessories: Partial<Record<AccessorySlot, string>>; // slot -> filename without extension
|
|
portraitFeatures?: Partial<Record<PortraitSlot, string>>; // portrait-only facial features
|
|
}
|
|
|
|
export interface Needs {
|
|
hunger: number; // 0-100
|
|
thirst: number; // 0-100
|
|
energy: number; // 0-100
|
|
productivity: number; // 0-100
|
|
}
|
|
|
|
export interface Stats {
|
|
// Physical
|
|
strength: number;
|
|
dexterity: number;
|
|
constitution: number;
|
|
intelligence: number;
|
|
perception: number;
|
|
// Personality
|
|
sociability: number;
|
|
courage: number;
|
|
curiosity: number;
|
|
empathy: number;
|
|
temperament: number;
|
|
}
|
|
|
|
export type StatName = keyof Stats;
|
|
|
|
export interface StatModifier {
|
|
stat: StatName;
|
|
value: number;
|
|
remaining: number; // ticks remaining, -1 = permanent (needs-based, replaced each tick)
|
|
}
|
|
|
|
export interface StatModifiers {
|
|
modifiers: StatModifier[];
|
|
}
|
|
|
|
export type MovementState = 'idle' | 'walking';
|
|
export type GoalType = 'wander' | 'eat' | 'drink' | 'forage' | 'sleep' | 'gather' | 'craft' | 'build' | 'dropoff' | 'pickup';
|
|
|
|
export interface Movement {
|
|
state: MovementState;
|
|
target: Position | null;
|
|
path: Position[];
|
|
direction: number; // Direction enum value
|
|
moveProgress: number; // fractional tile accumulator
|
|
}
|
|
|
|
export interface PlayerControlled {
|
|
playerId: string;
|
|
mode: 'avatar' | 'camera';
|
|
}
|
|
|
|
export interface NPCBrain {
|
|
currentGoal: GoalType | null;
|
|
goalQueue: GoalType[];
|
|
gatherTarget?: { x: number; y: number; resourceType: string } | null;
|
|
}
|
|
|
|
export type InteractionPhase = 'none' | 'facing' | 'pausing' | 'emoting' | 'proposing';
|
|
export type InteractionOutcome = 'positive' | 'negative';
|
|
|
|
export interface SocialState {
|
|
phase: InteractionPhase;
|
|
partnerId: EntityId | null;
|
|
phaseTimer: number;
|
|
outcome: InteractionOutcome | null;
|
|
globalCooldown: number;
|
|
pairCooldowns: Map<EntityId, number>;
|
|
lastOutcome: LastOutcome | null;
|
|
proposalCooldown: number;
|
|
pendingProposal: { targetId: EntityId; type: string } | null;
|
|
isProposalInteraction: boolean;
|
|
}
|
|
|
|
export interface RelationshipData {
|
|
value: number; // -100 to 100
|
|
interactions: number; // total interaction count
|
|
lastInteractionTick: number;
|
|
status: 'active' | 'memory';
|
|
}
|
|
|
|
export type Relationships = Map<EntityId, RelationshipData>;
|
|
|
|
export interface LastOutcome {
|
|
partnerId: EntityId;
|
|
outcome: InteractionOutcome;
|
|
tick: number;
|
|
}
|
|
|
|
// Network protocol messages
|
|
export interface EntityState {
|
|
id: EntityId;
|
|
position: Position;
|
|
movement: Movement;
|
|
appearance: Appearance;
|
|
needs?: Needs;
|
|
stats?: Stats;
|
|
npcBrain?: NPCBrain;
|
|
playerControlled?: PlayerControlled;
|
|
name?: string; // NPC display name
|
|
backstory?: string;
|
|
inventory?: Record<string, number>;
|
|
desires?: { description: string; category: DesireCategory; }[];
|
|
socialState?: {
|
|
phase: InteractionPhase;
|
|
partnerId: EntityId | null;
|
|
outcome: InteractionOutcome | null;
|
|
};
|
|
relationships?: Array<{
|
|
entityId: EntityId;
|
|
name: string;
|
|
value: number;
|
|
classification: string;
|
|
status: 'active' | 'memory';
|
|
bond: string | null;
|
|
}>;
|
|
}
|
|
|
|
export interface WorldState {
|
|
entities: EntityState[];
|
|
worldWidth: number;
|
|
worldHeight: number;
|
|
tileSize: number;
|
|
obstacles: Position[];
|
|
pointsOfInterest: { type: 'food'; position: Position }[];
|
|
terrain: number[]; // flat array [y * width + x], terrain type per tile
|
|
decorations: number[]; // flat array [y * width + x], decoration tile index (-1 = none)
|
|
trunkDecorations: number[]; // flat array [y * width + x], trunk tile index (-1 = none)
|
|
resourceTiles: Array<{ x: number; y: number; resourceType: string }>;
|
|
}
|
|
|
|
export interface StateUpdate {
|
|
entities: EntityState[];
|
|
tick: number;
|
|
gameTime: number; // 0.0-1.0 normalized position in day/night cycle
|
|
dayNumber: number; // 1-indexed day counter
|
|
}
|
|
|
|
export interface PlayerJoined {
|
|
playerId: string;
|
|
entityId: EntityId;
|
|
}
|
|
|
|
export interface PlayerLeft {
|
|
playerId: string;
|
|
}
|
|
|
|
export interface PlayerInput {
|
|
type: 'move' | 'toggle-mode' | 'follow' | 'interact';
|
|
direction?: { dx: number; dy: number };
|
|
targetPlayerId?: string;
|
|
targetEntityId?: EntityId;
|
|
}
|
|
|
|
export interface SuperlativeEntry {
|
|
entityId: EntityId;
|
|
name: string;
|
|
value: number;
|
|
}
|
|
|
|
export interface SuperlativesData {
|
|
mostLoved: SuperlativeEntry | null;
|
|
mostPopular: SuperlativeEntry | null;
|
|
mostReviled: SuperlativeEntry | null;
|
|
mostAnnoying: SuperlativeEntry | null;
|
|
shyest: SuperlativeEntry | null;
|
|
mostOutgoing: SuperlativeEntry | null;
|
|
biggestHeartbreaker: SuperlativeEntry | null;
|
|
mostDevoted: SuperlativeEntry | null;
|
|
loneliest: SuperlativeEntry | null;
|
|
mostPolarizing: SuperlativeEntry | null;
|
|
socialButterfly: SuperlativeEntry | null;
|
|
mostInventive: SuperlativeEntry | null;
|
|
}
|
|
|
|
export interface InventionSummary {
|
|
itemId: string;
|
|
name: string;
|
|
category: 'resource' | 'tool' | 'material' | 'structure';
|
|
inputs: { itemId: string; quantity: number }[];
|
|
workshopType: string | null;
|
|
toolRequired: string | null;
|
|
inventorName: string;
|
|
day: number;
|
|
}
|
|
|
|
export interface StockpileLogEntry {
|
|
npcName: string;
|
|
action: 'dropoff' | 'pickup';
|
|
itemId: string;
|
|
quantity: number;
|
|
tick: number;
|
|
}
|
|
|
|
// Desire system types
|
|
export type DesireCategory =
|
|
| 'material' // wants a specific item/tool
|
|
| 'social' // wants relationships, bonds
|
|
| 'shelter' // wants housing/personal space
|
|
| 'comfort' // wants food security, rest, quality of life
|
|
| 'community' // wants to improve the settlement for everyone
|
|
| 'creative'; // wants to make/invent something novel
|
|
|
|
export type FulfillmentCriteria =
|
|
| { type: 'own_item'; itemId: string; quantity: number }
|
|
| { type: 'own_item_category'; category: string; quantity: number }
|
|
| { type: 'structure_exists'; structureType: string }
|
|
| { type: 'building_exists'; buildingType: string }
|
|
| { type: 'relationship_tier'; tier: string; count: number }
|
|
| { type: 'recipe_exists'; tag: string }
|
|
| { type: 'custom'; check: string };
|
|
|
|
export interface Desire {
|
|
id: string;
|
|
description: string;
|
|
category: DesireCategory;
|
|
fulfillment: FulfillmentCriteria;
|
|
priority: number; // 0-1
|
|
source: 'spawn' | 'event' | 'periodic';
|
|
sourceDetail?: string;
|
|
createdAtTick: number;
|
|
cooldownTicks?: number;
|
|
}
|
|
|
|
// Admin panel types
|
|
export interface TunableConstants {
|
|
TICK_RATE: number;
|
|
BROADCAST_EVERY_N_TICKS: number;
|
|
HUNGER_DECAY_PER_TICK: number;
|
|
ENERGY_DECAY_PER_TICK: number;
|
|
HUNGER_THRESHOLD: number;
|
|
THIRST_DECAY_PER_TICK: number;
|
|
THIRST_THRESHOLD: number;
|
|
ENERGY_THRESHOLD: number;
|
|
NEED_RECOVERY_RATE: number;
|
|
SLEEP_ENERGY_RECOVERY_PER_TICK: number;
|
|
SLEEP_HUNGER_DECAY_MULTIPLIER: number;
|
|
SLEEP_WAKE_THRESHOLD: number;
|
|
SLEEP_VOLUNTARY_ENERGY_THRESHOLD: number;
|
|
PRODUCTIVITY_DECAY_PER_TICK: number;
|
|
PRODUCTIVITY_THRESHOLD: number;
|
|
PRODUCTIVITY_RECOVERY_RATE: number;
|
|
DAY_NIGHT_RATIO: number;
|
|
DAY_HOURS: number;
|
|
SUNSET_DURATION_HOURS: number;
|
|
SUNRISE_DURATION_HOURS: number;
|
|
NIGHT_DARKNESS: number;
|
|
AWARENESS_RADIUS: number;
|
|
FACING_DURATION: number;
|
|
PAUSING_DURATION: number;
|
|
EMOTING_DURATION: number;
|
|
SOCIAL_GLOBAL_COOLDOWN: number;
|
|
SOCIAL_PAIR_COOLDOWN: number;
|
|
PROPOSAL_EMOTING_DURATION: number;
|
|
MAX_NPC_COUNT: number;
|
|
MOVE_SPEED: number;
|
|
}
|
|
|
|
export type TunableKey = keyof TunableConstants;
|
|
|
|
// Log panel types
|
|
export type LogSeverity = 'error' | 'warning' | 'info';
|
|
export type LogCategory = 'LLM' | 'Save' | 'Network' | 'Performance' | 'Game';
|
|
|
|
export interface LogEntry {
|
|
timestamp: number;
|
|
severity: LogSeverity;
|
|
category: LogCategory;
|
|
message: string;
|
|
}
|
|
|
|
export interface LlmCallTypeStats {
|
|
templateName: string;
|
|
label: string;
|
|
count: number;
|
|
totalCost: number;
|
|
minCost: number;
|
|
maxCost: number;
|
|
avgCost: number;
|
|
retryCount: number;
|
|
failCount: number;
|
|
retryPct: number;
|
|
failPct: number;
|
|
}
|
|
|
|
export interface LlmStatsData {
|
|
types: LlmCallTypeStats[];
|
|
totalCount: number;
|
|
totalCost: number;
|
|
totalRetries: number;
|
|
totalFailures: number;
|
|
trackingEnabled: boolean;
|
|
}
|
|
|
|
// Server -> Client events
|
|
export interface ServerEvents {
|
|
'world-state': (data: WorldState) => void;
|
|
'state-update': (data: StateUpdate) => void;
|
|
'player-joined': (data: PlayerJoined) => void;
|
|
'player-left': (data: PlayerLeft) => void;
|
|
'npc-recomposed': (data: { entityId: EntityId; appearance: Appearance }) => void;
|
|
'superlatives-update': (data: SuperlativesData) => void;
|
|
'narration-event': (data: NarrationEvent) => void;
|
|
'narration-update': (data: { id: number; narration: string }) => void;
|
|
'narration-history': (data: NarrationEvent[]) => void;
|
|
'npc-thought': (data: { entityId: EntityId; text: string; emoji: string }) => void;
|
|
'memory-event': (data: { entityId: EntityId; event: MemoryEvent }) => void;
|
|
'memory-history': (data: { entityId: EntityId; events: MemoryEvent[] }) => void;
|
|
'invention-event': (data: InventionSummary) => void;
|
|
'invention-history': (data: InventionSummary[]) => void;
|
|
'stockpile-event': (data: StockpileLogEntry) => void;
|
|
'stockpile-history': (data: StockpileLogEntry[]) => void;
|
|
'stockpile-summary': (data: Record<string, number>) => void;
|
|
'admin-auth-result': (data: { success: boolean; constants?: TunableConstants }) => void;
|
|
'admin-constants-updated': (data: TunableConstants) => void;
|
|
'log-history': (data: LogEntry[]) => void;
|
|
'log-entry': (data: LogEntry) => void;
|
|
'admin-llm-stats-result': (data: LlmStatsData) => void;
|
|
'admin-llm-tracking-toggled': (data: { enabled: boolean }) => void;
|
|
}
|
|
|
|
// Client -> Server events
|
|
export interface ClientEvents {
|
|
'player-input': (data: PlayerInput) => void;
|
|
'spawn-npc': (data: { x: number; y: number }) => void;
|
|
'superlatives-subscribe': () => void;
|
|
'superlatives-unsubscribe': () => void;
|
|
'follow-npc': (data: { entityId: EntityId | null }) => void;
|
|
'admin-auth': (data: { password: string }) => void;
|
|
'admin-update-constant': (data: { key: TunableKey; value: number }) => void;
|
|
'admin-reset-defaults': () => void;
|
|
'log-subscribe': () => void;
|
|
'log-unsubscribe': () => void;
|
|
'admin-llm-stats': () => void;
|
|
'admin-toggle-llm-tracking': (data: { enabled: boolean }) => void;
|
|
}
|