Commit Graph
271 Commits
Author SHA1 Message Date
opencode.energia.arti.ee a12fb47d5f chore(termtris): handoff script and notes for the next machine
apps/termtris/verify.sh runs the whole acceptance: ROM md5 guard, 2ine
check, MAME build (--skip-mame to reuse one), the six tests, the
acceptance summary and the evidence list, all logged under build/.

HANDOFF.md states what is done, what is still open (the docs' figures and
screenshots predate the VR319 default because the installed MAME binary is
the 30 Oct build), the expected outputs, and the failure modes already
diagnosed. Refresh the acceptance numbers from the verify run:
348/19, 338/13, 292/11.
2026-11-12 05:14:33 +00:00
opencode.energia.arti.ee be5b323f5d docs(termtris): build the harness binary before reproducing evidence 2026-11-11 17:59:30 +00:00
opencode.energia.arti.ee 7a2cef56ad fix(termtris): review pass -- seed, focus/pause, expose, test assertions
- seed the game from the raw 0x1E93C0 counter (now_ms() returns 0 on its
  first call, so the old seed was always zero)
- track pause ourselves: FocusOut pauses once, FocusIn resumes only what we
  paused (game_input('p') is a toggle)
- select ExposureMask so the Expose redraw path actually fires
- play.sh/play_keys.sh rebuild the COFF (OUTNAME=termtris) and assert the
  run: status=0, tick>0, loops/events/quit per mode; render.sh asserts the
  renderer finished; map symbol lookup is column-order independent
- run.sh passes -lightgun -lightgun_device mouse so the pointer drives the
  tablet
- docs: link map addresses, monochrome/reverse wording, honest caption and
  limits text

Verified here: play.sh and input.sh pass. Still open: the MAME driver in the
tree is newer than the installed binary (Oct 30 build, VR150-era geometry),
so the acceptance suite must be re-run after a rebuild.
2026-11-11 17:59:16 +00:00
opencode.energia.arti.ee eb896f7b8c test(termtris): assert the play acceptance results; drop the dead drain hook 2026-11-03 20:11:57 +00:00
opencode.energia.arti.ee 64bd781aa6 chore(termtris): stop tracking per-test build outputs 2026-11-03 20:07:41 +00:00
opencode.energia.arti.ee 3e2dddeb6a docs(termtris): port report, run wrapper and harness write-up 2026-11-03 20:07:35 +00:00
opencode.energia.arti.ee 41d3cecd49 feat(termtris): run the game on the VT1200 X server
Board, score panel, preview and help/scores toggles draw through the
ROM Xlib cell grid; keys arrive as real X KeyPress events. The backend
is now monochrome (the VT1200 raster is one bit deep) and flushes whole
rows so the in-machine server can drain the request stream.
2026-11-03 20:03:47 +00:00
opencode.energia.arti.ee c1945cfc73 feat(termtris): X11 key map, millisecond clock and main loop 2026-11-03 19:03:14 +00:00
opencode.energia.arti.ee 264b53a6a0 feat(termtris): 80x24 cell-grid X11 renderer (6x13 cells) 2026-11-03 19:00:42 +00:00
opencode.energia.arti.ee a8041818bb feat(termtris): compat sprintf/stdio for the TI r5.01 runtime 2026-11-03 18:51:22 +00:00
opencode.energia.arti.ee a8612681ba feat(termtris): vendor source and VT1200 Xlib XG smoke test 2026-11-03 18:49:44 +00:00
opencode.energia.arti.ee 2c80547cc3 docs(termtris): implementation plan for the VT1200 X11 port 2026-11-03 18:42:47 +00:00
opencode.energia.arti.ee 989b8a11e1 docs(termtris): VT1200 X11 port design; local Xlib XG fix 2026-11-03 17:51:23 +00:00
arti 17dfd24340 docs(xie): the VT1200 ROM has no DCT (JPEG) scheme -- measured
The question was whether this ROM answers "unsupported" for XieK_DCT.  It does,
at both layers, and it behaves exactly like an out-of-range scheme value.

