From 7fd323e10f5bf90650c297e9f2aeee61410c22bb Mon Sep 17 00:00:00 2001 From: CaYatur Date: Thu, 6 Aug 2026 00:49:22 +0300 Subject: [PATCH] Put players where they actually are on the public map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported as a placement bug: on the web the players sit beside where they are standing, and in the app they do not. It was not a placement bug — the public surfaces round positions to 64 blocks for privacy and the app does not round at all, so a player was drawn up to 32 blocks from their house. That default made sense when the public map was dots on an empty grid, where being half a chunk out was invisible. The map draws real terrain now, and against terrain the same rounding is indistinguishable from a bug — it was reported as one. So exact by default, on both the public site and the map page. The protection is real and stays: an operator who wants it sets the field, and the field now says what it costs rather than calling itself "Round to (blocks)" — a number that reads as a display nicety and actually means every player is drawn up to half of it away from where they are. EN and TR. Deliberately NOT migrating anyone's stored value. `round: 64` is persisted in site.json, and it is indistinguishable from a deliberate choice, so rewriting it would publish exact coordinates on the public website of every operator who meant to round. New configs get the new default; an existing one is one field in Website settings. The existing redaction test asked for its rounding through the defaults, so it began asserting the feature was broken the moment the default changed. It now passes `round` explicitly — "does redaction work when switched on" and "is it on by default" are two questions and only the first belonged there. The field-level protections (no y, no world, no uuid) are asserted with rounding both on and off, since those have no setting. Proved failable: putting the default back to 64 fails with "the public map rounds positions by default: 64". Gates green: MSMS_SMOKE, MSMS_SMOKE_WEB. --- src/main/smoke.ts | 37 ++++++++++++++++++++++--- src/renderer/src/locales/en.ts | 2 ++ src/renderer/src/locales/tr.ts | 2 ++ src/renderer/src/views/SiteView.tsx | 8 ++++++ src/renderer/src/views/WebPanelView.tsx | 1 + src/shared/livemap.ts | 11 +++++++- src/shared/mapPage.ts | 4 ++- 7 files changed, 59 insertions(+), 6 deletions(-) diff --git a/src/main/smoke.ts b/src/main/smoke.ts index c874ba1..89554e1 100644 --- a/src/main/smoke.ts +++ b/src/main/smoke.ts @@ -8978,19 +8978,30 @@ export async function runWebSmoke(): Promise { const exact: LivePlayer[] = [ { name: 'Alex', uuid: 'u-1', world: 'world', dim: 'overworld', x: 1234, y: 12, z: -987 } ] - const pub = redactPlayers(exact, { ...PUBLIC_MAP_DEFAULTS, enabled: true, serverId: 's' }) + // Rounding is asked for EXPLICITLY here rather than taken from the + // defaults. It used to come from them, and when the default changed to + // exact this block started asserting that the feature was broken — the + // two questions are "does redaction work when switched on" and "is it on + // by default", and only the first belongs in this block. + const pub = redactPlayers(exact, { ...PUBLIC_MAP_DEFAULTS, enabled: true, serverId: 's', round: 64 }) const one = pub[0] as unknown as Record // The panel payload's fields must not arrive here by being spread // through. Height is the sharp one: y=12 says "in a cave", which is - // when a player cannot defend the base you would then walk to. + // when a player cannot defend the base you would then walk to. This + // holds whatever the rounding is — the fields are the protection that + // does not have a setting. + const bare = redactPlayers(exact, { ...PUBLIC_MAP_DEFAULTS, enabled: true, serverId: 's' })[0] for (const leaked of ['y', 'world', 'uuid']) { if (leaked in one) return fail('the public map payload carries "' + leaked + '"') + if (leaked in (bare as unknown as Record)) { + return fail('with rounding off the public map payload carries "' + leaked + '"') + } } - if (one.x === 1234 || one.z === -987) return fail('the public map published exact coordinates') + if (one.x === 1234 || one.z === -987) return fail('rounding did not move a player at all') if (Math.abs((one.x as number) - 1234) > 32) return fail('rounding moved a player more than half a cell') // Deterministic, not jittered: a watcher who samples a stationary // player repeatedly must not be able to average the noise away. - const again = redactPlayers(exact, { ...PUBLIC_MAP_DEFAULTS, enabled: true, serverId: 's' }) + const again = redactPlayers(exact, { ...PUBLIC_MAP_DEFAULTS, enabled: true, serverId: 's', round: 64 }) if (again[0].x !== pub[0].x || again[0].z !== pub[0].z) return fail('redaction is not deterministic') // Opt-ins. // Heads are drawn from the name since #116, so heads-on must publish @@ -9009,6 +9020,24 @@ export async function runWebSmoke(): Promise { if ('name' in (noNames[0] as unknown as Record)) { return fail('names off still published a name') } + // Exact by default. Rounding is a real protection and stays available, + // but it was ON at 64 blocks for everyone, and on a map that now draws + // terrain that puts a player visibly beside the house they are standing + // in — which reads as a placement bug, and was reported as one. + if (PUBLIC_MAP_DEFAULTS.round !== 0) { + return fail('the public map rounds positions by default: ' + PUBLIC_MAP_DEFAULTS.round) + } + if (MAP_PAGE_DEFAULTS.round !== 0) { + return fail('the map page rounds positions by default: ' + MAP_PAGE_DEFAULTS.round) + } + // Still available, and still exactly as strong when asked for. + { + const exactCfg = { ...PUBLIC_MAP_DEFAULTS, enabled: true, serverId: 's' } + const p = redactPlayers(exact, exactCfg)[0] + if (p.x !== 1234 || p.z !== -987) return fail('the default no longer publishes exact positions') + const rounded = redactPlayers(exact, { ...exactCfg, round: 64 })[0] + if (rounded.x % 64 !== 0 || rounded.z % 64 !== 0) return fail('opting into rounding stopped working') + } if (clampRound(-5) !== 0) return fail('a negative rounding was accepted') if (clampRound(99999) !== 512) return fail('rounding was not capped') if (clampRound('lots') !== PUBLIC_MAP_DEFAULTS.round) return fail('a junk rounding did not fall back') diff --git a/src/renderer/src/locales/en.ts b/src/renderer/src/locales/en.ts index ab46c48..d27ee5a 100644 --- a/src/renderer/src/locales/en.ts +++ b/src/renderer/src/locales/en.ts @@ -764,6 +764,7 @@ export default { mapServer: 'Live map server', showMap: 'Show live map on the site', mapRound: 'Round to (blocks)', + mapRoundHint: '0 publishes exact positions. Any other value hides them: a player is drawn up to half of it away from where they really are, which on a terrain map looks like a mistake rather than like privacy.', mapNames: 'Show names', mapHeads: 'Draw skin heads', mapWorld: 'Show the terrain', @@ -914,6 +915,7 @@ export default { mapStructures: 'Structures', mapHeat: 'Heatmap', mapRound: 'Round positions to', + mapRoundHint: '0 publishes exact positions. Any other value draws a player up to half of it from where they really are.', mapPin: 'Pin one world', mapPinAny: 'Let visitors switch', siteSection: 'Public website', diff --git a/src/renderer/src/locales/tr.ts b/src/renderer/src/locales/tr.ts index 07ec2a0..d837740 100644 --- a/src/renderer/src/locales/tr.ts +++ b/src/renderer/src/locales/tr.ts @@ -768,6 +768,7 @@ const tr: typeof en = { mapServer: 'Canlı harita sunucusu', showMap: 'Sitede canlı haritayı göster', mapRound: 'Yuvarlama (blok)', + mapRoundHint: '0 tam konumu yayımlar. Başka bir değer konumu gizler: oyuncu, gerçekte durduğu yerden bu değerin yarısı kadar uzakta çizilir — arazi haritasında bu, gizlilikten çok yerleştirme hatası gibi görünür.', mapNames: 'İsimleri göster', mapHeads: 'Oyuncu kafalarını çiz', mapWorld: 'Araziyi göster', @@ -918,6 +919,7 @@ const tr: typeof en = { mapStructures: 'Yapılar', mapHeat: 'Isı haritası', mapRound: 'Konumları şuna yuvarla', + mapRoundHint: '0 tam konumu yayımlar. Başka bir değer oyuncuyu gerçek yerinden bu değerin yarısı kadar uzakta çizer.', mapPin: 'Tek dünyaya sabitle', mapPinAny: 'Ziyaretçi değiştirebilsin', siteSection: 'Herkese açık site', diff --git a/src/renderer/src/views/SiteView.tsx b/src/renderer/src/views/SiteView.tsx index 9686eb8..ed03632 100644 --- a/src/renderer/src/views/SiteView.tsx +++ b/src/renderer/src/views/SiteView.tsx @@ -196,6 +196,11 @@ export function SiteView(): JSX.Element { patchMap({ enabled: e.target.checked })} /> {t('site.showMap')} + {/* The number that decides whether the map looks right. Said in + blocks and in consequences, because "Round to 64" reads as a + display nicety and is actually "every player is drawn up to 32 + blocks from where they are" — which on a map that now draws real + terrain is indistinguishable from a bug. */}
patchMap({ round: Number(e.target.value) })} />
+

+ {t('site.mapRoundHint')} +