From 065f946e2f59031edd0bb9bf3a437bf3782b37ef Mon Sep 17 00:00:00 2001 From: Arti Zirk Date: Fri, 16 Oct 2026 16:07:36 +0300 Subject: [PATCH] harness: VSXXX-AB tablet on DUART2, driven by the absolute host pointer The terminal tells the two pointing devices apart from the 'T' self-test device code (0010 mouse / 0100 tablet) and switches report framing on it: 3-byte relative for the mouse, 5-byte absolute (header 110) for the tablet. Verified against the running OS: with the tablet it reads the self-test as A0 04 00 00 and then reads RHRB in groups of 5 (groups of 3 for the mouse). Refactor the VSXXX model into a shared base + mouse/tablet subclasses and select the device on DUART2-B with VT1200_TABLET=1 (default: the mouse, so the mouse stays and is selectable, not replaced). The tablet reports its documented 0..2199 active area (VCB02 D.1) and reads the host pointer through IPT_LIGHTGUN_X/Y, MAME's absolute control, so the emulated cursor follows the host pointer positionally: corner (0,0) -> (194,38), centre (1100,1100) -> (704,467), corner (2199,2199) -> (1214,898), reproducible pixel-for-pixel on a repeated coordinate. Driving it from the host mouse needs '-lightgun -lightgun_device mouse' (documented for run.sh; run.sh is not edited here). The mouse and keyboard are unchanged, and all baselines hold: no env PC=3FDCE400 samples=198 (3 PCs); VT1200_IO_PATH_STUB=1 samples=53 (75 PCs); ROM md5 unchanged. Nothing is tuned to satisfy level 0's I/O data path test. --- harness/mame/README.md | 31 +- harness/mame/TABLET_REPORT.md | 219 ++++++++++ harness/mame/tab_inject.lua | 38 ++ harness/mame/tablet_cursor_br_f4850.png | Bin 0 -> 19487 bytes harness/mame/tablet_cursor_centre_f5100.png | Bin 0 -> 19592 bytes harness/mame/tablet_cursor_positions.png | Bin 0 -> 1550 bytes harness/mame/tablet_cursor_tl_f4600.png | Bin 0 -> 19499 bytes harness/mame/vt1200.cpp | 455 +++++++++++++++----- 8 files changed, 638 insertions(+), 105 deletions(-) create mode 100644 harness/mame/TABLET_REPORT.md create mode 100644 harness/mame/tab_inject.lua create mode 100644 harness/mame/tablet_cursor_br_f4850.png create mode 100644 harness/mame/tablet_cursor_centre_f5100.png create mode 100644 harness/mame/tablet_cursor_positions.png create mode 100644 harness/mame/tablet_cursor_tl_f4600.png diff --git a/harness/mame/README.md b/harness/mame/README.md index bfd72dd..477f114 100644 --- a/harness/mame/README.md +++ b/harness/mame/README.md @@ -22,6 +22,7 @@ Reports in this directory: | file | what it records | |---|---| | `GATE_CLEANUP_REPORT.md` | promoting the working config to the default; retiring the settled env gates | +| `TABLET_REPORT.md` | the VSXXX-AB tablet: the self-test device-code ID, the 5-byte absolute report, and absolute host-pointer input | | `RENDER_REPORT.md` | the active-pixel scanline renderer, and the geometry it derived | | `DUART_REPORT.md` | the DUARTs: wiring, the ISR-driven transmit, the datasheet-verified model edits | | `BRINGUP_LOG.md` | the whole bring-up narrative, **including withdrawn claims** — read it sceptically | @@ -68,11 +69,13 @@ to be a six-variable incantation; it is now the default, and a bare board write-back latch answers a read of DUART2's **write-only** IMR with its own written value, so the OS services a phantom interrupt that can never clear. * The **real input devices** on DUART2, driven by MAME's host input: a - **DEC LK401 keyboard** (channel A, 4800 8N1) and a **DEC VSXXX-AA mouse** - (channel B, 4800 8O1), both modelled from - `reference/keyboard-mouse/PROTOCOL.md` and wired to the SCN2681 at the - bit level (`rx_a_w`/`rx_b_w` and `a_tx_cb`/`b_tx_cb`). They answer `FD`/`T` - because that is the protocol, not to satisfy a test. See `INPUT_REPORT.md`. + **DEC LK401 keyboard** (channel A, 4800 8N1) and a pointing device on + channel B (4800 8O1) — either a **DEC VSXXX-AA mouse** (the default, relative) + or, with `VT1200_TABLET=1`, a **DEC VSXXX-AB tablet** (absolute). All are + modelled from `reference/keyboard-mouse/PROTOCOL.md` and wired to the SCN2681 + at the bit level (`rx_a_w`/`rx_b_w` and `a_tx_cb`/`b_tx_cb`). They answer + `FD`/`T` because that is the protocol, not to satisfy a test. See + `INPUT_REPORT.md` and `TABLET_REPORT.md`. * A mono palette (pens 0/1 = black/white) and an active-pixel scanline renderer that reads VRAM. See `RENDER_REPORT.md`. @@ -152,6 +155,13 @@ those commands, the OS reads `01 00 00 00` / `A0 02 00 00`, switches the mouse t stream mode, and the X11 cursor moves with the mouse while keycodes reach the OS. See `INPUT_REPORT.md` (the cursor evidence is the `input_cursor_*.png` frames). +With **`VT1200_TABLET=1`** the channel-B device is the **VSXXX-AB tablet** +instead: it identifies as device code `0100`, so the OS switches to 5-byte +absolute reports, and the emulated cursor follows the **host pointer's absolute +position** (driven through `IPT_LIGHTGUN_X/Y`, i.e. MAME needs +`-lightgun -lightgun_device mouse`). See `TABLET_REPORT.md` (evidence: +`tablet_cursor_*.png`). The mouse stays the default. + Not modelled: **LANCE Ethernet**. It does not block the X11 bring-up. ### Getting past level 1 @@ -165,7 +175,7 @@ behaviour**, and is **not** evidence about the real terminal's POST. The skip is deliberately **not** the default: the real machine runs level 1, so the faithful configuration stays the default and the switch is how we diverge. -## Environment knobs (18; down from 33) +## Environment knobs (19; down from 33) All opt-in, and all off by default — there is no longer a "working incantation" to remember, because everything the firmware needs to run is now the default. @@ -174,9 +184,12 @@ Instrumentation: `VT1200_DUARTLOG`, `VT1200_BOARD_LOG`, `VT1200_BOARD_MAP`, `VT1200_IRQLOG`, `VT1200_LOG_UNMAPPED`, `VT1200_DPYLOG` (+`_FRAMES`), `VT1200_DUMP_FRAME` (+`_FRAMES`), `VT1200_DUMP_RAM` (+`_FRAMES`), `VT1200_DUMP_VRAM` (+`_FRAMES`), `VT1200_DUMP_DIR`, `VT1200_NO_RENDER`, -`VT1200_INPUT_LOG` (per-byte keyboard/mouse TX/RX trace, with emulated time). -Behaviour switches: `VT1200_SKIP_LEVEL1`, `VT1200_IO_PATH_STUB` (the level-0 -test-fixture stand-in described above — not a keyboard model, off by default). +`VT1200_INPUT_LOG` (per-byte keyboard/mouse/tablet TX/RX trace, with emulated +time). Behaviour switches: `VT1200_SKIP_LEVEL1`, `VT1200_TABLET` (put the +VSXXX-AB tablet on DUART2-B instead of the VSXXX-AA mouse — the tablet needs +MAME's `-lightgun -lightgun_device mouse` to follow the host pointer), +`VT1200_IO_PATH_STUB` (the level-0 test-fixture stand-in described above — not a +keyboard model, off by default). ## Regression baseline diff --git a/harness/mame/TABLET_REPORT.md b/harness/mame/TABLET_REPORT.md new file mode 100644 index 0000000..fd5c2ff --- /dev/null +++ b/harness/mame/TABLET_REPORT.md @@ -0,0 +1,219 @@ +# VSXXX-AB tablet on DUART2 — report + +Adds the VT1200's **DEC VSXXX-AB digitizing tablet** as a third real device +model, selects it in place of the **VSXXX-AA mouse** on DUART2 channel B, and +drives it from the **host pointer's absolute position**. The emulated X11 cursor +now follows the host pointer positionally: moving the host pointer to a corner +puts the cursor in that corner. + +Status: **works**. The OS accepts the tablet (it frames 5-byte absolute reports +instead of 3-byte relative ones), the cursor tracks the host pointer across the +whole screen, the mouse is still the default and still works, and every existing +regression baseline is unchanged. + +--- + +## 1. The selection mechanism — how the terminal tells a tablet from a mouse + +**It is an ID query, and the answer is one nibble of the self-test report.** +There is no separate "are you a tablet?" command and no setup/NVR record. + +The host sends `T` (0x54, "self-test & identify"). The device returns the same +**4-byte self-test report** either way; byte 2's low nibble is the **device +code**, `0010` for the mouse and `0100` for the tablet (VCB02 §C.6.5 figure C-5, +§C.7.4). Only *then* does the report format differ: + +``` +100 - 3-byte relative position report (mouse) +101 - 4-byte self-test report (mouse or tablet) +110 - 5-byte absolute position report (tablet) +``` + +(VCB02 §C.6.6; Appendix D.3 / figure D-3 for the 5-byte layout.) + +### Evidence that the terminal uses it + +Captured from the running OS (`VT1200_SKIP_LEVEL1=1`, `VT1200_DUARTLOG`), which +is the terminal's own driver, not a test: + +| step | mouse (default) | tablet (`VT1200_TABLET=1`) | +|---|---|---| +| host writes `T` to THRB (`reg B`) | `54` | `54` | +| host reads the 4-byte self-test | `A0 02 00 00` | `A0 04 00 00` | +| host writes `R` (stream mode) | `52` | `52` | +| **host reads RHRB per report** | **3 bytes** | **5 bytes** | +| example report | `98 20 00` (header `100`) | `C1 0C 11 0B 11` (header `110`) | + +The reads are grouped by the waiting time between them (≈14 323 DUART cycles +inside a report, ≈75 519 between reports for the mouse): the OS reads exactly +3 bytes for a `100` header and exactly 5 for a `110` header. So the device code +from `T` is what switches the host's report framing — get it wrong and every +report is mis-framed, exactly as the brief warned. + +**`PROTOCOL.md` is correct about all of this** — §4.5 states the tablet +self-test "differs only in device code (`0100`) and a 5-byte absolute-position +report (header `110`/`0xC0`)". No correction is needed. One fact worth adding +(sourced, not conflicting): the tablet's **coordinate range is `0..2199`** — +the 11-inch active area at 200 counts/inch (VCB02 Appendix D.1) — which is what +the terminal's pointer scaling expects (see §3). + +--- + +## 2. What was implemented + +`harness/mame/vt1200.cpp` only. + +* The VSXXX device was refactored into a shared base + (`vt1200_vsxxx_base`: 4800 8**O**1 framing, the printable-ASCII command set + `R`/`D`/`P`/`T`/`S`, prompt power-up, idle-high TxD) with two subclasses: + * `vt1200_vsxxxaa_device` — the mouse, unchanged in behaviour (relative + 3-byte reports, device code `0010`). + * `vt1200_vsxxxab_device` — the tablet, absolute (5-byte reports, device code + `0100`). +* Channel B carries **one** of them, chosen by **`VT1200_TABLET=1`** at machine + configuration time (default: the mouse). Only the selected device is + instantiated, so the connector carries one real part; the mouse is selectable, + not replaced. +* The tablet's `send_self_test()` reports device code `0100` + (`A0 04 00 00`); its `send_abs()` emits the Appendix-D figure D-3 format: + + ``` + [0] 1 1 0 B4 B3 B2 B1 Pr + [1] 0 0 X5..X0 [2] 0 0 X11..X6 + [3] 0 0 Y5..Y0 [4] 0 0 Y11..Y6 + ``` + Y is reported DEC-style (up); the host pointer's Y is screen-style (down), so + it is inverted. `P` (request position) returns the current absolute report; + in stream mode a report is sent when the position or a button changes, and + one is forced on entering stream mode so the host gets an initial position. +* Host input for the tablet is **`IPT_LIGHTGUN_X` / `IPT_LIGHTGUN_Y`** with + `PORT_MINMAX(0, 2199)` — MAME's **absolute** pointing controls, so the value is + the host pointer's window position scaled into `0..2199`, not an accumulated + delta. Buttons B1/B2/B3 map to mouse left/middle/right; `Pr` (proximity) is + asserted, since the emulated pointer is always over the tablet. +* Both devices still hold TxD at mark when idle (the DUART receiver-sync trap + from `INPUT_REPORT.md`), inherited unchanged from the base `scan()`. + +Nothing is tuned to satisfy level 0's I/O data-path test: the tablet's `T` +reply is 4 bytes, and the test is untouched (still fails honestly without +`VT1200_IO_PATH_STUB`). + +### Why `IPT_LIGHTGUN_X/Y` and not something else + +MAME's only *absolute* pointing control a host mouse can drive is +`IPT_LIGHTGUN_X/Y` (the SDL lightgun provider reads the mouse's window position +and maps it to the absolute axis: `input_sdl.cpp`, `sdl_lightgun_device`). +`IPT_MOUSE_X/Y` are relative (they would reproduce exactly the problem the owner +is trying to get away from). `IPT_POSITIONAL` is absolute too, but the SDL +mouse/lightgun providers do **not** supply it — only joystick axes do — so it +would need a joystick, not the host pointer. Lightgun it is, and because it is +absolute the pointer is **not captured** (no relative-mode grab), which is the +owner's stated motivation. + +--- + +## 3. Does the cursor follow the host pointer? — YES, absolutely + +Run: `VT1200_TABLET=1 VT1200_SKIP_LEVEL1=1`, tablet driven by +`harness/mame/tab_inject.lua`, frames dumped at each settled position. + +| tablet coordinate (host) | device report | cursor on screen | +|---|---|---| +| `(0, 0)` top-left corner | `C1 00 00 17 22` | **(194, 38)** | +| `(2199, 2199)` bottom-right | `C1 17 22 00 00` | **(1214, 898)** | +| `(1100, 1100)` centre | `C1 0C 11 0B 11` | **(704, 467)** | + +Each row is a different host position producing a different, correct cursor +position; the cursor moves across the whole screen (top-left corner, centre, +bottom-right corner). Evidence: `tablet_cursor_tl_f4600.png`, +`tablet_cursor_centre_f5100.png`, `tablet_cursor_br_f4850.png` (full frames, +downscaled) and `tablet_cursor_positions.png` (3× zoom at the cursor). + +The mapping is **linear and absolute**, verified by repeating a coordinate in +one run: on an x-sweep with `y` fixed, `x=1024` produced a pixel-identical +cursor both times it was visited (`x1024a == x1024b`), so the cursor is a +function of the current absolute coordinate, not of the path taken. + +The measured map is `screen_x ≈ 0.464·x` and `screen_y ≈ 898 − 0.392·(2199−y)` +in MAME-frame pixels. The apparent offsets are **not** terminal quirks: this +driver's renderer draws framebuffer column 0 at the device's horizontal-blanking +start (`heblnk = 192`) and framebuffer row 0 at `veblnk = 36` +(`render_active_line()`), and the tablet's `0..2199` maps onto the framebuffer's +`0..1024` / `0..864` visible rows. So the tablet's active area lands on the +terminal's *whole* screen — that is why the corners reach `(194,38)` and +`(1214,898)` rather than the frame's `(0,0)`/`(1279,900)`. + +--- + +## 4. What it needs on the command line + +To drive `IPT_LIGHTGUN_X/Y` from the host mouse, MAME needs + +``` +-lightgun -lightgun_device mouse +``` + +(`lightgun_device` defaults to `keyboard`; either option alone is enough to +auto-enable the host mouse for the lightgun control — MAME logs +*"Autoenabling mouse due to presence of a lightgun"* — but both is the +documented, explicit form). **`run.sh` is the owner's to edit**; the tablet mode +is therefore + +``` +VT1200_TABLET=1 VT1200_SKIP_LEVEL1=1 vt1200 ... \ + -lightgun -lightgun_device mouse ... -autoboot_script harness/mame/tab_inject.lua +``` + +There is **no** extra option needed for the mouse (the default) and none to +*select* the tablet beyond the `VT1200_TABLET=1` environment knob, which is +consistent with the other `VT1200_*` behaviour switches. Note `-video none` +(headless) has no window, so the host-mouse path is only meaningful in a real +window; the headless acceptance run drives the same ports from Lua. + +--- + +## 5. What still works + +### The mouse is the default and still works + +No `VT1200_TABLET` ⇒ `:mouse` is the VSXXX-AA: + +* self-test `A0 02 00 00` (device code `0010`), then `R`, then 3-byte relative + reports `98 20 00`. +* the X11 cursor still moves: it was at `(768,468)` in frame 4400 and had left + that location by frame 4800 (same result as `INPUT_REPORT.md`). + +### The keyboard still works + +`input_inject.lua` presses `A`; the OS reads keycode `0xC2` from DUART2 channel A +at `t=62.2856 s` (`2 R reg=3 val=00C2 pc=3FB42710`), as before. + +### Regression baselines — unchanged + +| configuration | expected | observed | changed? | +|---|---|---|---| +| no env | level 0 fails honestly, menu | `PC=3FDCE400 samples=198` (3 PCs) | no | +| `VT1200_IO_PATH_STUB=1` | reaches level 1 | `PC=3FDCE400 samples=53` (75 PCs) | no | +| `VT1200_SKIP_LEVEL1=1` | X11 weave + Terminal Manager | frame 3000+ = Terminal Manager, "All Diagnostics Passed" | no | + +ROM `md5 b46a3e442ccb86134d2356bd70d22fd2` — unchanged, and the driver's +`ROM_LOAD` CRC/SHA1 are untouched. Nothing was tuned to satisfy level 0's I/O +data-path test; that puzzle (`reference/level0-10byte-puzzle.md`) stands. + +--- + +## 6. Caveats + +* **HLE, not firmware.** Like the keyboard and mouse, this models what the + VSXXX-AB *does on the wire*, from `PROTOCOL.md` and VCB02 Appendix D; it does + not execute tablet firmware. +* **Tablet extras not modelled.** VCB02 Appendix D.4 adds report-rate commands + `K`/`L`/`M` (55/72/120 reports/s) and a `B` baud-switch (9600). They are + accepted and ignored (as the mouse ignores unknown commands); the VT1200 OS + uses none of them. Only 4800 baud is modelled, which is the default and what + the OS selects. +* **Coordinate range.** The model reports in the documented `0..2199` active + area. The 12-bit report field could carry more, but the part does not report + outside its active area, and the terminal's scaling expects this range. +* The exact pointer placement is the terminal's own (`heblnk`/`veblnk` frame + offset aside); the emulator only delivers an honest absolute coordinate. diff --git a/harness/mame/tab_inject.lua b/harness/mame/tab_inject.lua new file mode 100644 index 0000000..7e7068c --- /dev/null +++ b/harness/mame/tab_inject.lua @@ -0,0 +1,38 @@ +-- Acceptance test: drive the modelled VSXXX-AB tablet with the host absolute +-- pointer and show the emulated cursor following it -- a corner, the opposite +-- corner, and the centre. +-- +-- VT1200_TABLET=1 VT1200_SKIP_LEVEL1=1 \ +-- VT1200_DUMP_FRAMES=4600,4850,5100 VT1200_DUMP_DIR= \ +-- vt1200 ... -autoboot_script harness/mame/tab_inject.lua +-- +-- Coordinates are the tablet's own: 0..2199 (11 in at 200 counts/in, VCB02 D.1), +-- Y screen-style downward (the device inverts it for the DEC report). A report +-- is emitted whenever the position changes, so the cursor jumps between points +-- and holds. + +local machine = manager.machine +local ports = machine.ioport.ports +local tx = ports[":tablet:TABLET_X"].fields["Tablet X"] +local ty = ports[":tablet:TABLET_Y"].fields["Tablet Y"] + +local PTS = { + { 4400, 0, 0, "corner top-left" }, + { 4650, 2199, 2199, "corner bot-right" }, + { 4900, 1100, 1100, "centre" }, +} + +local frames = 0 +local next = 1 +emu.register_frame_done(function() + frames = frames + 1 + if next <= #PTS and frames == PTS[next][1] then + tx:set_value(PTS[next][2]) + ty:set_value(PTS[next][3]) + print(string.format("tab: frame %d %-18s x=%d y=%d", frames, PTS[next][4], PTS[next][2], PTS[next][3])) + next = next + 1 + end + if frames >= 5150 then + machine:exit() + end +end, "vt1200-tab-inject") diff --git a/harness/mame/tablet_cursor_br_f4850.png b/harness/mame/tablet_cursor_br_f4850.png new file mode 100644 index 0000000000000000000000000000000000000000..67ef40ec93241ca201986cf069eaf80cb2354098 GIT binary patch literal 19487 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®rOWN7wcrD_rt@oxx<>Vi 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 z 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 literal 0 HcmV?d00001 diff --git a/harness/mame/tablet_cursor_centre_f5100.png b/harness/mame/tablet_cursor_centre_f5100.png new file mode 100644 index 0000000000000000000000000000000000000000..c7556483699e9305395bad9f8185f1a0a46594d3 GIT binary patch literal 19592 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 k7Yc&ktE)Wa#a)k>ntme z+Q`hT+={+z%tq>$|f}qSS2$}@0%s~j^5g|w) z13{z$2wKW6z7^&NKFt31D=Gzou1Xy|z#E1eyfqM`$J$t1IJz%Mng*`Vxq;icu}p3p ziIp4&4CF!d@Nsn~y1M&BxO54Q{`KxFyFHW(^zK;7! zl)B^FBDVCeEE-ufs!B2*+nirIv38>+-rrc@x>P$CS(!R?c*$E!)6wDhizZzsX~*lw z>R4&|s~6<#Qn#zuOBq!SBS*>E3eJWYkEAZd7Em=Ob7+3iF(@K@yGoHVmI!f{H7*JL+jIVCiOfJT$!&X%MVbY~0%~fo# ziTa5Zxr3?%#d%(vhh-0kF5gG5Gwb9e+-r{H@|~-=LMM0ZojR6j2ph#X@Is4H& za-2cN|A#%EQI51$!6yL)XbC?|##(a)fH7Ib4_NTm!(;6NjJQy?J2Cd;1+pja>^dwNv+t!qIOb!mw`s~ z?eapyL_6cKW zLbgM4kX7(*VWOYq;Ha-K3a?zph<)Ioj zVN`CMHuzH2L$!wG05aM-{7wJ$$J$F%8p?WUVEwt5O@2{OJ0a*wLRi!xmlJ+qqd z5iPzw&mWiUzhR~sj>V^av|&1&3zL=I2 zoh3G@HTIEd72Djx5EEePo?67uNp>^~+1%S|^HM0xu)Uuta$?DXq`9g5=~=-(&Cc|% S55F@1>4F18DA&G>Ir=yH*vfJM literal 0 HcmV?d00001 diff --git a/harness/mame/tablet_cursor_tl_f4600.png b/harness/mame/tablet_cursor_tl_f4600.png new file mode 100644 index 0000000000000000000000000000000000000000..9dcfdbed96e53390ccb7c3a99c274a95c5c44a50 GIT binary patch literal 19499 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

