Commit Graph
12 Commits
Author SHA1 Message Date
arti 8f831eaf33 fix(video): model the real unit's VR319 monitor
The harness forced the VR150 monitor switch, so the firmware wrote the OS
screen at 1024 px / 128-byte rows.  VT1200_VRAM_REAL then re-read that
framebuffer at the VR319 pitch, which sheared the OS and wrapped the right
quarter of every row.

Model VR319 (switch 0x60) by default -- the real unit's mode -- so the
firmware itself programs 1280x1024 with a 256-byte row pitch (CONVDP=0x14).
The renderer's pitch follows VT1200_MONITOR, and the destination span now
comes from HEBLNK..HSBLNK instead of x=0, placing the active 1280 px at
x=336 in MAME's 1696-wide raster.

Verified: clean Terminal Manager at frame 600, legible faithful diagnostics
menu and level-1 error screens, tile mode PASS with tile_check 1280/1280
tiles, tablet/mouse pixel-identical.  VT1200_MONITOR=VR150 reproduces the
old geometry; the regression baseline moves 198 -> 199 (2 PCs) and is
recorded in harness/mame/README.md.
2026-10-31 20:53:51 +02:00
arti 9af7b4fb8d fix(rx): release descriptors individually instead of flipping the whole ring
The receive loop re-armed all 16 descriptors into the other slot after every
completed frame.  During a burst that re-pointed descriptors the chip had
ALREADY filled but the consumer had not read, so those frames were lost without
a trace: the LANCE DMA log shows all 23 dictionary datagrams written by the
chip, 1.132 ms apart (the MTU/bandwidth poll cadence), while the payload's own
counters accounted for 4 (DICT_BYTES=1448, dict_bad=3).  Nothing was dropped by
MAME; the ring was being rewritten under the frames it held.

rx_switch()/rx_armed are gone.  A descriptor is held (rx_hold) while its
datagram belongs to the frame being assembled, and rx_release() hands each one
back -- into the other slot -- only once that frame has been parsed or
abandoned.  Nothing the chip delivered is ever re-pointed unread.

Measured, same zero-gap 23-datagram dictionary upload:
  before                      dgram=4   DICT_BYTES=1448   dict_bad=3
  release fix, 16 descriptors dgram=18  DICT_BYTES=24616  dict_bad=0
  plus a 32-descriptor ring   dgram=23  DICT_BYTES=32768  dict_bad=0

The ring is now 32 descriptors because 23 datagrams is the burst the protocol
actually defines (a whole 32 KB dictionary at MAX_BODY 1448), and 16 could not
hold it while the consumer copied.  SLOT_BYTES grows to 49152 to hold the ring;
POOL_BYTES does NOT grow -- the 160 KB "framebuffer reserve" was unused slack
(the decoder paints straight from the fragments), so the extra 24,576 B per slot
comes out of it and the LANCE block keeps its fixed 0x3C000 tail.

RX_OK is now published after rx_release(), not inside the dispatch.  It is the
"frame done" signal a reader polls, and it used to become visible while
descriptors were still being rewritten: ring mode caught a descriptor holding
slot B's high byte with slot A's low word.  A reader that sees RX_OK move now
sees a painted picture and a consistent ring.

vidstream.lua's ring mode follows: it asserts the invariant that replaced the
flip -- the consumed descriptors move to the other slot, the untouched ones stay
-- instead of "all 16 in slot B".

Evidence: 146 unit tests pass; modes rx, draw, ring, tiles, delta and replay all
PASS with their pixel checkers (draw 1024/1024 rows, tiles and replay via
tile_check).  End to end over rdebug with the STOCK sender (no pacing): the
dictionary burst completes (DICT_READY=1, DICT_BYTES=32768, dict_bad=0) and
RX_OK=100 frames paint, TILES_DROP=0.
2026-10-31 19:45:34 +02:00
opencode.energia.arti.ee a84f5f2811 fix(tiles): early status on dictionary failure, decode-time field, chunk bound 2026-10-31 03:35:14 +00:00
opencode.energia.arti.ee 8f8454461d feat(tiles): delta frames and periodic keyframes 2026-10-31 02:38:40 +00:00
opencode.energia.arti.ee 2fa232ba56 feat(tiles): 32x32 PIXBLT B,XY tile blit and full-frame paint 2026-10-31 02:20:50 +00:00
opencode.energia.arti.ee 0b726fda3a feat(tiles): dictionary storage and kind dispatch in the payload
The payload assembles KIND_DICT_CHUNK frames into a 32 KB dictionary at
POOL_PTR + 2 * SLOT_BYTES and learns the tile kinds:

* frame_parse.h gains FP_KIND_TILES_FULL (2), FP_KIND_TILES_DELTA (3) and
  FP_KIND_DICT_CHUNK (4), exactly frame.py's KINDS table, plus the marker
  cells DICT_READY/DICT_BYTES/DICT_BAD and TILES_DROP.  A host test parses
  the header and compares it with KINDS so the two cannot drift apart.
* dict_chunk() enforces an ascending, contiguous offset (no gap, overlap,
  duplicate or reorder; a mid-entry offset only as a continuation), a chunk
  that does not overrun the last entry, and no chunk after DICT_READY.  A
  refused chunk leaves the prefix alone.  Readiness is exactly 32768 bytes
  (256 whole entries), so a short upload never becomes ready.
* The dispatcher routes STRIP to the blit, DICT_CHUNK to the assembler, and
  refuses TILES_FULL/TILES_DELTA without painting, counting them in
  TILES_DROP (Task 4 adds the tile blit).
* A compile-time assertion refuses a POOL_BYTES that cannot hold the
  dictionary; the 1987 preprocessor does not fold macro arithmetic in #if,
  so it is a defined array whose negative length fails the build.
* VT1200_MODE=dict injects both tile kinds before the dictionary, 23
  contiguous chunks with a duplicate offset, a gap and an overrunning tail
  mixed in, and reads the stored 32 KB back byte for byte.
2026-10-31 01:55:06 +00:00
opencode.energia.arti.ee 22c539bf74 fix(stream): ring ownership window, FCS-error accounting, unit loader procedure 2026-10-31 01:25:14 +00:00
opencode.energia.arti.ee 49a943583c feat(stream): real rx ring over chained descriptors; unit measurement script 2026-10-31 01:03:59 +00:00
opencode.energia.arti.ee 53b1580abd feat(stream): paced sender and terminal status frames 2026-10-31 00:26:26 +00:00
opencode.energia.arti.ee 45110270a7 feat(stream): row-strip blit with SPTCH=0 vertical stretch
One PIXBLT B,XY per source row paints the whole screen: DYDX names
`stretch` rows and SPTCH = 0 makes each of them read the same packed
source row, so the vertical stretch costs no extra CPU work.

vram_b.asm carries the primitive (PSIZE=1, CONVSP/CONVDP=0x14,
PMASK=0, OFFSET=0x04000000, DPTCH=0x800, raster op 0) plus DINT/EINT
thunks; vidstream.c masks interrupts for the burst (spec 8.9), refuses
geometry the screen cannot hold, and counts an OS repaint through a
sentinel word in the unused right edge of the last screen row
(OS_TOUCH, 0x01810220).

MAME mode draw injects a 128-row frame (stretch 8 = 1024 screen rows,
ruling R3) and dumps the screen; vidstream_check.py compares all 1024
rows against the bytes actually delivered.  RED: 0/1024 rows matched.
GREEN: 1024/1024, RX_OK=1, OS_TOUCH=0.

Fixes a latent harness bug found on the way: `bpclear <address>` takes
a breakpoint INDEX, so the idle-loop breakpoint was never cleared; once
a burst masked interrupts long enough, its stale action fired again and
re-entered main (a second malloc, counters back to 0).
2026-10-30 23:43:02 +00:00
opencode.energia.arti.ee 487bb1789a feat(stream): frame parser shared by host tests and payload 2026-10-30 23:16:48 +00:00
opencode.energia.arti.ee 4f6b0bf1a6 feat(stream): payload skeleton with one startup malloc 2026-10-30 22:40:23 +00:00