SetTransport (minor 20), raw wire request, a fresh bitonal photomap per scheme:
  PCM / G31D / G32D / G42D -> accepted
  DCT (4) and 5            -> BadIDChoice(14), minor_op 20
So the ROM does not even implement the reference server's IsBitonal_ rules
(G42D is accepted on a 256-level photomap, where XieTransport.c would BadMatch),
but it does refuse 4 and above.

Export (minor 21) with the photomap's compression byte forced to each value
(the server never initialises it -- it holds the heap fill 0x55, which is why
harness/mame/xie_fix.lua pokes file 0x1B256):
  0..3 -> pixmap 4096/4096, no X error
  4    -> BadValue, nothing drawn
  5    -> BadValue, nothing drawn

And the ROM carries no JPEG machinery: no "JPEG"/"DCT" string, and the standard
Annex K quantisation table is absent in both zigzag and natural order (no flat
8x8 either).  A DCT codec carries one of those; this ROM carries none.

"DDX_RENDER: unsupported compression scheme" exists in the ROM but did not fire
in these runs, so it belongs to a path these probes do not reach -- recorded as
an open detail rather than claimed as the explanation.

Probes: xie/xiescheme.c (libXie, one scheme per connection) and
xie/xie_schemes_raw.py (raw protocol, exact error codes).
2026-11-01 23:55:27 +02:00
arti 742501be67 docs(xie): the compression schemes the XIE headers define
DEC XIE 2.3 (what the VT1200 speaks) defines exactly five, in XieAppl.h:
XieK_PCM 0, XieK_G31D 1, XieK_G32D 2, XieK_G42D 3, XieK_DCT 4.  XieK_DCT is
the JPEG/DCT scheme; there is no XieK_JPEG and no TIFF/RLE/LZW constant.

Worth recording because it explains the BadValue/BadMatch history: the server's
ProcSetTransport allows PCM unconditionally, allows G3/G4 only for a bitonal
image outside XieK_Tile mode, and allows DCT only for a NON-bitonal image
outside XieK_Tile and outside XieK_BitByPlane -- anything else is BadValue.  A
bitonal 64x64 photomap in Tile mode therefore admits PCM only, which is what
the working client sends.  In ProcGetStream/ProcSetStream G31D and G32D are
literally "not yet supported", and DCT/G42D are delegated to the DDX hooks
DdxEncodeDct_ / DdxEncodeG4_ -- i.e. to the board's accelerator.

The X Consortium X11R6.1 lineage (xc/) is a different, larger list: ten
Decode/EncodeTechnique values including JPEGBaseline 10, JPEGLossless 12,
TIFF2 14 and TIFFPackBits 16.

Cross-referenced with DTJ Vol. 3 No. 4 (Fall 1991), "Hardware Accelerators for
Bitonal Image Processing": the ASIC described there is a CCITT (G3-1D, G3-2D,
G4-2D) decompressor plus scaler and rotator -- the scalution hardware -- while
JPEG/DCT is that article's continuous-tone counterpart (citing C-Cube CL550-10,
not the bitonal chip).  The two map onto XieK_G42D and XieK_DCT.
2026-11-01 23:38:53 +02:00
arti d8d54bb990 feat(xie): a client-chosen XIE image is displayed on the VT1200
The XIE path now runs end to end: XieCreateImage -> XieCreatePhoto ->
XiePutTile -> XieExport(photomap -> depth-1 pixmap) -> XCopyArea(pixmap ->
window), and the client's tile appears on screen at the client's own window
position, pixel-exact.

TWO FAULTS BEHIND THE LONG DEAD END

