How I Fixed Misaligned Touch Coordinates on Physical Phones
VOID STRIKER was originally built for keyboard controls, with touch controls added later. It worked correctly in desktop browsers and Chrome's device toolbar, but testing on physical smartphones revealed reports such as, "I tapped START on the title screen but opened HANGAR," and, "I tapped an item in HANGAR but selected the item below it." The tap location and the actual hit-detection position did not match. This troublesome class of bug was difficult to reproduce with emulators or developer-tool touch simulation and only became apparent on physical devices.
Narrowing Down the Symptoms
I first suspected the hit-detection logic, such as the HANGAR row hitboxes and title-menu Y coordinates, but found no error in the code. I then examined the touch-coordinate conversion. VOID STRIKER displays its internal 480×640 canvas scaled with CSS to fit the actual screen. Browser coordinates from a tap (clientX/clientY) must be divided by that scale to convert them into internal coordinates, and the problem was how this scale was calculated.
At the time, I used transform: scale(...) to shrink the element wrapping the entire screen. A CSS transform changes only the visual size, not the width and height used for layout, or the size of the box the element actually occupies. To the browser, there was still a large 480px element exceeding the screen width. Smartphone browsers may automatically zoom out to fit a page when its content appears wider than the screen. This auto-zoom was applied on top of the intentional scaling from transform:scale, creating a mismatch between the actual displayed size and the value JavaScript treated as the display size.
Building a Debug Overlay First
Bugs that reproduce only on physical devices are difficult to diagnose from logs alone. I first implemented a debug overlay that, when the URL contains the debugtouch query, draws a crosshair and coordinate values at the tapped internal position for about one second. Comparing the location I knew I had tapped with the recorded internal coordinates on a physical device made it possible to measure the direction and size of the offset. Building a visualization tool before testing the hypothesis proved useful for this kind of investigation beyond this particular bug.
The Root Fix: Switching to an Actual-Size Layout
The final fix was to stop using transform:scale and directly set the game wrapper's (#frame) width/height to pixel values based on the calculated scale. Specifically, the code calculates scale = min(screen width / 480, screen height / 640) * 0.97, then sets Math.round(480 * scale)px / Math.round(640 * scale)px as the element's actual width and height. The layout and visual sizes now match exactly from the browser's perspective, so the browser no longer mistakes the content for something wider than the screen and has no reason to trigger auto-zoom. The values from getBoundingClientRect() used to convert touch coordinates also match this actual-size layout, resolving the offset caused by double scaling at its source.
One More Fix: Making the Cursor Follow Taps
One minor bug remained after fixing the coordinate offset. Tapping HANGAR, CODE, or the title menu executed the correct item, but the on-screen cursor highlight remained on a different item. The cause was simple: tap handling directly executed the item in place without updating the cursor position used by keyboard controls, such as titleCursor or hangarCursor. The fix moves the cursor to the tapped item before running its confirm action, keeping the visual state consistent with the actual result.
What I Learned
This series of fixes reinforced the importance of treating visual scaling and layout size as separate concepts. CSS transforms are convenient, but they can be a poor fit for calculations that depend on actual occupied space, such as layout and touch-coordinate conversion. In mobile canvas games, keeping the visual scale aligned with the element's actual dimensions is a direct way to prevent touch-accuracy bugs.