/** * FROZEN CONTRACTS — the seams between lanes A (blob/course), B (paint), * C (machine). Lanes never edit this file; integration (game.ts) owns changes. * Lanes communicate through these types and world.events ONLY. */ import type * as THREE from 'three' import type RAPIER from '@dimforge/rapier3d-compat' // ---- Paint palette (GDD §6) ------------------------------------------------ export type PaintColor = | 'red' | 'orange' | 'yellow' | 'green' | 'blue' | 'purple' | 'pink' export const PALETTE: Record = { red: '#FF3B30', orange: '#FF9500', yellow: '#FFD60A', green: '#34C759', blue: '#0A84FF', purple: '#AF52DE', pink: '#FF6EB4', } export const BASE_WHITE = '#F5F5F7' /** Fractions of TOTAL body surface, 0..1. Unpainted = 1 - total. */ export interface CoverageReport { total: number byColor: Record } // ---- Blob ------------------------------------------------------------------ /** Written by BuffSystem (lane B); read every frame by the controller (lane A). */ export interface BlobModifiers { speedMul: number // 1 = base jumpMul: number // 1 = base massMul: number // 1 = base; paint = weight grip: number // 0..1 extra effective friction size: number // 1 = base uniform scale (visual + collider) waterproof: boolean glow: number // 0..1 super-state emissive pulse } export const defaultModifiers = (): BlobModifiers => ({ speedMul: 1, jumpMul: 1, massMul: 1, grip: 0, size: 1, waterproof: false, glow: 0, }) export interface PaintSkin { applySplat(uv: THREE.Vector2, color: PaintColor, radius: number): void splatAtPoint(worldPoint: THREE.Vector3, color: PaintColor, radius: number): void coverage(): CoverageReport cleanse(fraction: number): void } export interface Blob { group: THREE.Group // root, added to scene mesh: THREE.Mesh // UV-mapped paintable body mesh body: RAPIER.RigidBody modifiers: BlobModifiers paint?: PaintSkin // attached during integration } // ---- Systems / world ------------------------------------------------------- export interface System { /** Called at fixed 60Hz, after physics step. */ update(dt: number): void } export type EventHandler = (payload: any) => void export interface Events { on(name: string, fn: EventHandler): () => void emit(name: string, payload?: any): void } export interface World { scene: THREE.Scene camera: THREE.PerspectiveCamera renderer: THREE.WebGLRenderer physics: RAPIER.World rapier: typeof RAPIER events: Events blob?: Blob addSystem(s: System): void /** Per-render-frame hooks (visual-only work: cameras, jiggle, particles). */ onFrame(fn: (dt: number) => void): void /** Manually advance the fixed-step sim (tests, headless verification). */ tick(steps?: number): void /** Run frame hooks + render one frame (screenshots while backgrounded). */ renderOnce(): void start(): void } // ---- Event names (the wire protocol between lanes) ------------------------- // 'machine:signal' {id: string} // 'paint:request-splat' {point: THREE.Vector3, color: PaintColor, radius: number} // 'paint:request-cleanse' {fraction: number} // 'paint:splatted' {color: PaintColor, target: 'blob'|'world'} // 'blob:landed' {impact: number} // 'blob:jumped' {}