1. Device model (harness/mame/vt1200.cpp, opt-in VT1200_ACCEL=1).  The
   accelerator's output port 0x5600200 returns the pixel stream the firmware
   pushed into the FIFO -- the firmware then stores it into the drawable itself
   (file 0x125528: MOVE *B6,*B1 ; ADDK 0x10,B1).  The FIFO read pointer was
   never reset between transfers, so consecutive exports read stale words and
   the same fill produced 759 then 516 set pixels.  Reset m_fifo_in/out on the
   data-register write; the export is now deterministic.

2. Measurement, not the device.
   - dwt/screenshot.py without --real reads 1024x1024 at a 128-byte row stride,
     but the VR319 default programs 1280x1024 at a 2048-bit (256-byte) pitch.
     That is the "shifted" picture; --real is the correct read.
   - The earlier "0 differing bytes over 128 KiB" VRAM comparison stepped peek
     by 1024 *bit* addresses, so it scanned rows 0..63 only -- never the window
     at rows 200..263.  Over the real raster the white/black diff is 576 bytes
     spanning exactly those rows.
   - New instrument: decclient does a server-side XGetImage readback of the
     pixmap and window and counts set pixels.  That separated "the server never
     drew it" from "the framebuffer view is wrong".

EVIDENCE (reproduced twice, the second time from a fresh machine)

  no export vs 0xFF : 4096 / 4096 pixels differ
  no export vs 0x00 :    0 / 4096   (the window background is black)
  0xFF      vs 0x00 : 4096 / 4096
  0xAA      vs 0x0F : 2048 / 4096   (the two stripe phases)

Tile byte 0xAA reproduces as 1-pixel vertical stripes, 0x0F as 4-pixel, with
no shear -- so the placement is right and the pixels are the client's.

Contents: xie/ holds DEC XIE 2.3 headers, 84 man pages and complete
client+server source; a libXie.a and decclient built from DEC's own code; the
XIE probes; and evidence/ with every measurement, including explicit
retractions of the earlier wrong readings (VRAM_IDENTICAL_FOR_BOTH_FILLS and
friends).  xie/evidence/XIE_IMAGE_DISPLAYED.md is the report; frames are in
xie/evidence/png/.

The accelerator model stays opt-in: it answers the status poll and relays the
pixel stream, but performs no compositing, so it is a stand-in and the verified
default configuration is unchanged (a no-env run still parks in the level-0
diagnostics menu at PC=3FDCE400).
2026-11-01 23:22:25 +02:00
arti 437c92e6ea docs(dtj): archive the full DTJ Vol. 3 No. 4 (Fall 1991) issue
The tree only had two excerpts of this issue, not the issue: a text-only
reflow of article 2 (X Window Terminals) and a 17-page scan of article 1
(Hardware Accelerators for Bitonal Image Processing). Add the complete
83-page issue from vmssoftware.com, plus a pdftotext rendering and a note
recording provenance, contents and how the two excerpts relate to it.

