feat(player): reposition mobile player focal point

This commit is contained in:
2026-09-10 22:26:55 +03:00
parent 2501345740
commit 8949891a6b
8 changed files with 201 additions and 12 deletions
+22 -2
View File
@@ -190,6 +190,18 @@
};
}
function playerFocalPoint(stageWidth, stageHeight) {
if (![stageWidth, stageHeight].every(value => Number.isFinite(value) && value > 0)) return {x: 0, y: 0};
if (stageWidth > stageHeight) return {x: Math.round(stageWidth * .42), y: Math.round(stageHeight * .45)};
return {x: Math.round(stageWidth / 2), y: Math.round(stageHeight * .3)};
}
function playerFocalMapOffset(selfIndex, width, minX, minY, columns, rows, cellSize, stageWidth, stageHeight) {
const centered = playerMapOffset(selfIndex, width, minX, minY, columns, rows, cellSize);
const focal = playerFocalPoint(stageWidth, stageHeight);
return {x: centered.x + focal.x - stageWidth / 2, y: centered.y + focal.y - stageHeight / 2};
}
function nextOnboardingStep(currentStep) {
return currentStep >= ONBOARDING_STEPS.length - 1 ? null : currentStep + 1;
}
@@ -495,12 +507,15 @@
const rows = maxY - minY + 1;
const mapShell = byId("map-shell");
const cellSize = playerCellSize(mapShell.clientWidth, mapShell.clientHeight, columns, rows);
const offset = playerMapOffset(snapshot.self, snapshot.width, minX, minY, columns, rows, cellSize);
const focal = playerFocalPoint(mapShell.clientWidth, mapShell.clientHeight);
const offset = playerFocalMapOffset(snapshot.self, snapshot.width, minX, minY, columns, rows, cellSize, mapShell.clientWidth, mapShell.clientHeight);
maze.style.setProperty("--cell", `${cellSize}px`);
maze.style.gridTemplateColumns = `repeat(${columns}, var(--cell))`;
maze.style.gridTemplateRows = `repeat(${rows}, var(--cell))`;
maze.style.setProperty("--map-offset-x", `${offset.x}px`);
maze.style.setProperty("--map-offset-y", `${offset.y}px`);
mapShell.style.setProperty("--player-focal-x", `${focal.x}px`);
mapShell.style.setProperty("--player-focal-y", `${focal.y}px`);
const objects = new Map();
snapshot.objects.forEach(object => { const list = objects.get(object[3]) || []; list.push(object); objects.set(object[3], list); });
coordinates.forEach(data => {
@@ -837,6 +852,11 @@
byId("avatar-control").addEventListener("pointercancel", stopAvatarControl);
byId("avatar-control").addEventListener("lostpointercapture", stopAvatarControl);
windowObject.addEventListener("blur", resetPlayerControls);
const refreshPlayerFocalPoint = () => {
if (state.snapshot && state.snapshot.role === "player") windowObject.requestAnimationFrame(() => renderMap(state.snapshot));
};
windowObject.addEventListener("resize", refreshPlayerFocalPoint);
windowObject.addEventListener("orientationchange", refreshPlayerFocalPoint);
documentObject.addEventListener("visibilitychange", () => { if (documentObject.hidden) resetPlayerControls(); });
documentObject.querySelectorAll("[data-direction]").forEach(button => {
button.addEventListener("pointerdown", event => {
@@ -902,7 +922,7 @@
return {state, handleMessage, move, resetAvatarControl, latencySamples: () => state.latency.snapshot(), pointerFeedbackSamples: () => state.latency.pointerSnapshot()};
}
const api = Object.freeze({TOKEN_KEY, MAX_VIEW_CHUNKS, NAME_COMBINATION_COUNT, LOGIN_HEADLINES, ONBOARDING_STEPS, LATENCY_SAMPLE_LIMIT, AVATAR_TOUCH_TARGET_SIZE, AVATAR_MAX_DEFLECTION, AVATAR_DEAD_ZONE, AVATAR_DIRECTION_HYSTERESIS, AVATAR_SLOW_REPEAT_MS, AVATAR_FAST_REPEAT_MS, DIRECTIONAL_PAD_REPEAT_MS, LatestIntentFlow, LatencyTracker, directionFromKey, directionFromSwipe, avatarDirection, avatarRepeatMs, avatarVector, playerCellSize, playerMapOffset, nextOnboardingStep, randomIndex, randomSuggestedName, randomLoginHeadline, createEnvelope, snapshotIsValid, SnapshotAssembler, projectedCellIndices, clampObserverZoom, observerFitScale, boot});
const api = Object.freeze({TOKEN_KEY, MAX_VIEW_CHUNKS, NAME_ADJECTIVES, NAME_NOUNS, NAME_COMBINATION_COUNT, LOGIN_HEADLINES, ONBOARDING_STEPS, LATENCY_SAMPLE_LIMIT, AVATAR_TOUCH_TARGET_SIZE, AVATAR_MAX_DEFLECTION, AVATAR_DEAD_ZONE, AVATAR_DIRECTION_HYSTERESIS, AVATAR_SLOW_REPEAT_MS, AVATAR_FAST_REPEAT_MS, DIRECTIONAL_PAD_REPEAT_MS, LatestIntentFlow, LatencyTracker, directionFromKey, directionFromSwipe, avatarDirection, avatarRepeatMs, avatarVector, playerCellSize, playerMapOffset, playerFocalPoint, playerFocalMapOffset, nextOnboardingStep, randomIndex, randomSuggestedName, randomLoginHeadline, createEnvelope, snapshotIsValid, SnapshotAssembler, projectedCellIndices, clampObserverZoom, observerFitScale, boot});
if (typeof module !== "undefined" && module.exports) module.exports = api;
root.Maze32Player = api;
if (root.document) root.document.addEventListener("DOMContentLoaded", () => { root.maze32PlayerApp = boot(root.document, root); });