s1N zTo>{-5Ay^E)k5yvx~Bg}j_=QjjGW>*AI>lDOo4Ck^;Ul$!uSK94ky-!6A(X(hVhaz zKM;&tyK4LJ!wNcW-1>7^WGBJv#ZLBZ_IP%#AjIolz8me+V2Ha>7%Ur_2fS7eE-BDi z8N3EJF_GQQyxIQMp6@?gNk97crD?a)Zs|EED)%q3GByxcp3lJrx+x7F~8#A{xXVfoDg6?1{<512HX_^ zI*Ok)Nu8=^18?Bsx_#HDTH5IPctl>PiM{)3jPYEr%0DgFOo-@x(mA}inptE{2Zhp< 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 left, B2->middle, B3->right; B4 is the puck's fourth button. + // Pr ("in proximity") is held asserted: the emulated pointer is always + // present over the tablet, so the host always has a valid position. + PORT_START("TABLET_BTN") + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_BUTTON1) PORT_CODE(MOUSECODE_BUTTON1) PORT_PLAYER(1) // B1 left + PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_BUTTON2) PORT_CODE(MOUSECODE_BUTTON2) PORT_PLAYER(1) // B2 middle + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_BUTTON3) PORT_CODE(MOUSECODE_BUTTON3) PORT_PLAYER(1) // B3 right + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_BUTTON4) PORT_CODE(MOUSECODE_BUTTON4) PORT_PLAYER(1) // B4 +INPUT_PORTS_END + +// --- VSXXX family: VSXXX-AA/-GA mouse + VSXXX-AB tablet --------------------- // 4800 8O1 (VCB02 section C.6.1). Commands are printable ASCII; the self-test // (`T`, and at power-up) returns a 4-byte report. Movement is reported as // 3-byte incremental packets in stream mode (`R`), or on request (`P`). +// +// The mouse and the tablet share that whole serial interface and command set +// (VCB02 C.7.4: "the tablet uses the same connector and serial interface +// format as the mouse, and recognizes the same commands"). Exactly two things +// differ, and they are the subclass hooks below: +// +// * the self-test ID: byte 2's device code is 0010 for the mouse and 0100 +// for the tablet (VCB02 C.6.5 figure C-5 + C.7.4). This is how the host +// tells the two apart -- there is no separate "is it a tablet?" command. +// * the position report: 3-byte relative (header 100) for the mouse vs +// 5-byte absolute (header 110) for the tablet (VCB02 C.6.6 + D.3). enum : uint8_t { - MS_CMD_STREAM = 'R', - MS_CMD_PROMPT = 'D', - MS_CMD_POSITION = 'P', - MS_CMD_SELFTEST = 'T', - MS_CMD_FORMAT = 'S' + VS_CMD_STREAM = 'R', + VS_CMD_PROMPT = 'D', + VS_CMD_POSITION = 'P', + VS_CMD_SELFTEST = 'T', + VS_CMD_FORMAT = 'S' }; -class vt1200_vsxxxaa_device : public device_t, public device_buffered_serial_interface<16U> +class vt1200_vsxxx_base : public device_t, public device_buffered_serial_interface<16U> { public: - vt1200_vsxxxaa_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0); + vt1200_vsxxx_base(const machine_config &mconfig, device_type type, const char *tag, + device_t *owner, uint32_t clock, const char *x_tag, const char *y_tag, + const char *btn_tag); auto tx_handler() { return m_tx_handler.bind(); } protected: virtual void device_start() override ATTR_COLD; virtual void device_reset() override ATTR_COLD; - virtual ioport_constructor device_input_ports() const override ATTR_COLD; virtual void tra_callback() override; virtual void received_byte(u8 byte) override; @@ -433,9 +470,14 @@ protected: TIMER_CALLBACK_MEMBER(scan); TIMER_CALLBACK_MEMBER(power_up_selftest); -private: - void send_self_test(); - void send_report(int dx, int dy, int buttons); + // What the two parts do differently. + virtual void send_self_test() = 0; // device-code byte differs + virtual void sample_host() = 0; // read the host input for this tick + virtual bool stream_report_due() = 0; // a report is waiting to be sent + virtual void emit_stream_report() = 0; // ...send it (REL vs ABS) + virtual void emit_position_report() = 0; // answer `P` + virtual void on_stream_mode() { } // entered stream mode (`R`) + void queue(u8 byte); devcb_write_line m_tx_handler; @@ -446,18 +488,67 @@ private: emu_timer *m_power_up_timer = nullptr; bool m_stream = false; // false = prompt mode (power-up default) + FILE *m_log = nullptr; +}; + +// VSXXX-AA/-GA mouse: relative. +class vt1200_vsxxxaa_device : public vt1200_vsxxx_base +{ +public: + vt1200_vsxxxaa_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0); + +protected: + virtual void device_reset() override ATTR_COLD; + virtual ioport_constructor device_input_ports() const override ATTR_COLD; + + virtual void send_self_test() override; + virtual void sample_host() override; + virtual bool stream_report_due() override; + virtual void emit_stream_report() override; + virtual void emit_position_report() override; + +private: + void send_report(int dx, int dy, int buttons); + u8 m_last_x = 0; u8 m_last_y = 0; int m_acc_dx = 0; int m_acc_dy = 0; int m_last_buttons = 0; - bool m_buttons_dirty = false; + int m_buttons = 0; +}; - FILE *m_log = nullptr; +// VSXXX-AB tablet: absolute. +class vt1200_vsxxxab_device : public vt1200_vsxxx_base +{ +public: + vt1200_vsxxxab_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0); + +protected: + virtual void device_reset() override ATTR_COLD; + virtual ioport_constructor device_input_ports() const override ATTR_COLD; + + virtual void send_self_test() override; + virtual void sample_host() override; + virtual bool stream_report_due() override; + virtual void emit_stream_report() override; + virtual void emit_position_report() override; + virtual void on_stream_mode() override; + +private: + void send_abs(int x, int y, int buttons); + + int m_x = 0; // 12-bit absolute tablet coordinates + int m_y = 0; // (host pointer scaled into 0..0xfff) + int m_buttons = 0; + int m_last_x = -1; // -1 forces a first report on entering stream + int m_last_y = -1; + int m_last_buttons = 0; }; DECLARE_DEVICE_TYPE(VT1200_LK401, vt1200_lk401_device) DECLARE_DEVICE_TYPE(VT1200_VSXXXAA, vt1200_vsxxxaa_device) +DECLARE_DEVICE_TYPE(VT1200_VSXXXAB, vt1200_vsxxxab_device) namespace { @@ -496,6 +587,7 @@ public: , m_duart2(*this, "duart2") , m_lk401(*this, "lk401") , m_mouse(*this, "mouse") + , m_tablet(*this, "tablet") , m_vram(*this, "vram") { } @@ -560,10 +652,16 @@ private: required_device m_duart2; // 0x0620000: keyboard(A)/mouse(B) bool m_duart1_int2 = false; // set by VT1200_SKIP_LEVEL1 - // The real devices on DUART2. Keyboard on channel A, mouse on channel B, - // as the board has them. Wired bit-by-bit to the SCN2681's serial lines. + // The real devices on DUART2. Keyboard on channel A, pointing device on + // channel B, as the board has them. Wired bit-by-bit to the SCN2681's + // serial lines. Channel B carries EITHER the VSXXX-AA mouse (the default) + // OR the VSXXX-AB tablet, selected at machine-configuration time by + // VT1200_TABLET=1; exactly one is instantiated, so the connector carries + // one real part. required_device m_lk401; - required_device m_mouse; + optional_device m_mouse; + optional_device m_tablet; + bool m_tablet_selected = false; void duart2_a_tx_w(int state); void duart2_b_tx_w(int state); @@ -1393,17 +1491,29 @@ void vt1200_state::vt1200(machine_config &config) m_duart2->irq_cb().set(FUNC(vt1200_state::duart2_irq_w)); // The real devices on DUART2: channel A = keyboard (4800 8N1), channel B = - // mouse (4800 8O1), exactly as the board wires them. Both talk to the - // SCN2681 through the genuine device_serial_interface bit lines: the - // device's transmit output drives the DUART's receiver input - // (rx_a_w / rx_b_w), and the DUART's transmitter output - // (a_tx_cb / b_tx_cb) drives the device's receiver input (rx_w). No - // byte-level shortcut, so the framing and the 4800-baud timing are the - // DUART's own. + // the pointing device (4800 8O1), exactly as the board wires them. Both + // talk to the SCN2681 through the genuine device_serial_interface bit lines: + // the device's transmit output drives the DUART's receiver input + // (rx_a_w / rx_b_w), and the DUART's transmitter output (a_tx_cb / b_tx_cb) + // drives the device's receiver input (rx_w). No byte-level shortcut, so the + // framing and the 4800-baud timing are the DUART's own. + // + // VT1200_TABLET=1 puts the VSXXX-AB tablet on channel B instead of the + // VSXXX-AA mouse. The two are the same connector; the terminal tells them + // apart from the self-test ID, so only one is instantiated. + m_tablet_selected = (getenv("VT1200_TABLET") != nullptr); VT1200_LK401(config, m_lk401, 0); - VT1200_VSXXXAA(config, m_mouse, 0); + if (m_tablet_selected) + { + VT1200_VSXXXAB(config, m_tablet, 0); + m_tablet->tx_handler().set(m_duart2, FUNC(scn2681_device::rx_b_w)); + } + else + { + VT1200_VSXXXAA(config, m_mouse, 0); + m_mouse->tx_handler().set(m_duart2, FUNC(scn2681_device::rx_b_w)); + } m_lk401->tx_handler().set(m_duart2, FUNC(scn2681_device::rx_a_w)); - m_mouse->tx_handler().set(m_duart2, FUNC(scn2681_device::rx_b_w)); m_duart2->a_tx_cb().set(FUNC(vt1200_state::duart2_a_tx_w)); m_duart2->b_tx_cb().set(FUNC(vt1200_state::duart2_b_tx_w)); } @@ -1412,7 +1522,13 @@ void vt1200_state::vt1200(machine_config &config) // device's receiver. (A state-level forward keeps the devcb binding simple; // rx_w() is public on device_serial_interface.) void vt1200_state::duart2_a_tx_w(int state) { m_lk401->rx_w(state); } -void vt1200_state::duart2_b_tx_w(int state) { m_mouse->rx_w(state); } +void vt1200_state::duart2_b_tx_w(int state) +{ + if (m_tablet_selected) + m_tablet->rx_w(state); + else + m_mouse->rx_w(state); +} INPUT_PORTS_START(vt1200) INPUT_PORTS_END @@ -1706,38 +1822,32 @@ TIMER_CALLBACK_MEMBER(vt1200_lk401_device::scan) //************************************************************************** -// vt1200_vsxxxaa_device -- DEC VSXXX-AA mouse +// vt1200_vsxxx_base -- shared DEC VSXXX serial pointer interface //************************************************************************** -DEFINE_DEVICE_TYPE(VT1200_VSXXXAA, vt1200_vsxxxaa_device, "vt1200vsxxxaa", "DEC VSXXX-AA Mouse") - -vt1200_vsxxxaa_device::vt1200_vsxxxaa_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : device_t(mconfig, VT1200_VSXXXAA, tag, owner, clock) +vt1200_vsxxx_base::vt1200_vsxxx_base(const machine_config &mconfig, device_type type, const char *tag, + device_t *owner, uint32_t clock, const char *x_tag, const char *y_tag, const char *btn_tag) + : device_t(mconfig, type, tag, owner, clock) , device_buffered_serial_interface(mconfig, *this) , m_tx_handler(*this) - , m_host_x(*this, "MOUSE_X") - , m_host_y(*this, "MOUSE_Y") - , m_host_buttons(*this, "MOUSE_BTN") + , m_host_x(*this, x_tag) + , m_host_y(*this, y_tag) + , m_host_buttons(*this, btn_tag) { } -ioport_constructor vt1200_vsxxxaa_device::device_input_ports() const +void vt1200_vsxxx_base::device_start() { - return INPUT_PORTS_NAME(vt1200_vsxxxaa); -} - -void vt1200_vsxxxaa_device::device_start() -{ - m_scan_timer = timer_alloc(FUNC(vt1200_vsxxxaa_device::scan), this); - m_power_up_timer = timer_alloc(FUNC(vt1200_vsxxxaa_device::power_up_selftest), this); + m_scan_timer = timer_alloc(FUNC(vt1200_vsxxx_base::scan), this); + m_power_up_timer = timer_alloc(FUNC(vt1200_vsxxx_base::power_up_selftest), this); if (const char *p = getenv("VT1200_INPUT_LOG")) m_log = fopen(p, "a"); if (m_log) - fprintf(m_log, "# VSXXX-AA device started\n"); + fprintf(m_log, "# %s device started\n", shortname()); } -void vt1200_vsxxxaa_device::device_reset() +void vt1200_vsxxx_base::device_reset() { // 4800 baud, 8 data bits, ODD parity, 1 stop bit (PROTOCOL.md section 4.1). set_data_frame(1, 8, PARITY_ODD, STOP_BITS_1); @@ -1746,12 +1856,6 @@ void vt1200_vsxxxaa_device::device_reset() clear_fifo(); receive_register_reset(); m_stream = false; // prompt mode is the power-up default - m_last_x = u8(m_host_x->read()); - m_last_y = u8(m_host_y->read()); - m_acc_dx = 0; - m_acc_dy = 0; - m_last_buttons = 0; - m_buttons_dirty = false; if (m_power_up_timer) m_power_up_timer->adjust(attotime::from_msec(250)); @@ -1759,39 +1863,39 @@ void vt1200_vsxxxaa_device::device_reset() m_scan_timer->adjust(attotime::from_hz(120), 0, attotime::from_hz(120)); } -void vt1200_vsxxxaa_device::queue(u8 byte) +void vt1200_vsxxx_base::queue(u8 byte) { if (m_log) fprintf(m_log, "VSXXX tx %02X t=%.6f\n", byte, machine().time().as_double()); transmit_byte(byte); } -void vt1200_vsxxxaa_device::tra_callback() +void vt1200_vsxxx_base::tra_callback() { m_tx_handler(transmit_register_get_data_bit()); } -void vt1200_vsxxxaa_device::received_byte(u8 byte) +void vt1200_vsxxx_base::received_byte(u8 byte) { if (m_log) fprintf(m_log, "VSXXX rx %02X (%c) t=%.6f\n", byte, (byte >= 0x20 && byte < 0x7f) ? byte : '.', machine().time().as_double()); switch (byte) { - case MS_CMD_SELFTEST: + case VS_CMD_SELFTEST: send_self_test(); break; - case MS_CMD_STREAM: + case VS_CMD_STREAM: m_stream = true; + on_stream_mode(); break; - case MS_CMD_PROMPT: + case VS_CMD_PROMPT: m_stream = false; break; - case MS_CMD_POSITION: + case VS_CMD_POSITION: m_stream = false; - send_report(m_acc_dx, m_acc_dy, 0); - m_acc_dx = m_acc_dy = 0; + emit_position_report(); break; - case MS_CMD_FORMAT: + case VS_CMD_FORMAT: // Standard format: already the only one modelled. break; default: @@ -1799,11 +1903,97 @@ void vt1200_vsxxxaa_device::received_byte(u8 byte) } } +TIMER_CALLBACK_MEMBER(vt1200_vsxxx_base::power_up_selftest) +{ + send_self_test(); +} + +TIMER_CALLBACK_MEMBER(vt1200_vsxxx_base::scan) +{ + // Hold TxD at idle-high when not transmitting (see the LK401 scan note). + if (fifo_empty() && is_transmit_register_empty()) + m_tx_handler(1); + + sample_host(); + if (m_stream && stream_report_due()) + emit_stream_report(); +} + + +//************************************************************************** +// vt1200_vsxxxaa_device -- DEC VSXXX-AA mouse (relative) +//************************************************************************** + +DEFINE_DEVICE_TYPE(VT1200_VSXXXAA, vt1200_vsxxxaa_device, "vt1200vsxxxaa", "DEC VSXXX-AA Mouse") + +vt1200_vsxxxaa_device::vt1200_vsxxxaa_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : vt1200_vsxxx_base(mconfig, VT1200_VSXXXAA, tag, owner, clock, "MOUSE_X", "MOUSE_Y", "MOUSE_BTN") +{ +} + +ioport_constructor vt1200_vsxxxaa_device::device_input_ports() const +{ + return INPUT_PORTS_NAME(vt1200_vsxxxaa); +} + +void vt1200_vsxxxaa_device::device_reset() +{ + vt1200_vsxxx_base::device_reset(); + m_last_x = u8(m_host_x->read()); + m_last_y = u8(m_host_y->read()); + m_acc_dx = 0; + m_acc_dy = 0; + m_last_buttons = 0; + m_buttons = 0; +} + +void vt1200_vsxxxaa_device::sample_host() +{ + const u8 now_x = u8(m_host_x->read()); + const u8 now_y = u8(m_host_y->read()); + + // The MAME mouse inputs are relative; MAME accumulates them into the port + // value, so a signed 8-bit difference (with wrap) is the movement since the + // last scan. Screen Y grows downward; the VSXXX reports DEC Y (up). + int dx = int(now_x) - int(m_last_x); + int dy = int(now_y) - int(m_last_y); + if (dx > 0x80) dx -= 0x100; else if (dx < -0x80) dx += 0x100; + if (dy > 0x80) dy -= 0x100; else if (dy < -0x80) dy += 0x100; + m_last_x = now_x; + m_last_y = now_y; + + if (dx || dy) + { + m_acc_dx += dx; + m_acc_dy -= dy; // DEC Y is inverted relative to screen Y + } + m_buttons = m_host_buttons->read() & 0x07; +} + +bool vt1200_vsxxxaa_device::stream_report_due() +{ + return m_acc_dx || m_acc_dy || m_buttons != m_last_buttons; +} + +void vt1200_vsxxxaa_device::emit_stream_report() +{ + send_report(m_acc_dx, m_acc_dy, m_buttons); + m_acc_dx = m_acc_dy = 0; + m_last_buttons = m_buttons; +} + +void vt1200_vsxxxaa_device::emit_position_report() +{ + send_report(m_acc_dx, m_acc_dy, m_buttons); + m_acc_dx = m_acc_dy = 0; + m_last_buttons = m_buttons; +} + void vt1200_vsxxxaa_device::send_self_test() { - // Four-byte power-on/self-test report (PROTOCOL.md section 4.4, figure C-5): - // header 101 + revision, manufacturer 0 + device code 0010 (mouse), error - // 00, no buttons. + // Four-byte power-on/self-test report (PROTOCOL.md section 4.4, VCB02 figure + // C-5): header 101 + revision, manufacturer 0 + device code 0010 (mouse), + // error 00, no buttons. queue(0xa0); // 1010 0000: header 101, revision 0 queue(0x02); // manufacturer 0, device code 0010 = mouse queue(0x00); // error code 0 = OK @@ -1825,47 +2015,120 @@ void vt1200_vsxxxaa_device::send_report(int dx, int dy, int buttons) queue(u8(ay)); } -TIMER_CALLBACK_MEMBER(vt1200_vsxxxaa_device::power_up_selftest) + +//************************************************************************** +// vt1200_vsxxxab_device -- DEC VSXXX-AB tablet (absolute) +//************************************************************************** +// +// Same connector, same 4800 8O1 serial format and the same command set as the +// mouse (VCB02 C.7.4). The two differences are the self-test device code +// (0100) and the 5-byte absolute position report (header 110). Position comes +// from the host pointer via IPT_LIGHTGUN_X/Y, so the emulated cursor is +// positionally mapped rather than integrated from deltas. + +DEFINE_DEVICE_TYPE(VT1200_VSXXXAB, vt1200_vsxxxab_device, "vt1200vsxxxab", "DEC VSXXX-AB Tablet") + +// The tablet's coordinate space is its active area: 11 in square at 200 +// counts/in = 2200 counts, so 0..2199 (VCB02 Appendix D.1). The 12-bit report +// field (figure D-3) can carry a larger value, but the part only reports inside +// its active area. The host absolute pointer is scaled into this range by the +// ioport layer; see INPUT_PORTS_NAME(vt1200_vsxxxab). +static const int VSXXX_ABS_MAX = 2199; + +vt1200_vsxxxab_device::vt1200_vsxxxab_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : vt1200_vsxxx_base(mconfig, VT1200_VSXXXAB, tag, owner, clock, "TABLET_X", "TABLET_Y", "TABLET_BTN") { - send_self_test(); } -TIMER_CALLBACK_MEMBER(vt1200_vsxxxaa_device::scan) +ioport_constructor vt1200_vsxxxab_device::device_input_ports() const { - // Hold TxD at idle-high when not transmitting (see the LK401 scan note). - if (fifo_empty() && is_transmit_register_empty()) - m_tx_handler(1); + return INPUT_PORTS_NAME(vt1200_vsxxxab); +} - const u8 now_x = u8(m_host_x->read()); - const u8 now_y = u8(m_host_y->read()); +void vt1200_vsxxxab_device::device_reset() +{ + vt1200_vsxxx_base::device_reset(); + m_x = 0; + m_y = 0; + m_buttons = 0; + m_last_x = -1; // force a first report when stream mode starts + m_last_y = -1; + m_last_buttons = 0; +} - // The MAME mouse inputs are relative; MAME accumulates them into the port - // value, so a signed 8-bit difference (with wrap) is the movement since the - // last scan. Screen Y grows downward; the VSXXX reports DEC Y (up). - int dx = int(now_x) - int(m_last_x); - int dy = int(now_y) - int(m_last_y); - if (dx > 0x80) dx -= 0x100; else if (dx < -0x80) dx += 0x100; - if (dy > 0x80) dy -= 0x100; else if (dy < -0x80) dy += 0x100; - m_last_x = now_x; - m_last_y = now_y; +void vt1200_vsxxxab_device::on_stream_mode() +{ + m_last_x = -1; // send the current absolute position at once + m_last_y = -1; +} - const int buttons = m_host_buttons->read() & 0x07; +void vt1200_vsxxxab_device::sample_host() +{ + // The port already scales the host pointer into 0..VSXXX_ABS_MAX; clamp + // defensively (do NOT mask -- VSXXX_ABS_MAX is a value, not a bit mask). + m_x = int(m_host_x->read()); + m_y = int(m_host_y->read()); + if (m_x < 0) m_x = 0; + if (m_x > VSXXX_ABS_MAX) m_x = VSXXX_ABS_MAX; + if (m_y < 0) m_y = 0; + if (m_y > VSXXX_ABS_MAX) m_y = VSXXX_ABS_MAX; + m_buttons = m_host_buttons->read() & 0x1e; // B4..B1 (Pr is added on send) +} - if (dx || dy) - { - m_acc_dx += dx; - m_acc_dy -= dy; // DEC Y is inverted relative to screen Y - } +bool vt1200_vsxxxab_device::stream_report_due() +{ + return m_x != m_last_x || m_y != m_last_y || m_buttons != m_last_buttons; +} - if (m_stream) - { - if (m_acc_dx || m_acc_dy || buttons != m_last_buttons) - { - send_report(m_acc_dx, m_acc_dy, buttons); - m_acc_dx = m_acc_dy = 0; - } - } - m_last_buttons = buttons; +void vt1200_vsxxxab_device::emit_stream_report() +{ + send_abs(m_x, m_y, m_buttons); + m_last_x = m_x; + m_last_y = m_y; + m_last_buttons = m_buttons; +} + +void vt1200_vsxxxab_device::emit_position_report() +{ + sample_host(); + send_abs(m_x, m_y, m_buttons); +} + +void vt1200_vsxxxab_device::send_self_test() +{ + // Same 4-byte self-test format as the mouse, but device code 0100 = tablet + // (VCB02 C.6.5 figure C-5 and C.7.4). This is what a host reads to learn + // that the pointing device is a tablet rather than a mouse. + queue(0xa0); // 1010 0000: header 101, revision 0 + queue(0x04); // manufacturer 0, device code 0100 = tablet + queue(0x00); // error code 0 = OK + queue(0x00); // no buttons +} + +void vt1200_vsxxxab_device::send_abs(int x, int y, int buttons) +{ + // VSXXX-AB absolute position report, 5 bytes, header 110 (VCB02 D.3 / + // figure D-3): + // [0] 1 1 0 B4 B3 B2 B1 Pr + // [1] 0 0 X5 X4 X3 X2 X1 X0 + // [2] 0 0 X11 X10 X9 X8 X7 X6 + // [3] 0 0 Y5 Y4 Y3 Y2 Y1 Y0 + // [4] 0 0 Y11 Y10 Y9 Y8 Y7 Y6 + // Y is reported DEC-style (up); the host pointer's Y is screen-style (down), + // so it is inverted. Pr is asserted: the emulated pointer is always in + // proximity, so the host always has a valid position. + if (x < 0) x = 0; + if (x > VSXXX_ABS_MAX) x = VSXXX_ABS_MAX; + if (y < 0) y = 0; + if (y > VSXXX_ABS_MAX) y = VSXXX_ABS_MAX; + const int dy = VSXXX_ABS_MAX - y; + const u8 b0 = u8(0xc0 | (buttons & 0x1e) | 0x01); + + queue(b0); + queue(u8(x & 0x3f)); + queue(u8((x >> 6) & 0x3f)); + queue(u8(dy & 0x3f)); + queue(u8((dy >> 6) & 0x3f)); }