md5 46e69c98eb669fabec7a6ce704d35850, 4,882,168 bytes, 2006 Paper Capture
OCR (so the text layer carries OCR errors).
2026-11-01 22:45:18 +02:00
arti 0d9435f650 docs(badapple): benchmark the r5.01 compiler output 2026-11-01 11:49:25 +02:00
arti 2e3875da3b docs(badapple): record the r5.01 toolchain build result 2026-11-01 11:45:08 +02:00
arti a3cec55795 docs(badapple): record where hd60.mp4 came from 2026-11-01 11:33:31 +02:00
arti 638656bd05 docs(badapple): current MAME demo quick start 2026-11-01 11:24:59 +02:00
arti 371fe27bc2 merge: Bad Apple 30s vector prototype 2026-11-01 11:23:38 +02:00
arti cb2d1c37df docs(badapple): record the potrace y-up orientation fix 2026-11-01 11:22:05 +02:00
arti 983784d9ec fix(badapple): flip potrace y-up polygons to the framebuffer 2026-11-01 11:15:57 +02:00
arti c3ae1269e4 fix(badapple): distinguish UDP load failures and repeat the send 2026-11-01 11:08:55 +02:00
arti 6b6bfaf135 fix(badapple): pace the UDP load at 2 ms per packet 2026-11-01 11:06:49 +02:00
arti 1d9da7e3cf fix(badapple): checksum, packet bounds, ticks and marker placement 2026-11-01 10:29:49 +02:00
arti 4013f75434 docs(badapple): usage, measurements and gaps 2026-11-01 10:20:23 +02:00
arti 8a1cd1bfb1 feat(badapple): UDP network loader and sender 2026-11-01 10:19:36 +02:00
arti 116356a975 feat(badapple): full 30s stream and MAME exactness 2026-11-01 10:16:12 +02:00
arti 150a6d86c0 feat(badapple): playback loop and vblank pacing 2026-11-01 10:10:52 +02:00
arti dbfc7b60c5 feat(badapple): FILL primitive and one-frame MAME check 2026-11-01 10:04:52 +02:00
arti beb45942ad feat(badapple): host-tested movie decoder 2026-11-01 00:24:20 +02:00
arti 5a1f8429f0 feat(badapple): cost model, frame selection and build CLI 2026-11-01 00:23:08 +02:00
arti 8371cdad12 feat(badapple): tile delta format and movie packer 2026-11-01 00:22:16 +02:00
arti 0c93078ee3 feat(badapple): polygon tracing and rasterisation 2026-11-01 00:21:17 +02:00
arti c7569efb7d feat(badapple): frame extraction primitives 2026-11-01 00:20:35 +02:00
arti 9deada6cef docs: run the r5.01 C toolchain end to end under 2ine
The null-selector fix in the 2ine fork unblocks gspac/gspopt/gspcg, so
document the full pipeline: build 2ine, extract the tools, the four C
stages, absolute placement, and the firmware round-trip.

Record the two gotchas found while testing: gspcl still stops after
gspac because the children exit 139 at teardown, and GSPA 5.01 rejects
b15, so the 32 KB round-trip differs at one SP access (same physical
register, A15/B15).
2026-11-01 00:18:06 +02:00
arti b75048fb87 Merge remote-tracking branch 'wut/master' 2026-11-01 00:06:09 +02:00
arti 3d586106bf plan: Bad Apple 30s vector demo implementation 2026-11-01 00:03:03 +02:00
arti bc3b20113c spec: Bad Apple 30s vector-polygon demo 2026-10-31 23:59:46 +02:00
arti 6febcf00f4 feat(rx): rectangle blits for stretch-1 strips; ring depth 16 -> 32 in the encoder
Two things the 60 fps work needed, and one ceiling it found.

Rectangle blit: a strip frame with stretch 1 has one source row per screen row,
so the source advances by 1280 bits and a group of rows inside one fragment is
contiguous -- one PIXBLT with SPTCH = 1280 copies the group.  Measured on the
tile path, a blit call costs ~0.45 ms in MAME regardless of size (1,200 tile
calls accepted 9 frames where 128 strip calls accepted 73 for the same pixels),
so call count, not word count, is what a frame is charged for.  paint_rect()
takes the stretch-1 frame 9 rows at a time and falls back to the per-row path if
a row straddles a datagram.

Ring depth: vidsrc still said RING_DATAGRAMS = 16 and capped a strip frame at
144 rows, which stopped being true when the receive ring was deepened to 32 for
the dictionary upload.  It is 32 now, the encoder may send up to the header's
255 rows, and the three tests that encoded the old ceiling were updated to it.

The ceiling this found: a picture-as-strips full frame is 29 datagrams, i.e.
1,740 datagrams/s at 60 fps, while the emulated LANCE delivers ~450/s (1,935
datagrams over 4.3 s, measured).  So keyframes cannot be sent as whole strips at
60 fps in MAME whatever the decoder does -- the datagram rate binds first.  The
tile delta (about one datagram a frame) remains the right transport; what costs
is its blit-call count.

