Files
root b2ac0e7a00 feat: add Reset Stats button to admin LLM tracking panel
Allows resetting the LLM stats aggregation window without deleting
historical data. Stores a reset timestamp in metadata and filters
the aggregation query to only include calls after that point.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 01:59:24 +00:00

398 lines
11 KiB
TypeScript

import type { AccessorySlot, PortraitSlot, BerryPhase } from './constants.js';
import type { NarrationEvent } from './narration.js';
export type EntityId = number;
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' | 'thirst' | 'energy' | 'productivity';
oldGoal?: string;
newGoal?: string;
}
export interface Position {
x: number;
y: number;
}
export interface Velocity {
dx: number;
dy: number;
}
export interface Appearance {
skinId: string;
accessories: Partial<Record<AccessorySlot, string>>;
portraitFeatures?: Partial<Record<PortraitSlot, string>>;
}
export interface Needs {
hunger: number;
thirst: number;
energy: number;
productivity: number;
}
export interface Stats {
strength: number;
dexterity: number;
constitution: number;
intelligence: number;
perception: number;
sociability: number;
courage: number;
curiosity: number;
empathy: number;
temperament: number;
}
export type StatName = keyof Stats;
export interface StatModifier {
stat: StatName;
value: number;
remaining: number;
}
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;
moveProgress: number;
}
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;
interactions: number;
lastInteractionTick: number;
status: 'active' | 'memory';
}
export type Relationships = Map<EntityId, RelationshipData>;
export interface LastOutcome {
partnerId: EntityId;
outcome: InteractionOutcome;
tick: number;
}
export interface EntityState {
id: EntityId;
position: Position;
movement: Movement;
appearance: Appearance;
needs?: Needs;
stats?: Stats;
npcBrain?: NPCBrain;
playerControlled?: PlayerControlled;
name?: string;
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[];
terrain: number[];
decorations: number[];
trunkDecorations: number[];
resourceTiles: Array<{
x: number;
y: number;
resourceType: string;
}>;
berryBushStates: Array<{
x: number;
y: number;
phase: BerryPhase;
}>;
}
export interface StateUpdate {
entities: EntityState[];
tick: number;
gameTime: number;
dayNumber: number;
berryBushStates: Array<{
x: number;
y: number;
phase: BerryPhase;
}>;
}
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;
}
export type DesireCategory = 'material' | 'social' | 'shelter' | 'comfort' | 'community' | 'creative';
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;
source: 'spawn' | 'event' | 'periodic';
sourceDetail?: string;
createdAtTick: number;
cooldownTicks?: number;
}
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;
NAP_BUFFER: 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;
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;
}
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;
}
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;
'admin-reset-llm-stats': () => void;
}