Put the hover marker back in the box's coordinate space, letterbox and all
Some checks failed
CI / backend (push) Failing after 7m12s
CI / naming (push) Failing after 7m18s
CI / frontend (push) Failing after 7m22s
CI / migrations-mysql (push) Failing after 7m10s

2bf1531 fixed the reported symptom and introduced the opposite one: markers that
had been landing left came right, and then sat well ABOVE where they belonged.

It was the right diagnosis and the wrong remedy. The drawing is painted with
object-fit: contain inside a fixed 390x300 preview, so it is letterboxed on one
axis and a percentage of the BOX is not a percentage of the DRAWING. That part
was true. But the fix resized the frame to shrink-wrap the image, and the
centring transform above it translates by percentages - which resolve against
that same element. Making the frame image-sized silently broke the transform's
assumption that the frame is the box. Width still matched at 390, height no
longer did, which is exactly x-right and y-wrong.

The frame fills the box again and object-fit: contain is back. Where the bars
fall is now computed instead: paintedBox works out the fraction of the box the
drawing covers and the gap before it, per axis, and the marker maths folds that
in. The transform's assumption stays true because the element it translates
never changes size.

Checked per axis rather than asserted. A tall drawing pillarboxes, so x
compresses and offsets inward - a left edge at 2 percent of the drawing lands at
13.1 percent of the box, which is where the drawing actually starts - and y is
untouched. A wide drawing does the reverse. A level shaped like the box is a
no-op, so nothing that already looked right moves. Centre maps to centre on
every shape.

NEW COUPLING, deliberately noted at both ends: PREVIEW_WIDTH and PREVIEW_HEIGHT
must stay in step with .map-preview in the styles. Resize the preview without
touching them and this drifts back.

Still not confirmed against a live instance, and this is the second attempt at
this bug, so it wants eyes on three cases: one that was landing left, one that
was landing high, and one that was right all along.
This commit is contained in:
cproudlock
2026-08-21 10:19:56 -04:00
parent 2bf1531d5b
commit 41d875da4e

View File

@@ -97,11 +97,15 @@ const blueprintUrl = computed(() => {
// the wrong level's dimensions is precisely how a marker ends up plausibly // the wrong level's dimensions is precisely how a marker ends up plausibly
// placed and wrong. // placed and wrong.
const markerX = computed(() => { const markerX = computed(() => {
return (props.left / dimensionsFor(props.levelid).width) * 100 const fraction = props.left / dimensionsFor(props.levelid).width
const { offsetX, scaleX } = paintedBox.value
return (offsetX + fraction * scaleX) * 100
}) })
const markerY = computed(() => { const markerY = computed(() => {
return (props.top / dimensionsFor(props.levelid).height) * 100 const fraction = props.top / dimensionsFor(props.levelid).height
const { offsetY, scaleY } = paintedBox.value
return (offsetY + fraction * scaleY) * 100
}) })
// Marker style with counter-scale to maintain constant size // Marker style with counter-scale to maintain constant size
@@ -120,9 +124,33 @@ const markerStyle = computed(() => ({
// lands in the grey gutter beside the map. Levels near 390:300 looked correct, // lands in the grey gutter beside the map. Levels near 390:300 looked correct,
// which is why it only affected some assets. // which is why it only affected some assets.
// //
// The fix is to let the IMAGE size the frame: the frame shrink-wraps the drawing, // The preview is a fixed box and object-fit: contain paints the drawing inside it
// so a percentage of the frame IS a percentage of the drawing, whatever the // at the drawing's own aspect ratio, so the drawing does not fill the box: it is
// level's aspect ratio. The letterboxing moves out to the preview around it. // letterboxed on one axis. A marker placed at a percentage of the BOX therefore
// lands somewhere else than the same percentage of the DRAWING, on any level
// whose shape is not the box's.
//
// So convert: work out where the painted drawing sits inside the box, then map
// the marker into box coordinates. Doing it here rather than by resizing the
// frame keeps the centring transform below honest - its percentages resolve
// against the element, so the element has to stay the size of the box.
const PREVIEW_WIDTH = 390 // keep in step with .map-preview in the styles
const PREVIEW_HEIGHT = 300
const PREVIEW_ASPECT = PREVIEW_WIDTH / PREVIEW_HEIGHT
// Fraction of the box the drawing covers, and the gap before it, per axis.
const paintedBox = computed(() => {
const { width, height } = dimensionsFor(props.levelid)
const levelAspect = width / height
if (levelAspect >= PREVIEW_ASPECT) {
// Wider than the box: full width, bars above and below.
const scale = PREVIEW_ASPECT / levelAspect
return { scaleX: 1, offsetX: 0, scaleY: scale, offsetY: (1 - scale) / 2 }
}
// Taller than the box: full height, bars left and right.
const scale = levelAspect / PREVIEW_ASPECT
return { scaleX: scale, offsetX: (1 - scale) / 2, scaleY: 1, offsetY: 0 }
})
// Transform style that centers on the marker and zooms toward it // Transform style that centers on the marker and zooms toward it
const transformStyle = computed(() => { const transformStyle = computed(() => {
// Calculate translation to center the marker in the preview // Calculate translation to center the marker in the preview
@@ -265,12 +293,6 @@ watch(currentTheme, () => {
.map-preview { .map-preview {
position: relative; position: relative;
/* Centres the aspect-fitted frame, which is where the letterboxing now
happens. The frame carries the drawing AND the marker, so the two can no
longer disagree. */
display: flex;
align-items: center;
justify-content: center;
/* Smaller than it was: this is a glance-and-move-on preview, and at 500px it /* Smaller than it was: this is a glance-and-move-on preview, and at 500px it
covered the row it was launched from. Aspect ratio kept. */ covered the row it was launched from. Aspect ratio kept. */
width: 390px; width: 390px;
@@ -281,23 +303,21 @@ watch(currentTheme, () => {
.map-transform { .map-transform {
position: relative; position: relative;
/* Shrink-wraps the image rather than filling the preview, so the box the /* Fills the preview. The centring transform translates by percentages, which
marker is positioned in is the box the drawing is painted in. */ resolve against THIS element, so it must stay the size of the box it is
max-width: 100%; centring within. The letterbox is accounted for in the marker maths. */
max-height: 100%; width: 100%;
height: 100%;
transition: transform 0.15s ease-out; transition: transform 0.15s ease-out;
} }
.map-image { .map-image {
/* Sizes itself from the drawing, scaled down to fit the preview. NOT width: 100%;
width/height 100% with object-fit: contain - that painted the drawing height: 100%;
letterboxed inside a box the marker was measured against, which put the two /* contain, so a level of any shape is shown whole and undistorted. Where the
in different coordinate spaces on any level that is not 390:300. */ bars fall is computed in paintedBox and folded into the marker position. */
object-fit: contain;
display: block; display: block;
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
} }
.marker-dot { .marker-dot {