Files
dflike/shared/src/types.ts
T
2026-03-10 20:26:54 +00:00

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;
}