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":"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"}
\ 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":"AAAA,YAAY,CAAC;;AACb,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAEhE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAa,MAAM,OAAO,CAAC;AACtD,OAAO,WAAW,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAMzD,6FAA6F;AAC7F,MAAM,WAAW,GAAG,CAAC,EAAE,CAAC;AACxB,MAAM,YAAY,GAAG,CAAC,CAAC;AACvB,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAC7B,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAE9B,sDAAsD;AACtD,SAAS,gBAAgB,CAAC,KAAyB;IACjD,MAAM,CAAC,EAAE,KAAK,CAAC,GAAG,UAAU,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACtD,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACzD,OAAO,KAAK,CAAC,MAAM,EAAE,CAAC;AACxB,CAAC;AAED,SAAS,kBAAkB,CAAC,EAAE,MAAM,EAAgC;IAClE,MAAM,OAAO,GAAG,MAAM,CAAkB,IAAI,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,MAAM,CAAO,IAAI,CAAC,CAAC;IAEnC,oEAAoE;IACpE,sEAAsE;IACtE,sEAAsE;IACtE,sEAAsE;IACtE,MAAM,aAAa,GAAG,MAAM,CAA2B;QACrD,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAClB,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAClB,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;KACnB,CAAC,CAAC;IAEH,wDAAwD;IACxD,MAAM,MAAM,GAAG,MAAM,CACnB,IAAI,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CACxE,CAAC;IACF,8EAA8E;IAC9E,MAAM,SAAS,GAAG,MAAM,CACtB,IAAI,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CACxE,CAAC;IAEF,uEAAuE;IACvE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IAE/E,MAAM,OAAO,GACX,sBAAsB,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,sBAAsB,CAAC,IAAI,CAAC;IAC5E,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;IAEzD,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE;QACjB,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;QAC7B,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;QAC1B,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC;YAAE,OAAO;QAExB,yCAAyC;QACzC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QAC/B,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACpE,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACpE,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAEpE,8EAA8E;QAC9E,IAAI,CAAC,2BAA2B,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAEpD,6DAA6D;QAC7D,MAAM,WAAW,GAAG,SAAS,CAAC,KAAK,CACjC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,GAAG,WAAW,CAAC,GAAG,CAAC,YAAY,GAAG,WAAW,CAAC,EAClE,CAAC,EACD,CAAC,CACF,CAAC;QACF,MAAM,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,gBAAgB,EAAE,gBAAgB,EAAE,WAAW,CAAC,CAAC;QAE1E,uEAAuE;QACvE,CAAC,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;QAExB,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACvC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,OAAO,CACL,MAAC,SAAS,IACR,GAAG,EAAE,OAAO,EACZ,IAAI,EAAC,mBAAmB,EACxB,SAAS,EAAE,KAAK,EAChB,QAAQ,EAAE,aAAa,CAAC,OAAO,EAC/B,gBAAgB,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,aAEvC,KAAC,cAAc,IAAC,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,GAAI,EAC5E,KAAC,WAAW,IACV,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,KAAK,MAAM,GAClC,IACQ,CACb,CAAC;AACJ,CAAC;AAOD,sEAAsE;AACtE,MAAM,UAAU,aAAa,CAAC,EAAE,KAAK,EAAE,cAAc,EAAsB;IACzE,MAAM,OAAO,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,cAAc,CAAC,CAAC;IACpE,OAAO,CACL,4BACG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAClB,KAAC,kBAAkB,IAAkB,MAAM,EAAE,CAAC,IAArB,CAAC,CAAC,QAAQ,CAAe,CACnD,CAAC,GACD,CACJ,CAAC;AACJ,CAAC"}
\ 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) => (
+
+ ))}
+ >
+ );
+}