fix(int33): restore original DOSBox mickey formula in getRelMickey - #24
fix(int33): restore original DOSBox mickey formula in getRelMickey#24abedegno wants to merge 3 commits into
Conversation
Symptom: DOS games that track the cursor via INT 33h fn 0x0B (Read Motion
Data) or the INT 74 mouse callback see cursor motion at roughly half the
expected rate. In Ultima Underworld 1/2 the main-menu crosshair travels
about half the width/height of the viewport for a full edge-to-edge host
mouse drag. Same engine on native DOS/DOSBox tracks 1:1.
Root cause: getRelMickey in native/jsdos/jsdos-mouse.cpp diverged from the
original DOSBox formula by introducing two extra factors:
pxPerCol = surfaceWidth / (max_x - min_x) ≈ 0.5 for VGA mode 13h
(INT 33h reports X in a
doubled 0..639 range over
a 320-pixel screen)
<div by 2> on mickey_y // why div 2?
Together they halve the mickey count that fn 0x0B returns. UW1/UW2
(disassembled UW.EXE at 0x22c98 shows the mickey reader as
'imul bx, 100; idiv [0x360]=200' → mickey/2) inherit that loss and
render the cursor at half speed on both axes.
Fix: drop the two extra factors and use the original DOSBox formula,
'mickey = delta * mickeysPerPixel', applied directly to the absolute
col/row delta. Invert it the same way to compute rel.col / rel.row.
Also carry a per-axis fractional residual across getRelMickey calls so
that sub-mickey motions (e.g. dCol = 0.5 spread over many cumulative
pass-throughs) don't silently round to zero. Without this, slow drags
stutter or stall when the computed mickey delta rounds to 0 and rel.col
never advances past the previous mickeyCol checkpoint.
Verified against UW1 inside the js-dos-emu build: cursor now tracks 1:1
with the host mouse across the full canvas on both X and Y, matching
native DOSBox behavior.
The getRelMickey sync pass zeroed mouse.col and mouse.row while the mickeyRelSyncTries counter was non-zero. Because every wc-mouse-sync message re-arms that counter to 3, any bridge that calls ci.sendMouseSync() after each event (e.g. abedegno/dos-mcp) keeps the reset loop permanently active — so INT 33h fn 0x03 reports POS_X=POS_Y=0 forever, even while Mouse_CursorMoved is landing correct absolute coords in mouse.col/mouse.row. The intent of the sync (per caiiiycuk's comment) is to clear accumulated RELATIVE mickey drift on focus-regain, not to teleport the cursor to (0,0). Drop the mouse.col/row zeroing and keep the mickeyCol/mickeyRow reset; return the current col/row in the mickey struct so callers downstream of getRelMickey see the true absolute position.
The prior patch kept mouse.col/mouse.row untouched through the getRelMickey sync-retry loop so INT 33h fn 0x03 would read the correct absolute position, but it still returned a sentinel -(max - min) mickey delta each pass. Games that derive cursor position from fn 0x0B / INT 74 callbacks (Ultima Underworld being the canonical case) accumulate that delta and walk the in-game cursor off-screen fast — one big negative per 55 ms poll. A sync is meant to flush the internal mickey accumulator, not teleport the consumer. Return zero mickey delta plus current col/row so fn 0x0B consumers stay in lockstep with fn 0x03 consumers during the sync window.
|
Hi, nice. Did you also compare this implementation to dosbox-x? |
|
@caiiiycuk Thanks! I've gone and compared against dosbox-x's INT 33h path ( Short version: the two substantive parts of this change line up with dosbox-x, and there are two known deltas I want to be upfront about rather than claim a perfect match. Where this matches dosbox-x:
Two places I differ from dosbox-x:
One more thing not in dosbox-x's scope: the sync-pass change (returning a zero mickey delta + current absolute If you'd rather I bring this fully in line with dosbox-x - i.e. add |
|
Just releases jsdos v8.4.0, now I can look into it! |
|
@abedegno , please take a look: abedegno#1 |
|
I also tested it locally, so far looks good, also please rebase |
Summary
getRelMickeyinnative/jsdos/jsdos-mouse.cpphas two factors that don't appear in upstream DOSBox and that together halve the mickey stream delivered to DOS programs via INT 33h fn 0x0B (Read Motion Data) and the INT 74 mouse callback:Upstream DOSBox uses the simpler
mickey += delta * mickeysPerPixel. ThepxPerColfactor is ≈ 0.5 for standard VGA mode 13h because INT 33h reports X over a doubled 0..639 range while the rendered screen is 320 pixels wide — so every mickey we report is half what upstream DOSBox would report. The/2on Y is flagged as suspect by the very comment next to it.Any DOS game that relies on the mickey stream (most mouse-aware games from the Microsoft-mouse-driver era) therefore sees cursor motion at roughly ½ the expected rate in js-dos, while the same game run on native DOSBox tracks 1:1.
Repro
Ultima Underworld I/II is a clean case.
UW.EXEreads cursor motion exclusively through fn 0x0B, never fn 0x03 (confirmed by disassembling the threeint 33hsites in the binary):0x22c68— init:mov ax,0; int 33h, then stores 200 into[0x360]and[0x362].0x22c87— tick:mov ax,0Bh; int 33h, then calls the helper at0x22c98on bothmickey_x(from CX) andmickey_y(from DX).0x22ca3— button read only:mov ax,3; int 33h; and ax, 3; ret(POS_X / POS_Y are discarded).Helper at
0x22c98is:UW1's cursor-per-pixel rate is therefore exactly
mickey / 2. With the current js-dos rate (half-mickey per dCol), UW1's crosshair travels ≈ ½ the width of the viewport for a full host-mouse drag across the canvas. With the formula restored to upstream, it tracks 1:1 — verified by driving UW1 inside a local build.Fix
Drop the
pxPerCol/pxPerRowand/2factors. Use the original DOSBox formula, applied directly to the absolute col/row deltas, and invert it the same way forrel.col/rel.row. Also carry a per-axis fractional residual across calls so sub-mickey motions (e.g.dCol = 0.5from a slow incremental pass-through) don't round to zero and leavemouse.mickeyColstuck behindmouse.col— which was causing a secondary bug where slow drags stutter or stall.Compatibility note
This doubles the mickey rate delivered by fn 0x0B / INT 74 vs previous js-dos releases. If anyone had calibrated their integration around the halved rate, their game will now feel twice as fast. The new rate matches native DOSBox, so any game that works on native DOSBox will work here. But if you'd prefer to gate this behind a flag rather than change default behavior, happy to reshape it that way.
Paths not affected:
mouse.col/mouse.rowdirectly, unchanged.server_mouse_moved, unchanged.mickeysPerPixel_x/yis still multiplied in, so game-set rates still apply.Related issues
Plausibly the root cause (or a contributor) for: