Spec 8.3's hoist, and the 60 fps work showed it was the per-tile cost that
mattered, not the per-frame one.
Every tile_blit() call used to save 8 B registers, load B1/B3/B4/B7/B8/B9 and
write 11 I/O registers (PSIZE, CONVSP, CONVDP, PMASK, the CONTROL read-modify-
write and its read-back) -- all constants. The user's guide marks only SADDR
and DADDR changed by PIXBLT, and the TMS34010 agent's reading of it confirms
B1/B3 survive, so those two are the only ones a per-tile call has to reload.
vram_tile.asm now exports tile_init_() (the constants, once per frame, called
from paint_tiles/paint_delta) and a tile_blit_() that is setf, two pushes, two
register loads, pixblt, two pops, rets.
Measured on the real 140-tile deltas at a 60 fps send, same window, terminal's
own status frames:
before the hoist 162 accepted
after the hoist 231 accepted (+43%)
and the one-tile stream (where tile cost is nil) already runs at 60 fps with
DINT: 600 frames sent at 59.8 fps, 579 accepted (96.5%), drops=0.
Pixel verdicts unchanged: modes tiles, delta and replay all RESULT PASS with
tile_check; 155 unit tests pass.
What the remaining gap is, for the next reader: MAME's PIXBLT moves about
3.4 us per destination word (0.22 ms per 32x32 tile, measured from the accepted
rate) against the unit's measured 0.40 us/word for FILL. A 140-tile delta is
8,960 words, i.e. ~30 ms of MAME CPU against a 16.7 ms frame -- so 60 fps for
this content is bounded by the emulator's pixel engine, not by the design.
Whether the unit can do it turns on the one number nobody has measured there:
PIXBLT B,XY's per-word rate (spec 10, risk table).
The tile pipeline packed every row MSB first: pixel i -> bit 7 - i % 8. The
encoder and tile_check.py shared that convention, so every MAME replay check
passed while a display showed each group of eight pixels mirrored.
Evidence, from the real clip's own replay dump (vram-replay-200.bin, frame 200
of Bad Apple, 1,228,800 pixels):
decoded MSB first 1,215 mismatched <- what the pipeline produced
decoded LSB first 3,113 mismatched <- the mirrored picture, a sawtooth
and both the real unit and MAME decode LSB first: dwt/screenshot.py --real
("LSB-first bits", measured on live VRAM) and vt1200.cpp's
(vram[bit >> 4] >> (bit & 15)) & 1. A zoomed edge of that dump showed the
comb of triangles; after this change the same edge is a smooth curve.
vidsrc.Rows.pack_row now sets bit i % 8 of byte i // 8. The test that pins it
was written first and watched fail: it decodes the packed row with the
hardware's rule, which no MSB-first packer can pass. The two older tests that
asserted MSB first were rewritten -- they were asserting the bug -- and the
synthetic clip now packs through a bit-reversal table, so the committed cache
is regenerated (PSNR 29.47 -> 31.30 dB). The layout claims in tilecost.py,
vram_tile.asm and make_tiles.load_clip are corrected so the clip format states
the order instead of leaving it to whoever writes the frames.
Separately, MAME's renderer reads 128-byte rows while a payload targeting the
real unit writes 256-byte rows (vram_tile.asm DPTCH=0x800; c-hello/README.md:
"a payload writing at row*2048 bits lands on scanline row"). VT1200_VRAM_REAL=1
now draws the framebuffer at 2048 bits / 1280 px from x=0. Off by default: the
Level-1 error text is only legible at the derived 128-byte pitch, and that is
what the faithful boot needs. The branch only touches the bitmap, so the
regression baseline is unmoved (PROG_FRAMES=200 -> PC=3FDCE400, samples=198,
3 distinct PCs), and the default frame-3000 render of the Terminal Manager is
unchanged.
Verified end to end on the 3,292-frame Bad Apple clip: RX_OK=3196, TILES_DROP=0,
3292/3292 frames dumped and matched by tile_check.py, and the dumps decode to
1,379 / 4,125 mismatched pixels of 1,228,800 (quantisation) under the hardware
rule versus 3,101 / 9,793 under MSB. MAME's own frame dumps with
VT1200_VRAM_REAL=1 show the clip unsheared.
Revert note for the driver: delete the m_render_real branch and its getenv and
vt1200.cpp returns to the measured 128-byte renderer byte for byte. No second
reader reviewed that citation; it is quoted in the comment for the next one.