feat: add shared types and constants for ECS, networking, and assets

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 18:10:52 -05:00
parent 39c19568c1
commit 1fdae9e45c
3 changed files with 134 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
export const TILE_SIZE = 48;
export const WORLD_WIDTH = 64;
export const WORLD_HEIGHT = 64;
export const TICK_RATE = 10; // server ticks per second
export const BROADCAST_EVERY_N_TICKS = 3; // state broadcast frequency
export const SPRITE_FRAME_WIDTH = 48;
export const SPRITE_FRAME_HEIGHT = 48;
export const SPRITE_COLS = 6;
export const SPRITE_ROWS = 4;
// NPC needs
export const HUNGER_DECAY_PER_TICK = 0.05;
export const ENERGY_DECAY_PER_TICK = 0.03;
export const HUNGER_THRESHOLD = 30;
export const ENERGY_THRESHOLD = 20;
export const NEED_RECOVERY_RATE = 0.5;
// Directions (row index in spritesheet)
export const Direction = {
DOWN: 0,
LEFT: 1,
RIGHT: 2,
UP: 3,
} as const;
export type Direction = (typeof Direction)[keyof typeof Direction];
// Accessory slot names in z-order (bottom to top rendering)
export const ACCESSORY_SLOTS = [
'bottom', 'feet', 'chest', 'arm', 'shoulder',
'waist', 'back', 'facialHair', 'haircut', 'hat',
] as const;
export type AccessorySlot = (typeof ACCESSORY_SLOTS)[number];
+2
View File
@@ -0,0 +1,2 @@
export * from './constants.js';
export * from './types.js';
+100
View File
@@ -0,0 +1,100 @@
import type { AccessorySlot } from './constants.js';
// Entity is just a numeric ID
export type EntityId = number;
// 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
}
export interface Needs {
hunger: number; // 0-100
energy: number; // 0-100
}
export type MovementState = 'idle' | 'walking';
export type GoalType = 'wander' | 'eat' | 'rest';
export interface Movement {
state: MovementState;
target: Position | null;
path: Position[];
direction: number; // Direction enum value
}
export interface PlayerControlled {
playerId: string;
mode: 'avatar' | 'camera';
}
export interface NPCBrain {
currentGoal: GoalType | null;
goalQueue: GoalType[];
}
// Network protocol messages
export interface EntityState {
id: EntityId;
position: Position;
movement: Movement;
appearance: Appearance;
needs?: Needs;
npcBrain?: NPCBrain;
playerControlled?: PlayerControlled;
}
export interface WorldState {
entities: EntityState[];
worldWidth: number;
worldHeight: number;
tileSize: number;
obstacles: Position[];
pointsOfInterest: { type: 'food' | 'rest'; position: Position }[];
}
export interface StateUpdate {
entities: EntityState[];
tick: number;
}
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;
}
// 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;
}
// Client -> Server events
export interface ClientEvents {
'player-input': (data: PlayerInput) => void;
}