diff --git a/client/public/version.js b/client/public/version.js index 51897d6..13e9b05 100644 --- a/client/public/version.js +++ b/client/public/version.js @@ -1,5 +1,5 @@ // Maintainer-controlled web client version. // Format: YYYY.MM.DD Rn (example: 2026.02.20 R2) -window.CHGRID_WEB_VERSION = "2026.02.25 R240"; +window.CHGRID_WEB_VERSION = "2026.02.25 R241"; // Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid. window.CHGRID_TIME_ZONE = "America/Detroit"; diff --git a/client/src/main.ts b/client/src/main.ts index e225ecd..2cdffa9 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -1012,6 +1012,20 @@ function persistPlayerPosition(): void { } } +/** Loads previously persisted local player coordinates, when available and valid. */ +function getPersistedPlayerPosition(): { x: number; y: number } | null { + const raw = localStorage.getItem('spatialChatPosition'); + if (!raw) return null; + try { + const parsed = JSON.parse(raw) as { x?: unknown; y?: unknown }; + if (typeof parsed.x !== 'number' || typeof parsed.y !== 'number') return null; + if (!Number.isFinite(parsed.x) || !Number.isFinite(parsed.y)) return null; + return { x: Math.round(parsed.x), y: Math.round(parsed.y) }; + } catch { + return null; + } +} + /** Picks one random footstep sample URL. */ function randomFootstepUrl(): string { return FOOTSTEP_SOUND_URLS[Math.floor(Math.random() * FOOTSTEP_SOUND_URLS.length)]; @@ -1355,6 +1369,7 @@ const onAppMessage = createOnMessageHandler({ mediaSession.setConnecting(value); updateConnectAvailability(); }, + getPersistedPlayerPosition, rendererSetGridSize: (size) => renderer.setGridSize(size), applyServerItemUiDefinitions: (defs) => applyServerItemUiDefinitions(defs as Parameters[0]), state, diff --git a/client/src/network/messageHandlers.ts b/client/src/network/messageHandlers.ts index e3f24d0..6cfe5bf 100644 --- a/client/src/network/messageHandlers.ts +++ b/client/src/network/messageHandlers.ts @@ -9,6 +9,7 @@ type MessageHandlerDeps = { setWorldGridSize: (size: number) => void; setMovementTickMs: (value: number) => void; setConnecting: (value: boolean) => void; + getPersistedPlayerPosition: () => { x: number; y: number } | null; rendererSetGridSize: (size: number) => void; applyServerItemUiDefinitions: (defs: unknown) => boolean; state: { @@ -97,8 +98,11 @@ export function createOnMessageHandler(deps: MessageHandlerDeps): (message: Inco deps.state.player.id = message.id; deps.state.running = true; deps.setConnecting(false); - deps.state.player.x = Math.max(0, Math.min(deps.getWorldGridSize() - 1, message.player.x)); - deps.state.player.y = Math.max(0, Math.min(deps.getWorldGridSize() - 1, message.player.y)); + const persistedPosition = deps.getPersistedPlayerPosition(); + const targetX = persistedPosition?.x ?? message.player.x; + const targetY = persistedPosition?.y ?? message.player.y; + deps.state.player.x = Math.max(0, Math.min(deps.getWorldGridSize() - 1, targetX)); + deps.state.player.y = Math.max(0, Math.min(deps.getWorldGridSize() - 1, targetY)); deps.dom.nicknameContainer.classList.add('hidden'); deps.dom.connectButton.classList.add('hidden'); deps.dom.disconnectButton.classList.remove('hidden');