Verified: modes rx, draw and tiles still PASS (draw and tiles with their pixel
checkers); 155 unit tests pass.
2026-10-31 23:27:27 +02:00
arti c5526332f0 feat(badapple): stylise the clip into blocks, and let the sender widen its keyframe cadence
Two knobs for the 60 fps attempt, both measured on Bad Apple:

clip_from_video.py --block N averages each NxN block, thresholds the average and
draws the block flat.  The point is destination words: at 60 fps the clip changes
80 tiles/frame unstylised, 54 at 8x8 and 29 at 16x16, against the ~44-tile budget
that MAME's ~3.4 us/word allows in 16.7 ms.  A side effect worth knowing: with
16-pixel blocks every 32x32 tile is one of exactly 2^4 combinations, so the 256
entry dictionary represents the stylised clip with zero error (measured: 16
entries used, "reconstruction 0 bit(s)", PSNR inf, mean delta 141 B against 496 B
for the unstylised clip).

vidsend.py --keyframe-every N exposes the cadence that TileSender already had as
a constructor argument.  A keyframe repaints all 1,280 tiles (~264 ms of MAME CPU
at the measured 0.22 ms/tile), so the default 60-frame cadence costs ~4.4 ms on
average per frame at 60 fps -- a tenth of the budget spent on repairs.  The
13,167-frame stylised clip ships 220 keyframes at the default; 600 measured 6 in
the same window.

Measured on the stylised 60 fps cache (13,167 frames, 16 dictionary entries):
  --fps 60, default cadence   643 of 805 accepted (80%), drops=8
  --fps 45                     484 of 520 accepted, drops=0, sender PASS
  --fps 60, --keyframe-every 600  451 of 519 accepted, drops=5
So the stylised clip roughly doubles acceptance against the same 60 fps send
(40% -> 80%) but MAME still caps the clean rate around 36-45 fps.
2026-10-31 23:12:25 +02:00
opencode.energia.arti.ee 005dfb02d5 docs: how to use the 1990 r5.01 (OS/2) TMS34010 toolchain
The r5.01 tools are a superset of the 1987 DOS set: ANSI C, one driver,
gspopt, a bigger runtime, and 35.8% smaller .text at -o2 on the same
source. Add a practical guide:

* what the package contains and how to extract it
* running it: OS/2 1.3 under 86Box (full toolchain) and 2ine (assembler/
  linker/converter, with the patched fork linked)
* the gspcl option table and verified command lines (-o0/-o1/-o2, -pk,
  -al, -z/-lrts.lib), plus the manual gspac -> gspopt -> gspcg -> gspa
  pipeline and the linker command file
* gotchas: -o semantics, bit-addressed section sizes, CRLF, AUTOEXEC not
  running in the guest, emulated-keyboard limits, one-way assembler
  compatibility
* measured code-size and throughput numbers for both toolchains

Link it from the docs index and the repository layout.
2026-10-31 20:51:06 +00:00
arti 9139322892 perf(tiles): program the blit constants once per frame, not once per tile
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).
2026-10-31 22:40:42 +02:00
arti 1bb3643144 feat(badapple): measure the 60 fps wire budget before changing any format
Spec 11's first task: decide from the clip, not from assumptions, whether the
raw-tile scheme (no dictionary, tag + bitmap per changed tile) fits a 60 fps
frame.  measure_wire.py extracts at 1280x960/60 fps with the hard threshold the
design requires, diffs each frame on the 40x30 picture grid, and totals the
payload a TILES_DELTA would carry:

    4 + 3*count + 128*m

