diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 0000000..a3a564a --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,23 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "web-demo (Next.js)", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "dev", "-w", "apps/web-demo"], + "port": 3000 + }, + { + "name": "web-demo (Next.js, debug mode)", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "dev:debug", "-w", "apps/web-demo"], + "port": 3000 + }, + { + "name": "PartyKit (multiplayer server)", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "party:dev", "-w", "apps/web-demo"], + "port": 1999 + } + ] +} diff --git a/apps/web-demo/app/components/GameTouchCanvas.tsx b/apps/web-demo/app/components/GameTouchCanvas.tsx index 2a1b3e5..b918880 100644 --- a/apps/web-demo/app/components/GameTouchCanvas.tsx +++ b/apps/web-demo/app/components/GameTouchCanvas.tsx @@ -53,6 +53,8 @@ const EMPTY_TRANSITIONS: GameSceneTransition[] = []; type GameTouchCanvasProps = { debug?: boolean; onRuntimeEvent?: (event: RuntimeEvent) => void; + /** Contenido extra dentro del mundo físico (p.ej. en /multiplayer). */ + extraCanvasChildren?: React.ReactNode; }; /** @@ -69,6 +71,7 @@ function SceneReadyReporter({ onReady }: { onReady: () => void }) { export default function GameTouchCanvas({ debug: debugOverride, onRuntimeEvent, + extraCanvasChildren, }: GameTouchCanvasProps = {}) { const selectedCharacter: GameCharacterName = "Dave"; const [sceneReady, setSceneReady] = useState(false); @@ -451,6 +454,7 @@ export default function GameTouchCanvas({ debug={runtimeDebug} onTransitionTriggered={handleTransitionTriggered} /> + {extraCanvasChildren} diff --git a/apps/web-demo/app/components/net/RoomLobby.tsx b/apps/web-demo/app/components/net/RoomLobby.tsx new file mode 100644 index 0000000..01c1926 --- /dev/null +++ b/apps/web-demo/app/components/net/RoomLobby.tsx @@ -0,0 +1,69 @@ +"use client"; +import { useState } from "react"; + +export interface RoomLobbyProps { + code: string | null; + remoteCount: number; // otros jugadores en mi escena + capacity: number; // 4 + displayName: string; + onCreate: () => void; + onJoin: (code: string) => void; + onReset: () => void; + onRename: (name: string) => void; +} + +export function RoomLobby(p: RoomLobbyProps) { + const [joinCode, setJoinCode] = useState(""); + const total = p.remoteCount + 1; + const missing = p.capacity - total; + return ( +
+ {!p.code ? ( +
+ + setJoinCode(e.target.value)} + /> + +
+ ) : ( +
+
+ Room: {p.code}{" "} + +
+
+ Jugadores:{" "} + + {total}/{p.capacity} + +
+ {missing > 0 && ( +
Faltan {missing} jugador(es)…
+ )} + p.onRename(e.target.value)} /> + +
+ )} +
+ ); +} diff --git a/apps/web-demo/app/lib/engine/runtime/useInventoryRuntimeController.ts b/apps/web-demo/app/lib/engine/runtime/useInventoryRuntimeController.ts index d1131e4..dc52858 100644 --- a/apps/web-demo/app/lib/engine/runtime/useInventoryRuntimeController.ts +++ b/apps/web-demo/app/lib/engine/runtime/useInventoryRuntimeController.ts @@ -122,10 +122,10 @@ export function useInventoryRuntimeController({ const [draggedStack, setDraggedStack] = useState( null, ); - const [placedItems, setPlacedItems] = useState([]); + // placedItems lives in the store so remote updates (via applyRemoteEvent) propagate reactively. + const placedItems = usePlacedItemsStore((s) => s.items); const pickupLockRef = useRef>(new Set()); const inventorySlotsRef = useRef(inventorySlots); - const setPlacedItemsInStore = usePlacedItemsStore((s) => s.setItems); // Keep ref in sync with current state (avoids stale closure in callbacks) useEffect(() => { @@ -150,39 +150,20 @@ export function useInventoryRuntimeController({ } }, [placedItems]); - // Manage placed items: maintain all items in state (persisted to store) + // Seed initial items for personalRoom once per device. useEffect(() => { - const { items: storedItems, initialItemsCreated } = usePlacedItemsStore.getState(); - - setPlacedItems((prev) => { - // Start with all stored items (already persisted in localStorage) - const allItems = storedItems.length > 0 ? storedItems : prev; - - // For personalRoom, create initial spawn items only once per session - // Only create if the item doesn't exist in ANY scene (not just this one) - if (sceneId === "personalRoom" && !initialItemsCreated) { - const trophyTakenOrMovedElsewhere = allItems.some((i) => i.itemId === "trophy"); - // Only recreate the trophy if it has never left this room - // If it exists anywhere, assume it was taken/moved by the player - if (!trophyTakenOrMovedElsewhere) { - return [...allItems, ...createInitialPlacedItems(sceneId)]; - } - } - - return allItems; - }); - - // Mark initial items as created AFTER setState + const { items, initialItemsCreated } = usePlacedItemsStore.getState(); if (sceneId === "personalRoom" && !initialItemsCreated) { + const trophyExists = items.some((i) => i.itemId === "trophy"); + if (!trophyExists) { + createInitialPlacedItems(sceneId).forEach((item) => + usePlacedItemsStore.getState().addItem(item), + ); + } usePlacedItemsStore.getState().markInitialItemsCreated(); } }, [sceneId]); - // Sync all placed items to localStorage via store - useEffect(() => { - setPlacedItemsInStore(placedItems); - }, [placedItems, setPlacedItemsInStore]); - const handleBoundaryHit = useCallback( (phrase: string) => { showDialog(phrase, "boundaryHit"); @@ -251,19 +232,18 @@ export function useInventoryRuntimeController({ } if (decision.kind === "place") { + const placedItemWithScene = { ...decision.placedItem, sceneId }; emitRuntimeEvent(onRuntimeEvent, { type: "onDrop", outcome: "place", itemId: payload.stack.id, interactionId: interaction.id, + placedItem: placedItemWithScene, }); setInventorySlots((currentSlots) => removeOneFromSlot(currentSlots, decision.fromSlotIndex), ); - setPlacedItems((currentPlaced) => [ - ...currentPlaced, - { ...decision.placedItem, sceneId }, // Add sceneId to track which scene this item belongs to - ]); + usePlacedItemsStore.getState().addItem(placedItemWithScene); showSpeechBubble(getRandomPhrase(decision.dialogKey), { dialogKey: decision.dialogKey, }); @@ -394,11 +374,7 @@ export function useInventoryRuntimeController({ setInventorySlots(inventoryResult.slots); // 2. Remove from placed items - setPlacedItems((currentPlaced) => - currentPlaced.filter( - (currentItem) => currentItem.id !== decision.placedItemId, - ), - ); + usePlacedItemsStore.getState().removeItemById(decision.placedItemId); emitRuntimeEvent(onRuntimeEvent, { type: "onDrop", @@ -413,14 +389,11 @@ export function useInventoryRuntimeController({ const updatePlacedItemPosition = useCallback( (id: string, axis: 0 | 1 | 2, value: number) => { - setPlacedItems((currentPlaced) => - currentPlaced.map((item) => { + const current = usePlacedItemsStore.getState().items; + usePlacedItemsStore.getState().setItems( + current.map((item) => { if (item.id !== id) return item; - const worldPosition = [...item.worldPosition] as [ - number, - number, - number, - ]; + const worldPosition = [...item.worldPosition] as [number, number, number]; worldPosition[axis] = value; return { ...item, worldPosition }; }), @@ -431,16 +404,13 @@ export function useInventoryRuntimeController({ const movePlacedItemToPlayer = useCallback( (id: string) => { - setPlacedItems((currentPlaced) => - currentPlaced.map((item) => { + const current = usePlacedItemsStore.getState().items; + usePlacedItemsStore.getState().setItems( + current.map((item) => { if (item.id !== id) return item; return { ...item, - worldPosition: [ - playerPosition[0], - item.worldPosition[1], - playerPosition[2], - ], + worldPosition: [playerPosition[0], item.worldPosition[1], playerPosition[2]], }; }), ); @@ -449,9 +419,7 @@ export function useInventoryRuntimeController({ ); const removePlacedItemById = useCallback((id: string) => { - setPlacedItems((currentPlaced) => - currentPlaced.filter((item) => item.id !== id), - ); + usePlacedItemsStore.getState().removeItemById(id); }, []); const handleStartInventoryDrag = useCallback( diff --git a/apps/web-demo/app/lib/net/__tests__/roomSession.test.ts b/apps/web-demo/app/lib/net/__tests__/roomSession.test.ts new file mode 100644 index 0000000..9439b10 --- /dev/null +++ b/apps/web-demo/app/lib/net/__tests__/roomSession.test.ts @@ -0,0 +1,47 @@ +import { describe, it, expect } from "vitest"; +import { createRoomSession, MAX_PLAYERS } from "../roomSession"; +import { generateRoomCode, isValidRoomCode } from "../roomCode"; +import { NoopStorageAdapter } from "../../platform-web"; + +describe("roomSession", () => { + it("generates valid codes without ambiguous chars", () => { + const c = generateRoomCode(() => 0); + expect(isValidRoomCode(c)).toBe(true); + expect(c).not.toMatch(/[0O1I]/); + }); + it("create persists and restore returns it", () => { + const s1 = new NoopStorageAdapter(); + const a = createRoomSession(s1); + const code = a.create(); + const b = createRoomSession(s1); + expect(b.restore()).toBe(code); + }); + it("reset produces a fresh code", () => { + let calls = 0; + // rng determinista que cambia entre llamadas para garantizar códigos distintos + const rng = () => { + calls += 1; + return (calls % 31) / 31; + }; + const s = createRoomSession(new NoopStorageAdapter(), rng); + const first = s.create(); + const second = s.reset(); + expect(second).not.toBe(first); + }); + it("capacity helpers reflect N/4", () => { + const s = createRoomSession(new NoopStorageAdapter()); + expect(s.freeSlots(0)).toBe(MAX_PLAYERS - 1); // solo yo → 3 libres + expect(s.isFull(3)).toBe(true); // 3 remotos + yo = 4 + }); + it("join rejects invalid codes", () => { + const s = createRoomSession(new NoopStorageAdapter()); + expect(() => s.join("xx")).toThrow(); + }); + it("forget clears persisted code", () => { + const storage = new NoopStorageAdapter(); + const s = createRoomSession(storage); + s.create(); + s.forget(); + expect(createRoomSession(storage).restore()).toBeNull(); + }); +}); diff --git a/apps/web-demo/app/lib/net/createMultiplayerRuntime.ts b/apps/web-demo/app/lib/net/createMultiplayerRuntime.ts new file mode 100644 index 0000000..fed801f --- /dev/null +++ b/apps/web-demo/app/lib/net/createMultiplayerRuntime.ts @@ -0,0 +1,91 @@ +import { createGameRuntime, type GameSceneConfig } from "../engine/publicApi"; +import { + createMultiplayerSession, + createRemotePlayersStore, + createSelfDescriptor, + getSceneState, + type GameEvent, + type PlacedSceneItem, + type RemotePlayersStore, +} from "@pointclick-engine/engine-core"; +import { usePlacedItemsStore } from "../../store/placedItemsStore"; +import { createPartyKitAdapter } from "./partyKitAdapter"; + +export interface MultiplayerRuntime { + remotePlayers: RemotePlayersStore; + setName: (name: string) => void; + dispose: () => void; +} + +type RuntimeConfig = NonNullable[0]>; + +export function createMultiplayerRuntime(opts: { + room: string; + host: string; + scenes: GameSceneConfig[]; + displayName: string; + inventoryAdapter?: RuntimeConfig["inventoryAdapter"]; + dialogAdapter?: RuntimeConfig["dialogAdapter"]; +}): MultiplayerRuntime { + const runtime = createGameRuntime({ + scenes: opts.scenes, + inventoryAdapter: opts.inventoryAdapter, + dialogAdapter: opts.dialogAdapter, + }); + const remotePlayers = createRemotePlayersStore(); + const port = createPartyKitAdapter(opts.host); + const scene = getSceneState(); + + const session = createMultiplayerSession({ + port, + bus: runtime, // el handle cumple SessionBus (on/emit) + room: opts.room, + self: createSelfDescriptor({ + playerId: globalThis.crypto?.randomUUID?.() ?? `p-${Date.now()}`, + sceneId: scene.sceneId, + position: scene.playerPosition, + displayName: opts.displayName, + }), + remotePlayers, + // Apply remote world events: update shared stores THEN emit to the bus + // (audio, door logic, etc.). applyingRemote is already true here so the + // session won't re-broadcast what we emit (anti-loop guard). + applyRemoteEvent: (event: GameEvent) => { + if (event.type === "item:dropped") { + const store = usePlacedItemsStore.getState(); + if (event.outcome === "place" && event.placedItem) { + store.addItem(event.placedItem); + } else if (event.outcome === "pickup-success" && event.interactionId) { + store.removeItemByInteractionId(event.interactionId); + } + } + runtime.emit(event); + }, + applySnapshot: (world: Record) => { + const store = usePlacedItemsStore.getState(); + for (const [key, raw] of Object.entries(world)) { + if (!key.startsWith("item:") || !key.endsWith(":placed")) continue; + const entry = raw as { value: boolean; placedItem?: PlacedSceneItem }; + if (entry.value && entry.placedItem) { + store.addItem(entry.placedItem); + } + } + }, + presenceThrottleMs: 80, + }); + + // Limpia jugadores que no manden heartbeat en 10 min (o que cerraron la ventana, + // que el servidor ya elimina vía __left inmediatamente). + const STALE_MS = 10 * 60 * 1000; + const prune = setInterval(() => remotePlayers.pruneStale(STALE_MS), 60_000); + + return { + remotePlayers, + setName: (name) => session.updateSelf({ displayName: name }), + dispose: () => { + clearInterval(prune); + session.dispose(); + runtime.dispose(); + }, + }; +} diff --git a/apps/web-demo/app/lib/net/partyKitAdapter.ts b/apps/web-demo/app/lib/net/partyKitAdapter.ts new file mode 100644 index 0000000..b712a5f --- /dev/null +++ b/apps/web-demo/app/lib/net/partyKitAdapter.ts @@ -0,0 +1,61 @@ +import { PartySocket } from "partysocket"; +import type { + MultiplayerPort, + NetEnvelope, + ConnectOptions, + ConnectionStatus, +} from "@pointclick-engine/engine-core"; + +/** Adapter PartyKit. `host` = NEXT_PUBLIC_PARTYKIT_HOST (p.ej. "127.0.0.1:1999" en dev). */ +export function createPartyKitAdapter(host: string): MultiplayerPort { + let socket: PartySocket | null = null; + let msgHandlers: Array<(m: NetEnvelope) => void> = []; + let statusHandlers: Array<(s: ConnectionStatus) => void> = []; + + return { + connect(opts: ConnectOptions) { + statusHandlers.forEach((h) => h({ state: "connecting" })); + // Pass our stable UUID as the PartyKit party-key so conn.id on the + // server equals selfId. Without this, PartySocket generates its own ID, + // the server echoes it back as status.selfId, MultiplayerSession updates + // self.playerId to the new ID, and subsequent presences look like a + // second player to peers who already stored the initial UUID-based entry. + socket = new PartySocket({ host, party: "multiplayer", room: opts.room, id: opts.selfId }); + socket.addEventListener("message", (e: MessageEvent) => { + let env: { kind?: string; payload?: unknown }; + try { + env = JSON.parse(e.data as string); + } catch { + return; + } + if (env.kind === "status") { + statusHandlers.forEach((h) => h(env.payload as ConnectionStatus)); + return; + } + msgHandlers.forEach((h) => h(env as NetEnvelope)); + }); + socket.addEventListener("close", () => + statusHandlers.forEach((h) => h({ state: "disconnected" })), + ); + }, + disconnect() { + socket?.close(); + socket = null; + }, + send(m: NetEnvelope) { + socket?.send(JSON.stringify(m)); + }, + onMessage(h) { + msgHandlers.push(h); + return () => { + msgHandlers = msgHandlers.filter((x) => x !== h); + }; + }, + onStatus(h) { + statusHandlers.push(h); + return () => { + statusHandlers = statusHandlers.filter((x) => x !== h); + }; + }, + }; +} diff --git a/apps/web-demo/app/lib/net/roomCode.ts b/apps/web-demo/app/lib/net/roomCode.ts new file mode 100644 index 0000000..98524e6 --- /dev/null +++ b/apps/web-demo/app/lib/net/roomCode.ts @@ -0,0 +1,20 @@ +const ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // sin 0/O/1/I ambiguos +export const ROOM_CODE_LENGTH = 6; + +export function generateRoomCode(rng: () => number = Math.random): string { + let out = ""; + for (let i = 0; i < ROOM_CODE_LENGTH; i++) { + out += ALPHABET[Math.floor(rng() * ALPHABET.length)]; + } + return out; +} + +export function isValidRoomCode(code: string): boolean { + return new RegExp(`^[${ALPHABET}]{${ROOM_CODE_LENGTH}}$`).test( + code.toUpperCase(), + ); +} + +export function normalizeRoomCode(code: string): string { + return code.trim().toUpperCase(); +} diff --git a/apps/web-demo/app/lib/net/roomCodeStorage.ts b/apps/web-demo/app/lib/net/roomCodeStorage.ts new file mode 100644 index 0000000..c0b305d --- /dev/null +++ b/apps/web-demo/app/lib/net/roomCodeStorage.ts @@ -0,0 +1,12 @@ +import type { StoragePort } from "../platform-web"; + +export const ROOM_CODE_STORAGE_KEY = "pce:multiplayer:roomCode"; + +/** Persiste el último código de room (reutilizable al volver). */ +export function createRoomCodeStorage(storage: StoragePort) { + return { + load: (): string | null => storage.getItem(ROOM_CODE_STORAGE_KEY), + save: (code: string): void => storage.setItem(ROOM_CODE_STORAGE_KEY, code), + clear: (): void => storage.removeItem(ROOM_CODE_STORAGE_KEY), + }; +} diff --git a/apps/web-demo/app/lib/net/roomSession.ts b/apps/web-demo/app/lib/net/roomSession.ts new file mode 100644 index 0000000..f37d3e1 --- /dev/null +++ b/apps/web-demo/app/lib/net/roomSession.ts @@ -0,0 +1,58 @@ +import { generateRoomCode, normalizeRoomCode, isValidRoomCode } from "./roomCode"; +import { createRoomCodeStorage } from "./roomCodeStorage"; +import type { StoragePort } from "../platform-web"; + +export const MAX_PLAYERS = 4; + +export interface RoomSession { + /** Código activo (null si aún no se ha creado/unido). */ + getCode: () => string | null; + /** Crea una room nueva (genera código), la persiste y la devuelve. */ + create: () => string; + /** Une a un código existente (valida + persiste). Lanza si inválido. */ + join: (code: string) => string; + /** Reset: nueva room vacía (solo-play hasta que entre alguien). */ + reset: () => string; + /** Olvida el código persistido (empezar limpio). */ + forget: () => void; + /** Restaura el último código guardado, si existe. */ + restore: () => string | null; + capacity: number; + isFull: (remoteCount: number) => boolean; + freeSlots: (remoteCount: number) => number; +} + +export function createRoomSession( + storage: StoragePort, + rng: () => number = Math.random, +): RoomSession { + const persist = createRoomCodeStorage(storage); + let code: string | null = null; + const setCode = (c: string) => { + code = c; + persist.save(c); + return c; + }; + return { + getCode: () => code, + create: () => setCode(generateRoomCode(rng)), + join: (raw) => { + const c = normalizeRoomCode(raw); + if (!isValidRoomCode(c)) throw new Error(`código inválido: ${raw}`); + return setCode(c); + }, + reset: () => setCode(generateRoomCode(rng)), + forget: () => { + code = null; + persist.clear(); + }, + restore: () => { + code = persist.load(); + return code; + }, + capacity: MAX_PLAYERS, + // remoteCount = otros jugadores; +1 = yo + isFull: (remoteCount) => remoteCount + 1 >= MAX_PLAYERS, + freeSlots: (remoteCount) => Math.max(0, MAX_PLAYERS - (remoteCount + 1)), + }; +} diff --git a/apps/web-demo/app/multiplayer/page.tsx b/apps/web-demo/app/multiplayer/page.tsx new file mode 100644 index 0000000..9f3b162 --- /dev/null +++ b/apps/web-demo/app/multiplayer/page.tsx @@ -0,0 +1,79 @@ +"use client"; +import { useEffect, useMemo, useReducer, useRef, useState } from "react"; +import GameTouchCanvas from "../components/GameTouchCanvas"; +import { RemotePlayers } from "@pointclick-engine/engine-renderer-r3f"; +import { useSceneStore, generateRandomName } from "@pointclick-engine/engine-core"; +import { SCENES } from "../../demo-content/scenes/scenes"; +import type { GameSceneConfig } from "../lib/engine/publicApi"; +import { + createMultiplayerRuntime, + type MultiplayerRuntime, +} from "../lib/net/createMultiplayerRuntime"; +import { createRoomSession } from "../lib/net/roomSession"; +import { localStorageAdapter } from "../lib/platform-web"; +import { RoomLobby } from "../components/net/RoomLobby"; + +const HOST = process.env.NEXT_PUBLIC_PARTYKIT_HOST ?? "127.0.0.1:1999"; + +export default function MultiplayerPage() { + const room = useMemo(() => createRoomSession(localStorageAdapter), []); + const [code, setCode] = useState(null); + const [name, setName] = useState(() => generateRandomName()); + const mpRef = useRef(null); + const [, force] = useReducer((x: number) => x + 1, 0); + const sceneId = useSceneStore((s) => s.sceneId); + + useEffect(() => { + if (!code) return; + const mp = createMultiplayerRuntime({ + room: code, + host: HOST, + displayName: name, + scenes: Object.values(SCENES) as GameSceneConfig[], + }); + mpRef.current = mp; + const unsub = mp.remotePlayers.subscribe(() => force()); // refresca el contador N/4 + return () => { + unsub(); + mp.dispose(); + mpRef.current = null; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [code]); + + const remoteCount = mpRef.current?.remotePlayers.getInScene(sceneId).length ?? 0; + + return ( + <> + + ) : null + } + /> + setCode(room.create())} + onJoin={(c) => { + try { + setCode(room.join(c)); + } catch { + alert("código inválido"); + } + }} + onReset={() => setCode(room.reset())} + onRename={(n) => { + setName(n); + mpRef.current?.setName(n); + }} + /> + + ); +} diff --git a/apps/web-demo/app/store/placedItemsStore.ts b/apps/web-demo/app/store/placedItemsStore.ts index 401710d..e3d842a 100644 --- a/apps/web-demo/app/store/placedItemsStore.ts +++ b/apps/web-demo/app/store/placedItemsStore.ts @@ -5,6 +5,9 @@ import type { PlacedSceneItem } from "@pointclick-engine/engine-core"; type PlacedItemsStoreState = { items: PlacedSceneItem[]; setItems: (items: PlacedSceneItem[]) => void; + addItem: (item: PlacedSceneItem) => void; + removeItemByInteractionId: (interactionId: string) => void; + removeItemById: (id: string) => void; initialItemsCreated: boolean; markInitialItemsCreated: () => void; }; @@ -15,7 +18,16 @@ export const usePlacedItemsStore = create()( persist( (set) => ({ items: [], - setItems: (items: PlacedSceneItem[]) => set({ items }), + setItems: (items) => set({ items }), + addItem: (item) => + set((s) => { + if (s.items.some((i) => i.id === item.id)) return s; + return { items: [...s.items, item] }; + }), + removeItemByInteractionId: (interactionId) => + set((s) => ({ items: s.items.filter((i) => i.interactionId !== interactionId) })), + removeItemById: (id) => + set((s) => ({ items: s.items.filter((i) => i.id !== id) })), initialItemsCreated: false, markInitialItemsCreated: () => set({ initialItemsCreated: true }), }), diff --git a/apps/web-demo/package.json b/apps/web-demo/package.json index c951a4c..8bcb9c3 100644 --- a/apps/web-demo/package.json +++ b/apps/web-demo/package.json @@ -8,7 +8,9 @@ "build": "next build", "start": "next start", "lint": "eslint", - "test": "vitest run" + "test": "vitest run", + "party:dev": "partykit dev", + "party:deploy": "partykit deploy" }, "dependencies": { "@pointclick-engine/engine-core": "file:../../packages/engine-core", @@ -20,6 +22,7 @@ "gsap": "^3.15.0", "next": "16.2.6", "nipplejs": "^1.0.2", + "partykit": "^0.0.115", "pixelarticons": "^2.1.0", "react": "19.2.4", "react-dom": "19.2.4", diff --git a/apps/web-demo/party/multiplayer.ts b/apps/web-demo/party/multiplayer.ts new file mode 100644 index 0000000..692c42e --- /dev/null +++ b/apps/web-demo/party/multiplayer.ts @@ -0,0 +1,97 @@ +import type * as Party from "partykit/server"; + +const MAX_PLAYERS = 4; + +/** + * Servidor PartyKit de la partida. Es la autoridad: mantiene el snapshot del + * world, hace fan-out de los sobres y aplica el límite de capacidad (4). + * + * room.id = código de partida (ver app/lib/net/roomSession.ts). + */ +export default class MultiplayerServer implements Party.Server { + /** Snapshot LWW del world (registros `${entityId}:${field}` → {value,ts,by}). */ + private world: Record = {}; + + constructor(readonly room: Party.Room) {} + + onConnect(conn: Party.Connection) { + const count = [...this.room.getConnections()].length; // incluye a `conn` + if (count > MAX_PLAYERS) { + conn.send( + JSON.stringify({ + kind: "status", + payload: { state: "disconnected", reason: "room-full" }, + }), + ); + conn.close(); + return; + } + // 1) snapshot del world 2) status connected con el id asignado + conn.send( + JSON.stringify({ + v: 1, + room: this.room.id, + from: "server", + ts: Date.now(), + kind: "snapshot", + payload: this.world, + }), + ); + conn.send( + JSON.stringify({ + kind: "status", + payload: { state: "connected", selfId: conn.id }, + }), + ); + } + + onMessage(message: string, sender: Party.Connection) { + let env: { kind?: string; payload?: unknown }; + try { + env = JSON.parse(message); + } catch { + return; + } + if (env.kind === "event") this.applyWorldEvent(env.payload); + // fan-out a todos menos el emisor + this.room.broadcast(message, [sender.id]); + } + + onClose(conn: Party.Connection) { + this.room.broadcast( + JSON.stringify({ + v: 1, + room: this.room.id, + from: conn.id, + ts: Date.now(), + kind: "presence", + payload: { playerId: conn.id, __left: true }, + }), + ); + } + + /** Aplica un evento world al snapshot (item placed/removed). */ + private applyWorldEvent(payload: unknown) { + const e = payload as { + type?: string; + itemId?: string; + outcome?: string; + interactionId?: string; + placedItem?: unknown; + }; + if (e?.type === "item:dropped" && e.itemId) { + const key = `item:${e.itemId}:placed`; + if (e.outcome === "place") { + // Store full item data so late joiners can reconstruct the scene. + this.world[key] = { + value: true, + placedItem: e.placedItem, + ts: Date.now(), + by: "server", + }; + } else { + this.world[key] = { value: false, ts: Date.now(), by: "server" }; + } + } + } +} diff --git a/apps/web-demo/partykit.json b/apps/web-demo/partykit.json new file mode 100644 index 0000000..02bf6af --- /dev/null +++ b/apps/web-demo/partykit.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://www.partykit.io/schema.json", + "name": "pointclick-multiplayer", + "main": "party/multiplayer.ts", + "parties": { "multiplayer": "party/multiplayer.ts" }, + "compatibilityDate": "2024-01-01" +} diff --git a/docs/phases/phase-14-multiplayer/DEPLOY.md b/docs/phases/phase-14-multiplayer/DEPLOY.md new file mode 100644 index 0000000..15ee9e3 --- /dev/null +++ b/docs/phases/phase-14-multiplayer/DEPLOY.md @@ -0,0 +1,65 @@ +# Multiplayer — Runbook dev + prod (Vercel) + +El motor es agnóstico del transporte; el realtime corre en **PartyKit** (Cloudflare), no en +Vercel. Vercel solo sirve la app Next.js. El cliente apunta a PartyKit vía +`NEXT_PUBLIC_PARTYKIT_HOST` (inlined en build). + +## Variables de entorno + +| Var | Dev | Prod (Vercel) | +|-----|-----|---------------| +| `NEXT_PUBLIC_PARTYKIT_HOST` | `127.0.0.1:1999` (default si no se define) | `..partykit.dev` | + +> ⚠️ `NEXT_PUBLIC_*` se inyecta en **build time**. Debe estar configurada en Vercel **antes** de +> compilar, o el cliente caerá al fallback `127.0.0.1:1999` y no conectará en producción. + +## Dev (local) + +```bash +# Terminal 1 — servidor realtime +npm run party:dev -w apps/web-demo # PartyKit en http://127.0.0.1:1999 + +# Terminal 2 — Next.js +npm run dev -w apps/web-demo # http://localhost:3000 +``` + +Abre `http://localhost:3000/multiplayer` en dos pestañas → **Crear partida** en una, copia el +código, **Unirse** en la otra. Verás el avatar del otro moverse en la misma escena. + +✅ Verificado 2026-06-10: room `FK4CFY` creada, WS `GET /parties/multiplayer/FK4CFY 101 Switching +Protocols`, lobby `1/4` + nombre aleatorio + reset. + +## Prod (Vercel + PartyKit) + +### 1. Desplegar el servidor PartyKit (una vez + en cada cambio de `party/`) + +```bash +cd apps/web-demo +npx partykit login # primera vez (cuenta PartyKit/GitHub) +npx partykit deploy # usa partykit.json → name: "pointclick-multiplayer" +# → URL: https://pointclick-multiplayer..partykit.dev +``` + +### 2. Configurar Vercel + +- Project → Settings → **Environment Variables**: + `NEXT_PUBLIC_PARTYKIT_HOST = pointclick-multiplayer..partykit.dev` + (sin `https://`, sin barra final). Aplícala a Production (y Preview si quieres). +- **Root Directory**: `apps/web-demo` (monorepo). +- Re-deploy para que el valor se inyecte en el bundle. + +### 3. Verificar + +- `https://.vercel.app/multiplayer` en dos pestañas/dispositivos. +- `partysocket` usa **wss** automáticamente para hosts no-localhost. +- Logs del server: PartyKit dashboard o `npx partykit tail`. + +## Notas + +- El **límite de 4 jugadores** lo aplica el servidor (`party/multiplayer.ts`); el 5º recibe + `room-full`. +- Estado world **efímero** en memoria de la room (no persiste entre redeploys del DO). Persistencia + server-side = 2ª iteración. +- `next build` verificado EXIT 0 con `/multiplayer` como ruta estática (Vercel-ready). +- Bug conocido: `partykit dev` en **Windows** falla en versiones < 0.0.115 (`ERR_INVALID_URL`); + usa `partykit@^0.0.115`. diff --git a/docs/phases/phase-14-multiplayer/tasks/01-define-multiplayer-port.md b/docs/phases/phase-14-multiplayer/tasks/01-define-multiplayer-port.md index 651cd32..aa7e675 100644 --- a/docs/phases/phase-14-multiplayer/tasks/01-define-multiplayer-port.md +++ b/docs/phases/phase-14-multiplayer/tasks/01-define-multiplayer-port.md @@ -110,20 +110,22 @@ import type { * Bus en memoria que conecta varios HeadlessMultiplayerAdapter (simula la red * en tests). Reenvía cada `send` a los demás adapters de la MISMA room. */ +interface HubPeer { room: string; inbound: (m: NetEnvelope) => void; } + export class InMemoryHub { - private peers = new Map void>(); + private peers = new Map(); - register(id: PlayerId, inbound: (m: NetEnvelope) => void): () => void { - this.peers.set(id, inbound); + register(id: PlayerId, room: string, inbound: (m: NetEnvelope) => void): () => void { + this.peers.set(id, { room, inbound }); return () => this.peers.delete(id); } - /** Fan-out a todos menos al emisor (sin self-echo). */ - broadcast(from: PlayerId, room: string, message: NetEnvelope): void { - for (const [id, inbound] of this.peers) { + /** Fan-out a los peers de la MISMA room que el mensaje, menos al emisor. */ + broadcast(from: PlayerId, _room: string, message: NetEnvelope): void { + for (const [id, peer] of this.peers) { if (id === from) continue; - if (message.room !== room) continue; - inbound(message); + if (peer.room !== message.room) continue; // aísla por room del receptor + peer.inbound(message); } } } @@ -146,7 +148,7 @@ export class HeadlessMultiplayerAdapter implements MultiplayerPort { connect(opts: ConnectOptions): void { this.room = opts.room; this.connected = true; - this.unregister = this.hub.register(this.selfId, (m) => { + this.unregister = this.hub.register(this.selfId, this.room, (m) => { this.msgHandlers.forEach((h) => h(m)); }); this.emitStatus({ state: "connected", selfId: this.selfId }); diff --git a/docs/phases/phase-14-multiplayer/tracking.md b/docs/phases/phase-14-multiplayer/tracking.md index 54ae080..222d50d 100644 --- a/docs/phases/phase-14-multiplayer/tracking.md +++ b/docs/phases/phase-14-multiplayer/tracking.md @@ -3,30 +3,32 @@ > Marca `[x]` al completar. Detalle en cada task file. Ver `docs/workflow/how-to-track-tasks.md`. ## Week 1 — Fundamentos agnósticos (core) -- [ ] [01-define-multiplayer-port](tasks/01-define-multiplayer-port.md) -- [ ] [02-state-partition-classification](tasks/02-state-partition-classification.md) -- [ ] [03-player-identity-model](tasks/03-player-identity-model.md) +- [x] [01-define-multiplayer-port](tasks/01-define-multiplayer-port.md) +- [x] [02-state-partition-classification](tasks/02-state-partition-classification.md) +- [x] [03-player-identity-model](tasks/03-player-identity-model.md) ## Week 2 — Replicación -- [ ] [04-replication-engine](tasks/04-replication-engine.md) -- [ ] [06-presence-and-interest-management](tasks/06-presence-and-interest-management.md) +- [x] [04-replication-engine](tasks/04-replication-engine.md) +- [x] [06-presence-and-interest-management](tasks/06-presence-and-interest-management.md) ## Week 3 — Render + adapter PartyKit + rooms -- [ ] [07-renderer-remote-avatars](tasks/07-renderer-remote-avatars.md) -- [ ] [08-transport-adapters-and-vercel](tasks/08-transport-adapters-and-vercel.md) (PartyKit) +- [x] [07-renderer-remote-avatars](tasks/07-renderer-remote-avatars.md) +- [x] [08-transport-adapters-and-vercel](tasks/08-transport-adapters-and-vercel.md) (PartyKit) - [ ] [10-room-session-lifecycle](tasks/10-room-session-lifecycle.md) (código, join, solo, reset, cap 4) ## Week 4 — Autoridad y robustez (segunda iteración) -- [ ] [05-authority-and-conflict-resolution](tasks/05-authority-and-conflict-resolution.md) -- [ ] [09-optimistic-prediction-reconciliation](tasks/09-optimistic-prediction-reconciliation.md) +- [x] [05-authority-and-conflict-resolution](tasks/05-authority-and-conflict-resolution.md) +- [x] [09-optimistic-prediction-reconciliation](tasks/09-optimistic-prediction-reconciliation.md) ## Week 5 — Demo /multiplayer + validación -- [ ] [11-demo-multiplayer-route](tasks/11-demo-multiplayer-route.md) (ruta `/multiplayer`, lobby, RemotePlayers) -- [ ] [12-validation-gate](tasks/12-validation-gate.md) (tests + build + agnosticismo + escenarios A–E) +- [x] [11-demo-multiplayer-route](tasks/11-demo-multiplayer-route.md) (ruta `/multiplayer`, lobby, RemotePlayers) +- [~] [12-validation-gate](tasks/12-validation-gate.md) — gate automático ✅; escenarios two-tab E2E pendientes de run manual --- ## Gates -- [ ] Test de agnosticismo del core verde (sin imports de red/`window`/localStorage) -- [ ] No regresión single-player (suite actual pasa sin `MultiplayerPort`) -- [ ] Demo two-tab manual: presence + world + claim + room (código/solo/reset/cap 4) verificados +- [x] Test de agnosticismo del core verde (sin imports de red/`window`/localStorage) +- [x] No regresión single-player (suite actual pasa sin `MultiplayerPort`; fallos web-demo son pre-existentes) +- [ ] Demo two-tab manual: presence + room (código/solo/reset/cap 4) — pendiente de run con `party:dev` + +> Estado: **MVP implementado** (tasks 01–11). Ver `validation-report.md`. 273 tests core + 6 roomSession verdes. diff --git a/docs/phases/phase-14-multiplayer/validation-report.md b/docs/phases/phase-14-multiplayer/validation-report.md new file mode 100644 index 0000000..26888ee --- /dev/null +++ b/docs/phases/phase-14-multiplayer/validation-report.md @@ -0,0 +1,66 @@ +# Phase 14 — Validation Report (MVP implementation) + +**Fecha**: 2026-06-10 | **Rama**: `claude/phase-14-multiplayer-impl` + +## Alcance implementado + +MVP de presencia en tiempo real + rooms por código, end-to-end. Tasks 01–11 implementadas; +task 12 (gate) parcialmente: gate automático ✅, escenarios E2E de dos pestañas pendientes de run +manual (requieren `partykit dev` + navegador, no verificable headless). + +## Gate automático + +| Check | Resultado | +|-------|-----------| +| `npm test -w packages/engine-core` | ✅ **273 passed** (37 files) — +29 tests nuevos sobre baseline 244 | +| `npm test -w apps/web-demo` (roomSession) | ✅ **6/6** roomSession | +| Test de agnosticismo del core (`netAgnosticism.test.ts`) | ✅ verde (game/net + ports/multiplayer sin red/`window`/`localStorage`) | +| `npm run build -w packages/engine-core` (tsc) | ✅ | +| `npm run build -w packages/engine-renderer-r3f` (tsc) | ✅ | +| `tsc --noEmit` app — **archivos nuevos** | ✅ **0 errores** en `lib/net/`, `multiplayer/`, `party/`, `components/net/`, `RemotePlayers` | +| No regresión single-player | ✅ los únicos fallos (4 en `publicApi.test.ts`, 10 errores tsc en 2 test files) **pre-existen en main** (verificado por stash) | + +### Tests nuevos del core (29) +`multiplayerPort` (4), `eventClassification` (5), `remotePlayersStore` (4), `worldStateLww` (3), +`itemClaim` (3), `presence`/throttle (1), `optimisticReconcile` (3), `multiplayerSession` (5 — +replica world, bloquea private, anti-eco, presence join, propaga posición), `netAgnosticism` (1). + +## Verificación en vivo (2026-06-10) + +Servidores levantados: PartyKit (`:1999`) + Next.js (`:3000`). + +| # | Escenario | Estado | +|---|-----------|--------| +| — | Ruta `/multiplayer` renderiza (juego + RoomLobby) | ✅ verificado (screenshot) | +| — | Crear room → WebSocket a PartyKit | ✅ `GET /parties/multiplayer/FK4CFY 101 Switching Protocols` | +| E | Room lifecycle: código `FK4CFY`, `Jugadores 1/4`, "Faltan 3", nombre `Vecino-B9F`, reset | ✅ verificado (lobby) | +| A | Presence por escena (2 avatares moviéndose) | ⏳ requiere 2ª pestaña interactiva — lógica cubierta por `multiplayerSession.test.ts` | +| B/C/D | Puerta/llave/claim world compartido | ⏳ 2ª iteración (item-sync completo) | + +### Prod build (Vercel-ready) + +- `next build` → **EXIT 0**; `/multiplayer` listada como ruta estática prerenderizada. +- TypeScript de build OK (los 10 errores tsc pre-existentes están en test files, no bloquean el build). +- Despliegue documentado en `DEPLOY.md` (PartyKit deploy + `NEXT_PUBLIC_PARTYKIT_HOST` en Vercel). + +### Issue dev no bloqueante + +- Hydration mismatch en `InventoryUI` (`aria-label` i18n: clave cruda en SSR vs traducida en cliente). + **Pre-existente** (también en `/`), no relacionado con multijugador. Solo warning de dev. + +## Notas de implementación (desviaciones del plan) + +- `InMemoryHub.register(id, room, inbound)` lleva la room del peer y `broadcast` filtra por + `message.room` (aísla rooms correctamente). El doc de task 01 se actualizó para reflejarlo. +- Deps añadidas a `apps/web-demo`: `partysocket` (cliente) y `partykit` (servidor/CLI). +- E2E real (dos navegadores) no es verificable en entorno headless; queda documentado el comando. + +## Cómo probar en local + +```bash +# Terminal 1: servidor PartyKit +npm run party:dev -w apps/web-demo +# Terminal 2: Next +npm run dev -w apps/web-demo +# Abre http://localhost:3000/multiplayer en dos pestañas → Crear / Unirse con el código +``` diff --git a/package-lock.json b/package-lock.json index 0775e9c..dd3a168 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "gsap": "^3.15.0", "next": "16.2.6", "nipplejs": "^1.0.2", + "partykit": "^0.0.115", "pixelarticons": "^2.1.0", "react": "19.2.4", "react-dom": "19.2.4", @@ -284,6 +285,114 @@ "node": ">=6.9.0" } }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20240718.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20240718.0.tgz", + "integrity": "sha512-BsPZcSCgoGnufog2GIgdPuiKicYTNyO/Dp++HbpLRH+yQdX3x4aWx83M+a0suTl1xv76dO4g9aw7SIB6OSgIyQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20240718.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20240718.0.tgz", + "integrity": "sha512-nlr4gaOO5gcJerILJQph3+2rnas/nx/lYsuaot1ntHu4LAPBoQo1q/Pucj2cSIav4UiMzTbDmoDwPlls4Kteog==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20240718.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20240718.0.tgz", + "integrity": "sha512-LJ/k3y47pBcjax0ee4K+6ZRrSsqWlfU4lbU8Dn6u5tSC9yzwI4YFNXDrKWInB0vd7RT3w4Yqq1S6ZEbfRrqVUg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20240718.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20240718.0.tgz", + "integrity": "sha512-zBEZvy88EcAMGRGfuVtS00Yl7lJdUM9sH7i651OoL+q0Plv9kphlCC0REQPwzxrEYT1qibSYtWcD9IxQGgx2/g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20240718.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20240718.0.tgz", + "integrity": "sha512-YpCRvvT47XanFum7C3SedOZKK6BfVhqmwdAAVAQFyc4gsCdegZo0JkUkdloC/jwuWlbCACOG2HTADHOqyeolzQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workers-types": { + "version": "4.20240718.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240718.0.tgz", + "integrity": "sha512-7RqxXIM9HyhjfZ9ztXjITuc7mL0w4s+zXgypqKmMuvuObC3DgXutJ3bOYbQ+Ss5QbywrzWSNMlmGdL/ldg/yZg==", + "license": "MIT OR Apache-2.0" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@dimforge/rapier3d-compat": { "version": "0.19.2", "license": "Apache-2.0" @@ -880,6 +989,15 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/@humanfs/core": { "version": "0.19.2", "dev": true, @@ -981,7 +1099,6 @@ }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -989,7 +1106,6 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -2445,7 +2561,6 @@ }, "node_modules/acorn": { "version": "8.16.0", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -2462,6 +2577,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.15.0", "dev": true, @@ -2648,6 +2775,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/as-table": { + "version": "1.0.55", + "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", + "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", + "license": "MIT", + "dependencies": { + "printable-characters": "^1.0.42" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "dev": true, @@ -2902,6 +3038,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/capnp-ts": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/capnp-ts/-/capnp-ts-0.7.0.tgz", + "integrity": "sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.1", + "tslib": "^2.2.0" + } + }, "node_modules/chai": { "version": "5.3.3", "dev": true, @@ -2944,6 +3090,23 @@ "version": "0.0.1", "license": "MIT" }, + "node_modules/clipboardy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-4.0.0.tgz", + "integrity": "sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==", + "license": "MIT", + "dependencies": { + "execa": "^8.0.1", + "is-wsl": "^3.1.0", + "is64bit": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/color-convert": { "version": "2.0.1", "dev": true, @@ -2965,11 +3128,26 @@ "dev": true, "license": "MIT" }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, "node_modules/convert-source-map": { "version": "2.0.0", "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cross-env": { "version": "7.0.3", "license": "MIT", @@ -3007,6 +3185,12 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/data-uri-to-buffer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", + "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", + "license": "MIT" + }, "node_modules/data-view-buffer": { "version": "1.0.2", "dev": true, @@ -3057,7 +3241,6 @@ }, "node_modules/debug": { "version": "4.4.3", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -3116,6 +3299,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "license": "MIT" + }, "node_modules/detect-gpu": { "version": "5.0.70", "license": "MIT", @@ -3780,6 +3969,41 @@ "node": ">=0.10.0" } }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit-hook": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", + "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/expect-type": { "version": "1.3.0", "dev": true, @@ -3929,7 +4153,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -4026,6 +4249,28 @@ "node": ">= 0.4" } }, + "node_modules/get-source": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", + "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", + "license": "Unlicense", + "dependencies": { + "data-uri-to-buffer": "^2.0.0", + "source-map": "^0.6.1" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-symbol-description": { "version": "1.1.0", "dev": true, @@ -4064,6 +4309,12 @@ "node": ">=10.13.0" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" + }, "node_modules/globals": { "version": "14.0.0", "dev": true, @@ -4211,6 +4462,15 @@ "version": "1.6.16", "license": "Apache-2.0" }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "funding": [ @@ -4415,6 +4675,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "dev": true, @@ -4466,6 +4741,24 @@ "node": ">=0.10.0" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-map": { "version": "2.0.3", "dev": true, @@ -4557,6 +4850,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-string": { "version": "1.1.1", "dev": true, @@ -4642,6 +4947,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is64bit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is64bit/-/is64bit-2.0.0.tgz", + "integrity": "sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==", + "license": "MIT", + "dependencies": { + "system-architecture": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "2.0.5", "dev": true, @@ -5124,6 +5459,12 @@ "node": ">= 0.4" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, "node_modules/merge2": { "version": "1.4.1", "dev": true, @@ -5166,6 +5507,53 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/miniflare": { + "version": "3.20240718.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240718.0.tgz", + "integrity": "sha512-TKgSeyqPBeT8TBLxbDJOKPWlq/wydoJRHjAyDdgxbw59N6wbP8JucK6AU1vXCfu21eKhrEin77ssXOpbfekzPA==", + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "acorn": "^8.8.0", + "acorn-walk": "^8.2.0", + "capnp-ts": "^0.7.0", + "exit-hook": "^2.2.1", + "glob-to-regexp": "^0.4.1", + "stoppable": "^1.1.0", + "undici": "^5.28.4", + "workerd": "1.20240718.0", + "ws": "^8.17.1", + "youch": "^3.2.2", + "zod": "^3.22.3" + }, + "bin": { + "miniflare": "bootstrap.js" + }, + "engines": { + "node": ">=16.13" + } + }, + "node_modules/miniflare/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/minimatch": { "version": "3.1.5", "dev": true, @@ -5185,11 +5573,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, "node_modules/ms": { "version": "2.1.3", - "dev": true, "license": "MIT" }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/nanoid": { "version": "3.3.12", "funding": [ @@ -5331,17 +5739,44 @@ "node": ">=18" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "dev": true, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/object-inspect": { - "version": "1.13.4", - "dev": true, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5438,6 +5873,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ohash": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.6.tgz", + "integrity": "sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg==", + "license": "MIT" + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "dev": true, @@ -5509,6 +5965,433 @@ "node": ">=6" } }, + "node_modules/partykit": { + "version": "0.0.115", + "resolved": "https://registry.npmjs.org/partykit/-/partykit-0.0.115.tgz", + "integrity": "sha512-WHmJIZsAzRWrm1lrtU7wcl0tpD4rg2vgmn4+hXGPjU4mnXgFxyjVq2ZzO547xRXa1IbETOP6J9INl/ergR99bA==", + "license": "MIT", + "dependencies": { + "@cloudflare/workers-types": "4.20240718.0", + "clipboardy": "4.0.0", + "esbuild": "0.21.5", + "miniflare": "3.20240718.0", + "ts-dedent": "^2.2.0", + "unenv": "2.0.0-rc.0", + "yoga-wasm-web": "0.3.3" + }, + "bin": { + "partykit": "dist/bin.mjs" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + } + }, + "node_modules/partykit/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/partykit/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "node_modules/path-exists": { "version": "4.0.0", "dev": true, @@ -5531,7 +6414,6 @@ }, "node_modules/pathe": { "version": "2.0.3", - "dev": true, "license": "MIT" }, "node_modules/pathval": { @@ -5567,6 +6449,17 @@ "react": ">=16" } }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, "node_modules/playwright": { "version": "1.60.0", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", @@ -5603,7 +6496,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -5661,6 +6553,12 @@ "node": ">= 0.8.0" } }, + "node_modules/printable-characters": { + "version": "1.0.42", + "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", + "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", + "license": "Unlicense" + }, "node_modules/promise-worker-transferable": { "version": "1.0.4", "license": "Apache-2.0", @@ -6153,6 +7051,27 @@ "dev": true, "license": "ISC" }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "license": "BSD-3-Clause", @@ -6170,6 +7089,16 @@ "dev": true, "license": "MIT" }, + "node_modules/stacktracey": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.2.0.tgz", + "integrity": "sha512-ETyQEz+CzXiLjEbyJqpbp+/T79RQD/6wqFucRBIlVNZfYq2Ay7wbretD4cxpbymZlaPWx58aIhPEY1Cr8DlVvg==", + "license": "Unlicense", + "dependencies": { + "as-table": "^1.0.36", + "get-source": "^2.0.12" + } + }, "node_modules/stats-gl": { "version": "2.4.2", "license": "MIT", @@ -6207,6 +7136,16 @@ "node": ">= 0.4" } }, + "node_modules/stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "license": "MIT", + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, "node_modules/string.prototype.includes": { "version": "2.0.1", "dev": true, @@ -6316,6 +7255,18 @@ "node": ">=4" } }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "dev": true, @@ -6388,6 +7339,18 @@ "react": ">=17.0" } }, + "node_modules/system-architecture": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/system-architecture/-/system-architecture-0.1.0.tgz", + "integrity": "sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tailwindcss": { "version": "4.3.0", "dev": true, @@ -6530,6 +7493,15 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "dev": true, @@ -6704,6 +7676,12 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "license": "MIT" + }, "node_modules/unbox-primitive": { "version": "1.1.0", "dev": true, @@ -6721,11 +7699,42 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/undici-types": { "version": "6.21.0", "dev": true, "license": "MIT" }, + "node_modules/unenv": { + "version": "2.0.0-rc.0", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.0.tgz", + "integrity": "sha512-H0kl2w8jFL/FAk0xvjVing4bS3jd//mbg1QChDnn58l9Sc5RtduaKmLAL8n+eBw5jJo8ZjYV7CrEGage5LAOZQ==", + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "mlly": "^1.7.4", + "ohash": "^1.1.4", + "pathe": "^1.1.2", + "ufo": "^1.5.4" + } + }, + "node_modules/unenv/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "license": "MIT" + }, "node_modules/unrs-resolver": { "version": "1.12.2", "dev": true, @@ -7112,6 +8121,47 @@ "node": ">=0.10.0" } }, + "node_modules/workerd": { + "version": "1.20240718.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20240718.0.tgz", + "integrity": "sha512-w7lOLRy0XecQTg/ujTLWBiJJuoQvzB3CdQ6/8Wgex3QxFhV9Pbnh3UbwIuUfMw3OCCPQc4o7y+1P+mISAgp6yg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20240718.0", + "@cloudflare/workerd-darwin-arm64": "1.20240718.0", + "@cloudflare/workerd-linux-64": "1.20240718.0", + "@cloudflare/workerd-linux-arm64": "1.20240718.0", + "@cloudflare/workerd-windows-64": "1.20240718.0" + } + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yallist": { "version": "3.1.1", "dev": true, @@ -7128,6 +8178,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoga-wasm-web": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz", + "integrity": "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==", + "license": "MIT" + }, + "node_modules/youch": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.4.tgz", + "integrity": "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==", + "license": "MIT", + "dependencies": { + "cookie": "^0.7.1", + "mustache": "^4.2.0", + "stacktracey": "^2.1.8" + } + }, "node_modules/zod": { "version": "4.4.3", "dev": true, diff --git a/packages/engine-core/dist/game/commands/types.d.ts b/packages/engine-core/dist/game/commands/types.d.ts index b411cc8..e1f402d 100644 --- a/packages/engine-core/dist/game/commands/types.d.ts +++ b/packages/engine-core/dist/game/commands/types.d.ts @@ -57,6 +57,15 @@ export type GameCommand = { type: "i18n:registerDictionary"; locale: Locale; dict: DialogDictionary; +} | { + type: "net:join"; + room: string; + displayName?: string; +} | { + type: "net:leave"; +} | { + type: "net:setName"; + displayName: string; }; export type GameCommandType = GameCommand["type"]; export {}; diff --git a/packages/engine-core/dist/game/commands/types.d.ts.map b/packages/engine-core/dist/game/commands/types.d.ts.map index ad62c13..960689c 100644 --- a/packages/engine-core/dist/game/commands/types.d.ts.map +++ b/packages/engine-core/dist/game/commands/types.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/game/commands/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGlF,KAAK,eAAe,GAAG,QAAQ,GAAG,aAAa,CAAC;AAEhD;;;GAGG;AACH,MAAM,MAAM,WAAW,GAEnB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,GAEzB;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GACvB;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,GAE7C;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE,GAC5B;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAE7D;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,GAE1B;IAAE,IAAI,EAAE,qBAAqB,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAEnD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,aAAa,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACtF;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GACnE;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,MAAM,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAEpE;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,yBAAyB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAEhF,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC"} \ No newline at end of file +{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/game/commands/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGlF,KAAK,eAAe,GAAG,QAAQ,GAAG,aAAa,CAAC;AAEhD;;;GAGG;AACH,MAAM,MAAM,WAAW,GAEnB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,GAEzB;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GACvB;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,GAE7C;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE,GAC5B;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAE7D;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,GAE1B;IAAE,IAAI,EAAE,qBAAqB,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAEnD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,aAAa,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACtF;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GACnE;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,MAAM,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAEpE;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,yBAAyB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,GAE3E;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjD,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/events/legacyAdapter.d.ts.map b/packages/engine-core/dist/game/events/legacyAdapter.d.ts.map index cb85333..d384ff8 100644 --- a/packages/engine-core/dist/game/events/legacyAdapter.d.ts.map +++ b/packages/engine-core/dist/game/events/legacyAdapter.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"legacyAdapter.d.ts","sourceRoot":"","sources":["../../../src/game/events/legacyAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,EAAE,EAAE,YAAY,GAAG,SAAS,CAyBzE;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,EAAE,EAAE,SAAS,GAAG,YAAY,GAAG,IAAI,CAuBhF"} \ No newline at end of file +{"version":3,"file":"legacyAdapter.d.ts","sourceRoot":"","sources":["../../../src/game/events/legacyAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,EAAE,EAAE,YAAY,GAAG,SAAS,CA0BzE;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,EAAE,EAAE,SAAS,GAAG,YAAY,GAAG,IAAI,CAuBhF"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/events/legacyAdapter.js b/packages/engine-core/dist/game/events/legacyAdapter.js index f6ac1e6..9845bff 100644 --- a/packages/engine-core/dist/game/events/legacyAdapter.js +++ b/packages/engine-core/dist/game/events/legacyAdapter.js @@ -19,6 +19,7 @@ export function legacyRuntimeEventToGameEvent(ev) { itemId: ev.itemId, outcome: ev.outcome, interactionId: ev.interactionId, + ...(ev.placedItem ? { placedItem: ev.placedItem } : {}), }; case "onDialog": return { diff --git a/packages/engine-core/dist/game/events/legacyAdapter.js.map b/packages/engine-core/dist/game/events/legacyAdapter.js.map index 1a76f12..db49dc2 100644 --- a/packages/engine-core/dist/game/events/legacyAdapter.js.map +++ b/packages/engine-core/dist/game/events/legacyAdapter.js.map @@ -1 +1 @@ -{"version":3,"file":"legacyAdapter.js","sourceRoot":"","sources":["../../../src/game/events/legacyAdapter.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAAC,EAAgB;IAC5D,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;QAChB,KAAK,QAAQ;YACX,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC;QAC5E,KAAK,WAAW;YACd,OAAO;gBACL,IAAI,EAAE,iBAAiB;gBACvB,MAAM,EAAE,EAAE,CAAC,MAAM;gBACjB,QAAQ,EAAE,EAAE,CAAC,QAAQ;aACtB,CAAC;QACJ,KAAK,QAAQ;YACX,OAAO;gBACL,IAAI,EAAE,cAAc;gBACpB,MAAM,EAAE,EAAE,CAAC,MAAM;gBACjB,OAAO,EAAE,EAAE,CAAC,OAAyC;gBACrD,aAAa,EAAE,EAAE,CAAC,aAAa;aAChC,CAAC;QACJ,KAAK,UAAU;YACb,OAAO;gBACL,IAAI,EAAE,kBAAkB;gBACxB,IAAI,EAAE,EAAE,CAAC,IAAI;gBACb,SAAS,EAAE,EAAE,CAAC,SAAS;gBACvB,MAAM,EAAE,EAAE,CAAC,MAAM;aAClB,CAAC;IACN,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,6BAA6B,CAAC,EAAa;IACzD,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;QAChB,KAAK,cAAc;YACjB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC;QACtE,KAAK,iBAAiB;YACpB,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE,CAAC;QACzE,KAAK,cAAc;YACjB,OAAO;gBACL,IAAI,EAAE,QAAQ;gBACd,MAAM,EAAE,EAAE,CAAC,MAAM;gBACjB,OAAO,EAAE,EAAE,CAAC,OAAO;gBACnB,aAAa,EAAE,EAAE,CAAC,aAAa;aAChC,CAAC;QACJ,KAAK,kBAAkB;YACrB,OAAO;gBACL,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,EAAE,CAAC,IAAI;gBACb,SAAS,EAAE,EAAE,CAAC,SAAS;gBACvB,MAAM,EAAE,EAAE,CAAC,MAA4C;aACxD,CAAC;QACJ;YACE,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC"} \ No newline at end of file +{"version":3,"file":"legacyAdapter.js","sourceRoot":"","sources":["../../../src/game/events/legacyAdapter.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAAC,EAAgB;IAC5D,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;QAChB,KAAK,QAAQ;YACX,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC;QAC5E,KAAK,WAAW;YACd,OAAO;gBACL,IAAI,EAAE,iBAAiB;gBACvB,MAAM,EAAE,EAAE,CAAC,MAAM;gBACjB,QAAQ,EAAE,EAAE,CAAC,QAAQ;aACtB,CAAC;QACJ,KAAK,QAAQ;YACX,OAAO;gBACL,IAAI,EAAE,cAAc;gBACpB,MAAM,EAAE,EAAE,CAAC,MAAM;gBACjB,OAAO,EAAE,EAAE,CAAC,OAA4D;gBACxE,aAAa,EAAE,EAAE,CAAC,aAAa;gBAC/B,GAAG,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACxD,CAAC;QACJ,KAAK,UAAU;YACb,OAAO;gBACL,IAAI,EAAE,kBAAkB;gBACxB,IAAI,EAAE,EAAE,CAAC,IAAI;gBACb,SAAS,EAAE,EAAE,CAAC,SAAS;gBACvB,MAAM,EAAE,EAAE,CAAC,MAAM;aAClB,CAAC;IACN,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,6BAA6B,CAAC,EAAa;IACzD,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;QAChB,KAAK,cAAc;YACjB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC;QACtE,KAAK,iBAAiB;YACpB,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE,CAAC;QACzE,KAAK,cAAc;YACjB,OAAO;gBACL,IAAI,EAAE,QAAQ;gBACd,MAAM,EAAE,EAAE,CAAC,MAAM;gBACjB,OAAO,EAAE,EAAE,CAAC,OAAO;gBACnB,aAAa,EAAE,EAAE,CAAC,aAAa;aAChC,CAAC;QACJ,KAAK,kBAAkB;YACrB,OAAO;gBACL,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,EAAE,CAAC,IAAI;gBACb,SAAS,EAAE,EAAE,CAAC,SAAS;gBACvB,MAAM,EAAE,EAAE,CAAC,MAA4C;aACxD,CAAC;QACJ;YACE,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/events/types.d.ts b/packages/engine-core/dist/game/events/types.d.ts index e00bd18..4f2d6a4 100644 --- a/packages/engine-core/dist/game/events/types.d.ts +++ b/packages/engine-core/dist/game/events/types.d.ts @@ -1,4 +1,6 @@ -import type { AudioSettings, GameScene, GameVec3, Locale, SoundCategory } from "../types"; +import type { AudioSettings, GameScene, GameVec3, Locale, PlacedSceneItem, SoundCategory } from "../types"; +import type { PlayerDescriptor } from "../net/playerIdentity"; +import type { ConnectionStatus, PlayerId } from "../../ports/multiplayer"; /** * Union exhaustiva de todos los eventos que el motor puede emitir. * Naming: : (ver ADR-0006). @@ -25,8 +27,10 @@ export type GameEvent = { } | { type: "item:dropped"; itemId: string; - outcome: "consume" | "place" | "return"; + outcome: "consume" | "place" | "return" | "pickup-success"; interactionId?: string; + /** Full placed-item data; present only for outcome "place" — used by peers to add the item to their scene. */ + placedItem?: PlacedSceneItem; } | { type: "dialog:triggered"; text: string; @@ -84,6 +88,15 @@ export type GameEvent = { type: "i18n:dictionaryUpdated"; locale: Locale; keysAdded: number; +} | { + type: "net:playerJoined"; + player: PlayerDescriptor; +} | { + type: "net:playerLeft"; + playerId: PlayerId; +} | { + type: "net:status"; + status: ConnectionStatus; }; export type GameEventType = GameEvent["type"]; export type GameEventHandler = (event: Extract= the physics collider halfX (0.55) minus the +// obstacle padding (0.6) so the net clearance from obstacle surface is > 0. +const PLAYER_PATH_HALF_WIDTH = 0.28; const NEIGHBOR_OFFSETS = [ [-1, -1], [-1, 0], @@ -26,7 +32,7 @@ export function findPath({ start, goal, bounds, walls, interactions, cellSize = .filter((interaction) => interaction.hasCollision) .map((interaction) => toObstacle(interaction.position[0], interaction.position[2], interaction.halfSize[0], interaction.halfSize[2], interaction.rotationY ?? 0, interactionPadding)), ]; - if (isSegmentClear(start, goal, bounds, obstacles, segmentSampleStep)) { + if (isThickSegmentClear(start, goal, bounds, obstacles, segmentSampleStep)) { return [goal]; } const width = Math.max(1, Math.floor((bounds.maxX - bounds.minX) / cellSize) + 1); @@ -260,7 +266,7 @@ function smoothPath(points, bounds, obstacles, segmentSampleStep) { while (anchorIndex < points.length - 1) { let nextIndex = points.length - 1; while (nextIndex > anchorIndex + 1) { - if (isSegmentClear(points[anchorIndex], points[nextIndex], bounds, obstacles, segmentSampleStep)) { + if (isThickSegmentClear(points[anchorIndex], points[nextIndex], bounds, obstacles, segmentSampleStep)) { break; } nextIndex -= 1; @@ -297,6 +303,27 @@ function findNearestOpenCell(cell, width, height, blocked) { } return null; } +/** + * Like isSegmentClear but also checks two paths offset perpendicular to the + * movement direction by PLAYER_PATH_HALF_WIDTH. This prevents path smoothing + * from creating shortcuts that clip obstacle corners: even if the centre line + * is clear, a shortcut is only allowed when the full swept width of the player + * would fit without touching a padded obstacle boundary. + */ +function isThickSegmentClear(start, goal, bounds, obstacles, segmentSampleStep) { + if (!isSegmentClear(start, goal, bounds, obstacles, segmentSampleStep)) { + return false; + } + const dx = goal.x - start.x; + const dz = goal.z - start.z; + const len = Math.hypot(dx, dz); + if (len < 0.001) + return true; + const perpX = (-dz / len) * PLAYER_PATH_HALF_WIDTH; + const perpZ = (dx / len) * PLAYER_PATH_HALF_WIDTH; + return (isSegmentClear({ x: start.x + perpX, z: start.z + perpZ }, { x: goal.x + perpX, z: goal.z + perpZ }, bounds, obstacles, segmentSampleStep) && + isSegmentClear({ x: start.x - perpX, z: start.z - perpZ }, { x: goal.x - perpX, z: goal.z - perpZ }, bounds, obstacles, segmentSampleStep)); +} function isSegmentClear(start, goal, bounds, obstacles, segmentSampleStep) { const distance = Math.hypot(goal.x - start.x, goal.z - start.z); const samples = Math.max(1, Math.ceil(distance / segmentSampleStep)); diff --git a/packages/engine-core/dist/game/logic/pathfinding/findPath.js.map b/packages/engine-core/dist/game/logic/pathfinding/findPath.js.map index 7a93b0f..5d37184 100644 --- a/packages/engine-core/dist/game/logic/pathfinding/findPath.js.map +++ b/packages/engine-core/dist/game/logic/pathfinding/findPath.js.map @@ -1 +1 @@ -{"version":3,"file":"findPath.js","sourceRoot":"","sources":["../../../../src/game/logic/pathfinding/findPath.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"findPath.js","sourceRoot":"","sources":["../../../../src/game/logic/pathfinding/findPath.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/MultiplayerSession.d.ts b/packages/engine-core/dist/game/net/MultiplayerSession.d.ts new file mode 100644 index 0000000..1da5807 --- /dev/null +++ b/packages/engine-core/dist/game/net/MultiplayerSession.d.ts @@ -0,0 +1,40 @@ +import type { GameEvent, GameEventType } from "../events/types"; +import type { MultiplayerPort, RoomId } from "../../ports/multiplayer"; +import type { PlayerDescriptor } from "./playerIdentity"; +import type { RemotePlayersStore } from "./remotePlayersStore"; +import { type ThrottleClock } from "./throttle"; +import type { OptimisticReconciler } from "./optimistic"; +/** Bus mínimo que la sesión necesita (lo cumple el handle de createGameRuntime). */ +export interface SessionBus { + on: (type: GameEventType, handler: (e: GameEvent) => void) => () => void; + emit: (event: GameEvent) => void; +} +export interface MultiplayerSessionOptions { + port: MultiplayerPort; + bus: SessionBus; + room: RoomId; + self: PlayerDescriptor; + remotePlayers: RemotePlayersStore; + /** Aplica un evento world remoto al estado local (el host lo cablea a los stores). */ + applyRemoteEvent: (event: GameEvent) => void; + /** Reloj lógico para `ts`. Default Date.now (task 05 puede inyectar HLC). */ + now?: () => number; + /** ms entre envíos de presence. Default 80 (~12.5 Hz). 0 = sin throttle. */ + presenceThrottleMs?: number; + /** Reloj/scheduler para el throttle (inyectable en tests). */ + clock?: ThrottleClock; + /** Reconciliador optimista (task 09) para claim-result/snapshot. */ + reconciler?: OptimisticReconciler; + /** Called once on join with the server's world snapshot so late joiners can hydrate shared state. */ + applySnapshot?: (world: Record) => void; + /** ms entre heartbeats para jugadores inactivos. Default 30 000 (30 s). 0 = sin heartbeat. */ + heartbeatMs?: number; +} +export interface MultiplayerSession { + dispose: () => void; + getSelf: () => PlayerDescriptor; + /** Actualiza el descriptor local y difunde presence (usado por setName/task 06). */ + updateSelf: (patch: Partial) => void; +} +export declare function createMultiplayerSession(opts: MultiplayerSessionOptions): MultiplayerSession; +//# sourceMappingURL=MultiplayerSession.d.ts.map \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/MultiplayerSession.d.ts.map b/packages/engine-core/dist/game/net/MultiplayerSession.d.ts.map new file mode 100644 index 0000000..fec8e28 --- /dev/null +++ b/packages/engine-core/dist/game/net/MultiplayerSession.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"MultiplayerSession.d.ts","sourceRoot":"","sources":["../../../src/game/net/MultiplayerSession.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,KAAK,EAAE,eAAe,EAAe,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEpF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE/D,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAClE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEzD,oFAAoF;AACpF,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,SAAS,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IACzE,IAAI,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,eAAe,CAAC;IACtB,GAAG,EAAE,UAAU,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,gBAAgB,CAAC;IACvB,aAAa,EAAE,kBAAkB,CAAC;IAClC,sFAAsF;IACtF,gBAAgB,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IAC7C,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,4EAA4E;IAC5E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,8DAA8D;IAC9D,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,oEAAoE;IACpE,UAAU,CAAC,EAAE,oBAAoB,CAAC;IAClC,qGAAqG;IACrG,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACzD,8FAA8F;IAC9F,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,gBAAgB,CAAC;IAChC,oFAAoF;IACpF,UAAU,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;CACxD;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,yBAAyB,GAC9B,kBAAkB,CAwHpB"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/MultiplayerSession.js b/packages/engine-core/dist/game/net/MultiplayerSession.js new file mode 100644 index 0000000..3c229b4 --- /dev/null +++ b/packages/engine-core/dist/game/net/MultiplayerSession.js @@ -0,0 +1,125 @@ +import { NET_PROTOCOL_VERSION } from "../../ports/multiplayer"; +import { EVENT_CLASSIFICATION, shouldReplicate, isWorld } from "./eventClassification"; +import { throttleTrailing } from "./throttle"; +export function createMultiplayerSession(opts) { + const { port, bus, room, remotePlayers, applyRemoteEvent } = opts; + const now = opts.now ?? (() => Date.now()); + let self = { ...opts.self }; + let applyingRemote = false; // ⟵ anti-bucle: true mientras aplicamos algo recibido + const wrap = (kind, payload) => ({ + v: NET_PROTOCOL_VERSION, + room, + from: self.playerId, + ts: now(), + kind, + payload, + }); + const rawSendPresence = () => { + self = { ...self, lastSeenTs: now() }; + port.send(wrap("presence", self)); + }; + const clock = opts.clock ?? { + now: () => Date.now(), + schedule: (cb, d) => { + setTimeout(cb, d); + }, + }; + const throttleMs = opts.presenceThrottleMs ?? 80; + const sendPresence = throttleMs > 0 ? throttleTrailing(rawSendPresence, throttleMs, clock) : rawSendPresence; + // ── Salida: suscribe a TODOS los tipos de evento ──────────────────────────── + const types = Object.keys(EVENT_CLASSIFICATION); + const unsubs = types.map((type) => bus.on(type, (event) => { + if (applyingRemote) + return; // no re-difundir lo aplicado desde la red + if (!shouldReplicate(type)) + return; // private → nunca sale + if (isWorld(type)) { + port.send(wrap("event", event)); + } + if (type === "player:moved") { + const e = event; + self = { ...self, position: e.position, action: e.action }; + sendPresence(); + } + else if (type === "scene:changed") { + self = { + ...self, + sceneId: event.sceneId, + }; + sendPresence(); + } + else if (type === "transition:completed") { + self = { + ...self, + sceneId: event + .toSceneId, + }; + sendPresence(); + } + })); + // ── Entrada: aplica envelopes remotos ─────────────────────────────────────── + const offMsg = port.onMessage((m) => { + if (m.from === self.playerId) + return; // defensivo (headless ya filtra self) + if (m.kind === "presence") { + const d = m.payload; + if (d.__left) { + remotePlayers.remove(d.playerId); + bus.emit({ type: "net:playerLeft", playerId: d.playerId }); + return; + } + const isNew = remotePlayers.getAll().every((p) => p.playerId !== d.playerId); + remotePlayers.upsert(d); + if (isNew) + bus.emit({ type: "net:playerJoined", player: d }); + } + else if (m.kind === "event") { + applyingRemote = true; + try { + applyRemoteEvent(m.payload); + } + finally { + applyingRemote = false; + } + } + else if (m.kind === "claim-result") { + const r = m.payload; + if (r.granted) + opts.reconciler?.confirm(r.entityId); + else + opts.reconciler?.reject(r.entityId); + } + else if (m.kind === "snapshot") { + opts.reconciler?.onSnapshot(); + opts.applySnapshot?.(m.payload); + } + }); + const offStatus = port.onStatus((status) => { + bus.emit({ type: "net:status", status }); + if (status.state === "connected" && status.selfId) { + self = { ...self, playerId: status.selfId }; + } + }); + port.connect({ room, selfId: self.playerId }); + rawSendPresence(); // anúnciate al entrar (inmediato, sin throttle) + // Heartbeat: mantiene la presence viva aunque el jugador esté inactivo. + // El receptor solo descartará al jugador si supera el stale threshold (p.ej. 10 min). + const heartbeatMs = opts.heartbeatMs ?? 30000; + const heartbeatTimer = heartbeatMs > 0 ? setInterval(rawSendPresence, heartbeatMs) : null; + return { + dispose: () => { + if (heartbeatTimer !== null) + clearInterval(heartbeatTimer); + unsubs.forEach((u) => u()); + offMsg(); + offStatus(); + port.disconnect(); + }, + getSelf: () => self, + updateSelf: (patch) => { + self = { ...self, ...patch }; + sendPresence(); + }, + }; +} +//# sourceMappingURL=MultiplayerSession.js.map \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/MultiplayerSession.js.map b/packages/engine-core/dist/game/net/MultiplayerSession.js.map new file mode 100644 index 0000000..e490124 --- /dev/null +++ b/packages/engine-core/dist/game/net/MultiplayerSession.js.map @@ -0,0 +1 @@ +{"version":3,"file":"MultiplayerSession.js","sourceRoot":"","sources":["../../../src/game/net/MultiplayerSession.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/claims.d.ts b/packages/engine-core/dist/game/net/claims.d.ts new file mode 100644 index 0000000..3ca1023 --- /dev/null +++ b/packages/engine-core/dist/game/net/claims.d.ts @@ -0,0 +1,17 @@ +import type { PlayerId } from "../../ports/multiplayer"; +export interface ItemClaim { + entityId: string; + by: PlayerId; + ts: number; +} +export interface ClaimResult { + entityId: string; + by: PlayerId; + granted: boolean; +} +/** + * Resuelve un conjunto de claims sobre el MISMO entityId (orden de llegada irrelevante). + * Gana el de menor ts (first-writer-wins); empate → menor playerId. Determinista. + */ +export declare function resolveClaim(claims: ItemClaim[]): ClaimResult[]; +//# sourceMappingURL=claims.d.ts.map \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/claims.d.ts.map b/packages/engine-core/dist/game/net/claims.d.ts.map new file mode 100644 index 0000000..379cfb5 --- /dev/null +++ b/packages/engine-core/dist/game/net/claims.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"claims.d.ts","sourceRoot":"","sources":["../../../src/game/net/claims.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAExD,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,QAAQ,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AACD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,QAAQ,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,WAAW,EAAE,CAQ/D"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/claims.js b/packages/engine-core/dist/game/net/claims.js new file mode 100644 index 0000000..3404c28 --- /dev/null +++ b/packages/engine-core/dist/game/net/claims.js @@ -0,0 +1,15 @@ +/** + * Resuelve un conjunto de claims sobre el MISMO entityId (orden de llegada irrelevante). + * Gana el de menor ts (first-writer-wins); empate → menor playerId. Determinista. + */ +export function resolveClaim(claims) { + if (claims.length === 0) + return []; + const winner = [...claims].sort((a, b) => a.ts - b.ts || (a.by < b.by ? -1 : 1))[0]; + return claims.map((c) => ({ + entityId: c.entityId, + by: c.by, + granted: c.by === winner.by, + })); +} +//# sourceMappingURL=claims.js.map \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/claims.js.map b/packages/engine-core/dist/game/net/claims.js.map new file mode 100644 index 0000000..e411b05 --- /dev/null +++ b/packages/engine-core/dist/game/net/claims.js.map @@ -0,0 +1 @@ +{"version":3,"file":"claims.js","sourceRoot":"","sources":["../../../src/game/net/claims.ts"],"names":[],"mappings":"AAaA;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,MAAmB;IAC9C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACnC,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC;IACrF,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACxB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,OAAO,EAAE,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,EAAE;KAC5B,CAAC,CAAC,CAAC;AACN,CAAC"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/clock.d.ts b/packages/engine-core/dist/game/net/clock.d.ts new file mode 100644 index 0000000..e575199 --- /dev/null +++ b/packages/engine-core/dist/game/net/clock.d.ts @@ -0,0 +1,9 @@ +export declare class Hlc { + private lastPhysical; + private counter; + /** Devuelve un timestamp codificado (number) monótono creciente. */ + now(wall?: number): number; + /** Actualiza el reloj al recibir un ts remoto (mantiene la causalidad). */ + update(remoteTs: number, wall?: number): number; +} +//# sourceMappingURL=clock.d.ts.map \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/clock.d.ts.map b/packages/engine-core/dist/game/net/clock.d.ts.map new file mode 100644 index 0000000..02d8f61 --- /dev/null +++ b/packages/engine-core/dist/game/net/clock.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"clock.d.ts","sourceRoot":"","sources":["../../../src/game/net/clock.ts"],"names":[],"mappings":"AAIA,qBAAa,GAAG;IACd,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,OAAO,CAAK;IAEpB,oEAAoE;IACpE,GAAG,CAAC,IAAI,GAAE,MAAmB,GAAG,MAAM;IAUtC,2EAA2E;IAC3E,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,GAAE,MAAmB,GAAG,MAAM;CAM5D"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/clock.js b/packages/engine-core/dist/game/net/clock.js new file mode 100644 index 0000000..db5190e --- /dev/null +++ b/packages/engine-core/dist/game/net/clock.js @@ -0,0 +1,28 @@ +/** HLC: combina reloj físico y contador lógico → orden total sin sincronizar relojes. */ +const COUNTER_BITS = 12; // hasta 4096 eventos por ms +const COUNTER_MASK = (1 << COUNTER_BITS) - 1; +export class Hlc { + constructor() { + this.lastPhysical = 0; + this.counter = 0; + } + /** Devuelve un timestamp codificado (number) monótono creciente. */ + now(wall = Date.now()) { + if (wall > this.lastPhysical) { + this.lastPhysical = wall; + this.counter = 0; + } + else { + this.counter = (this.counter + 1) & COUNTER_MASK; + } + return this.lastPhysical * (COUNTER_MASK + 1) + this.counter; + } + /** Actualiza el reloj al recibir un ts remoto (mantiene la causalidad). */ + update(remoteTs, wall = Date.now()) { + const remotePhysical = Math.floor(remoteTs / (COUNTER_MASK + 1)); + this.lastPhysical = Math.max(this.lastPhysical, remotePhysical, wall); + this.counter = (this.counter + 1) & COUNTER_MASK; + return this.lastPhysical * (COUNTER_MASK + 1) + this.counter; + } +} +//# sourceMappingURL=clock.js.map \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/clock.js.map b/packages/engine-core/dist/game/net/clock.js.map new file mode 100644 index 0000000..b95a5f8 --- /dev/null +++ b/packages/engine-core/dist/game/net/clock.js.map @@ -0,0 +1 @@ +{"version":3,"file":"clock.js","sourceRoot":"","sources":["../../../src/game/net/clock.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,MAAM,YAAY,GAAG,EAAE,CAAC,CAAC,4BAA4B;AACrD,MAAM,YAAY,GAAG,CAAC,CAAC,IAAI,YAAY,CAAC,GAAG,CAAC,CAAC;AAE7C,MAAM,OAAO,GAAG;IAAhB;QACU,iBAAY,GAAG,CAAC,CAAC;QACjB,YAAO,GAAG,CAAC,CAAC;IAoBtB,CAAC;IAlBC,oEAAoE;IACpE,GAAG,CAAC,OAAe,IAAI,CAAC,GAAG,EAAE;QAC3B,IAAI,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;YAC7B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;YACzB,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC;QACnB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,GAAG,YAAY,CAAC;QACnD,CAAC;QACD,OAAO,IAAI,CAAC,YAAY,GAAG,CAAC,YAAY,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC;IAC/D,CAAC;IAED,2EAA2E;IAC3E,MAAM,CAAC,QAAgB,EAAE,OAAe,IAAI,CAAC,GAAG,EAAE;QAChD,MAAM,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC;QACjE,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,cAAc,EAAE,IAAI,CAAC,CAAC;QACtE,IAAI,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,GAAG,YAAY,CAAC;QACjD,OAAO,IAAI,CAAC,YAAY,GAAG,CAAC,YAAY,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC;IAC/D,CAAC;CACF"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/eventClassification.d.ts b/packages/engine-core/dist/game/net/eventClassification.d.ts new file mode 100644 index 0000000..54109dd --- /dev/null +++ b/packages/engine-core/dist/game/net/eventClassification.d.ts @@ -0,0 +1,43 @@ +import type { GameEventType } from "../events/types"; +/** Clase de sincronización de un evento. Un evento puede pertenecer a varias. */ +export type SyncClass = "world" | "presence" | "private"; +/** + * Clasificación exhaustiva. `satisfies` obliga a cubrir TODOS los GameEventType: + * si añades un evento nuevo y no lo clasificas, TypeScript falla aquí. + * + * - world → estado compartido, replicado a todos, persistente (puertas, ítems colocados). + * - presence → efímero por jugador, fan-out a misma escena (posición, escena). + * - private → local, NUNCA cruza la red (diálogos, audio, animación de walk). + */ +export declare const EVENT_CLASSIFICATION: { + "scene:changed": "presence"[]; + "scene:respawnRequested": "private"[]; + "player:moved": "presence"[]; + "player:collided": "private"[]; + "item:pickedUp": ("world" | "private")[]; + "item:dropped": "world"[]; + "dialog:triggered": "private"[]; + "dialog:dismissed": "private"[]; + "transition:triggered": "private"[]; + "transition:started": "private"[]; + "transition:completed": "presence"[]; + "transition:colliderClicked": "private"[]; + "player:walkStarted": "private"[]; + "player:walkCompleted": "private"[]; + "player:walkAborted": "private"[]; + "audio:sfxRequested": "private"[]; + "audio:musicRequested": "private"[]; + "audio:musicStopped": "private"[]; + "audio:settingsChanged": "private"[]; + "i18n:localeChanged": "private"[]; + "i18n:dictionaryUpdated": "private"[]; + "net:playerJoined": "private"[]; + "net:playerLeft": "private"[]; + "net:status": "private"[]; +}; +export declare function classifyEvent(type: GameEventType): SyncClass[]; +export declare const isWorld: (t: GameEventType) => boolean; +export declare const isPresence: (t: GameEventType) => boolean; +/** ¿Debe salir a la red? (world o presence). Los `private` jamás. */ +export declare const shouldReplicate: (t: GameEventType) => boolean; +//# sourceMappingURL=eventClassification.d.ts.map \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/eventClassification.d.ts.map b/packages/engine-core/dist/game/net/eventClassification.d.ts.map new file mode 100644 index 0000000..8812f1d --- /dev/null +++ b/packages/engine-core/dist/game/net/eventClassification.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"eventClassification.d.ts","sourceRoot":"","sources":["../../../src/game/net/eventClassification.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD,iFAAiF;AACjF,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;AAEzD;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;CAkCa,CAAC;AAE/C,wBAAgB,aAAa,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS,EAAE,CAE9D;AACD,eAAO,MAAM,OAAO,GAAI,GAAG,aAAa,KAAG,OAA6C,CAAC;AACzF,eAAO,MAAM,UAAU,GAAI,GAAG,aAAa,KAAG,OACP,CAAC;AACxC,qEAAqE;AACrE,eAAO,MAAM,eAAe,GAAI,GAAG,aAAa,KAAG,OACtB,CAAC"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/eventClassification.js b/packages/engine-core/dist/game/net/eventClassification.js new file mode 100644 index 0000000..a2dcdc8 --- /dev/null +++ b/packages/engine-core/dist/game/net/eventClassification.js @@ -0,0 +1,51 @@ +/** + * Clasificación exhaustiva. `satisfies` obliga a cubrir TODOS los GameEventType: + * si añades un evento nuevo y no lo clasificas, TypeScript falla aquí. + * + * - world → estado compartido, replicado a todos, persistente (puertas, ítems colocados). + * - presence → efímero por jugador, fan-out a misma escena (posición, escena). + * - private → local, NUNCA cruza la red (diálogos, audio, animación de walk). + */ +export const EVENT_CLASSIFICATION = { + // Scene + "scene:changed": ["presence"], // cambia MI escena → actualiza mi presence + "scene:respawnRequested": ["private"], + // Player + "player:moved": ["presence"], // alta frecuencia; throttle en el borde + "player:collided": ["private"], + // Inventory (pickup quita del world y añade a mi inventario) + "item:pickedUp": ["world", "private"], + "item:dropped": ["world"], // place/consume mutan placedItems compartidos + // Dialog + "dialog:triggered": ["private"], + "dialog:dismissed": ["private"], + // Transitions + "transition:triggered": ["private"], + "transition:started": ["private"], + "transition:completed": ["presence"], // cambio de escena propio + "transition:colliderClicked": ["private"], + // Player walking (se deriva de presence en remotos) + "player:walkStarted": ["private"], + "player:walkCompleted": ["private"], + "player:walkAborted": ["private"], + // Audio (local) + "audio:sfxRequested": ["private"], + "audio:musicRequested": ["private"], + "audio:musicStopped": ["private"], + "audio:settingsChanged": ["private"], + // i18n (local) + "i18n:localeChanged": ["private"], + "i18n:dictionaryUpdated": ["private"], + // Net (locales para UI; nunca se replican) + "net:playerJoined": ["private"], + "net:playerLeft": ["private"], + "net:status": ["private"], +}; +export function classifyEvent(type) { + return EVENT_CLASSIFICATION[type] ?? ["private"]; +} +export const isWorld = (t) => classifyEvent(t).includes("world"); +export const isPresence = (t) => classifyEvent(t).includes("presence"); +/** ¿Debe salir a la red? (world o presence). Los `private` jamás. */ +export const shouldReplicate = (t) => isWorld(t) || isPresence(t); +//# sourceMappingURL=eventClassification.js.map \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/eventClassification.js.map b/packages/engine-core/dist/game/net/eventClassification.js.map new file mode 100644 index 0000000..9e1e373 --- /dev/null +++ b/packages/engine-core/dist/game/net/eventClassification.js.map @@ -0,0 +1 @@ +{"version":3,"file":"eventClassification.js","sourceRoot":"","sources":["../../../src/game/net/eventClassification.ts"],"names":[],"mappings":"AAKA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,QAAQ;IACR,eAAe,EAAE,CAAC,UAAU,CAAC,EAAE,2CAA2C;IAC1E,wBAAwB,EAAE,CAAC,SAAS,CAAC;IACrC,SAAS;IACT,cAAc,EAAE,CAAC,UAAU,CAAC,EAAE,wCAAwC;IACtE,iBAAiB,EAAE,CAAC,SAAS,CAAC;IAC9B,6DAA6D;IAC7D,eAAe,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC;IACrC,cAAc,EAAE,CAAC,OAAO,CAAC,EAAE,8CAA8C;IACzE,SAAS;IACT,kBAAkB,EAAE,CAAC,SAAS,CAAC;IAC/B,kBAAkB,EAAE,CAAC,SAAS,CAAC;IAC/B,cAAc;IACd,sBAAsB,EAAE,CAAC,SAAS,CAAC;IACnC,oBAAoB,EAAE,CAAC,SAAS,CAAC;IACjC,sBAAsB,EAAE,CAAC,UAAU,CAAC,EAAE,0BAA0B;IAChE,4BAA4B,EAAE,CAAC,SAAS,CAAC;IACzC,oDAAoD;IACpD,oBAAoB,EAAE,CAAC,SAAS,CAAC;IACjC,sBAAsB,EAAE,CAAC,SAAS,CAAC;IACnC,oBAAoB,EAAE,CAAC,SAAS,CAAC;IACjC,gBAAgB;IAChB,oBAAoB,EAAE,CAAC,SAAS,CAAC;IACjC,sBAAsB,EAAE,CAAC,SAAS,CAAC;IACnC,oBAAoB,EAAE,CAAC,SAAS,CAAC;IACjC,uBAAuB,EAAE,CAAC,SAAS,CAAC;IACpC,eAAe;IACf,oBAAoB,EAAE,CAAC,SAAS,CAAC;IACjC,wBAAwB,EAAE,CAAC,SAAS,CAAC;IACrC,2CAA2C;IAC3C,kBAAkB,EAAE,CAAC,SAAS,CAAC;IAC/B,gBAAgB,EAAE,CAAC,SAAS,CAAC;IAC7B,YAAY,EAAE,CAAC,SAAS,CAAC;CACmB,CAAC;AAE/C,MAAM,UAAU,aAAa,CAAC,IAAmB;IAC/C,OAAO,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AACnD,CAAC;AACD,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,CAAgB,EAAW,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AACzF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAgB,EAAW,EAAE,CACtD,aAAa,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;AACxC,qEAAqE;AACrE,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAgB,EAAW,EAAE,CAC3D,OAAO,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/index.d.ts b/packages/engine-core/dist/game/net/index.d.ts new file mode 100644 index 0000000..be45baf --- /dev/null +++ b/packages/engine-core/dist/game/net/index.d.ts @@ -0,0 +1,10 @@ +export * from "./eventClassification"; +export * from "./playerIdentity"; +export * from "./remotePlayersStore"; +export * from "./MultiplayerSession"; +export * from "./clock"; +export * from "./worldState"; +export * from "./claims"; +export * from "./throttle"; +export * from "./optimistic"; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/index.d.ts.map b/packages/engine-core/dist/game/net/index.d.ts.map new file mode 100644 index 0000000..80104ad --- /dev/null +++ b/packages/engine-core/dist/game/net/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/game/net/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/index.js b/packages/engine-core/dist/game/net/index.js new file mode 100644 index 0000000..5b9dde6 --- /dev/null +++ b/packages/engine-core/dist/game/net/index.js @@ -0,0 +1,10 @@ +export * from "./eventClassification"; +export * from "./playerIdentity"; +export * from "./remotePlayersStore"; +export * from "./MultiplayerSession"; +export * from "./clock"; +export * from "./worldState"; +export * from "./claims"; +export * from "./throttle"; +export * from "./optimistic"; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/index.js.map b/packages/engine-core/dist/game/net/index.js.map new file mode 100644 index 0000000..c308715 --- /dev/null +++ b/packages/engine-core/dist/game/net/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/game/net/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/optimistic.d.ts b/packages/engine-core/dist/game/net/optimistic.d.ts new file mode 100644 index 0000000..a44f579 --- /dev/null +++ b/packages/engine-core/dist/game/net/optimistic.d.ts @@ -0,0 +1,21 @@ +export interface PendingAction { + entityId: string; + rollback: () => void; +} +/** + * Buffer de acciones optimistas pendientes de confirmación. Cada acción aporta + * su propio `rollback` (cómo deshacerla). Reconciliación determinista. + */ +export declare function createOptimisticReconciler(): { + /** Registra una predicción local ya aplicada. */ + track(entityId: string, rollback: () => void): void; + /** El servidor confirmó: descarta la predicción (no revierte). */ + confirm(entityId: string): void; + /** El servidor rechazó: revierte y descarta. */ + reject(entityId: string): void; + /** Reconexión: revierte TODO lo no confirmado antes de aplicar el snapshot. */ + onSnapshot(): void; + size(): number; +}; +export type OptimisticReconciler = ReturnType; +//# sourceMappingURL=optimistic.d.ts.map \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/optimistic.d.ts.map b/packages/engine-core/dist/game/net/optimistic.d.ts.map new file mode 100644 index 0000000..b8a197c --- /dev/null +++ b/packages/engine-core/dist/game/net/optimistic.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"optimistic.d.ts","sourceRoot":"","sources":["../../../src/game/net/optimistic.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,0BAA0B;IAGtC,iDAAiD;oBACjC,MAAM,YAAY,MAAM,IAAI,GAAG,IAAI;IAGnD,kEAAkE;sBAChD,MAAM,GAAG,IAAI;IAG/B,gDAAgD;qBAC/B,MAAM,GAAG,IAAI;IAO9B,+EAA+E;kBACjE,IAAI;YAIV,MAAM;EAIjB;AAED,MAAM,MAAM,oBAAoB,GAAG,UAAU,CAAC,OAAO,0BAA0B,CAAC,CAAC"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/optimistic.js b/packages/engine-core/dist/game/net/optimistic.js new file mode 100644 index 0000000..a28c26e --- /dev/null +++ b/packages/engine-core/dist/game/net/optimistic.js @@ -0,0 +1,34 @@ +/** + * Buffer de acciones optimistas pendientes de confirmación. Cada acción aporta + * su propio `rollback` (cómo deshacerla). Reconciliación determinista. + */ +export function createOptimisticReconciler() { + const pending = new Map(); + return { + /** Registra una predicción local ya aplicada. */ + track(entityId, rollback) { + pending.set(entityId, { entityId, rollback }); + }, + /** El servidor confirmó: descarta la predicción (no revierte). */ + confirm(entityId) { + pending.delete(entityId); + }, + /** El servidor rechazó: revierte y descarta. */ + reject(entityId) { + const p = pending.get(entityId); + if (p) { + p.rollback(); + pending.delete(entityId); + } + }, + /** Reconexión: revierte TODO lo no confirmado antes de aplicar el snapshot. */ + onSnapshot() { + pending.forEach((p) => p.rollback()); + pending.clear(); + }, + size() { + return pending.size; + }, + }; +} +//# sourceMappingURL=optimistic.js.map \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/optimistic.js.map b/packages/engine-core/dist/game/net/optimistic.js.map new file mode 100644 index 0000000..37b4541 --- /dev/null +++ b/packages/engine-core/dist/game/net/optimistic.js.map @@ -0,0 +1 @@ +{"version":3,"file":"optimistic.js","sourceRoot":"","sources":["../../../src/game/net/optimistic.ts"],"names":[],"mappings":"AAKA;;;GAGG;AACH,MAAM,UAAU,0BAA0B;IACxC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAyB,CAAC;IACjD,OAAO;QACL,iDAAiD;QACjD,KAAK,CAAC,QAAgB,EAAE,QAAoB;YAC1C,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;QAChD,CAAC;QACD,kEAAkE;QAClE,OAAO,CAAC,QAAgB;YACtB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC3B,CAAC;QACD,gDAAgD;QAChD,MAAM,CAAC,QAAgB;YACrB,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAChC,IAAI,CAAC,EAAE,CAAC;gBACN,CAAC,CAAC,QAAQ,EAAE,CAAC;gBACb,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;QACD,+EAA+E;QAC/E,UAAU;YACR,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;YACrC,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,CAAC;QACD,IAAI;YACF,OAAO,OAAO,CAAC,IAAI,CAAC;QACtB,CAAC;KACF,CAAC;AACJ,CAAC"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/playerIdentity.d.ts b/packages/engine-core/dist/game/net/playerIdentity.d.ts new file mode 100644 index 0000000..e5abfa6 --- /dev/null +++ b/packages/engine-core/dist/game/net/playerIdentity.d.ts @@ -0,0 +1,29 @@ +import type { GameVec3 } from "../types"; +import type { PlayerId } from "../../ports/multiplayer"; +/** Personaje seleccionable. Hoy solo "Dave"; reservado para multi-character futuro. */ +export type CharacterId = "Dave"; +export declare const DEFAULT_CHARACTER_ID: CharacterId; +export type PlayerAction = "idle" | "north" | "south" | "west" | "east"; +/** Descriptor de un jugador. Serializable: viaja como presence en NetEnvelope.payload. */ +export interface PlayerDescriptor { + playerId: PlayerId; + displayName: string; + characterId: CharacterId; + sceneId: string; + position: GameVec3; + action: PlayerAction; + /** Último timestamp visto (para limpiar jugadores stale). */ + lastSeenTs: number; +} +/** Genera un nombre anónimo legible, p.ej. "Viajero-A3F". RNG inyectable para tests. */ +export declare function generateRandomName(rng?: () => number): string; +/** Crea el descriptor inicial del jugador local. */ +export declare function createSelfDescriptor(opts: { + playerId: PlayerId; + sceneId: string; + position: GameVec3; + displayName?: string; + characterId?: CharacterId; + now?: () => number; +}): PlayerDescriptor; +//# sourceMappingURL=playerIdentity.d.ts.map \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/playerIdentity.d.ts.map b/packages/engine-core/dist/game/net/playerIdentity.d.ts.map new file mode 100644 index 0000000..11f1398 --- /dev/null +++ b/packages/engine-core/dist/game/net/playerIdentity.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"playerIdentity.d.ts","sourceRoot":"","sources":["../../../src/game/net/playerIdentity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAExD,uFAAuF;AACvF,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC;AACjC,eAAO,MAAM,oBAAoB,EAAE,WAAoB,CAAC;AAExD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAExE,0FAA0F;AAC1F,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,YAAY,CAAC;IACrB,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;CACpB;AAWD,wFAAwF;AACxF,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,MAAM,MAAoB,GAAG,MAAM,CAO1E;AAED,oDAAoD;AACpD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,QAAQ,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB,GAAG,gBAAgB,CAUnB"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/playerIdentity.js b/packages/engine-core/dist/game/net/playerIdentity.js new file mode 100644 index 0000000..2c6f509 --- /dev/null +++ b/packages/engine-core/dist/game/net/playerIdentity.js @@ -0,0 +1,31 @@ +export const DEFAULT_CHARACTER_ID = "Dave"; +const NAME_PREFIXES = [ + "Viajero", + "Errante", + "Nómada", + "Curioso", + "Vecino", + "Explorador", +]; +/** Genera un nombre anónimo legible, p.ej. "Viajero-A3F". RNG inyectable para tests. */ +export function generateRandomName(rng = Math.random) { + const prefix = NAME_PREFIXES[Math.floor(rng() * NAME_PREFIXES.length)]; + const suffix = Math.floor(rng() * 0xfff) + .toString(16) + .toUpperCase() + .padStart(3, "0"); + return `${prefix}-${suffix}`; +} +/** Crea el descriptor inicial del jugador local. */ +export function createSelfDescriptor(opts) { + return { + playerId: opts.playerId, + displayName: opts.displayName ?? generateRandomName(), + characterId: opts.characterId ?? DEFAULT_CHARACTER_ID, + sceneId: opts.sceneId, + position: opts.position, + action: "idle", + lastSeenTs: (opts.now ?? Date.now)(), + }; +} +//# sourceMappingURL=playerIdentity.js.map \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/playerIdentity.js.map b/packages/engine-core/dist/game/net/playerIdentity.js.map new file mode 100644 index 0000000..8132a19 --- /dev/null +++ b/packages/engine-core/dist/game/net/playerIdentity.js.map @@ -0,0 +1 @@ +{"version":3,"file":"playerIdentity.js","sourceRoot":"","sources":["../../../src/game/net/playerIdentity.ts"],"names":[],"mappings":"AAKA,MAAM,CAAC,MAAM,oBAAoB,GAAgB,MAAM,CAAC;AAgBxD,MAAM,aAAa,GAAG;IACpB,SAAS;IACT,SAAS;IACT,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,YAAY;CACJ,CAAC;AAEX,wFAAwF;AACxF,MAAM,UAAU,kBAAkB,CAAC,MAAoB,IAAI,CAAC,MAAM;IAChE,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,CAAE,CAAC;IACxE,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;SACrC,QAAQ,CAAC,EAAE,CAAC;SACZ,WAAW,EAAE;SACb,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACpB,OAAO,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC;AAC/B,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,oBAAoB,CAAC,IAOpC;IACC,OAAO;QACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,kBAAkB,EAAE;QACrD,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,oBAAoB;QACrD,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,MAAM,EAAE,MAAM;QACd,UAAU,EAAE,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE;KACrC,CAAC;AACJ,CAAC"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/remotePlayersStore.d.ts b/packages/engine-core/dist/game/net/remotePlayersStore.d.ts new file mode 100644 index 0000000..e9614b7 --- /dev/null +++ b/packages/engine-core/dist/game/net/remotePlayersStore.d.ts @@ -0,0 +1,19 @@ +import type { PlayerDescriptor } from "./playerIdentity"; +import type { PlayerId } from "../../ports/multiplayer"; +export interface RemotePlayersStore { + /** Inserta o actualiza un jugador remoto (merge parcial sobre el existente). */ + upsert: (descriptor: Partial & { + playerId: PlayerId; + }) => void; + remove: (playerId: PlayerId) => void; + /** Solo los jugadores presentes en la escena dada (interest management en el borde). */ + getInScene: (sceneId: string) => PlayerDescriptor[]; + getAll: () => PlayerDescriptor[]; + /** Elimina jugadores sin presence en > maxAgeMs. Devuelve los ids eliminados. */ + pruneStale: (maxAgeMs: number, now?: number) => PlayerId[]; + reset: () => void; + /** Notifica en cada cambio (para que el renderer re-renderice). */ + subscribe: (listener: () => void) => () => void; +} +export declare function createRemotePlayersStore(): RemotePlayersStore; +//# sourceMappingURL=remotePlayersStore.d.ts.map \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/remotePlayersStore.d.ts.map b/packages/engine-core/dist/game/net/remotePlayersStore.d.ts.map new file mode 100644 index 0000000..6e697b6 --- /dev/null +++ b/packages/engine-core/dist/game/net/remotePlayersStore.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"remotePlayersStore.d.ts","sourceRoot":"","sources":["../../../src/game/net/remotePlayersStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAExD,MAAM,WAAW,kBAAkB;IACjC,gFAAgF;IAChF,MAAM,EAAE,CAAC,UAAU,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG;QAAE,QAAQ,EAAE,QAAQ,CAAA;KAAE,KAAK,IAAI,CAAC;IACjF,MAAM,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;IACrC,wFAAwF;IACxF,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,gBAAgB,EAAE,CAAC;IACpD,MAAM,EAAE,MAAM,gBAAgB,EAAE,CAAC;IACjC,iFAAiF;IACjF,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,QAAQ,EAAE,CAAC;IAC3D,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,mEAAmE;IACnE,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;CACjD;AAED,wBAAgB,wBAAwB,IAAI,kBAAkB,CA6C7D"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/remotePlayersStore.js b/packages/engine-core/dist/game/net/remotePlayersStore.js new file mode 100644 index 0000000..f61fcee --- /dev/null +++ b/packages/engine-core/dist/game/net/remotePlayersStore.js @@ -0,0 +1,47 @@ +export function createRemotePlayersStore() { + const players = new Map(); + const listeners = new Set(); + const notify = () => listeners.forEach((l) => l()); + return { + upsert: (d) => { + const prev = players.get(d.playerId); + players.set(d.playerId, { + playerId: d.playerId, + displayName: d.displayName ?? prev?.displayName ?? d.playerId, + characterId: d.characterId ?? prev?.characterId ?? "Dave", + sceneId: d.sceneId ?? prev?.sceneId ?? "", + position: d.position ?? prev?.position ?? [0, 0, 0], + action: d.action ?? prev?.action ?? "idle", + lastSeenTs: d.lastSeenTs ?? Date.now(), + }); + notify(); + }, + remove: (id) => { + if (players.delete(id)) + notify(); + }, + getInScene: (sceneId) => [...players.values()].filter((p) => p.sceneId === sceneId), + getAll: () => [...players.values()], + pruneStale: (maxAgeMs, now = Date.now()) => { + const removed = []; + for (const [id, p] of players) { + if (now - p.lastSeenTs > maxAgeMs) { + players.delete(id); + removed.push(id); + } + } + if (removed.length) + notify(); + return removed; + }, + reset: () => { + players.clear(); + notify(); + }, + subscribe: (l) => { + listeners.add(l); + return () => listeners.delete(l); + }, + }; +} +//# sourceMappingURL=remotePlayersStore.js.map \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/remotePlayersStore.js.map b/packages/engine-core/dist/game/net/remotePlayersStore.js.map new file mode 100644 index 0000000..3b14be1 --- /dev/null +++ b/packages/engine-core/dist/game/net/remotePlayersStore.js.map @@ -0,0 +1 @@ +{"version":3,"file":"remotePlayersStore.js","sourceRoot":"","sources":["../../../src/game/net/remotePlayersStore.ts"],"names":[],"mappings":"AAiBA,MAAM,UAAU,wBAAwB;IACtC,MAAM,OAAO,GAAG,IAAI,GAAG,EAA8B,CAAC;IACtD,MAAM,SAAS,GAAG,IAAI,GAAG,EAAc,CAAC;IACxC,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IAEnD,OAAO;QACL,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE;YACZ,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;YACrC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE;gBACtB,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,IAAI,EAAE,WAAW,IAAI,CAAC,CAAC,QAAQ;gBAC7D,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,IAAI,EAAE,WAAW,IAAI,MAAM;gBACzD,OAAO,EAAE,CAAC,CAAC,OAAO,IAAI,IAAI,EAAE,OAAO,IAAI,EAAE;gBACzC,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,IAAI,EAAE,QAAQ,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;gBACnD,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,IAAI,EAAE,MAAM,IAAI,MAAM;gBAC1C,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,IAAI,CAAC,GAAG,EAAE;aACvC,CAAC,CAAC;YACH,MAAM,EAAE,CAAC;QACX,CAAC;QACD,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE;YACb,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAAE,MAAM,EAAE,CAAC;QACnC,CAAC;QACD,UAAU,EAAE,CAAC,OAAO,EAAE,EAAE,CACtB,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC;QAC5D,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;QACnC,UAAU,EAAE,CAAC,QAAQ,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE;YACzC,MAAM,OAAO,GAAe,EAAE,CAAC;YAC/B,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC;gBAC9B,IAAI,GAAG,GAAG,CAAC,CAAC,UAAU,GAAG,QAAQ,EAAE,CAAC;oBAClC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;oBACnB,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBACnB,CAAC;YACH,CAAC;YACD,IAAI,OAAO,CAAC,MAAM;gBAAE,MAAM,EAAE,CAAC;YAC7B,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,KAAK,EAAE,GAAG,EAAE;YACV,OAAO,CAAC,KAAK,EAAE,CAAC;YAChB,MAAM,EAAE,CAAC;QACX,CAAC;QACD,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE;YACf,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACjB,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACnC,CAAC;KACF,CAAC;AACJ,CAAC"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/throttle.d.ts b/packages/engine-core/dist/game/net/throttle.d.ts new file mode 100644 index 0000000..12c0964 --- /dev/null +++ b/packages/engine-core/dist/game/net/throttle.d.ts @@ -0,0 +1,11 @@ +/** Reloj/scheduler inyectable para el throttle (permite tests deterministas). */ +export interface ThrottleClock { + now: () => number; + schedule: (cb: () => void, delay: number) => void; +} +/** + * Throttle con trailing: ejecuta `fn` como mucho 1 vez por `ms`, pero garantiza + * que la ÚLTIMA llamada dentro de la ventana se ejecuta (con el estado final). + */ +export declare function throttleTrailing(fn: () => void, ms: number, clock: ThrottleClock): () => void; +//# sourceMappingURL=throttle.d.ts.map \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/throttle.d.ts.map b/packages/engine-core/dist/game/net/throttle.d.ts.map new file mode 100644 index 0000000..681773a --- /dev/null +++ b/packages/engine-core/dist/game/net/throttle.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"throttle.d.ts","sourceRoot":"","sources":["../../../src/game/net/throttle.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,MAAM,IAAI,EACd,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,aAAa,GACnB,MAAM,IAAI,CAoBZ"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/throttle.js b/packages/engine-core/dist/game/net/throttle.js new file mode 100644 index 0000000..90c94b8 --- /dev/null +++ b/packages/engine-core/dist/game/net/throttle.js @@ -0,0 +1,26 @@ +/** + * Throttle con trailing: ejecuta `fn` como mucho 1 vez por `ms`, pero garantiza + * que la ÚLTIMA llamada dentro de la ventana se ejecuta (con el estado final). + */ +export function throttleTrailing(fn, ms, clock) { + let lastRun = -Infinity; + let scheduled = false; + return () => { + const t = clock.now(); + const elapsed = t - lastRun; + if (elapsed >= ms) { + lastRun = t; + fn(); + return; + } + if (!scheduled) { + scheduled = true; + clock.schedule(() => { + scheduled = false; + lastRun = clock.now(); + fn(); + }, ms - elapsed); + } + }; +} +//# sourceMappingURL=throttle.js.map \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/throttle.js.map b/packages/engine-core/dist/game/net/throttle.js.map new file mode 100644 index 0000000..ee592f5 --- /dev/null +++ b/packages/engine-core/dist/game/net/throttle.js.map @@ -0,0 +1 @@ +{"version":3,"file":"throttle.js","sourceRoot":"","sources":["../../../src/game/net/throttle.ts"],"names":[],"mappings":"AAMA;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,EAAc,EACd,EAAU,EACV,KAAoB;IAEpB,IAAI,OAAO,GAAG,CAAC,QAAQ,CAAC;IACxB,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,OAAO,GAAG,EAAE;QACV,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;QACtB,MAAM,OAAO,GAAG,CAAC,GAAG,OAAO,CAAC;QAC5B,IAAI,OAAO,IAAI,EAAE,EAAE,CAAC;YAClB,OAAO,GAAG,CAAC,CAAC;YACZ,EAAE,EAAE,CAAC;YACL,OAAO;QACT,CAAC;QACD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,SAAS,GAAG,IAAI,CAAC;YACjB,KAAK,CAAC,QAAQ,CAAC,GAAG,EAAE;gBAClB,SAAS,GAAG,KAAK,CAAC;gBAClB,OAAO,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;gBACtB,EAAE,EAAE,CAAC;YACP,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,CAAC;QACnB,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/worldState.d.ts b/packages/engine-core/dist/game/net/worldState.d.ts new file mode 100644 index 0000000..e417e2f --- /dev/null +++ b/packages/engine-core/dist/game/net/worldState.d.ts @@ -0,0 +1,17 @@ +import type { PlayerId } from "../../ports/multiplayer"; +export interface LwwRegister { + value: V; + ts: number; + by: PlayerId; +} +/** Mapa de registros LWW por `${entityId}:${field}`. Determinista entre clientes. */ +export declare class WorldStateLww { + private map; + private key; + get(entityId: string, field: string): V | undefined; + /** Aplica un registro remoto/local. Devuelve true si ganó (cambió el estado). */ + merge(entityId: string, field: string, reg: LwwRegister): boolean; + snapshot(): Record; + load(snap: Record): void; +} +//# sourceMappingURL=worldState.d.ts.map \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/worldState.d.ts.map b/packages/engine-core/dist/game/net/worldState.d.ts.map new file mode 100644 index 0000000..afecbcc --- /dev/null +++ b/packages/engine-core/dist/game/net/worldState.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"worldState.d.ts","sourceRoot":"","sources":["../../../src/game/net/worldState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAExD,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO;IACtC,KAAK,EAAE,CAAC,CAAC;IACT,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,QAAQ,CAAC;CACd;AAED,qFAAqF;AACrF,qBAAa,aAAa;IACxB,OAAO,CAAC,GAAG,CAAkC;IAC7C,OAAO,CAAC,GAAG,CAA+D;IAE1E,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAItD,iFAAiF;IACjF,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO;IAWjE,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC;IAGvC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,IAAI;CAG9C"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/worldState.js b/packages/engine-core/dist/game/net/worldState.js new file mode 100644 index 0000000..e42e552 --- /dev/null +++ b/packages/engine-core/dist/game/net/worldState.js @@ -0,0 +1,28 @@ +/** Mapa de registros LWW por `${entityId}:${field}`. Determinista entre clientes. */ +export class WorldStateLww { + constructor() { + this.map = new Map(); + this.key = (entityId, field) => `${entityId}:${field}`; + } + get(entityId, field) { + return this.map.get(this.key(entityId, field))?.value; + } + /** Aplica un registro remoto/local. Devuelve true si ganó (cambió el estado). */ + merge(entityId, field, reg) { + const k = this.key(entityId, field); + const cur = this.map.get(k); + // Gana mayor ts; empate → mayor playerId (criterio estable arbitrario). + if (!cur || reg.ts > cur.ts || (reg.ts === cur.ts && reg.by > cur.by)) { + this.map.set(k, reg); + return true; + } + return false; + } + snapshot() { + return Object.fromEntries(this.map); + } + load(snap) { + this.map = new Map(Object.entries(snap)); + } +} +//# sourceMappingURL=worldState.js.map \ No newline at end of file diff --git a/packages/engine-core/dist/game/net/worldState.js.map b/packages/engine-core/dist/game/net/worldState.js.map new file mode 100644 index 0000000..342fdc1 --- /dev/null +++ b/packages/engine-core/dist/game/net/worldState.js.map @@ -0,0 +1 @@ +{"version":3,"file":"worldState.js","sourceRoot":"","sources":["../../../src/game/net/worldState.ts"],"names":[],"mappings":"AAQA,qFAAqF;AACrF,MAAM,OAAO,aAAa;IAA1B;QACU,QAAG,GAAG,IAAI,GAAG,EAAuB,CAAC;QACrC,QAAG,GAAG,CAAC,QAAgB,EAAE,KAAa,EAAE,EAAE,CAAC,GAAG,QAAQ,IAAI,KAAK,EAAE,CAAC;IAwB5E,CAAC;IAtBC,GAAG,CAAI,QAAgB,EAAE,KAAa;QACpC,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,EAAE,KAAsB,CAAC;IACzE,CAAC;IAED,iFAAiF;IACjF,KAAK,CAAC,QAAgB,EAAE,KAAa,EAAE,GAAgB;QACrD,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QACpC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC5B,wEAAwE;QACxE,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YACtE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YACrB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,QAAQ;QACN,OAAO,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACtC,CAAC;IACD,IAAI,CAAC,IAAiC;QACpC,IAAI,CAAC,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3C,CAAC;CACF"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/types/index.d.ts b/packages/engine-core/dist/game/types/index.d.ts index 13753e6..2bb00f3 100644 --- a/packages/engine-core/dist/game/types/index.d.ts +++ b/packages/engine-core/dist/game/types/index.d.ts @@ -228,6 +228,8 @@ export type RuntimeDropEvent = { outcome: "place" | "consume" | "return" | "rule-miss" | "unknown-item" | "on-player" | "pickup-blocked" | "pickup-success" | "item-interact"; itemId: string; interactionId?: string; + /** Full placed-item data; set for outcome "place" so the event bus carries enough info for remote clients. */ + placedItem?: PlacedSceneItem; }; export type RuntimeDialogEvent = { type: "onDialog"; diff --git a/packages/engine-core/dist/game/types/index.d.ts.map b/packages/engine-core/dist/game/types/index.d.ts.map index 412ed09..42dae5c 100644 --- a/packages/engine-core/dist/game/types/index.d.ts.map +++ b/packages/engine-core/dist/game/types/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/game/types/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/game/types/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/engine-core/dist/game/types/index.js.map b/packages/engine-core/dist/game/types/index.js.map index 2b6c014..763fedd 100644 --- a/packages/engine-core/dist/game/types/index.js.map +++ b/packages/engine-core/dist/game/types/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/game/types/index.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,iCAAiC;AAwQjC,MAAM,CAAC,MAAM,sBAAsB,GAAkB;IACnD,WAAW,EAAE,KAAK;IAClB,UAAU,EAAE,KAAK;IACjB,QAAQ,EAAE,KAAK;IACf,YAAY,EAAE,GAAG;IACjB,WAAW,EAAE,GAAG;IAChB,SAAS,EAAE,GAAG;CACf,CAAC;AAqFF,6FAA6F;AAC7F,MAAM,CAAC,MAAM,mBAAmB,GAAe;IAC7C,aAAa,EAAE,IAAI;IACnB,cAAc,EAAE,IAAI;IACpB,gBAAgB,EAAE,CAAC,IAAI,CAAC;CACzB,CAAC"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/game/types/index.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,iCAAiC;AAwQjC,MAAM,CAAC,MAAM,sBAAsB,GAAkB;IACnD,WAAW,EAAE,KAAK;IAClB,UAAU,EAAE,KAAK;IACjB,QAAQ,EAAE,KAAK;IACf,YAAY,EAAE,GAAG;IACjB,WAAW,EAAE,GAAG;IAChB,SAAS,EAAE,GAAG;CACf,CAAC;AAuFF,6FAA6F;AAC7F,MAAM,CAAC,MAAM,mBAAmB,GAAe;IAC7C,aAAa,EAAE,IAAI;IACnB,cAAc,EAAE,IAAI;IACpB,gBAAgB,EAAE,CAAC,IAAI,CAAC;CACzB,CAAC"} \ No newline at end of file diff --git a/packages/engine-core/dist/index.d.ts b/packages/engine-core/dist/index.d.ts index 04c9a60..d4be133 100644 --- a/packages/engine-core/dist/index.d.ts +++ b/packages/engine-core/dist/index.d.ts @@ -4,6 +4,7 @@ export * from "./game/state"; export * from "./game/commands"; export * from "./game/events"; export * from "./game/i18n"; +export * from "./game/net"; export * from "./events"; export * from "./ports"; export declare const VERSION = "0.4.0"; diff --git a/packages/engine-core/dist/index.d.ts.map b/packages/engine-core/dist/index.d.ts.map index 2737960..c419988 100644 --- a/packages/engine-core/dist/index.d.ts.map +++ b/packages/engine-core/dist/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AAExB,eAAO,MAAM,OAAO,UAAU,CAAC"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AAExB,eAAO,MAAM,OAAO,UAAU,CAAC"} \ No newline at end of file diff --git a/packages/engine-core/dist/index.js b/packages/engine-core/dist/index.js index a31b0fe..04947fe 100644 --- a/packages/engine-core/dist/index.js +++ b/packages/engine-core/dist/index.js @@ -4,6 +4,7 @@ export * from "./game/state"; export * from "./game/commands"; export * from "./game/events"; export * from "./game/i18n"; +export * from "./game/net"; export * from "./events"; export * from "./ports"; export const VERSION = "0.4.0"; diff --git a/packages/engine-core/dist/index.js.map b/packages/engine-core/dist/index.js.map index 183441b..e8505e0 100644 --- a/packages/engine-core/dist/index.js.map +++ b/packages/engine-core/dist/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AAExB,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AAExB,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"} \ No newline at end of file diff --git a/packages/engine-core/dist/ports/headlessMultiplayer.d.ts b/packages/engine-core/dist/ports/headlessMultiplayer.d.ts new file mode 100644 index 0000000..5ebc28f --- /dev/null +++ b/packages/engine-core/dist/ports/headlessMultiplayer.d.ts @@ -0,0 +1,25 @@ +import type { ConnectOptions, ConnectionStatus, MultiplayerPort, NetEnvelope, PlayerId } from "./multiplayer"; +export declare class InMemoryHub { + private peers; + register(id: PlayerId, room: string, inbound: (m: NetEnvelope) => void): () => void; + /** Fan-out a los peers de la MISMA room que el mensaje, menos al emisor. */ + broadcast(from: PlayerId, _room: string, message: NetEnvelope): void; +} +/** Adapter de loopback para tests. Implementa MultiplayerPort contra un InMemoryHub. */ +export declare class HeadlessMultiplayerAdapter implements MultiplayerPort { + private hub; + readonly selfId: PlayerId; + private room; + private connected; + private unregister; + private msgHandlers; + private statusHandlers; + constructor(hub: InMemoryHub, selfId?: PlayerId); + connect(opts: ConnectOptions): void; + disconnect(): void; + send(message: NetEnvelope): void; + onMessage(handler: (m: NetEnvelope) => void): () => void; + onStatus(handler: (s: ConnectionStatus) => void): () => void; + private emitStatus; +} +//# sourceMappingURL=headlessMultiplayer.d.ts.map \ No newline at end of file diff --git a/packages/engine-core/dist/ports/headlessMultiplayer.d.ts.map b/packages/engine-core/dist/ports/headlessMultiplayer.d.ts.map new file mode 100644 index 0000000..7afcf02 --- /dev/null +++ b/packages/engine-core/dist/ports/headlessMultiplayer.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"headlessMultiplayer.d.ts","sourceRoot":"","sources":["../../src/ports/headlessMultiplayer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,QAAQ,EACT,MAAM,eAAe,CAAC;AAWvB,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAgC;IAE7C,QAAQ,CACN,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,CAAC,EAAE,WAAW,KAAK,IAAI,GAChC,MAAM,IAAI;IAKb,4EAA4E;IAC5E,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI;CAOrE;AAID,wFAAwF;AACxF,qBAAa,0BAA2B,YAAW,eAAe;IAS9D,OAAO,CAAC,GAAG;IARb,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,UAAU,CAA6B;IAC/C,OAAO,CAAC,WAAW,CAAuC;IAC1D,OAAO,CAAC,cAAc,CAA4C;gBAGxD,GAAG,EAAE,WAAW,EACxB,MAAM,CAAC,EAAE,QAAQ;IAKnB,OAAO,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI;IASnC,UAAU,IAAI,IAAI;IAOlB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAKhC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,WAAW,KAAK,IAAI,GAAG,MAAM,IAAI;IAOxD,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,gBAAgB,KAAK,IAAI,GAAG,MAAM,IAAI;IAO5D,OAAO,CAAC,UAAU;CAGnB"} \ No newline at end of file diff --git a/packages/engine-core/dist/ports/headlessMultiplayer.js b/packages/engine-core/dist/ports/headlessMultiplayer.js new file mode 100644 index 0000000..08571f4 --- /dev/null +++ b/packages/engine-core/dist/ports/headlessMultiplayer.js @@ -0,0 +1,67 @@ +export class InMemoryHub { + constructor() { + this.peers = new Map(); + } + register(id, room, inbound) { + this.peers.set(id, { room, inbound }); + return () => this.peers.delete(id); + } + /** Fan-out a los peers de la MISMA room que el mensaje, menos al emisor. */ + broadcast(from, _room, message) { + for (const [id, peer] of this.peers) { + if (id === from) + continue; + if (peer.room !== message.room) + continue; // aísla por room del receptor + peer.inbound(message); + } + } +} +let _autoId = 0; +/** Adapter de loopback para tests. Implementa MultiplayerPort contra un InMemoryHub. */ +export class HeadlessMultiplayerAdapter { + constructor(hub, selfId) { + this.hub = hub; + this.room = ""; + this.connected = false; + this.unregister = null; + this.msgHandlers = []; + this.statusHandlers = []; + this.selfId = selfId ?? `headless-${++_autoId}`; + } + connect(opts) { + this.room = opts.room; + this.connected = true; + this.unregister = this.hub.register(this.selfId, this.room, (m) => { + this.msgHandlers.forEach((h) => h(m)); + }); + this.emitStatus({ state: "connected", selfId: this.selfId }); + } + disconnect() { + this.connected = false; + this.unregister?.(); + this.unregister = null; + this.emitStatus({ state: "disconnected" }); + } + send(message) { + if (!this.connected) + return; + this.hub.broadcast(this.selfId, this.room, message); + } + onMessage(handler) { + this.msgHandlers.push(handler); + return () => { + this.msgHandlers = this.msgHandlers.filter((h) => h !== handler); + }; + } + onStatus(handler) { + this.statusHandlers.push(handler); + return () => { + this.statusHandlers = this.statusHandlers.filter((h) => h !== handler); + }; + } + emitStatus(s) { + this.statusHandlers.forEach((h) => h(s)); + } +} +//# sourceMappingURL=headlessMultiplayer.js.map \ No newline at end of file diff --git a/packages/engine-core/dist/ports/headlessMultiplayer.js.map b/packages/engine-core/dist/ports/headlessMultiplayer.js.map new file mode 100644 index 0000000..a1c59d5 --- /dev/null +++ b/packages/engine-core/dist/ports/headlessMultiplayer.js.map @@ -0,0 +1 @@ +{"version":3,"file":"headlessMultiplayer.js","sourceRoot":"","sources":["../../src/ports/headlessMultiplayer.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/engine-core/dist/ports/index.d.ts b/packages/engine-core/dist/ports/index.d.ts index 775501a..287fa0b 100644 --- a/packages/engine-core/dist/ports/index.d.ts +++ b/packages/engine-core/dist/ports/index.d.ts @@ -7,4 +7,7 @@ export { HeadlessInput } from "./headlessInput"; export { HeadlessAudioAdapter, type HeadlessAudioCall } from "./headlessAudio"; export type { I18nPort } from "./i18n"; export { HeadlessI18nAdapter, type HeadlessI18nCall } from "./headlessI18n"; +export type { PlayerId, RoomId, NetEnvelope, NetEnvelopeKind, ConnectionStatus, ConnectOptions, MultiplayerPort, } from "./multiplayer"; +export { NET_PROTOCOL_VERSION } from "./multiplayer"; +export { HeadlessMultiplayerAdapter, InMemoryHub } from "./headlessMultiplayer"; //# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/packages/engine-core/dist/ports/index.d.ts.map b/packages/engine-core/dist/ports/index.d.ts.map index 20dc153..2f8bb60 100644 --- a/packages/engine-core/dist/ports/index.d.ts.map +++ b/packages/engine-core/dist/ports/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ports/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,gBAAgB,EAChB,YAAY,EACZ,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,SAAS,GACV,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,GACV,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAE/E,YAAY,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,mBAAmB,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ports/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,gBAAgB,EAChB,YAAY,EACZ,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,SAAS,GACV,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,GACV,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAE/E,YAAY,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,mBAAmB,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAE5E,YAAY,EACV,QAAQ,EACR,MAAM,EACN,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,0BAA0B,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC"} \ No newline at end of file diff --git a/packages/engine-core/dist/ports/index.js b/packages/engine-core/dist/ports/index.js index cca10ce..c6339e3 100644 --- a/packages/engine-core/dist/ports/index.js +++ b/packages/engine-core/dist/ports/index.js @@ -2,4 +2,6 @@ export { HeadlessGameLoop } from "./headlessGameLoop"; export { HeadlessInput } from "./headlessInput"; export { HeadlessAudioAdapter } from "./headlessAudio"; export { HeadlessI18nAdapter } from "./headlessI18n"; +export { NET_PROTOCOL_VERSION } from "./multiplayer"; +export { HeadlessMultiplayerAdapter, InMemoryHub } from "./headlessMultiplayer"; //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/engine-core/dist/ports/index.js.map b/packages/engine-core/dist/ports/index.js.map index 3262b86..50739f8 100644 --- a/packages/engine-core/dist/ports/index.js.map +++ b/packages/engine-core/dist/ports/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ports/index.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAA0B,MAAM,iBAAiB,CAAC;AAG/E,OAAO,EAAE,mBAAmB,EAAyB,MAAM,gBAAgB,CAAC"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ports/index.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAA0B,MAAM,iBAAiB,CAAC;AAG/E,OAAO,EAAE,mBAAmB,EAAyB,MAAM,gBAAgB,CAAC;AAW5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,0BAA0B,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC"} \ No newline at end of file diff --git a/packages/engine-core/dist/ports/multiplayer.d.ts b/packages/engine-core/dist/ports/multiplayer.d.ts new file mode 100644 index 0000000..c89b44d --- /dev/null +++ b/packages/engine-core/dist/ports/multiplayer.d.ts @@ -0,0 +1,62 @@ +/** + * MultiplayerPort – contrato agnóstico de transporte multijugador. + * + * El core SOLO habla con esta interfaz. Los adapters (PartyKit, headless, …) la + * implementan fuera del core. Ningún import de red/`window`/proveedor aquí. + * + * Ver docs/architecture/09-multiplayer.md §3 y ADR-0008. + */ +/** Identificadores. */ +export type PlayerId = string; +export type RoomId = string; +/** Versión del protocolo de red. Súbela ante cambios incompatibles del sobre. */ +export declare const NET_PROTOCOL_VERSION = 1; +/** Tipo de carga del sobre. `payload` se interpreta según este discriminador. */ +export type NetEnvelopeKind = "command" | "event" | "presence" | "snapshot" | "claim" | "claim-result"; +/** Sobre serializable que viaja por el cable. SIEMPRE JSON-serializable. */ +export interface NetEnvelope { + /** Versión de protocolo (NET_PROTOCOL_VERSION). */ + v: number; + /** Room/partida destino. */ + room: RoomId; + /** Emisor. */ + from: PlayerId; + /** Reloj lógico (HLC en task 05; Date.now() hasta entonces). Para ordenar/LWW. */ + ts: number; + /** Discriminador de payload. */ + kind: NetEnvelopeKind; + /** Carga; su forma depende de `kind`. */ + payload: T; +} +/** Estado de conexión reportado por el transporte. */ +export type ConnectionStatus = { + state: "connecting"; +} | { + state: "connected"; + selfId: PlayerId; +} | { + state: "disconnected"; + reason?: string; +}; +/** Opciones para conectar a una room. `selfId` puede asignarlo el servidor. */ +export interface ConnectOptions { + room: RoomId; + selfId?: PlayerId; +} +/** + * Puerto agnóstico de transporte multijugador. Implementado fuera del core por + * adapters (PartyKit, headless, …). El core SOLO habla con esta interfaz. + */ +export interface MultiplayerPort { + /** Abre la conexión a la room. Idempotente si ya conectado. */ + connect(opts: ConnectOptions): Promise | void; + /** Cierra la conexión y libera recursos. */ + disconnect(): void; + /** Envía un sobre a los peers (o al relay, que hace fan-out). Fire-and-forget. */ + send(message: NetEnvelope): void; + /** Suscribe a sobres entrantes. Devuelve unsubscribe. */ + onMessage(handler: (message: NetEnvelope) => void): () => void; + /** Suscribe a cambios de estado de conexión. Devuelve unsubscribe. */ + onStatus(handler: (status: ConnectionStatus) => void): () => void; +} +//# sourceMappingURL=multiplayer.d.ts.map \ No newline at end of file diff --git a/packages/engine-core/dist/ports/multiplayer.d.ts.map b/packages/engine-core/dist/ports/multiplayer.d.ts.map new file mode 100644 index 0000000..8c3bc08 --- /dev/null +++ b/packages/engine-core/dist/ports/multiplayer.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"multiplayer.d.ts","sourceRoot":"","sources":["../../src/ports/multiplayer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,uBAAuB;AACvB,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAC9B,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAE5B,iFAAiF;AACjF,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC,iFAAiF;AACjF,MAAM,MAAM,eAAe,GACvB,SAAS,GACT,OAAO,GACP,UAAU,GACV,UAAU,GACV,OAAO,GACP,cAAc,CAAC;AAEnB,4EAA4E;AAC5E,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO;IACtC,mDAAmD;IACnD,CAAC,EAAE,MAAM,CAAC;IACV,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,cAAc;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,kFAAkF;IAClF,EAAE,EAAE,MAAM,CAAC;IACX,gCAAgC;IAChC,IAAI,EAAE,eAAe,CAAC;IACtB,yCAAyC;IACzC,OAAO,EAAE,CAAC,CAAC;CACZ;AAED,sDAAsD;AACtD,MAAM,MAAM,gBAAgB,GACxB;IAAE,KAAK,EAAE,YAAY,CAAA;CAAE,GACvB;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAA;CAAE,GACxC;IAAE,KAAK,EAAE,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/C,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,+DAA+D;IAC/D,OAAO,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACpD,4CAA4C;IAC5C,UAAU,IAAI,IAAI,CAAC;IACnB,kFAAkF;IAClF,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,yDAAyD;IACzD,SAAS,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAC/D,sEAAsE;IACtE,QAAQ,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACnE"} \ No newline at end of file diff --git a/packages/engine-core/dist/ports/multiplayer.js b/packages/engine-core/dist/ports/multiplayer.js new file mode 100644 index 0000000..96ee7ef --- /dev/null +++ b/packages/engine-core/dist/ports/multiplayer.js @@ -0,0 +1,11 @@ +/** + * MultiplayerPort – contrato agnóstico de transporte multijugador. + * + * El core SOLO habla con esta interfaz. Los adapters (PartyKit, headless, …) la + * implementan fuera del core. Ningún import de red/`window`/proveedor aquí. + * + * Ver docs/architecture/09-multiplayer.md §3 y ADR-0008. + */ +/** Versión del protocolo de red. Súbela ante cambios incompatibles del sobre. */ +export const NET_PROTOCOL_VERSION = 1; +//# sourceMappingURL=multiplayer.js.map \ No newline at end of file diff --git a/packages/engine-core/dist/ports/multiplayer.js.map b/packages/engine-core/dist/ports/multiplayer.js.map new file mode 100644 index 0000000..56e4aa7 --- /dev/null +++ b/packages/engine-core/dist/ports/multiplayer.js.map @@ -0,0 +1 @@ +{"version":3,"file":"multiplayer.js","sourceRoot":"","sources":["../../src/ports/multiplayer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,iFAAiF;AACjF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC"} \ No newline at end of file diff --git a/packages/engine-core/src/__tests__/eventClassification.test.ts b/packages/engine-core/src/__tests__/eventClassification.test.ts new file mode 100644 index 0000000..60856f6 --- /dev/null +++ b/packages/engine-core/src/__tests__/eventClassification.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from "vitest"; +import { + EVENT_CLASSIFICATION, + classifyEvent, + shouldReplicate, + isWorld, +} from "../game/net/eventClassification"; + +describe("eventClassification", () => { + it("classifies pickup as world+private", () => { + expect(classifyEvent("item:pickedUp")).toEqual(["world", "private"]); + }); + it("dialog never crosses the wire", () => { + expect(shouldReplicate("dialog:triggered")).toBe(false); + }); + it("player:moved is presence", () => { + expect(shouldReplicate("player:moved")).toBe(true); + expect(isWorld("player:moved")).toBe(false); + }); + it("net events are private (never replicated)", () => { + expect(shouldReplicate("net:playerJoined")).toBe(false); + expect(shouldReplicate("net:status")).toBe(false); + }); + it("every classified type has at least one class", () => { + for (const classes of Object.values(EVENT_CLASSIFICATION)) { + expect(classes.length).toBeGreaterThan(0); + } + }); +}); diff --git a/packages/engine-core/src/__tests__/itemClaim.test.ts b/packages/engine-core/src/__tests__/itemClaim.test.ts new file mode 100644 index 0000000..a4158ff --- /dev/null +++ b/packages/engine-core/src/__tests__/itemClaim.test.ts @@ -0,0 +1,23 @@ +import { describe, it, expect } from "vitest"; +import { resolveClaim } from "../game/net/claims"; + +describe("resolveClaim", () => { + it("exactly one winner for concurrent claims", () => { + const res = resolveClaim([ + { entityId: "key", by: "A", ts: 5 }, + { entityId: "key", by: "B", ts: 5 }, // same ts → tie-break by id + ]); + expect(res.filter((r) => r.granted)).toHaveLength(1); + expect(res.find((r) => r.granted)!.by).toBe("A"); + }); + it("first writer (lower ts) wins", () => { + const res = resolveClaim([ + { entityId: "key", by: "A", ts: 9 }, + { entityId: "key", by: "B", ts: 3 }, + ]); + expect(res.find((r) => r.granted)!.by).toBe("B"); + }); + it("empty input yields no results", () => { + expect(resolveClaim([])).toEqual([]); + }); +}); diff --git a/packages/engine-core/src/__tests__/multiplayerPort.test.ts b/packages/engine-core/src/__tests__/multiplayerPort.test.ts new file mode 100644 index 0000000..0107b1f --- /dev/null +++ b/packages/engine-core/src/__tests__/multiplayerPort.test.ts @@ -0,0 +1,67 @@ +import { describe, it, expect, vi } from "vitest"; +import { + HeadlessMultiplayerAdapter, + InMemoryHub, +} from "../ports/headlessMultiplayer"; +import { NET_PROTOCOL_VERSION, type NetEnvelope } from "../ports/multiplayer"; + +const env = (from: string, room = "r1"): NetEnvelope => ({ + v: NET_PROTOCOL_VERSION, + room, + from, + ts: 1, + kind: "event", + payload: { type: "ping" }, +}); + +describe("MultiplayerPort headless", () => { + it("delivers a sent message to peers but not to self", () => { + const hub = new InMemoryHub(); + const a = new HeadlessMultiplayerAdapter(hub, "A"); + const b = new HeadlessMultiplayerAdapter(hub, "B"); + a.connect({ room: "r1" }); + b.connect({ room: "r1" }); + const aSeen = vi.fn(); + const bSeen = vi.fn(); + a.onMessage(aSeen); + b.onMessage(bSeen); + a.send(env("A")); + expect(bSeen).toHaveBeenCalledTimes(1); + expect(aSeen).not.toHaveBeenCalled(); + }); + + it("does not deliver across different rooms", () => { + const hub = new InMemoryHub(); + const a = new HeadlessMultiplayerAdapter(hub, "A"); + const b = new HeadlessMultiplayerAdapter(hub, "B"); + a.connect({ room: "r1" }); + b.connect({ room: "r2" }); + const bSeen = vi.fn(); + b.onMessage(bSeen); + a.send(env("A", "r1")); + expect(bSeen).not.toHaveBeenCalled(); + }); + + it("reports connected/disconnected status", () => { + const hub = new InMemoryHub(); + const a = new HeadlessMultiplayerAdapter(hub, "A"); + const seen: string[] = []; + a.onStatus((s) => seen.push(s.state)); + a.connect({ room: "r1" }); + a.disconnect(); + expect(seen).toEqual(["connected", "disconnected"]); + }); + + it("stops receiving after disconnect", () => { + const hub = new InMemoryHub(); + const a = new HeadlessMultiplayerAdapter(hub, "A"); + const b = new HeadlessMultiplayerAdapter(hub, "B"); + a.connect({ room: "r1" }); + b.connect({ room: "r1" }); + const bSeen = vi.fn(); + b.onMessage(bSeen); + b.disconnect(); + a.send(env("A")); + expect(bSeen).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/engine-core/src/__tests__/multiplayerSession.test.ts b/packages/engine-core/src/__tests__/multiplayerSession.test.ts new file mode 100644 index 0000000..6a56199 --- /dev/null +++ b/packages/engine-core/src/__tests__/multiplayerSession.test.ts @@ -0,0 +1,101 @@ +import { describe, it, expect } from "vitest"; +import { InMemoryHub, HeadlessMultiplayerAdapter } from "../ports/headlessMultiplayer"; +import { createMultiplayerSession } from "../game/net/MultiplayerSession"; +import { createRemotePlayersStore } from "../game/net/remotePlayersStore"; +import { createSelfDescriptor } from "../game/net/playerIdentity"; +import type { GameEvent, GameEventType } from "../game/events/types"; + +// Bus de test: registro por tipo + emit que invoca a los listeners de ese tipo. +function makeBus() { + const map = new Map void>>(); + return { + on: (t: GameEventType, h: (e: GameEvent) => void) => { + const arr = map.get(t) ?? []; + arr.push(h); + map.set(t, arr); + return () => map.set(t, (map.get(t) ?? []).filter((x) => x !== h)); + }, + emit: (e: GameEvent) => (map.get(e.type) ?? []).forEach((h) => h(e)), + }; +} + +function peer(hub: InMemoryHub, id: string, applied: GameEvent[]) { + const bus = makeBus(); + const remotePlayers = createRemotePlayersStore(); + const session = createMultiplayerSession({ + port: new HeadlessMultiplayerAdapter(hub, id), + bus, + room: "r1", + remotePlayers, + self: createSelfDescriptor({ + playerId: id, + sceneId: "town", + position: [0, 0, 0], + displayName: id, + }), + applyRemoteEvent: (e) => { + applied.push(e); + bus.emit(e); + }, // re-emite al bus local + now: () => 1, + presenceThrottleMs: 0, // sin throttle para tests deterministas + }); + return { bus, remotePlayers, session }; +} + +describe("MultiplayerSession", () => { + it("replicates a world event to the other peer", () => { + const hub = new InMemoryHub(); + const appliedB: GameEvent[] = []; + const a = peer(hub, "A", []); + peer(hub, "B", appliedB); + a.bus.emit({ type: "item:dropped", itemId: "key", outcome: "place" }); + expect(appliedB).toContainEqual({ + type: "item:dropped", + itemId: "key", + outcome: "place", + }); + }); + + it("does NOT replicate a private event", () => { + const hub = new InMemoryHub(); + const appliedB: GameEvent[] = []; + const a = peer(hub, "A", []); + peer(hub, "B", appliedB); + a.bus.emit({ type: "dialog:triggered", text: "hi", source: "test" }); + expect(appliedB).toHaveLength(0); + }); + + it("does not echo a remote-applied event back to the wire", () => { + const hub = new InMemoryHub(); + const appliedA: GameEvent[] = []; + const appliedB: GameEvent[] = []; + const a = peer(hub, "A", appliedA); + peer(hub, "B", appliedB); + a.bus.emit({ type: "item:dropped", itemId: "key", outcome: "place" }); + expect(appliedB.filter((e) => e.type === "item:dropped")).toHaveLength(1); + expect(appliedA.filter((e) => e.type === "item:dropped")).toHaveLength(0); + }); + + it("upserts remote presence and emits net:playerJoined", () => { + const hub = new InMemoryHub(); + const joined: GameEvent[] = []; + const a = peer(hub, "A", []); + a.bus.on("net:playerJoined", (e) => joined.push(e)); + peer(hub, "B", []); // B se conecta → manda presence + expect( + a.remotePlayers.getInScene("town").some((p) => p.playerId === "B"), + ).toBe(true); + expect(joined.some((e) => e.type === "net:playerJoined")).toBe(true); + }); + + it("propagates presence position via player:moved", () => { + const hub = new InMemoryHub(); + const a = peer(hub, "A", []); + const b = peer(hub, "B", []); + b.bus.emit({ type: "player:moved", position: [5, 0, 7], action: "east" }); + const bOnA = a.remotePlayers.getInScene("town").find((p) => p.playerId === "B"); + expect(bOnA?.position).toEqual([5, 0, 7]); + expect(bOnA?.action).toBe("east"); + }); +}); diff --git a/packages/engine-core/src/__tests__/netAgnosticism.test.ts b/packages/engine-core/src/__tests__/netAgnosticism.test.ts new file mode 100644 index 0000000..7f6fd10 --- /dev/null +++ b/packages/engine-core/src/__tests__/netAgnosticism.test.ts @@ -0,0 +1,43 @@ +import { describe, it, expect } from "vitest"; +import { readFileSync, readdirSync, statSync } from "node:fs"; +import { join } from "node:path"; + +const FORBIDDEN = [ + /\bwindow\b/, + /\bdocument\b/, + /WebSocket/, + /localStorage/, + /partysocket/, + /from ["'].*\/apps\//, +]; + +function walk(dir: string): string[] { + return readdirSync(dir).flatMap((f) => { + const p = join(dir, f); + if (statSync(p).isDirectory()) return walk(p); + return p.endsWith(".ts") && !p.endsWith(".test.ts") ? [p] : []; + }); +} + +/** Quita comentarios para que menciones en prosa no cuenten como uso real. */ +function stripComments(code: string): string { + return code + .replace(/\/\*[\s\S]*?\*\//g, "") // block comments + .replace(/(^|[^:])\/\/.*$/gm, "$1"); // line comments (no rompe http://) +} + +describe("core agnosticism (net)", () => { + it("game/net and ports/multiplayer never touch the network or window", () => { + const files = [ + ...walk(join(__dirname, "..", "game", "net")), + join(__dirname, "..", "ports", "multiplayer.ts"), + join(__dirname, "..", "ports", "headlessMultiplayer.ts"), + ]; + for (const f of files) { + const code = stripComments(readFileSync(f, "utf8")); + for (const re of FORBIDDEN) { + expect(re.test(code), `${f} matched ${re}`).toBe(false); + } + } + }); +}); diff --git a/packages/engine-core/src/__tests__/optimisticReconcile.test.ts b/packages/engine-core/src/__tests__/optimisticReconcile.test.ts new file mode 100644 index 0000000..998850e --- /dev/null +++ b/packages/engine-core/src/__tests__/optimisticReconcile.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect, vi } from "vitest"; +import { createOptimisticReconciler } from "../game/net/optimistic"; + +describe("optimistic reconciler", () => { + it("confirm does not roll back", () => { + const r = createOptimisticReconciler(); + const rollback = vi.fn(); + r.track("key", rollback); + r.confirm("key"); + expect(rollback).not.toHaveBeenCalled(); + expect(r.size()).toBe(0); + }); + it("reject rolls back exactly once", () => { + const r = createOptimisticReconciler(); + const rollback = vi.fn(); + r.track("key", rollback); + r.reject("key"); + expect(rollback).toHaveBeenCalledTimes(1); + expect(r.size()).toBe(0); + }); + it("onSnapshot reverts all unconfirmed predictions", () => { + const r = createOptimisticReconciler(); + const a = vi.fn(); + const b = vi.fn(); + r.track("k1", a); + r.track("k2", b); + r.onSnapshot(); + expect(a).toHaveBeenCalledTimes(1); + expect(b).toHaveBeenCalledTimes(1); + expect(r.size()).toBe(0); + }); +}); diff --git a/packages/engine-core/src/__tests__/presence.test.ts b/packages/engine-core/src/__tests__/presence.test.ts new file mode 100644 index 0000000..6a2100c --- /dev/null +++ b/packages/engine-core/src/__tests__/presence.test.ts @@ -0,0 +1,24 @@ +import { describe, it, expect, vi } from "vitest"; +import { throttleTrailing } from "../game/net/throttle"; + +describe("throttleTrailing", () => { + it("runs immediately then coalesces, flushing the last call", () => { + let t = 0; + const scheduled: Array<{ cb: () => void; at: number }> = []; + const clock = { + now: () => t, + schedule: (cb: () => void, d: number) => scheduled.push({ cb, at: t + d }), + }; + const fn = vi.fn(); + const throttled = throttleTrailing(fn, 100, clock); + throttled(); // t=0 → runs (1) + t = 30; + throttled(); // within window → schedules trailing + t = 60; + throttled(); // still within → already scheduled + expect(fn).toHaveBeenCalledTimes(1); + t = 100; + scheduled.forEach((s) => s.cb()); // flush trailing + expect(fn).toHaveBeenCalledTimes(2); // last call flushed + }); +}); diff --git a/packages/engine-core/src/__tests__/remotePlayersStore.test.ts b/packages/engine-core/src/__tests__/remotePlayersStore.test.ts new file mode 100644 index 0000000..42894ce --- /dev/null +++ b/packages/engine-core/src/__tests__/remotePlayersStore.test.ts @@ -0,0 +1,33 @@ +import { describe, it, expect } from "vitest"; +import { createRemotePlayersStore } from "../game/net/remotePlayersStore"; +import { generateRandomName } from "../game/net/playerIdentity"; + +describe("remotePlayersStore", () => { + it("upsert merges partials and filters by scene", () => { + const s = createRemotePlayersStore(); + s.upsert({ playerId: "B", sceneId: "town", position: [1, 0, 2] }); + s.upsert({ playerId: "C", sceneId: "cave", position: [0, 0, 0] }); + expect(s.getInScene("town").map((p) => p.playerId)).toEqual(["B"]); + s.upsert({ playerId: "B", position: [3, 0, 4] }); // keeps sceneId=town + expect(s.getInScene("town")[0]!.position).toEqual([3, 0, 4]); + }); + it("pruneStale removes old players", () => { + const s = createRemotePlayersStore(); + s.upsert({ playerId: "B", sceneId: "town", lastSeenTs: 1000 }); + expect(s.pruneStale(500, 2000)).toEqual(["B"]); + expect(s.getAll()).toHaveLength(0); + }); + it("notifies subscribers on change", () => { + const s = createRemotePlayersStore(); + let n = 0; + const unsub = s.subscribe(() => n++); + s.upsert({ playerId: "B", sceneId: "town" }); + s.remove("B"); + unsub(); + s.upsert({ playerId: "C", sceneId: "town" }); + expect(n).toBe(2); + }); + it("generateRandomName is deterministic with injected rng", () => { + expect(generateRandomName(() => 0)).toMatch(/^Viajero-000$/); + }); +}); diff --git a/packages/engine-core/src/__tests__/worldStateLww.test.ts b/packages/engine-core/src/__tests__/worldStateLww.test.ts new file mode 100644 index 0000000..b2a16eb --- /dev/null +++ b/packages/engine-core/src/__tests__/worldStateLww.test.ts @@ -0,0 +1,27 @@ +import { describe, it, expect } from "vitest"; +import { WorldStateLww } from "../game/net/worldState"; +import { Hlc } from "../game/net/clock"; + +describe("WorldStateLww", () => { + it("keeps the higher ts", () => { + const w = new WorldStateLww(); + expect(w.merge("door1", "open", { value: false, ts: 1, by: "A" })).toBe(true); + expect(w.merge("door1", "open", { value: true, ts: 2, by: "B" })).toBe(true); + expect(w.merge("door1", "open", { value: false, ts: 1, by: "C" })).toBe(false); + expect(w.get("door1", "open")).toBe(true); + }); + it("round-trips via snapshot/load", () => { + const w = new WorldStateLww(); + w.merge("door1", "open", { value: true, ts: 5, by: "A" }); + const w2 = new WorldStateLww(); + w2.load(w.snapshot()); + expect(w2.get("door1", "open")).toBe(true); + }); + it("Hlc is monotonic even within the same wall ms", () => { + const hlc = new Hlc(); + const a = hlc.now(1000); + const b = hlc.now(1000); + const c = hlc.now(1000); + expect(a < b && b < c).toBe(true); + }); +}); diff --git a/packages/engine-core/src/game/commands/types.ts b/packages/engine-core/src/game/commands/types.ts index db3bf41..5865e04 100644 --- a/packages/engine-core/src/game/commands/types.ts +++ b/packages/engine-core/src/game/commands/types.ts @@ -31,6 +31,10 @@ export type GameCommand = | { type: "audio:setVolume"; target: AudioMuteTarget; volume: number } // i18n | { type: "i18n:setLocale"; locale: Locale } - | { type: "i18n:registerDictionary"; locale: Locale; dict: DialogDictionary }; + | { type: "i18n:registerDictionary"; locale: Locale; dict: DialogDictionary } + // Net + | { type: "net:join"; room: string; displayName?: string } + | { type: "net:leave" } + | { type: "net:setName"; displayName: string }; export type GameCommandType = GameCommand["type"]; diff --git a/packages/engine-core/src/game/events/legacyAdapter.ts b/packages/engine-core/src/game/events/legacyAdapter.ts index ef8f1ed..15925dd 100644 --- a/packages/engine-core/src/game/events/legacyAdapter.ts +++ b/packages/engine-core/src/game/events/legacyAdapter.ts @@ -20,8 +20,9 @@ export function legacyRuntimeEventToGameEvent(ev: RuntimeEvent): GameEvent { return { type: "item:dropped", itemId: ev.itemId, - outcome: ev.outcome as "consume" | "place" | "return", + outcome: ev.outcome as "consume" | "place" | "return" | "pickup-success", interactionId: ev.interactionId, + ...(ev.placedItem ? { placedItem: ev.placedItem } : {}), }; case "onDialog": return { diff --git a/packages/engine-core/src/game/events/types.ts b/packages/engine-core/src/game/events/types.ts index ad205f4..b441841 100644 --- a/packages/engine-core/src/game/events/types.ts +++ b/packages/engine-core/src/game/events/types.ts @@ -1,4 +1,6 @@ -import type { AudioSettings, GameScene, GameVec3, Locale, SoundCategory } from "../types"; +import type { AudioSettings, GameScene, GameVec3, Locale, PlacedSceneItem, SoundCategory } from "../types"; +import type { PlayerDescriptor } from "../net/playerIdentity"; +import type { ConnectionStatus, PlayerId } from "../../ports/multiplayer"; /** * Union exhaustiva de todos los eventos que el motor puede emitir. @@ -20,8 +22,10 @@ export type GameEvent = | { type: "item:dropped"; itemId: string; - outcome: "consume" | "place" | "return"; + outcome: "consume" | "place" | "return" | "pickup-success"; interactionId?: string; + /** Full placed-item data; present only for outcome "place" — used by peers to add the item to their scene. */ + placedItem?: PlacedSceneItem; } // Dialog | { type: "dialog:triggered"; text: string; dialogKey?: string; source: string } @@ -45,7 +49,11 @@ export type GameEvent = | { type: "audio:settingsChanged"; settings: AudioSettings } // i18n | { type: "i18n:localeChanged"; locale: Locale; previous: Locale } - | { type: "i18n:dictionaryUpdated"; locale: Locale; keysAdded: number }; + | { type: "i18n:dictionaryUpdated"; locale: Locale; keysAdded: number } + // Net (locales para UI; nunca se replican — ver eventClassification) + | { type: "net:playerJoined"; player: PlayerDescriptor } + | { type: "net:playerLeft"; playerId: PlayerId } + | { type: "net:status"; status: ConnectionStatus }; export type GameEventType = GameEvent["type"]; diff --git a/packages/engine-core/src/game/net/MultiplayerSession.ts b/packages/engine-core/src/game/net/MultiplayerSession.ts new file mode 100644 index 0000000..3983cf1 --- /dev/null +++ b/packages/engine-core/src/game/net/MultiplayerSession.ts @@ -0,0 +1,167 @@ +import type { GameEvent, GameEventType } from "../events/types"; +import type { MultiplayerPort, NetEnvelope, RoomId } from "../../ports/multiplayer"; +import { NET_PROTOCOL_VERSION } from "../../ports/multiplayer"; +import type { PlayerDescriptor } from "./playerIdentity"; +import type { RemotePlayersStore } from "./remotePlayersStore"; +import { EVENT_CLASSIFICATION, shouldReplicate, isWorld } from "./eventClassification"; +import { throttleTrailing, type ThrottleClock } from "./throttle"; +import type { OptimisticReconciler } from "./optimistic"; + +/** Bus mínimo que la sesión necesita (lo cumple el handle de createGameRuntime). */ +export interface SessionBus { + on: (type: GameEventType, handler: (e: GameEvent) => void) => () => void; + emit: (event: GameEvent) => void; +} + +export interface MultiplayerSessionOptions { + port: MultiplayerPort; + bus: SessionBus; + room: RoomId; + self: PlayerDescriptor; + remotePlayers: RemotePlayersStore; + /** Aplica un evento world remoto al estado local (el host lo cablea a los stores). */ + applyRemoteEvent: (event: GameEvent) => void; + /** Reloj lógico para `ts`. Default Date.now (task 05 puede inyectar HLC). */ + now?: () => number; + /** ms entre envíos de presence. Default 80 (~12.5 Hz). 0 = sin throttle. */ + presenceThrottleMs?: number; + /** Reloj/scheduler para el throttle (inyectable en tests). */ + clock?: ThrottleClock; + /** Reconciliador optimista (task 09) para claim-result/snapshot. */ + reconciler?: OptimisticReconciler; + /** Called once on join with the server's world snapshot so late joiners can hydrate shared state. */ + applySnapshot?: (world: Record) => void; + /** ms entre heartbeats para jugadores inactivos. Default 30 000 (30 s). 0 = sin heartbeat. */ + heartbeatMs?: number; +} + +export interface MultiplayerSession { + dispose: () => void; + getSelf: () => PlayerDescriptor; + /** Actualiza el descriptor local y difunde presence (usado por setName/task 06). */ + updateSelf: (patch: Partial) => void; +} + +export function createMultiplayerSession( + opts: MultiplayerSessionOptions, +): MultiplayerSession { + const { port, bus, room, remotePlayers, applyRemoteEvent } = opts; + const now = opts.now ?? (() => Date.now()); + let self = { ...opts.self }; + let applyingRemote = false; // ⟵ anti-bucle: true mientras aplicamos algo recibido + + const wrap = (kind: NetEnvelope["kind"], payload: unknown): NetEnvelope => ({ + v: NET_PROTOCOL_VERSION, + room, + from: self.playerId, + ts: now(), + kind, + payload, + }); + + const rawSendPresence = () => { + self = { ...self, lastSeenTs: now() }; + port.send(wrap("presence", self)); + }; + + const clock: ThrottleClock = opts.clock ?? { + now: () => Date.now(), + schedule: (cb, d) => { + setTimeout(cb, d); + }, + }; + const throttleMs = opts.presenceThrottleMs ?? 80; + const sendPresence = + throttleMs > 0 ? throttleTrailing(rawSendPresence, throttleMs, clock) : rawSendPresence; + + // ── Salida: suscribe a TODOS los tipos de evento ──────────────────────────── + const types = Object.keys(EVENT_CLASSIFICATION) as GameEventType[]; + const unsubs = types.map((type) => + bus.on(type, (event) => { + if (applyingRemote) return; // no re-difundir lo aplicado desde la red + if (!shouldReplicate(type)) return; // private → nunca sale + if (isWorld(type)) { + port.send(wrap("event", event)); + } + if (type === "player:moved") { + const e = event as Extract; + self = { ...self, position: e.position, action: e.action }; + sendPresence(); + } else if (type === "scene:changed") { + self = { + ...self, + sceneId: (event as Extract).sceneId, + }; + sendPresence(); + } else if (type === "transition:completed") { + self = { + ...self, + sceneId: (event as Extract) + .toSceneId, + }; + sendPresence(); + } + }), + ); + + // ── Entrada: aplica envelopes remotos ─────────────────────────────────────── + const offMsg = port.onMessage((m) => { + if (m.from === self.playerId) return; // defensivo (headless ya filtra self) + if (m.kind === "presence") { + const d = m.payload as PlayerDescriptor & { __left?: boolean }; + if (d.__left) { + remotePlayers.remove(d.playerId); + bus.emit({ type: "net:playerLeft", playerId: d.playerId }); + return; + } + const isNew = remotePlayers.getAll().every((p) => p.playerId !== d.playerId); + remotePlayers.upsert(d); + if (isNew) bus.emit({ type: "net:playerJoined", player: d }); + } else if (m.kind === "event") { + applyingRemote = true; + try { + applyRemoteEvent(m.payload as GameEvent); + } finally { + applyingRemote = false; + } + } else if (m.kind === "claim-result") { + const r = m.payload as { entityId: string; granted: boolean }; + if (r.granted) opts.reconciler?.confirm(r.entityId); + else opts.reconciler?.reject(r.entityId); + } else if (m.kind === "snapshot") { + opts.reconciler?.onSnapshot(); + opts.applySnapshot?.(m.payload as Record); + } + }); + + const offStatus = port.onStatus((status) => { + bus.emit({ type: "net:status", status }); + if (status.state === "connected" && status.selfId) { + self = { ...self, playerId: status.selfId }; + } + }); + + port.connect({ room, selfId: self.playerId }); + rawSendPresence(); // anúnciate al entrar (inmediato, sin throttle) + + // Heartbeat: mantiene la presence viva aunque el jugador esté inactivo. + // El receptor solo descartará al jugador si supera el stale threshold (p.ej. 10 min). + const heartbeatMs = opts.heartbeatMs ?? 30_000; + const heartbeatTimer = + heartbeatMs > 0 ? setInterval(rawSendPresence, heartbeatMs) : null; + + return { + dispose: () => { + if (heartbeatTimer !== null) clearInterval(heartbeatTimer); + unsubs.forEach((u) => u()); + offMsg(); + offStatus(); + port.disconnect(); + }, + getSelf: () => self, + updateSelf: (patch) => { + self = { ...self, ...patch }; + sendPresence(); + }, + }; +} diff --git a/packages/engine-core/src/game/net/claims.ts b/packages/engine-core/src/game/net/claims.ts new file mode 100644 index 0000000..597fabf --- /dev/null +++ b/packages/engine-core/src/game/net/claims.ts @@ -0,0 +1,26 @@ +import type { PlayerId } from "../../ports/multiplayer"; + +export interface ItemClaim { + entityId: string; + by: PlayerId; + ts: number; +} +export interface ClaimResult { + entityId: string; + by: PlayerId; + granted: boolean; +} + +/** + * Resuelve un conjunto de claims sobre el MISMO entityId (orden de llegada irrelevante). + * Gana el de menor ts (first-writer-wins); empate → menor playerId. Determinista. + */ +export function resolveClaim(claims: ItemClaim[]): ClaimResult[] { + if (claims.length === 0) return []; + const winner = [...claims].sort((a, b) => a.ts - b.ts || (a.by < b.by ? -1 : 1))[0]!; + return claims.map((c) => ({ + entityId: c.entityId, + by: c.by, + granted: c.by === winner.by, + })); +} diff --git a/packages/engine-core/src/game/net/clock.ts b/packages/engine-core/src/game/net/clock.ts new file mode 100644 index 0000000..399c4f7 --- /dev/null +++ b/packages/engine-core/src/game/net/clock.ts @@ -0,0 +1,27 @@ +/** HLC: combina reloj físico y contador lógico → orden total sin sincronizar relojes. */ +const COUNTER_BITS = 12; // hasta 4096 eventos por ms +const COUNTER_MASK = (1 << COUNTER_BITS) - 1; + +export class Hlc { + private lastPhysical = 0; + private counter = 0; + + /** Devuelve un timestamp codificado (number) monótono creciente. */ + now(wall: number = Date.now()): number { + if (wall > this.lastPhysical) { + this.lastPhysical = wall; + this.counter = 0; + } else { + this.counter = (this.counter + 1) & COUNTER_MASK; + } + return this.lastPhysical * (COUNTER_MASK + 1) + this.counter; + } + + /** Actualiza el reloj al recibir un ts remoto (mantiene la causalidad). */ + update(remoteTs: number, wall: number = Date.now()): number { + const remotePhysical = Math.floor(remoteTs / (COUNTER_MASK + 1)); + this.lastPhysical = Math.max(this.lastPhysical, remotePhysical, wall); + this.counter = (this.counter + 1) & COUNTER_MASK; + return this.lastPhysical * (COUNTER_MASK + 1) + this.counter; + } +} diff --git a/packages/engine-core/src/game/net/eventClassification.ts b/packages/engine-core/src/game/net/eventClassification.ts new file mode 100644 index 0000000..0d12225 --- /dev/null +++ b/packages/engine-core/src/game/net/eventClassification.ts @@ -0,0 +1,58 @@ +import type { GameEventType } from "../events/types"; + +/** Clase de sincronización de un evento. Un evento puede pertenecer a varias. */ +export type SyncClass = "world" | "presence" | "private"; + +/** + * Clasificación exhaustiva. `satisfies` obliga a cubrir TODOS los GameEventType: + * si añades un evento nuevo y no lo clasificas, TypeScript falla aquí. + * + * - world → estado compartido, replicado a todos, persistente (puertas, ítems colocados). + * - presence → efímero por jugador, fan-out a misma escena (posición, escena). + * - private → local, NUNCA cruza la red (diálogos, audio, animación de walk). + */ +export const EVENT_CLASSIFICATION = { + // Scene + "scene:changed": ["presence"], // cambia MI escena → actualiza mi presence + "scene:respawnRequested": ["private"], + // Player + "player:moved": ["presence"], // alta frecuencia; throttle en el borde + "player:collided": ["private"], + // Inventory (pickup quita del world y añade a mi inventario) + "item:pickedUp": ["world", "private"], + "item:dropped": ["world"], // place/consume mutan placedItems compartidos + // Dialog + "dialog:triggered": ["private"], + "dialog:dismissed": ["private"], + // Transitions + "transition:triggered": ["private"], + "transition:started": ["private"], + "transition:completed": ["presence"], // cambio de escena propio + "transition:colliderClicked": ["private"], + // Player walking (se deriva de presence en remotos) + "player:walkStarted": ["private"], + "player:walkCompleted": ["private"], + "player:walkAborted": ["private"], + // Audio (local) + "audio:sfxRequested": ["private"], + "audio:musicRequested": ["private"], + "audio:musicStopped": ["private"], + "audio:settingsChanged": ["private"], + // i18n (local) + "i18n:localeChanged": ["private"], + "i18n:dictionaryUpdated": ["private"], + // Net (locales para UI; nunca se replican) + "net:playerJoined": ["private"], + "net:playerLeft": ["private"], + "net:status": ["private"], +} satisfies Record; + +export function classifyEvent(type: GameEventType): SyncClass[] { + return EVENT_CLASSIFICATION[type] ?? ["private"]; +} +export const isWorld = (t: GameEventType): boolean => classifyEvent(t).includes("world"); +export const isPresence = (t: GameEventType): boolean => + classifyEvent(t).includes("presence"); +/** ¿Debe salir a la red? (world o presence). Los `private` jamás. */ +export const shouldReplicate = (t: GameEventType): boolean => + isWorld(t) || isPresence(t); diff --git a/packages/engine-core/src/game/net/index.ts b/packages/engine-core/src/game/net/index.ts new file mode 100644 index 0000000..6e3dc95 --- /dev/null +++ b/packages/engine-core/src/game/net/index.ts @@ -0,0 +1,9 @@ +export * from "./eventClassification"; +export * from "./playerIdentity"; +export * from "./remotePlayersStore"; +export * from "./MultiplayerSession"; +export * from "./clock"; +export * from "./worldState"; +export * from "./claims"; +export * from "./throttle"; +export * from "./optimistic"; diff --git a/packages/engine-core/src/game/net/optimistic.ts b/packages/engine-core/src/game/net/optimistic.ts new file mode 100644 index 0000000..af3ebfe --- /dev/null +++ b/packages/engine-core/src/game/net/optimistic.ts @@ -0,0 +1,40 @@ +export interface PendingAction { + entityId: string; + rollback: () => void; +} + +/** + * Buffer de acciones optimistas pendientes de confirmación. Cada acción aporta + * su propio `rollback` (cómo deshacerla). Reconciliación determinista. + */ +export function createOptimisticReconciler() { + const pending = new Map(); + return { + /** Registra una predicción local ya aplicada. */ + track(entityId: string, rollback: () => void): void { + pending.set(entityId, { entityId, rollback }); + }, + /** El servidor confirmó: descarta la predicción (no revierte). */ + confirm(entityId: string): void { + pending.delete(entityId); + }, + /** El servidor rechazó: revierte y descarta. */ + reject(entityId: string): void { + const p = pending.get(entityId); + if (p) { + p.rollback(); + pending.delete(entityId); + } + }, + /** Reconexión: revierte TODO lo no confirmado antes de aplicar el snapshot. */ + onSnapshot(): void { + pending.forEach((p) => p.rollback()); + pending.clear(); + }, + size(): number { + return pending.size; + }, + }; +} + +export type OptimisticReconciler = ReturnType; diff --git a/packages/engine-core/src/game/net/playerIdentity.ts b/packages/engine-core/src/game/net/playerIdentity.ts new file mode 100644 index 0000000..0e488a9 --- /dev/null +++ b/packages/engine-core/src/game/net/playerIdentity.ts @@ -0,0 +1,59 @@ +import type { GameVec3 } from "../types"; +import type { PlayerId } from "../../ports/multiplayer"; + +/** Personaje seleccionable. Hoy solo "Dave"; reservado para multi-character futuro. */ +export type CharacterId = "Dave"; +export const DEFAULT_CHARACTER_ID: CharacterId = "Dave"; + +export type PlayerAction = "idle" | "north" | "south" | "west" | "east"; + +/** Descriptor de un jugador. Serializable: viaja como presence en NetEnvelope.payload. */ +export interface PlayerDescriptor { + playerId: PlayerId; + displayName: string; + characterId: CharacterId; + sceneId: string; + position: GameVec3; + action: PlayerAction; + /** Último timestamp visto (para limpiar jugadores stale). */ + lastSeenTs: number; +} + +const NAME_PREFIXES = [ + "Viajero", + "Errante", + "Nómada", + "Curioso", + "Vecino", + "Explorador", +] as const; + +/** Genera un nombre anónimo legible, p.ej. "Viajero-A3F". RNG inyectable para tests. */ +export function generateRandomName(rng: () => number = Math.random): string { + const prefix = NAME_PREFIXES[Math.floor(rng() * NAME_PREFIXES.length)]!; + const suffix = Math.floor(rng() * 0xfff) + .toString(16) + .toUpperCase() + .padStart(3, "0"); + return `${prefix}-${suffix}`; +} + +/** Crea el descriptor inicial del jugador local. */ +export function createSelfDescriptor(opts: { + playerId: PlayerId; + sceneId: string; + position: GameVec3; + displayName?: string; + characterId?: CharacterId; + now?: () => number; +}): PlayerDescriptor { + return { + playerId: opts.playerId, + displayName: opts.displayName ?? generateRandomName(), + characterId: opts.characterId ?? DEFAULT_CHARACTER_ID, + sceneId: opts.sceneId, + position: opts.position, + action: "idle", + lastSeenTs: (opts.now ?? Date.now)(), + }; +} diff --git a/packages/engine-core/src/game/net/remotePlayersStore.ts b/packages/engine-core/src/game/net/remotePlayersStore.ts new file mode 100644 index 0000000..2e14e97 --- /dev/null +++ b/packages/engine-core/src/game/net/remotePlayersStore.ts @@ -0,0 +1,63 @@ +import type { PlayerDescriptor } from "./playerIdentity"; +import type { PlayerId } from "../../ports/multiplayer"; + +export interface RemotePlayersStore { + /** Inserta o actualiza un jugador remoto (merge parcial sobre el existente). */ + upsert: (descriptor: Partial & { playerId: PlayerId }) => void; + remove: (playerId: PlayerId) => void; + /** Solo los jugadores presentes en la escena dada (interest management en el borde). */ + getInScene: (sceneId: string) => PlayerDescriptor[]; + getAll: () => PlayerDescriptor[]; + /** Elimina jugadores sin presence en > maxAgeMs. Devuelve los ids eliminados. */ + pruneStale: (maxAgeMs: number, now?: number) => PlayerId[]; + reset: () => void; + /** Notifica en cada cambio (para que el renderer re-renderice). */ + subscribe: (listener: () => void) => () => void; +} + +export function createRemotePlayersStore(): RemotePlayersStore { + const players = new Map(); + const listeners = new Set<() => void>(); + const notify = () => listeners.forEach((l) => l()); + + return { + upsert: (d) => { + const prev = players.get(d.playerId); + players.set(d.playerId, { + playerId: d.playerId, + displayName: d.displayName ?? prev?.displayName ?? d.playerId, + characterId: d.characterId ?? prev?.characterId ?? "Dave", + sceneId: d.sceneId ?? prev?.sceneId ?? "", + position: d.position ?? prev?.position ?? [0, 0, 0], + action: d.action ?? prev?.action ?? "idle", + lastSeenTs: d.lastSeenTs ?? Date.now(), + }); + notify(); + }, + remove: (id) => { + if (players.delete(id)) notify(); + }, + getInScene: (sceneId) => + [...players.values()].filter((p) => p.sceneId === sceneId), + getAll: () => [...players.values()], + pruneStale: (maxAgeMs, now = Date.now()) => { + const removed: PlayerId[] = []; + for (const [id, p] of players) { + if (now - p.lastSeenTs > maxAgeMs) { + players.delete(id); + removed.push(id); + } + } + if (removed.length) notify(); + return removed; + }, + reset: () => { + players.clear(); + notify(); + }, + subscribe: (l) => { + listeners.add(l); + return () => listeners.delete(l); + }, + }; +} diff --git a/packages/engine-core/src/game/net/throttle.ts b/packages/engine-core/src/game/net/throttle.ts new file mode 100644 index 0000000..8baef7d --- /dev/null +++ b/packages/engine-core/src/game/net/throttle.ts @@ -0,0 +1,35 @@ +/** Reloj/scheduler inyectable para el throttle (permite tests deterministas). */ +export interface ThrottleClock { + now: () => number; + schedule: (cb: () => void, delay: number) => void; +} + +/** + * Throttle con trailing: ejecuta `fn` como mucho 1 vez por `ms`, pero garantiza + * que la ÚLTIMA llamada dentro de la ventana se ejecuta (con el estado final). + */ +export function throttleTrailing( + fn: () => void, + ms: number, + clock: ThrottleClock, +): () => void { + let lastRun = -Infinity; + let scheduled = false; + return () => { + const t = clock.now(); + const elapsed = t - lastRun; + if (elapsed >= ms) { + lastRun = t; + fn(); + return; + } + if (!scheduled) { + scheduled = true; + clock.schedule(() => { + scheduled = false; + lastRun = clock.now(); + fn(); + }, ms - elapsed); + } + }; +} diff --git a/packages/engine-core/src/game/net/worldState.ts b/packages/engine-core/src/game/net/worldState.ts new file mode 100644 index 0000000..a8e0043 --- /dev/null +++ b/packages/engine-core/src/game/net/worldState.ts @@ -0,0 +1,36 @@ +import type { PlayerId } from "../../ports/multiplayer"; + +export interface LwwRegister { + value: V; + ts: number; + by: PlayerId; +} + +/** Mapa de registros LWW por `${entityId}:${field}`. Determinista entre clientes. */ +export class WorldStateLww { + private map = new Map(); + private key = (entityId: string, field: string) => `${entityId}:${field}`; + + get(entityId: string, field: string): V | undefined { + return this.map.get(this.key(entityId, field))?.value as V | undefined; + } + + /** Aplica un registro remoto/local. Devuelve true si ganó (cambió el estado). */ + merge(entityId: string, field: string, reg: LwwRegister): boolean { + const k = this.key(entityId, field); + const cur = this.map.get(k); + // Gana mayor ts; empate → mayor playerId (criterio estable arbitrario). + if (!cur || reg.ts > cur.ts || (reg.ts === cur.ts && reg.by > cur.by)) { + this.map.set(k, reg); + return true; + } + return false; + } + + snapshot(): Record { + return Object.fromEntries(this.map); + } + load(snap: Record): void { + this.map = new Map(Object.entries(snap)); + } +} diff --git a/packages/engine-core/src/game/types/index.ts b/packages/engine-core/src/game/types/index.ts index 50c5861..ee48a95 100644 --- a/packages/engine-core/src/game/types/index.ts +++ b/packages/engine-core/src/game/types/index.ts @@ -299,6 +299,8 @@ export type RuntimeDropEvent = { | "item-interact"; itemId: string; interactionId?: string; + /** Full placed-item data; set for outcome "place" so the event bus carries enough info for remote clients. */ + placedItem?: PlacedSceneItem; }; export type RuntimeDialogEvent = { diff --git a/packages/engine-core/src/index.ts b/packages/engine-core/src/index.ts index 38bc77f..5b65c15 100644 --- a/packages/engine-core/src/index.ts +++ b/packages/engine-core/src/index.ts @@ -4,6 +4,7 @@ export * from "./game/state"; export * from "./game/commands"; export * from "./game/events"; export * from "./game/i18n"; +export * from "./game/net"; export * from "./events"; export * from "./ports"; diff --git a/packages/engine-core/src/ports/headlessMultiplayer.ts b/packages/engine-core/src/ports/headlessMultiplayer.ts new file mode 100644 index 0000000..0e18181 --- /dev/null +++ b/packages/engine-core/src/ports/headlessMultiplayer.ts @@ -0,0 +1,96 @@ +import type { + ConnectOptions, + ConnectionStatus, + MultiplayerPort, + NetEnvelope, + PlayerId, +} from "./multiplayer"; + +/** + * Bus en memoria que conecta varios HeadlessMultiplayerAdapter (simula la red + * en tests). Reenvía cada `send` a los demás adapters de la MISMA room. + */ +interface HubPeer { + room: string; + inbound: (m: NetEnvelope) => void; +} + +export class InMemoryHub { + private peers = new Map(); + + register( + id: PlayerId, + room: string, + inbound: (m: NetEnvelope) => void, + ): () => void { + this.peers.set(id, { room, inbound }); + return () => this.peers.delete(id); + } + + /** Fan-out a los peers de la MISMA room que el mensaje, menos al emisor. */ + broadcast(from: PlayerId, _room: string, message: NetEnvelope): void { + for (const [id, peer] of this.peers) { + if (id === from) continue; + if (peer.room !== message.room) continue; // aísla por room del receptor + peer.inbound(message); + } + } +} + +let _autoId = 0; + +/** Adapter de loopback para tests. Implementa MultiplayerPort contra un InMemoryHub. */ +export class HeadlessMultiplayerAdapter implements MultiplayerPort { + readonly selfId: PlayerId; + private room = ""; + private connected = false; + private unregister: (() => void) | null = null; + private msgHandlers: Array<(m: NetEnvelope) => void> = []; + private statusHandlers: Array<(s: ConnectionStatus) => void> = []; + + constructor( + private hub: InMemoryHub, + selfId?: PlayerId, + ) { + this.selfId = selfId ?? `headless-${++_autoId}`; + } + + connect(opts: ConnectOptions): void { + this.room = opts.room; + this.connected = true; + this.unregister = this.hub.register(this.selfId, this.room, (m) => { + this.msgHandlers.forEach((h) => h(m)); + }); + this.emitStatus({ state: "connected", selfId: this.selfId }); + } + + disconnect(): void { + this.connected = false; + this.unregister?.(); + this.unregister = null; + this.emitStatus({ state: "disconnected" }); + } + + send(message: NetEnvelope): void { + if (!this.connected) return; + this.hub.broadcast(this.selfId, this.room, message); + } + + onMessage(handler: (m: NetEnvelope) => void): () => void { + this.msgHandlers.push(handler); + return () => { + this.msgHandlers = this.msgHandlers.filter((h) => h !== handler); + }; + } + + onStatus(handler: (s: ConnectionStatus) => void): () => void { + this.statusHandlers.push(handler); + return () => { + this.statusHandlers = this.statusHandlers.filter((h) => h !== handler); + }; + } + + private emitStatus(s: ConnectionStatus): void { + this.statusHandlers.forEach((h) => h(s)); + } +} diff --git a/packages/engine-core/src/ports/index.ts b/packages/engine-core/src/ports/index.ts index 7bce3d8..514cc15 100644 --- a/packages/engine-core/src/ports/index.ts +++ b/packages/engine-core/src/ports/index.ts @@ -28,3 +28,15 @@ export { HeadlessAudioAdapter, type HeadlessAudioCall } from "./headlessAudio"; export type { I18nPort } from "./i18n"; export { HeadlessI18nAdapter, type HeadlessI18nCall } from "./headlessI18n"; + +export type { + PlayerId, + RoomId, + NetEnvelope, + NetEnvelopeKind, + ConnectionStatus, + ConnectOptions, + MultiplayerPort, +} from "./multiplayer"; +export { NET_PROTOCOL_VERSION } from "./multiplayer"; +export { HeadlessMultiplayerAdapter, InMemoryHub } from "./headlessMultiplayer"; diff --git a/packages/engine-core/src/ports/multiplayer.ts b/packages/engine-core/src/ports/multiplayer.ts new file mode 100644 index 0000000..d9cf4ce --- /dev/null +++ b/packages/engine-core/src/ports/multiplayer.ts @@ -0,0 +1,69 @@ +/** + * MultiplayerPort – contrato agnóstico de transporte multijugador. + * + * El core SOLO habla con esta interfaz. Los adapters (PartyKit, headless, …) la + * implementan fuera del core. Ningún import de red/`window`/proveedor aquí. + * + * Ver docs/architecture/09-multiplayer.md §3 y ADR-0008. + */ + +/** Identificadores. */ +export type PlayerId = string; +export type RoomId = string; + +/** Versión del protocolo de red. Súbela ante cambios incompatibles del sobre. */ +export const NET_PROTOCOL_VERSION = 1; + +/** Tipo de carga del sobre. `payload` se interpreta según este discriminador. */ +export type NetEnvelopeKind = + | "command" // payload: GameCommand (intent del cliente) + | "event" // payload: GameEvent (mutación world confirmada) + | "presence" // payload: PlayerDescriptor parcial (posición/escena) + | "snapshot" // payload: estado world completo al unirse + | "claim" // payload: { entityId } reclamo de ítem + | "claim-result"; // payload: { entityId; granted: boolean } + +/** Sobre serializable que viaja por el cable. SIEMPRE JSON-serializable. */ +export interface NetEnvelope { + /** Versión de protocolo (NET_PROTOCOL_VERSION). */ + v: number; + /** Room/partida destino. */ + room: RoomId; + /** Emisor. */ + from: PlayerId; + /** Reloj lógico (HLC en task 05; Date.now() hasta entonces). Para ordenar/LWW. */ + ts: number; + /** Discriminador de payload. */ + kind: NetEnvelopeKind; + /** Carga; su forma depende de `kind`. */ + payload: T; +} + +/** Estado de conexión reportado por el transporte. */ +export type ConnectionStatus = + | { state: "connecting" } + | { state: "connected"; selfId: PlayerId } + | { state: "disconnected"; reason?: string }; + +/** Opciones para conectar a una room. `selfId` puede asignarlo el servidor. */ +export interface ConnectOptions { + room: RoomId; + selfId?: PlayerId; +} + +/** + * Puerto agnóstico de transporte multijugador. Implementado fuera del core por + * adapters (PartyKit, headless, …). El core SOLO habla con esta interfaz. + */ +export interface MultiplayerPort { + /** Abre la conexión a la room. Idempotente si ya conectado. */ + connect(opts: ConnectOptions): Promise | void; + /** Cierra la conexión y libera recursos. */ + disconnect(): void; + /** Envía un sobre a los peers (o al relay, que hace fan-out). Fire-and-forget. */ + send(message: NetEnvelope): void; + /** Suscribe a sobres entrantes. Devuelve unsubscribe. */ + onMessage(handler: (message: NetEnvelope) => void): () => void; + /** Suscribe a cambios de estado de conexión. Devuelve unsubscribe. */ + onStatus(handler: (status: ConnectionStatus) => void): () => void; +} diff --git a/packages/engine-renderer-r3f/dist/GameTouchSpriteRuntime.d.ts.map b/packages/engine-renderer-r3f/dist/GameTouchSpriteRuntime.d.ts.map index c5ceb0b..de686c7 100644 --- a/packages/engine-renderer-r3f/dist/GameTouchSpriteRuntime.d.ts.map +++ b/packages/engine-renderer-r3f/dist/GameTouchSpriteRuntime.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"GameTouchSpriteRuntime.d.ts","sourceRoot":"","sources":["../src/GameTouchSpriteRuntime.tsx"],"names":[],"mappings":"AAUA,OAAO,EAGL,KAAK,iBAAiB,EAEvB,MAAM,gBAAgB,CAAC;AAOxB,OAAO,EAAmF,KAAK,mBAAmB,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAwElM,wBAAgB,sBAAsB,CAAC,EACrC,eAAe,EACf,KAAK,EACL,eAAe,EACf,cAAc,EACd,kBAA0B,EAC1B,eAA6B,EAC7B,uBAA8B,EAC9B,YAAY,EACZ,oBAAoB,EACpB,UAAU,EACV,aAAa,EACb,aAAa,EACb,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,cAAc,EAEd,cAAsD,EACtD,eAAe,EACf,SAAoB,EACpB,iBAAwB,EACxB,YAAY,EACZ,kBAAkB,EAClB,kBAA0B,EAC1B,qBAAqB,GACtB,EAAE;IACD,eAAe,EAAE,iBAAiB,CAAC;IACnC,KAAK,EAAE,OAAO,CAAC;IACf,eAAe,EAAE,OAAO,CAAC;IACzB,cAAc,EAAE,OAAO,CAAC;IACxB,yEAAyE;IACzE,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,+EAA+E;IAC/E,eAAe,CAAC,EAAE,WAAW,GAAG,QAAQ,CAAC;IACzC;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,YAAY,EAAE,YAAY,CAAC;IAC3B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,cAAc,CAAC,EAAE,mBAAmB,CAAC;IACrC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjE;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IAChD;;;;;OAKG;IACH,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,CACnB,OAAO,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,aAAa,KAC5C,IAAI,CAAC;IACV;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;;OAMG;IACH,qBAAqB,CAAC,EAAE,CACtB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,KACZ;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,GAAG,SAAS,CAAC;CAClD,2CAioBA"} \ No newline at end of file +{"version":3,"file":"GameTouchSpriteRuntime.d.ts","sourceRoot":"","sources":["../src/GameTouchSpriteRuntime.tsx"],"names":[],"mappings":"AAUA,OAAO,EAGL,KAAK,iBAAiB,EAEvB,MAAM,gBAAgB,CAAC;AAOxB,OAAO,EAAmF,KAAK,mBAAmB,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAwElM,wBAAgB,sBAAsB,CAAC,EACrC,eAAe,EACf,KAAK,EACL,eAAe,EACf,cAAc,EACd,kBAA0B,EAC1B,eAA6B,EAC7B,uBAA8B,EAC9B,YAAY,EACZ,oBAAoB,EACpB,UAAU,EACV,aAAa,EACb,aAAa,EACb,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,cAAc,EAEd,cAAsD,EACtD,eAAe,EACf,SAAoB,EACpB,iBAAwB,EACxB,YAAY,EACZ,kBAAkB,EAClB,kBAA0B,EAC1B,qBAAqB,GACtB,EAAE;IACD,eAAe,EAAE,iBAAiB,CAAC;IACnC,KAAK,EAAE,OAAO,CAAC;IACf,eAAe,EAAE,OAAO,CAAC;IACzB,cAAc,EAAE,OAAO,CAAC;IACxB,yEAAyE;IACzE,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,+EAA+E;IAC/E,eAAe,CAAC,EAAE,WAAW,GAAG,QAAQ,CAAC;IACzC;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,YAAY,EAAE,YAAY,CAAC;IAC3B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,cAAc,CAAC,EAAE,mBAAmB,CAAC;IACrC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjE;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IAChD;;;;;OAKG;IACH,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,CACnB,OAAO,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,aAAa,KAC5C,IAAI,CAAC;IACV;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;;OAMG;IACH,qBAAqB,CAAC,EAAE,CACtB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,KACZ;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,GAAG,SAAS,CAAC;CAClD,2CAorBA"} \ No newline at end of file diff --git a/packages/engine-renderer-r3f/dist/GameTouchSpriteRuntime.js b/packages/engine-renderer-r3f/dist/GameTouchSpriteRuntime.js index 31ec8d1..e0ce3f5 100644 --- a/packages/engine-renderer-r3f/dist/GameTouchSpriteRuntime.js +++ b/packages/engine-renderer-r3f/dist/GameTouchSpriteRuntime.js @@ -83,6 +83,12 @@ getMobileInput = () => ({ active: false, x: 0, z: 0 }), addWallWithData, getPhra const wallPointStartRef = useRef(null); const lastBoundaryHitRef = useRef(0); const lastStuckHitRef = useRef(0); + // Wall-slide detection: compare position delta each frame against the + // velocity we set last frame. If an axis moved much less than expected, + // a collider is blocking it — zero that component so the character slides + // along the unblocked axis instead of pressing into the wall. + const prevBodyPositionRef = useRef(null); + const prevBodyVelSetRef = useRef({ vx: 0, vz: 0 }); const playerSpawn = useSceneStore((s) => s.scene.playerSpawn); const playerPosition = useSceneStore((s) => s.playerPosition); const playerWalkingState = useSceneStore((s) => s.playerWalkingState); @@ -363,6 +369,30 @@ getMobileInput = () => ({ active: false, x: 0, z: 0 }), addWallWithData, getPhra if (!body) { return; } + // Wall-slide detection: compare last frame's intended velocity against the + // actual position change recorded by physics. If an axis barely moved + // relative to what was expected, a rigid-body is blocking it — suppress + // that component this frame so the character slides along the free axis + // rather than sticking against the obstacle surface. + const frameStartPos = body.translation(); + let slideBlockX = false; + let slideBlockZ = false; + { + const pp = prevBodyPositionRef.current; + const { vx: pvx, vz: pvz } = prevBodyVelSetRef.current; + if (pp && (Math.abs(pvx) > 0.3 || Math.abs(pvz) > 0.3)) { + const adx = frameStartPos.x - pp.x; + const adz = frameStartPos.z - pp.z; + // Expected displacement (approximated with current delta — consecutive + // frame durations are close enough for this heuristic). + const edx = pvx * delta; + const edz = pvz * delta; + if (Math.abs(edx) > 0.002) + slideBlockX = Math.abs(adx) < Math.abs(edx) * 0.25; + if (Math.abs(edz) > 0.002) + slideBlockZ = Math.abs(adz) < Math.abs(edz) * 0.25; + } + } const { moveLeft, moveRight, moveUp, moveDown, anyKeyPressed } = getKeyboardMovement(); const joystick = getMobileInput(); // Block input during walk animation @@ -418,6 +448,20 @@ getMobileInput = () => ({ active: false, x: 0, z: 0 }), addWallWithData, getPhra } horizontal = applyDeadzone(horizontal, MOVEMENT_INPUT_DEADZONE); vertical = applyDeadzone(vertical, MOVEMENT_INPUT_DEADZONE); + // Snapshot the intended direction BEFORE slide-zeroing so the animation + // keeps showing the walk direction even when a kinematic body (e.g. a + // remote player) is blocking the axis. Without this, slideBlockX zeroes + // `horizontal` → resolveAction(0,0) = "idle" every other frame → flicker. + const rawHorizontal = horizontal; + const rawVertical = vertical; + // Apply wall-slide: if a collider blocked an axis last frame, do not push + // into it again this frame. The perpendicular axis is kept intact so the + // character slides along the surface. Only suppress when *trying* to move + // in the blocked direction (same sign), not when already idle on that axis. + if (slideBlockX && horizontal !== 0) + horizontal = 0; + if (slideBlockZ && vertical !== 0) + vertical = 0; // During walk animation, calculate direction from current to target position let nextAction; if (isWalking && playerWalkingState?.targetPosition) { @@ -428,7 +472,8 @@ getMobileInput = () => ({ active: false, x: 0, z: 0 }), addWallWithData, getPhra nextAction = resolveAction(dx, dz); } else { - nextAction = resolveAction(horizontal, vertical); + // Use pre-zeroed direction so a blocked axis doesn't collapse to idle. + nextAction = resolveAction(rawHorizontal, rawVertical); } if (currentActionRef.current !== nextAction) { logRuntimeState("action-change", { @@ -537,6 +582,12 @@ getMobileInput = () => ({ active: false, x: 0, z: 0 }), addWallWithData, getPhra colliderWireframeRef.current.scale.set(1, spriteScale, 1); colliderWireframeRef.current.position.y = spriteScale - 0.95; } + // Persist velocity and position for next-frame wall-slide detection. + // Reading linvel() here returns the velocity we set above (after all + // in-frame corrections: wall-slide zeroing, boundary clamping, stuck reset). + const finalLinvel = body.linvel(); + prevBodyVelSetRef.current = { vx: finalLinvel.x, vz: finalLinvel.z }; + prevBodyPositionRef.current = { x: safePosition.x, z: safePosition.z }; }); const wallPointPreview = debug && wallToolMode === "points" && wallPointPreviewState?.resetSignal === wallPointResetSignal ? wallPointPreviewState @@ -546,7 +597,7 @@ getMobileInput = () => ({ active: false, x: 0, z: 0 }), addWallWithData, getPhra handleHoverWorld(x, z); handleHoverPointWallTool(x, z); } - : undefined, debug: debug && showDebugGround, depthNearZ: DEPTH_NEAR_Z, depthFarZ: DEPTH_FAR_Z }), _jsx(SceneWalls, { debug: debug && showDebugWalls, opacityMode: wallOpacityMode, interactionsEnabled: wallInteractionsEnabled, onStartWallMove: handleStartWallMove, onStartWallResize: handleStartWallResize, selectedWallIndex: selectedWallIndex, onSelectWall: onSelectWall }), debug && wallToolMode === "points" && (_jsx(SceneWallPointPreview, { preview: wallPointPreview, groundY: ground.y })), _jsx(SceneCollisionSphere, {}), _jsxs(RigidBody, { ref: characterBodyRef, type: "dynamic", colliders: false, position: renderPosition, gravityScale: 1.2, linearDamping: 7, angularDamping: 20, ccd: true, enabledRotations: [false, false, false], children: [_jsx(CuboidCollider, { ref: characterColliderRef, args: [0.55, 0.95, 0.18], friction: 2.2, restitution: 0 }), debug && showPlayerCollider && ( + : undefined, debug: debug && showDebugGround, depthNearZ: DEPTH_NEAR_Z, depthFarZ: DEPTH_FAR_Z }), _jsx(SceneWalls, { debug: debug && showDebugWalls, opacityMode: wallOpacityMode, interactionsEnabled: wallInteractionsEnabled, onStartWallMove: handleStartWallMove, onStartWallResize: handleStartWallResize, selectedWallIndex: selectedWallIndex, onSelectWall: onSelectWall }), debug && wallToolMode === "points" && (_jsx(SceneWallPointPreview, { preview: wallPointPreview, groundY: ground.y })), _jsx(SceneCollisionSphere, {}), _jsxs(RigidBody, { ref: characterBodyRef, type: "dynamic", colliders: false, position: renderPosition, gravityScale: 1.2, linearDamping: 7, angularDamping: 20, ccd: true, enabledRotations: [false, false, false], children: [_jsx(CuboidCollider, { ref: characterColliderRef, args: [0.55, 0.95, 0.18], friction: 0.05, restitution: 0 }), debug && showPlayerCollider && ( // box base height = 2; the useFrame above scales Y to `spriteScale` // and offsets Y position so the wireframe exactly mirrors the // physics collider (whose halfY is also set to `spriteScale`). diff --git a/packages/engine-renderer-r3f/dist/GameTouchSpriteRuntime.js.map b/packages/engine-renderer-r3f/dist/GameTouchSpriteRuntime.js.map index e63e54b..1f682e2 100644 --- a/packages/engine-renderer-r3f/dist/GameTouchSpriteRuntime.js.map +++ b/packages/engine-renderer-r3f/dist/GameTouchSpriteRuntime.js.map @@ -1 +1 @@ -{"version":3,"file":"GameTouchSpriteRuntime.js","sourceRoot":"","sources":["../src/GameTouchSpriteRuntime.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,SAAS,EAAwB,MAAM,qBAAqB,CAAC;AAEtF,OAAO,EAAE,SAAS,EAAQ,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAE1D,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAC1C,OAAO,WAAuC,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EACL,kBAAkB,EAClB,sBAAsB,GAGvB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,UAAU,EAAyB,MAAM,oBAAoB,CAAC;AAEvE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,gBAAgB,EAAE,oCAAoC,EAAmE,MAAM,gCAAgC,CAAC;AAClM,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAY/E,MAAM,wBAAwB,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC;AACtD,MAAM,WAAW,GAAG,CAAC,EAAE,CAAC;AACxB,MAAM,YAAY,GAAG,CAAC,CAAC;AACvB,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAC7B,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAC9B,MAAM,oBAAoB,GAAG,IAAI,CAAC;AAClC,MAAM,mBAAmB,GAAG,IAAI,CAAC;AACjC,MAAM,wBAAwB,GAAG,IAAI,CAAC;AACtC,MAAM,eAAe,GAA6B,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;AACjE,MAAM,4BAA4B,GAAG,GAAG,CAAC;AACzC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AACrC,MAAM,4BAA4B,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC;AAC3E,MAAM,6BAA6B,GAAG,GAAG,CAAC;AAS1C,IAAI,wBAAwB,GAAoC,IAAI,CAAC;AAErE,SAAS,eAAe,CAAC,KAAa,EAAE,OAAgC;IACtE,IAAI,CAAC,4BAA4B;QAAE,OAAO;IAC1C,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,MAAM,SAAS,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QACvE,MAAM,YAAY,GAAG,CAAE,MAAiD,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;QAC3F,MAAgD,CAAC,WAAW,GAAG,CAAC,GAAG,YAAY,EAAE,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC;IAC3G,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,mBAAmB,KAAK,EAAE,EAAE,OAAO,CAAC,CAAC;AACpD,CAAC;AAED,SAAS,aAAa,CAAC,KAAa,EAAE,SAAiB;IACrD,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACjD,CAAC;AAED,SAAS,aAAa,CAAC,UAAkB,EAAE,QAAgB;IACzD,IAAI,UAAU,KAAK,CAAC,IAAI,QAAQ,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC;IACtD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;IACnE,IAAI,KAAK,IAAI,wBAAwB,EAAE,CAAC;QACtC,OAAO,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;IAC1C,CAAC;IACD,OAAO,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;AAC1C,CAAC;AAED,SAAS,WAAW,CAAC,SAAiB;IACpC,OAAO;QACL,KAAK,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC7D,KAAK,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;KAC7D,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,OAAe,EAAE,OAAe,EAAE,MAAc,EAAE,MAAc,EAAE,IAAa;IACtG,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,EACrC,eAAe,EACf,KAAK,EACL,eAAe,EACf,cAAc,EACd,kBAAkB,GAAG,KAAK,EAC1B,eAAe,GAAG,WAAW,EAC7B,uBAAuB,GAAG,IAAI,EAC9B,YAAY,EACZ,oBAAoB,EACpB,UAAU,EACV,aAAa,EACb,aAAa,EACb,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,cAAc;AACd,0CAA0C;AAC1C,cAAc,GAAG,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EACtD,eAAe,EACf,SAAS,GAAG,GAAG,EAAE,CAAC,EAAE,EACpB,iBAAiB,GAAG,IAAI,EACxB,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,GAAG,KAAK,EAC1B,qBAAqB,GAiFtB;IACC,MAAM,SAAS,GAAG,MAAM,CAA2B,IAAI,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,MAAM,CAAO,IAAI,CAAC,CAAC;IACnC,MAAM,gBAAgB,GAAG,MAAM,CAAkB,IAAI,CAAC,CAAC;IACvD,MAAM,oBAAoB,GAAG,MAAM,CAAwB,IAAI,CAAC,CAAC;IACjE,MAAM,oBAAoB,GAAG,MAAM,CAAO,IAAI,CAAC,CAAC;IAChD,oFAAoF;IACpF,MAAM,sBAAsB,GAAG,MAAM,CAAC,IAAI,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IACrE,MAAM,sBAAsB,GAAG,MAAM,CAAC,IAAI,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5D,MAAM,gBAAgB,GAAG,MAAM,CAAiB,MAAM,CAAC,CAAC;IACxD,MAAM,mBAAmB,GAAG,MAAM,CAAmC,MAAM,CAAC,CAAC;IAC7E,MAAM,iBAAiB,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,wBAAwB,GAAG,MAAM,CAA6C,IAAI,CAAC,CAAC;IAC1F,MAAM,YAAY,GAAG,MAAM,CAAoD,IAAI,CAAC,CAAC;IACrF,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAiB,MAAM,CAAC,CAAC;IAC7D,MAAM,CAAC,qBAAqB,EAAE,wBAAwB,CAAC,GAAG,QAAQ,CAA0B,IAAI,CAAC,CAAC;IAClG,MAAM,kBAAkB,GAAG,MAAM,CAAkB,IAAI,CAAC,CAAC;IACzD,MAAM,iBAAiB,GAAG,MAAM,CAAwB,IAAI,CAAC,CAAC;IAC9D,MAAM,kBAAkB,GAAG,MAAM,CAAS,CAAC,CAAC,CAAC;IAC7C,MAAM,eAAe,GAAG,MAAM,CAAS,CAAC,CAAC,CAAC;IAE1C,MAAM,WAAW,GAAG,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC9D,MAAM,cAAc,GAAG,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;IAC9D,MAAM,kBAAkB,GAAG,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC;IACtE,sCAAsC;IACtC,+GAA+G;IAC/G,IAAI;IACJ,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACpD,MAAM,iBAAiB,GAAG,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC;IACpE,MAAM,aAAa,GAAG,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;IAE5D,iFAAiF;IACjF,MAAM,qBAAqB,GAAG,MAAM,CAAC,kBAAkB,CAAC,CAAC;IACzD,SAAS,CAAC,GAAG,EAAE,GAAG,qBAAqB,CAAC,OAAO,GAAG,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAE/F,4EAA4E;IAC5E,4EAA4E;IAC5E,2EAA2E;IAC3E,6EAA6E;IAC7E,sEAAsE;IACtE,MAAM,kBAAkB,GAAG,WAAW,CAAC,GAAG,EAAE;QAC1C,MAAM,EAAE,GAAG,qBAAqB,CAAC,OAAO,CAAC;QACzC,MAAM,IAAI,GAAG,gBAAgB,CAAC,OAAO,CAAC;QACtC,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI;YAAE,OAAO;QACzB,MAAM,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,cAAc,CAAC;QACzE,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACjE,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,sEAAsE;IACtE,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,GAAG,sBAAsB,CAAC,cAAc,EAAE,kBAAkB,EAAE,SAAS,EAAE,kBAAkB,CAAC,CAAC;IAClI,MAAM,cAAc,GAAG,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,cAAc,CAAC;IAErE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,GAAG,wBAAwB,EAAE,CAAC;IAClG,MAAM,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,GAAG,wBAAwB,EAAE,CAAC;IAE7E,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,EAAE;QAClC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAC/C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAC/C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAC/C,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,GAAG,mBAAmB,CAAC;QACvD,MAAM,YAAY,GAAG,eAAe,CAAC,CAAC,CAAC,GAAG,4BAA4B,CAAC;QACvE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;QAElD,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC;YAC1B,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC;YAC1B,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC;YAC1B,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC;SAC3B,CAAC;IACJ,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IAEzD,MAAM,mBAAmB,GAAG,WAAW,CAAC,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE;QAC/D,OAAO;YACL,CAAC,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,IAAI,EAAE,cAAc,CAAC,IAAI,CAAC;YAC/D,CAAC,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,IAAI,EAAE,cAAc,CAAC,IAAI,CAAC;SAChE,CAAC;IACJ,CAAC,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,cAAc,CAAC,IAAI,EAAE,cAAc,CAAC,IAAI,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;IAEzF,MAAM,gBAAgB,GAAG,WAAW,CAAC,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE;QAC5D,IAAI,kBAAkB,CAAC,OAAO;YAAE,OAAO;QAEvC,IAAI,KAAK,IAAI,YAAY,KAAK,QAAQ,EAAE,CAAC;YACvC,MAAM,OAAO,GAAG,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC1C,MAAM,YAAY,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;YACvD,MAAM,UAAU,GACd,iBAAiB,CAAC,OAAO,EAAE,WAAW,KAAK,oBAAoB;gBAC7D,CAAC,CAAC,iBAAiB,CAAC,OAAO,CAAC,KAAK;gBACjC,CAAC,CAAC,IAAI,CAAC;YAEX,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,iBAAiB,CAAC,OAAO,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAC;gBACvF,wBAAwB,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAC,CAAC;gBACxG,OAAO;YACT,CAAC;YAED,MAAM,EAAE,GAAG,YAAY,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;YACzC,MAAM,EAAE,GAAG,YAAY,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;YACzC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;YAE5C,IAAI,MAAM,GAAG,oBAAoB,GAAG,CAAC,EAAE,CAAC;gBACtC,wBAAwB,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,YAAY,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAC,CAAC;gBACtG,OAAO;YACT,CAAC;YAED,wEAAwE;YACxE,sEAAsE;YACtE,mDAAmD;YACnD,MAAM,UAAU,GAAG,CAAC,CAAC;YACrB,MAAM,aAAa,GAAG,IAAI,CAAC;YAC3B,MAAM,OAAO,GAAG,CAAC,UAAU,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACpD,MAAM,OAAO,GAAG,CAAC,UAAU,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACpD,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAEtC,eAAe,EAAE,CAAC;gBAChB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,GAAG,UAAU,EAAE,OAAO,CAAC;gBACnD,SAAS;gBACT,QAAQ,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,oBAAoB,EAAE,MAAM,GAAG,CAAC,CAAC,EAAE,UAAU,EAAE,aAAa,CAAC;aAClF,CAAC,CAAC;YAEH,iBAAiB,CAAC,OAAO,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAC;YACvF,wBAAwB,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAC,CAAC;YACxG,OAAO;QACT,CAAC;QAED,IAAI,kBAAkB;YAAE,OAAO;QAE/B,MAAM,OAAO,GAAG,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC1C,MAAM,IAAI,GAAG,gBAAgB,CAAC,OAAO,CAAC;QACtC,MAAM,KAAK,GAAG,aAAa,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC;QAC7C,MAAM,aAAa,GAAG,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;QAEtF,iDAAiD;QACjD,MAAM,eAAe,GAAG,oCAAoC,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;QAC1F,MAAM,SAAS,GAAG,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;QAEjH,qEAAqE;QACrE,mEAAmE;QACnE,mEAAmE;QACnE,uBAAuB;QACvB,MAAM,UAAU,GAAG,qBAAqB,EAAE,CACxC,SAAS,CAAC,CAAC,EACX,SAAS,CAAC,CAAC,EACX,aAAa,CAAC,CAAC,EACf,aAAa,CAAC,CAAC,CAChB,CAAC;QACF,MAAM,aAAa,GAAG,UAAU,IAAI,SAAS,CAAC;QAE9C,4EAA4E;QAC5E,uEAAuE;QACvE,wEAAwE;QACxE,sEAAsE;QACtE,MAAM,KAAK,GAAG,QAAQ,CAAC;YACrB,KAAK,EAAE,EAAE,CAAC,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,aAAa,CAAC,CAAC,EAAE;YACjD,IAAI,EAAE,aAAa;YACnB,MAAM,EAAE,cAAc;YACtB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,YAAY,EAAE,KAAK,CAAC,YAAY;SACjC,CAAC,CAAC;QAEH,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,QAAQ,CAAC,KAAK,CAAC,CAAC;YAChB,OAAO;QACT,CAAC;QAED,6EAA6E;QAC7E,gBAAgB,CAAC,cAAc,EAAE;YAC/B,IAAI,EAAE,WAAW;YACjB,MAAM,EAAE,aAAa;YACrB,QAAQ,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC;SACvD,CAAC,CAAC;IACL,CAAC,EAAE,CAAC,eAAe,EAAE,mBAAmB,EAAE,KAAK,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,CAAC,CAAC,EAAE,cAAc,EAAE,cAAc,EAAE,WAAW,EAAE,QAAQ,EAAE,oBAAoB,EAAE,YAAY,CAAC,CAAC,CAAC;IAElM,MAAM,mBAAmB,GAAG,WAAW,CAAC,GAAG,EAAE;QAC3C,kBAAkB,CAAC,OAAO,GAAG,IAAI,CAAC;IACpC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,mBAAmB,GAAG,WAAW,CAAC,CAAC,KAAa,EAAE,MAAc,EAAE,MAAc,EAAE,EAAE;QACxF,MAAM,IAAI,GAAG,aAAa,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACzD,IAAI,CAAC,IAAI;YAAE,OAAO;QAClB,kBAAkB,CAAC,OAAO,GAAG;YAC3B,IAAI,EAAE,MAAM;YACZ,KAAK;YACL,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;YAClC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;SACnC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,qBAAqB,GAAG,WAAW,CAAC,CAAC,KAAa,EAAE,MAAwB,EAAE,EAAE;QACpF,YAAY,EAAE,CAAC,KAAK,CAAC,CAAC;QACtB,kBAAkB,CAAC,OAAO,GAAG;YAC3B,IAAI,EAAE,QAAQ;YACd,KAAK;YACL,MAAM;SACP,CAAC;IACJ,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;IAEnB,MAAM,gBAAgB,GAAG,WAAW,CAAC,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE;QAC5D,MAAM,WAAW,GAAG,kBAAkB,CAAC,OAAO,CAAC;QAC/C,IAAI,CAAC,WAAW;YAAE,OAAO;QACzB,IAAI,CAAC,kBAAkB;YAAE,OAAO;QAEhC,6DAA6D;QAC7D,uEAAuE;QACvE,0EAA0E;QAC1E,uEAAuE;QACvE,MAAM,UAAU,GAAG,aAAa,CAAC,QAAQ,EAAE,CAAC;QAC5C,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACvD,IAAI,CAAC,IAAI;YAAE,OAAO;QAElB,IAAI,WAAW,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAChC,MAAM,KAAK,GAAG,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC;YACtC,MAAM,KAAK,GAAG,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC;YACtC,kBAAkB,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBAC5B,GAAG,IAAI;gBACP,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC;aAC3C,CAAC,CAAC,CAAC;YACJ,OAAO;QACT,CAAC;QAED,+EAA+E;QAC/E,0EAA0E;QAC1E,+DAA+D;QAC/D,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC;QACtC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;QAChD,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;QAC/E,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;QAE/E,kBAAkB,CAAC,CAAC,IAAI,EAAE,EAAE;YAC1B,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAA6B,CAAC;YAChE,QAAQ,WAAW,CAAC,MAAM,EAAE,CAAC;gBAC3B,KAAK,IAAI;oBACP,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAC;oBACpD,MAAM;gBACR,KAAK,IAAI;oBACP,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,oBAAoB,EAAE,CAAC,KAAK,CAAC,CAAC;oBACrD,MAAM;gBACR,KAAK,IAAI;oBACP,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAC;oBACpD,MAAM;gBACR,KAAK,IAAI;oBACP,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,oBAAoB,EAAE,CAAC,KAAK,CAAC,CAAC;oBACrD,MAAM;YACV,CAAC;YACD,OAAO,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC;QAC/B,CAAC,CAAC,CAAC;IACL,CAAC,EAAE,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAEzB,MAAM,wBAAwB,GAAG,WAAW,CAAC,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE;QACpE,IAAI,CAAC,KAAK,IAAI,YAAY,KAAK,QAAQ;YAAE,OAAO;QAChD,MAAM,UAAU,GACd,iBAAiB,CAAC,OAAO,EAAE,WAAW,KAAK,oBAAoB;YAC7D,CAAC,CAAC,iBAAiB,CAAC,OAAO,CAAC,KAAK;YACjC,CAAC,CAAC,IAAI,CAAC;QACX,IAAI,CAAC,UAAU;YAAE,OAAO;QACxB,MAAM,OAAO,GAAG,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC1C,wBAAwB,CAAC;YACvB,KAAK,EAAE,UAAU;YACjB,GAAG,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;YACtC,WAAW,EAAE,oBAAoB;SAClC,CAAC,CAAC;IACL,CAAC,EAAE,CAAC,mBAAmB,EAAE,KAAK,EAAE,oBAAoB,EAAE,YAAY,CAAC,CAAC,CAAC;IAErE,MAAM,cAAc,GAAG,OAAO,CAC5B,GAAG,EAAE,CAAC,sBAAsB,CAAC,eAAe,CAAC,EAC7C,CAAC,eAAe,CAAC,CAClB,CAAC;IACF,MAAM,mBAAmB,GAAG,OAAO,CACjC,GAAG,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,kBAAkB,CAAC,EAC5D,CAAC,cAAc,CAAC,CACjB,CAAC;IACF,MAAM,eAAe,GAAG,OAAO,CAC7B,GAAG,EAAE,CACH,aAAa,IAAI,MAAM,KAAK,MAAM;QAChC,CAAC,CAAC,sBAAsB,CAAC,aAAa,EAAE,oBAAoB,CAAC;QAC7D,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,EAC5B,CAAC,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,aAAa,EAAE,oBAAoB,CAAC,CAC7E,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,IAAI,GAAG,gBAAgB,CAAC,OAAO,CAAC;QACtC,IAAI,CAAC,IAAI;YAAE,OAAO;QAClB,MAAM,KAAK,GAAG,aAAa,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC;QACzD,MAAM,aAAa,GAAG,YAAY,CAAC,OAAO,CAAC;QAC3C,MAAM,MAAM,GAAG,CAAC,aAAa;YAC3B,CAAC,CAAC,eAAe;YACjB,CAAC,CAAC,aAAa,CAAC,OAAO,KAAK,OAAO;gBACjC,CAAC,CAAC,cAAc;gBAChB,CAAC,CAAC,SAAS,CAAC;QAEhB,eAAe,CAAC,cAAc,EAAE;YAC9B,MAAM;YACN,OAAO;YACP,aAAa;YACb,KAAK;SACN,CAAC,CAAC;QAEH,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,sBAAsB,GAAG,4BAA4B;eACtD,CAAC,MAAM,KAAK,eAAe,IAAI,MAAM,KAAK,SAAS,CAAC;eACpD,wBAAwB,IAAI,IAAI;eAChC,GAAG,GAAG,wBAAwB,CAAC,EAAE,GAAG,6BAA6B;eACjE,wBAAwB,CAAC,OAAO,KAAK,OAAO;eAC5C,wBAAwB,CAAC,aAAa,KAAK,aAAa;eACxD,wBAAwB,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC;eAC9C,wBAAwB,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC;eAC9C,wBAAwB,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC;QAEpD,IAAI,sBAAsB,EAAE,CAAC;YAC3B,eAAe,CAAC,sBAAsB,EAAE;gBACtC,MAAM,EAAE,qBAAqB;gBAC7B,cAAc,EAAE,MAAM;gBACtB,OAAO;gBACP,aAAa;gBACb,KAAK;aACN,CAAC,CAAC;YACH,YAAY,CAAC,OAAO,GAAG,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC;YAClD,OAAO;QACT,CAAC;QAED,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACrE,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC3C,YAAY,EAAE,CAAC;QACf,gBAAgB,EAAE,CAAC;QACnB,iBAAiB,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAClD,wBAAwB,CAAC,OAAO,GAAG,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7E,YAAY,CAAC,OAAO,GAAG,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC;QAClD,wBAAwB,GAAG;YACzB,OAAO;YACP,aAAa;YACb,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;YACrC,EAAE,EAAE,GAAG;SACR,CAAC;IACJ,CAAC,EAAE,CAAC,YAAY,EAAE,gBAAgB,EAAE,OAAO,EAAE,aAAa,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAEhF,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,CAAC,gBAAgB,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;QAC1D,OAAO,GAAG,EAAE;YACV,MAAM,CAAC,mBAAmB,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;QAC/D,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,CAAC;IAE1B,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE;QACpB,MAAM,IAAI,GAAG,gBAAgB,CAAC,OAAO,CAAC;QAEtC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO;QACT,CAAC;QAED,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,GAAG,mBAAmB,EAAE,CAAC;QAEvF,MAAM,QAAQ,GAAG,cAAc,EAAE,CAAC;QAClC,oCAAoC;QACpC,MAAM,YAAY,GAAG,SAAS,CAAC;QAC/B,MAAM,iBAAiB,GAAG,CAAC,aAAa,IAAI,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC;QAC9E,MAAM,aAAa,GAAqC,QAAQ,CAAC,MAAM;YACrE,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,aAAa;gBACb,CAAC,CAAC,UAAU;gBACZ,CAAC,CAAC,MAAM,CAAC;QAEb,IAAI,mBAAmB,CAAC,OAAO,KAAK,aAAa,EAAE,CAAC;YAClD,eAAe,CAAC,mBAAmB,EAAE;gBACnC,IAAI,EAAE,mBAAmB,CAAC,OAAO;gBACjC,EAAE,EAAE,aAAa;gBACjB,OAAO;aACR,CAAC,CAAC;YACH,mBAAmB,CAAC,OAAO,GAAG,aAAa,CAAC;QAC9C,CAAC;QAED,IAAI,iBAAiB,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,CAAC;YACpD,YAAY,EAAE,CAAC;YACf,eAAe,CAAC,sBAAsB,EAAE;gBACtC,MAAM,EAAE,cAAc;gBACtB,SAAS,EAAE,aAAa;gBACxB,OAAO;aACR,CAAC,CAAC;QACL,CAAC;QACD,iBAAiB,CAAC,OAAO,GAAG,iBAAiB,CAAC;QAE9C,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,IAAI,QAAQ,GAAG,CAAC,CAAC;QAEjB,0EAA0E;QAC1E,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;gBACpB,UAAU,GAAG,QAAQ,CAAC,CAAC,CAAC;gBACxB,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC;YACxB,CAAC;iBAAM,IAAI,aAAa,EAAE,CAAC;gBACzB,UAAU,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;gBAClD,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;YAC/C,CAAC;iBAAM,CAAC;gBACN,MAAM,eAAe,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;gBAC3C,MAAM,QAAQ,GAAG,gBAAgB,CAC/B,eAAe,CAAC,CAAC,EACjB,eAAe,CAAC,CAAC,EACjB,KAAK,EACL,iBAAiB,CAClB,CAAC;gBACF,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC;gBACjC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC;gBAE7B,IAAI,QAAQ,CAAC,YAAY,EAAE,CAAC;oBAC1B,IAAI,CAAC,cAAc,CACjB;wBACE,CAAC,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC;wBAC1B,CAAC,EAAE,eAAe,CAAC,CAAC;wBACpB,CAAC,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC;qBAC3B,EACD,IAAI,CACL,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;QAED,UAAU,GAAG,aAAa,CAAC,UAAU,EAAE,uBAAuB,CAAC,CAAC;QAChE,QAAQ,GAAG,aAAa,CAAC,QAAQ,EAAE,uBAAuB,CAAC,CAAC;QAE5D,6EAA6E;QAC7E,IAAI,UAA0B,CAAC;QAC/B,IAAI,SAAS,IAAI,kBAAkB,EAAE,cAAc,EAAE,CAAC;YACpD,MAAM,UAAU,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;YAC9D,MAAM,SAAS,GAAG,CAAC,kBAAkB,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,kBAAkB,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;YAC/F,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YACxC,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YACxC,UAAU,GAAG,aAAa,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACrC,CAAC;aAAM,CAAC;YACN,UAAU,GAAG,aAAa,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QACnD,CAAC;QAED,IAAI,gBAAgB,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;YAC5C,eAAe,CAAC,eAAe,EAAE;gBAC/B,IAAI,EAAE,gBAAgB,CAAC,OAAO;gBAC9B,EAAE,EAAE,UAAU;gBACd,SAAS,EAAE,mBAAmB,CAAC,OAAO;gBACtC,OAAO;aACR,CAAC,CAAC;YACH,gBAAgB,CAAC,OAAO,GAAG,UAAU,CAAC;YACtC,SAAS,CAAC,UAAU,CAAC,CAAC;QACxB,CAAC;QAED,MAAM,KAAK,GAAG,CAAC,CAAC;QAChB,MAAM,eAAe,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACtC,MAAM,aAAa,GAAG,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QACzD,MAAM,cAAc,GAAG;YACrB,CAAC,EAAE,UAAU,GAAG,KAAK;YACrB,CAAC,EAAE,eAAe,CAAC,CAAC;YACpB,CAAC,EAAE,QAAQ,GAAG,aAAa;SAC5B,CAAC;QAEF,IAAI,UAAU,KAAK,CAAC,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;YACvC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC7D,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,SAAS,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;QACvC,CAAC;QAED,MAAM,eAAe,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAC3C,MAAM,eAAe,GAAG,mBAAmB,CAAC,eAAe,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC;QAClF,MAAM,WAAW,GAAG,eAAe,CAAC,CAAC,KAAK,eAAe,CAAC,CAAC,CAAC;QAC5D,MAAM,WAAW,GAAG,eAAe,CAAC,CAAC,KAAK,eAAe,CAAC,CAAC,CAAC;QAC5D,IAAI,WAAW,IAAI,WAAW,EAAE,CAAC;YAC/B,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,eAAe,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YAChG,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YAC1B,IAAI,CAAC,SAAS,CACZ;gBACE,CAAC,EAAE,WAAW;oBACZ,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;oBACnF,CAAC,CAAC,GAAG,CAAC,CAAC;gBACT,CAAC,EAAE,GAAG,CAAC,CAAC;gBACR,CAAC,EAAE,WAAW;oBACZ,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;oBACnF,CAAC,CAAC,GAAG,CAAC,CAAC;aACV,EACD,IAAI,CACL,CAAC;YAEF,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;YAC9B,IAAI,GAAG,GAAG,kBAAkB,CAAC,OAAO,GAAG,wBAAwB,EAAE,CAAC;gBAChE,kBAAkB,CAAC,OAAO,GAAG,GAAG,CAAC;gBACjC,gBAAgB,CAAC,cAAc,EAAE;oBAC/B,IAAI,EAAE,WAAW;oBACjB,MAAM,EAAE,UAAU;oBAClB,QAAQ,EAAE,CAAC,eAAe,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC;iBACpE,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAExC,MAAM,EAAE,KAAK,EAAE,GAAG,gBAAgB,CAChC,YAAY,CAAC,CAAC,EACd,YAAY,CAAC,CAAC,EACd,KAAK,EACL,iBAAiB,CAClB,CAAC;QAEF,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YAC1B,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YAC/C,oEAAoE;YACpE,uEAAuE;YACvE,YAAY,EAAE,CAAC;YACf,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;YAC9B,IAAI,GAAG,GAAG,eAAe,CAAC,OAAO,GAAG,wBAAwB,EAAE,CAAC;gBAC7D,eAAe,CAAC,OAAO,GAAG,GAAG,CAAC;gBAC9B,gBAAgB,CAAC,cAAc,EAAE;oBAC/B,IAAI,EAAE,WAAW;oBACjB,MAAM,EAAE,OAAO;oBACf,QAAQ,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC;iBAC3D,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,MAAM,WAAW,GAAG,SAAS,CAAC,KAAK,CACjC,SAAS,CAAC,WAAW,CAAC,WAAW,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC,EAChE,CAAC,EACD,CAAC,CACF,CAAC;QAEF,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QACnD,MAAM,UAAU,GAAG,wBAAwB,CAAC,OAAO,CAAC;QACpD,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,CAAC,KAAK,QAAQ,IAAI,UAAU,CAAC,CAAC,KAAK,QAAQ,IAAI,UAAU,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;YACvG,wBAAwB,CAAC,OAAO,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC;YAC7E,iBAAiB,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;YAClD,gBAAgB,CAAC,cAAc,EAAE;gBAC/B,IAAI,EAAE,QAAQ;gBACd,QAAQ,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;gBACxC,MAAM,EAAE,UAAU;aACnB,CAAC,CAAC;QACL,CAAC;QAED,MAAM,WAAW,GAAG,SAAS,CAAC,IAAI,CAAC,gBAAgB,EAAE,gBAAgB,EAAE,WAAW,CAAC,CAAC;QACpF,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACzC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,GAAG,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;YAC/D,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,WAAW,GAAG,IAAI,CAAC;QAClD,CAAC;QAED,mEAAmE;QACnE,wEAAwE;QACxE,sEAAsE;QACtE,iDAAiD;QACjD,6CAA6C;QAC7C,+CAA+C;QAC/C,yBAAyB;QACzB,gCAAgC;QAChC,MAAM,QAAQ,GAAG,oBAAoB,CAAC,OAAO,CAAC;QAC9C,IAAI,QAAQ,EAAE,CAAC;YACb,sBAAsB,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;YAC5D,QAAQ,CAAC,cAAc,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;YACxD,sBAAsB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC;YAC7D,QAAQ,CAAC,uBAAuB,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;QACnE,CAAC;QACD,IAAI,oBAAoB,CAAC,OAAO,EAAE,CAAC;YACjC,oBAAoB,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;YAC1D,oBAAoB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,WAAW,GAAG,IAAI,CAAC;QAC/D,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,gBAAgB,GACpB,KAAK,IAAI,YAAY,KAAK,QAAQ,IAAI,qBAAqB,EAAE,WAAW,KAAK,oBAAoB;QAC/F,CAAC,CAAC,qBAAqB;QACvB,CAAC,CAAC,IAAI,CAAC;IAEX,OAAO,CACL,8BACE,KAAC,WAAW,IACV,YAAY,EAAE,gBAAgB,EAC9B,YAAY,EACV,KAAK;oBACH,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;wBACT,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;wBACvB,wBAAwB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;oBACjC,CAAC;oBACD,CAAC,CAAC,SAAS,EAEf,KAAK,EAAE,KAAK,IAAI,eAAe,EAC/B,UAAU,EAAE,YAAY,EACxB,SAAS,EAAE,WAAW,GACtB,EACF,KAAC,UAAU,IACT,KAAK,EAAE,KAAK,IAAI,cAAc,EAC9B,WAAW,EAAE,eAAe,EAC5B,mBAAmB,EAAE,uBAAuB,EAC5C,eAAe,EAAE,mBAAmB,EACpC,iBAAiB,EAAE,qBAAqB,EACxC,iBAAiB,EAAE,iBAAiB,EACpC,YAAY,EAAE,YAAY,GAC1B,EACD,KAAK,IAAI,YAAY,KAAK,QAAQ,IAAI,CACrC,KAAC,qBAAqB,IAAC,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,GAAI,CACxE,EACD,KAAC,oBAAoB,KAAG,EACxB,MAAC,SAAS,IACR,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAC,SAAS,EACd,SAAS,EAAE,KAAK,EAChB,QAAQ,EAAE,cAAc,EACxB,YAAY,EAAE,GAAG,EACjB,aAAa,EAAE,CAAC,EAChB,cAAc,EAAE,EAAE,EAClB,GAAG,QACH,gBAAgB,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,aAEvC,KAAC,cAAc,IACb,GAAG,EAAE,oBAAoB,EACzB,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EACxB,QAAQ,EAAE,GAAG,EACb,WAAW,EAAE,CAAC,GACd,EACD,KAAK,IAAI,kBAAkB,IAAI;oBAC9B,oEAAoE;oBACpE,8DAA8D;oBAC9D,+DAA+D;oBAC/D,gBAAM,GAAG,EAAE,oBAAoB,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,aAClD,sBAAa,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC,GAAI,EACrC,4BAAmB,KAAK,EAAC,SAAS,EAAC,SAAS,QAAC,WAAW,QAAC,OAAO,EAAE,IAAI,GAAI,IACrE,CACR,EACD,KAAC,WAAW,IACV,GAAG,EAAE,SAAS,EACd,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,eAAe,EAC1B,iBAAiB,EAAE,mBAAmB,EACtC,KAAK,EAAE,CAAC,gBAAgB,EAAE,gBAAgB,EAAE,CAAC,CAAC,EAC9C,QAAQ,EAAE,KAAK,GACf,EACF,KAAC,YAAY,IAEX,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,aAAa,EACtB,cAAc,EAAE,oBAAoB,EACpC,SAAS,EAAE,eAAe,EAC1B,gBAAgB,EAAE,mBAAmB,IANhC,aAAa,CAOlB,IACQ,IACX,CACJ,CAAC;AACJ,CAAC"} \ No newline at end of file +{"version":3,"file":"GameTouchSpriteRuntime.js","sourceRoot":"","sources":["../src/GameTouchSpriteRuntime.tsx"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/engine-renderer-r3f/dist/hooks/useClickToMoveController.d.ts.map b/packages/engine-renderer-r3f/dist/hooks/useClickToMoveController.d.ts.map index 6dd1863..fb6130f 100644 --- a/packages/engine-renderer-r3f/dist/hooks/useClickToMoveController.d.ts.map +++ b/packages/engine-renderer-r3f/dist/hooks/useClickToMoveController.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"useClickToMoveController.d.ts","sourceRoot":"","sources":["../../src/hooks/useClickToMoveController.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAQpE,KAAK,8BAA8B,GAAG;IACpC,gBAAgB,EAAE,MAAM,CAAC;IACzB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,iBAAiB,GAAG;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACzC,CAAC;AAQF,wBAAgB,wBAAwB,CACtC,MAAM,GAAE,OAAO,CAAC,8BAA8B,CAAM;mBAclB,MAAM,KAAK,MAAM;sBAMd,aAAa,EAAE;;iCAStC,MAAM,YACN,MAAM,SACT,MAAM,kBACG,OAAO,KACtB,iBAAiB;iCAwCR,MAAM,YACN,MAAM,SACT,MAAM,kBACG,OAAO,KACtB;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE;EAgCxB"} \ No newline at end of file +{"version":3,"file":"useClickToMoveController.d.ts","sourceRoot":"","sources":["../../src/hooks/useClickToMoveController.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAgBpE,KAAK,8BAA8B,GAAG;IACpC,gBAAgB,EAAE,MAAM,CAAC;IACzB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,iBAAiB,GAAG;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACzC,CAAC;AAQF,wBAAgB,wBAAwB,CACtC,MAAM,GAAE,OAAO,CAAC,8BAA8B,CAAM;mBAclB,MAAM,KAAK,MAAM;sBAMd,aAAa,EAAE;;iCAStC,MAAM,YACN,MAAM,SACT,MAAM,kBACG,OAAO,KACtB,iBAAiB;iCAuDR,MAAM,YACN,MAAM,SACT,MAAM,kBACG,OAAO,KACtB;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE;EAuCxB"} \ No newline at end of file diff --git a/packages/engine-renderer-r3f/dist/hooks/useClickToMoveController.js b/packages/engine-renderer-r3f/dist/hooks/useClickToMoveController.js index 7f4bb79..950e579 100644 --- a/packages/engine-renderer-r3f/dist/hooks/useClickToMoveController.js +++ b/packages/engine-renderer-r3f/dist/hooks/useClickToMoveController.js @@ -1,6 +1,11 @@ "use client"; import { useCallback, useRef } from "react"; import { MathUtils } from "three"; +// After this many ms of barely moving, start nudging perpendicular to the +// desired direction so the character slides around the obstacle instead of +// pressing into it. At SLIDE_SWITCH_MS the nudge flips to the opposite side. +const SLIDE_TRIGGER_MS = 150; +const SLIDE_SWITCH_MS = 350; const DEFAULT_CONFIG = { arrivalThreshold: 0.15, stuckMovementEpsilon: 0.015, @@ -57,6 +62,20 @@ export function useClickToMoveController(config = {}) { } return { horizontal: 0, vertical: 0 }; } + // Slide assist: when the character has been making slow progress for long + // enough, blend in a perpendicular nudge so it slides around the surface + // of the obstacle instead of pressing into it and freezing. + const prog = progressRef.current; + if (prog && prog.stuckMs >= SLIDE_TRIGGER_MS) { + const sign = prog.slideSign; + const perpX = (-dz / dist) * sign; + const perpZ = (dx / dist) * sign; + const t = MathUtils.clamp((prog.stuckMs - SLIDE_TRIGGER_MS) / 200, 0, 0.85); + return { + horizontal: MathUtils.clamp(dx / dist * (1 - t) + perpX * t, -1, 1), + vertical: MathUtils.clamp(dz / dist * (1 - t) + perpZ * t, -1, 1), + }; + } return { horizontal: MathUtils.clamp(dx / dist, -1, 1), vertical: MathUtils.clamp(dz / dist, -1, 1), @@ -69,12 +88,18 @@ export function useClickToMoveController(config = {}) { } const prev = progressRef.current; if (!prev) { - progressRef.current = { x: currentX, z: currentZ, stuckMs: 0 }; + progressRef.current = { x: currentX, z: currentZ, stuckMs: 0, slideSign: 1 }; return { stuck: false }; } const moved = Math.sqrt((currentX - prev.x) ** 2 + (currentZ - prev.z) ** 2); if (moved < mergedConfig.stuckMovementEpsilon) { prev.stuckMs += delta * 1000; + // Flip the perpendicular slide direction halfway through the timeout so + // the character tries the other side of the obstacle if the first side + // didn't work. + if (prev.stuckMs >= SLIDE_SWITCH_MS && prev.stuckMs - delta * 1000 < SLIDE_SWITCH_MS) { + prev.slideSign = prev.slideSign === 1 ? -1 : 1; + } if (prev.stuckMs > mergedConfig.stuckTimeoutMs) { progressRef.current = null; cancelTarget(); @@ -85,6 +110,7 @@ export function useClickToMoveController(config = {}) { prev.x = currentX; prev.z = currentZ; prev.stuckMs = 0; + prev.slideSign = 1; } return { stuck: false }; }, [cancelTarget, mergedConfig.stuckMovementEpsilon, mergedConfig.stuckTimeoutMs]); diff --git a/packages/engine-renderer-r3f/dist/hooks/useClickToMoveController.js.map b/packages/engine-renderer-r3f/dist/hooks/useClickToMoveController.js.map index 234fb15..9b71e39 100644 --- a/packages/engine-renderer-r3f/dist/hooks/useClickToMoveController.js.map +++ b/packages/engine-renderer-r3f/dist/hooks/useClickToMoveController.js.map @@ -1 +1 @@ -{"version":3,"file":"useClickToMoveController.js","sourceRoot":"","sources":["../../src/hooks/useClickToMoveController.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"useClickToMoveController.js","sourceRoot":"","sources":["../../src/hooks/useClickToMoveController.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/engine-renderer-r3f/dist/index.d.ts b/packages/engine-renderer-r3f/dist/index.d.ts index c355a65..567e87e 100644 --- a/packages/engine-renderer-r3f/dist/index.d.ts +++ b/packages/engine-renderer-r3f/dist/index.d.ts @@ -18,6 +18,7 @@ export { SceneWallPointPreview } from "./scene/SceneWallPointPreview"; export { SceneTransitions } from "./scene/SceneTransitions"; export { GameTouchSpriteRuntime } from "./GameTouchSpriteRuntime"; export { default as SpeechBubble } from "./SpeechBubble"; +export { RemotePlayers, type RemotePlayersProps } from "./net/RemotePlayers"; export { usePlayerWalkAnimation } from "./hooks/usePlayerWalkAnimation"; export { useAudioSystem } from "./hooks/useAudioSystem"; export { useClickToMoveController } from "./hooks/useClickToMoveController"; diff --git a/packages/engine-renderer-r3f/dist/index.d.ts.map b/packages/engine-renderer-r3f/dist/index.d.ts.map index 1e412c5..dbaccef 100644 --- a/packages/engine-renderer-r3f/dist/index.d.ts.map +++ b/packages/engine-renderer-r3f/dist/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,UAAU,CAAC;AAGxC,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,YAAY,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAGvE,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAG5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,YAAY,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAGpE,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,YAAY,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAG5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAC/E,YAAY,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAGjF,OAAO,EAAE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,GAC/B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACjF,OAAO,EACL,cAAc,EACd,KAAK,mBAAmB,EACxB,KAAK,0BAA0B,GAChC,MAAM,6BAA6B,CAAC"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,UAAU,CAAC;AAGxC,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,YAAY,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAGvE,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAG5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,YAAY,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAGpE,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,YAAY,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAG5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGzD,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAG7E,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAC/E,YAAY,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAGjF,OAAO,EAAE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,GAC/B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACjF,OAAO,EACL,cAAc,EACd,KAAK,mBAAmB,EACxB,KAAK,0BAA0B,GAChC,MAAM,6BAA6B,CAAC"} \ No newline at end of file diff --git a/packages/engine-renderer-r3f/dist/index.js b/packages/engine-renderer-r3f/dist/index.js index 249aeae..c8ce5ee 100644 --- a/packages/engine-renderer-r3f/dist/index.js +++ b/packages/engine-renderer-r3f/dist/index.js @@ -19,6 +19,8 @@ export { SceneTransitions } from "./scene/SceneTransitions"; // Runtime export { GameTouchSpriteRuntime } from "./GameTouchSpriteRuntime"; export { default as SpeechBubble } from "./SpeechBubble"; +// Net +export { RemotePlayers } from "./net/RemotePlayers"; // Hooks export { usePlayerWalkAnimation } from "./hooks/usePlayerWalkAnimation"; export { useAudioSystem } from "./hooks/useAudioSystem"; diff --git a/packages/engine-renderer-r3f/dist/index.js.map b/packages/engine-renderer-r3f/dist/index.js.map index 04e4851..ed85e3b 100644 --- a/packages/engine-renderer-r3f/dist/index.js.map +++ b/packages/engine-renderer-r3f/dist/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC;AAExC,cAAc;AACd,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAG7D,WAAW;AACX,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D,SAAS;AACT,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE9D,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAEpE,QAAQ;AACR,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D,UAAU;AACV,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEzD,QAAQ;AACR,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAG/E,OAAO;AACP,OAAO,EAAE,OAAO,EAAsB,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EACL,kBAAkB,GAEnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,YAAY,EAA0B,MAAM,2BAA2B,CAAC;AACjF,OAAO,EACL,cAAc,GAGf,MAAM,6BAA6B,CAAC"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC;AAExC,cAAc;AACd,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAG7D,WAAW;AACX,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D,SAAS;AACT,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE9D,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAEpE,QAAQ;AACR,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D,UAAU;AACV,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEzD,MAAM;AACN,OAAO,EAAE,aAAa,EAA2B,MAAM,qBAAqB,CAAC;AAE7E,QAAQ;AACR,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAG/E,OAAO;AACP,OAAO,EAAE,OAAO,EAAsB,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EACL,kBAAkB,GAEnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,YAAY,EAA0B,MAAM,2BAA2B,CAAC;AACjF,OAAO,EACL,cAAc,GAGf,MAAM,6BAA6B,CAAC"} \ No newline at end of file diff --git a/packages/engine-renderer-r3f/dist/net/RemotePlayers.d.ts b/packages/engine-renderer-r3f/dist/net/RemotePlayers.d.ts new file mode 100644 index 0000000..4eae0a7 --- /dev/null +++ b/packages/engine-renderer-r3f/dist/net/RemotePlayers.d.ts @@ -0,0 +1,8 @@ +import type { RemotePlayersStore } from "@pointclick-engine/engine-core"; +export interface RemotePlayersProps { + store: RemotePlayersStore; + currentSceneId: string; +} +/** Dibuja los avatares de los demás jugadores en la escena actual. */ +export declare function RemotePlayers({ store, currentSceneId }: RemotePlayersProps): import("react/jsx-runtime").JSX.Element; +//# sourceMappingURL=RemotePlayers.d.ts.map \ No newline at end of file diff --git a/packages/engine-renderer-r3f/dist/net/RemotePlayers.d.ts.map b/packages/engine-renderer-r3f/dist/net/RemotePlayers.d.ts.map new file mode 100644 index 0000000..c1878b1 --- /dev/null +++ b/packages/engine-renderer-r3f/dist/net/RemotePlayers.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"RemotePlayers.d.ts","sourceRoot":"","sources":["../../src/net/RemotePlayers.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAEV,kBAAkB,EACnB,MAAM,gCAAgC,CAAC;AA4FxC,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,kBAAkB,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,kBAAkB,2CAU1E"} \ No newline at end of file diff --git a/packages/engine-renderer-r3f/dist/net/RemotePlayers.js b/packages/engine-renderer-r3f/dist/net/RemotePlayers.js new file mode 100644 index 0000000..6e41b7f --- /dev/null +++ b/packages/engine-renderer-r3f/dist/net/RemotePlayers.js @@ -0,0 +1,68 @@ +"use client"; +import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; +import { useEffect, useReducer, useRef } from "react"; +import { useFrame } from "@react-three/fiber"; +import { RigidBody, CuboidCollider } from "@react-three/rapier"; +import { MathUtils, Vector3 } from "three"; +import DavidSprite from "../sprite/DavidSprite"; +import { GAME_CHARACTER_SPRITES } from "../sprite/clips"; +// Mismos valores que GameTouchSpriteRuntime para que la escala por profundidad sea idéntica. +const DEPTH_FAR_Z = -16; +const DEPTH_NEAR_Z = 8; +const SPRITE_MIN_SCALE = 1.4; +const SPRITE_MAX_SCALE = 2.94; +/** Suscribe a la store (re-render en cada cambio). */ +function useRemotePlayers(store) { + const [, force] = useReducer((x) => x + 1, 0); + useEffect(() => store.subscribe(() => force()), [store]); + return store.getAll(); +} +function RemotePlayerSprite({ player }) { + const bodyRef = useRef(null); + const meshRef = useRef(null); + // Frozen at mount — only used as RigidBody's initial position prop. + // All subsequent movement goes through setNextKinematicTranslation in + // useFrame so that smooth interpolation is never overridden by a prop + // update (which would teleport the body to the raw network position). + const spawnPosition = useRef([ + player.position[0], + player.position[1], + player.position[2], + ]); + // target = posición de física recibida por red (~12 Hz) + const target = useRef(new Vector3(player.position[0], player.position[1], player.position[2])); + // smoothPos = posición interpolada que se aplica al body kinematic cada frame + const smoothPos = useRef(new Vector3(player.position[0], player.position[1], player.position[2])); + // Actualiza el objetivo cada render (presence llega throttled ~12 Hz). + target.current.set(player.position[0], player.position[1], player.position[2]); + const sprites = GAME_CHARACTER_SPRITES[player.characterId] ?? GAME_CHARACTER_SPRITES.Dave; + const animation = sprites[player.action] ?? sprites.idle; + useFrame((_, dt) => { + const body = bodyRef.current; + const m = meshRef.current; + if (!body || !m) + return; + // Lerp suave hacia la posición objetivo. + const t = Math.min(1, dt * 10); + smoothPos.current.x += (target.current.x - smoothPos.current.x) * t; + smoothPos.current.y += (target.current.y - smoothPos.current.y) * t; + smoothPos.current.z += (target.current.z - smoothPos.current.z) * t; + // Mueve el cuerpo kinematic — Rapier propaga la colisión con el player local. + body.setNextKinematicTranslation(smoothPos.current); + // Escala por profundidad (igual que GameTouchSpriteRuntime). + const depthFactor = MathUtils.clamp((smoothPos.current.z - DEPTH_FAR_Z) / (DEPTH_NEAR_Z - DEPTH_FAR_Z), 0, 1); + const s = MathUtils.lerp(SPRITE_MIN_SCALE, SPRITE_MAX_SCALE, depthFactor); + // Offset Y del sprite dentro del RigidBody: mismo que el player local. + m.position.y = s - 0.95; + const flipX = animation.flipX ? -1 : 1; + m.scale.set(flipX * s, s, 1); + }); + return (_jsxs(RigidBody, { ref: bodyRef, type: "kinematicPosition", colliders: false, position: spawnPosition.current, enabledRotations: [false, false, false], children: [_jsx(CuboidCollider, { args: [0.55, 0.95, 0.18], friction: 0.05, restitution: 0 }), _jsx(DavidSprite, { animation: animation, meshRef: meshRef, isPaused: player.action === "idle" })] })); +} +/** Dibuja los avatares de los demás jugadores en la escena actual. */ +export function RemotePlayers({ store, currentSceneId }) { + const players = useRemotePlayers(store); + const inScene = players.filter((p) => p.sceneId === currentSceneId); + return (_jsx(_Fragment, { children: inScene.map((p) => (_jsx(RemotePlayerSprite, { player: p }, p.playerId))) })); +} +//# sourceMappingURL=RemotePlayers.js.map \ No newline at end of file diff --git a/packages/engine-renderer-r3f/dist/net/RemotePlayers.js.map b/packages/engine-renderer-r3f/dist/net/RemotePlayers.js.map new file mode 100644 index 0000000..3598cfb --- /dev/null +++ b/packages/engine-renderer-r3f/dist/net/RemotePlayers.js.map @@ -0,0 +1 @@ +{"version":3,"file":"RemotePlayers.js","sourceRoot":"","sources":["../../src/net/RemotePlayers.tsx"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/engine-renderer-r3f/src/GameTouchSpriteRuntime.tsx b/packages/engine-renderer-r3f/src/GameTouchSpriteRuntime.tsx index f471855..462861c 100644 --- a/packages/engine-renderer-r3f/src/GameTouchSpriteRuntime.tsx +++ b/packages/engine-renderer-r3f/src/GameTouchSpriteRuntime.tsx @@ -646,6 +646,13 @@ export function GameTouchSpriteRuntime({ horizontal = applyDeadzone(horizontal, MOVEMENT_INPUT_DEADZONE); vertical = applyDeadzone(vertical, MOVEMENT_INPUT_DEADZONE); + // Snapshot the intended direction BEFORE slide-zeroing so the animation + // keeps showing the walk direction even when a kinematic body (e.g. a + // remote player) is blocking the axis. Without this, slideBlockX zeroes + // `horizontal` → resolveAction(0,0) = "idle" every other frame → flicker. + const rawHorizontal = horizontal; + const rawVertical = vertical; + // Apply wall-slide: if a collider blocked an axis last frame, do not push // into it again this frame. The perpendicular axis is kept intact so the // character slides along the surface. Only suppress when *trying* to move @@ -662,7 +669,8 @@ export function GameTouchSpriteRuntime({ const dz = targetPos[1] - currentPos[1]; nextAction = resolveAction(dx, dz); } else { - nextAction = resolveAction(horizontal, vertical); + // Use pre-zeroed direction so a blocked axis doesn't collapse to idle. + nextAction = resolveAction(rawHorizontal, rawVertical); } if (currentActionRef.current !== nextAction) { diff --git a/packages/engine-renderer-r3f/src/index.ts b/packages/engine-renderer-r3f/src/index.ts index 179a25a..b037ca1 100644 --- a/packages/engine-renderer-r3f/src/index.ts +++ b/packages/engine-renderer-r3f/src/index.ts @@ -29,6 +29,9 @@ export { SceneTransitions } from "./scene/SceneTransitions"; export { GameTouchSpriteRuntime } from "./GameTouchSpriteRuntime"; export { default as SpeechBubble } from "./SpeechBubble"; +// Net +export { RemotePlayers, type RemotePlayersProps } from "./net/RemotePlayers"; + // Hooks export { usePlayerWalkAnimation } from "./hooks/usePlayerWalkAnimation"; export { useAudioSystem } from "./hooks/useAudioSystem"; diff --git a/packages/engine-renderer-r3f/src/net/RemotePlayers.tsx b/packages/engine-renderer-r3f/src/net/RemotePlayers.tsx new file mode 100644 index 0000000..c10f553 --- /dev/null +++ b/packages/engine-renderer-r3f/src/net/RemotePlayers.tsx @@ -0,0 +1,120 @@ +"use client"; +import { useEffect, useReducer, useRef } from "react"; +import { useFrame } from "@react-three/fiber"; +import { RigidBody, CuboidCollider } from "@react-three/rapier"; +import type { RapierRigidBody } from "@react-three/rapier"; +import { MathUtils, Vector3, type Mesh } from "three"; +import DavidSprite from "../sprite/DavidSprite"; +import { GAME_CHARACTER_SPRITES } from "../sprite/clips"; +import type { + PlayerDescriptor, + RemotePlayersStore, +} from "@pointclick-engine/engine-core"; + +// Mismos valores que GameTouchSpriteRuntime para que la escala por profundidad sea idéntica. +const DEPTH_FAR_Z = -16; +const DEPTH_NEAR_Z = 8; +const SPRITE_MIN_SCALE = 1.4; +const SPRITE_MAX_SCALE = 2.94; + +/** Suscribe a la store (re-render en cada cambio). */ +function useRemotePlayers(store: RemotePlayersStore): PlayerDescriptor[] { + const [, force] = useReducer((x: number) => x + 1, 0); + useEffect(() => store.subscribe(() => force()), [store]); + return store.getAll(); +} + +function RemotePlayerSprite({ player }: { player: PlayerDescriptor }) { + const bodyRef = useRef(null); + const meshRef = useRef(null); + + // Frozen at mount — only used as RigidBody's initial position prop. + // All subsequent movement goes through setNextKinematicTranslation in + // useFrame so that smooth interpolation is never overridden by a prop + // update (which would teleport the body to the raw network position). + const spawnPosition = useRef<[number, number, number]>([ + player.position[0], + player.position[1], + player.position[2], + ]); + + // target = posición de física recibida por red (~12 Hz) + const target = useRef( + new Vector3(player.position[0], player.position[1], player.position[2]), + ); + // smoothPos = posición interpolada que se aplica al body kinematic cada frame + const smoothPos = useRef( + new Vector3(player.position[0], player.position[1], player.position[2]), + ); + + // Actualiza el objetivo cada render (presence llega throttled ~12 Hz). + target.current.set(player.position[0], player.position[1], player.position[2]); + + const sprites = + GAME_CHARACTER_SPRITES[player.characterId] ?? GAME_CHARACTER_SPRITES.Dave; + const animation = sprites[player.action] ?? sprites.idle; + + useFrame((_, dt) => { + const body = bodyRef.current; + const m = meshRef.current; + if (!body || !m) return; + + // Lerp suave hacia la posición objetivo. + const t = Math.min(1, dt * 10); + smoothPos.current.x += (target.current.x - smoothPos.current.x) * t; + smoothPos.current.y += (target.current.y - smoothPos.current.y) * t; + smoothPos.current.z += (target.current.z - smoothPos.current.z) * t; + + // Mueve el cuerpo kinematic — Rapier propaga la colisión con el player local. + body.setNextKinematicTranslation(smoothPos.current); + + // Escala por profundidad (igual que GameTouchSpriteRuntime). + const depthFactor = MathUtils.clamp( + (smoothPos.current.z - DEPTH_FAR_Z) / (DEPTH_NEAR_Z - DEPTH_FAR_Z), + 0, + 1, + ); + const s = MathUtils.lerp(SPRITE_MIN_SCALE, SPRITE_MAX_SCALE, depthFactor); + + // Offset Y del sprite dentro del RigidBody: mismo que el player local. + m.position.y = s - 0.95; + + const flipX = animation.flipX ? -1 : 1; + m.scale.set(flipX * s, s, 1); + }); + + return ( + + + + + ); +} + +export interface RemotePlayersProps { + store: RemotePlayersStore; + currentSceneId: string; +} + +/** Dibuja los avatares de los demás jugadores en la escena actual. */ +export function RemotePlayers({ store, currentSceneId }: RemotePlayersProps) { + const players = useRemotePlayers(store); + const inScene = players.filter((p) => p.sceneId === currentSceneId); + return ( + <> + {inScene.map((p) => ( + + ))} + + ); +}