Full clip, 13,167 frames: mean 16,049 B/frame, p50 14,755, p95 33,353, max
51,523; 22.5% of frames exceed the 21 KB one-frame budget; 87% of changed tiles
are neither solid black nor white, so they carry a 128-byte bitmap (the design
assumed a third); mean 140 changed tiles/frame, max 1,173 at a cut.  Link duty
at 60 fps would be 72% of the measured 1.27 MB/s.

Two consequences for the design, recorded here rather than discovered later:
the raw-tile scheme is borderline rather than comfortable, and the tile churn at
60 fps (140) is nearly the same as at 15 fps (165) -- motion between adjacent
60 fps frames is mostly sub-tile, so sending faster does not divide the wire by
four.
2026-10-31 22:36:34 +02:00
arti 8de6d94e84 feat(rx): mask interrupts for good -- the OS timer was halving the frame rate
Spec 8.9 decides the payload may kill the resident OS; this is the first half of
that, and it is a rate fix as much as a robustness one.

With the OS still ticking, the payload's loop is preempted and the machine
accepts ~15 frames/s **whatever the payload size**: a 5-byte one-tile delta
measured the same as a 140-tile one (195 frames in 13.4 s, MAME at 100% speed,
frames_rx from the payload's own status frames).  That is scheduling, not
decode -- the loop itself runs ~20k iterations/s.

VID_OWN_CPU (default 1) takes one DINT before the main loop and makes vid_eint_()
a no-op, so nothing preempts the LANCE poll.  Measured on the same one-tile
stream:

  OS ticking (before)   15.0 fps accepted, 400 frames in 15.4 s
  DINT at startup       24.0 fps accepted, 385/400 frames, drops=0
  and, paced differently, 31.5 fps with 387/400 -- the sender, not the terminal,
  is now the throttle (its socket blocks once the tap's drain rate is exceeded)

Consequence stated where it is made: the OS's drivers stop being serviced, so
its console and network stack are gone for good.  The payload owns the machine
once it takes the LANCE either way.  The harness reaches the payload through the
debugger, not the OS, so the replay modes are unaffected -- verified: modes
tiles, delta and replay all RESULT PASS with their pixel checkers after the
change.

Set VID_OWN_CPU=0 at build time to reproduce the old burst-EINT behaviour for a
comparison run.
2026-10-31 22:33:07 +02:00
arti d211ff0770 fix(badapple): --no-load must not wait for ICMP; survive a slow readback
Two faults the 60 fps work exposed, both in the delivery tooling:

* `demo.sh --no-load` waited for the terminal to answer ping.  A loaded payload
  owns the LANCE and stops the OS's stack with it, so that gate can never pass
  once the payload is running -- the recovery path documented for "already
  jumped" deadlocked for its full 180 s.  The gate now runs only when this
  invocation is going to load the payload.

* `rdebug_load.py` used a 5 s socket timeout and no retry, so a readback peek
  that timed out on a freshly booted OS failed a load whose pokes had already
  been acknowledged.  Timeout is 15 s (--timeout) and the readback retries once
  before it is believed.

Tests: the retry path is pinned with a fake remote that times out on the first
peek (one timeout, then the successful pass over every chunk) and one that never
answers (it must raise rather than loop).  9 tests in harness/badapple pass.
2026-10-31 22:20:58 +02:00
arti c166c6fdec fix(badapple): refuse a non-cache --tiles path before the jump
demo.sh loaded (and jumped) the payload before it ever looked at --tiles, so a
wrong path -- the frames directory rather than the cache make_tiles.py writes --
only surfaced after the machine had been handed to a payload that never returns,
forcing a reboot to retry.  It now checks dictionary.bin/stream.bin first, names
the frames-vs-cache confusion when the directory holds raw frames, and exits 2
without touching the network.  The README's failure table gains that row and the
--no-load recovery for an instance that has already been jumped.
2026-10-31 22:05:48 +02:00
arti 650ec9a0cd spec: Bad Apple at 60 fps exact — raw tagged tiles, no dictionary 2026-10-31 21:54:48 +02:00