diff --git a/harness/mame/run-vidstream.sh b/harness/mame/run-vidstream.sh index a5d0b88..87f9bb1 100755 --- a/harness/mame/run-vidstream.sh +++ b/harness/mame/run-vidstream.sh @@ -63,8 +63,10 @@ # memory and compares them itself: the assertion is a relation between two # screens, not a rendering to re-derive, and it is why the first negative # control's valid first entry matters (a decoder that painted as it decoded -# would have painted it). Evidence: $LOGDIR/vram-delta-*.bin and -# delta-status.txt. +# would have painted it). A last delta changes all 1,280 tiles (3,842 payload +# bytes: three datagrams), which is the only case where the count, the position +# list and the value list straddle a fragment boundary; every tile is compared +# with its new entry. Evidence: $LOGDIR/vram-delta-*.bin and delta-status.txt. # # The run uses VT1200_SKIP_LEVEL1=1: the payload's heap is only initialised in # the OS phase. In the diagnostics menu the free counter (0x1E8BA0) and the @@ -132,8 +134,8 @@ fi if [ "$MODE" = "delta" ]; then rm -f "$LOGDIR/vram-delta-before.bin" "$LOGDIR/vram-delta-after.bin" \ "$LOGDIR/vram-delta-reject.bin" "$LOGDIR/vram-delta-recover.bin" \ - "$LOGDIR/dict-delta.bin" "$LOGDIR/indices-delta.bin" \ - "$LOGDIR/delta-status.txt" + "$LOGDIR/vram-delta-large.bin" "$LOGDIR/dict-delta.bin" \ + "$LOGDIR/indices-delta.bin" "$LOGDIR/delta-status.txt" fi # Absolute: the Lua runs with MAME's cwd, so a relative default only resolves # when the caller happens to be at the repo root. diff --git a/harness/mame/vidstream.lua b/harness/mame/vidstream.lua index 68f18f2..9d5fc40 100644 --- a/harness/mame/vidstream.lua +++ b/harness/mame/vidstream.lua @@ -178,10 +178,10 @@ elseif MODE == "tiles" and not os.getenv("VID_END") then -- exits, so this is only the catastrophic-stall backstop. DEFAULT_END = AT + 26 * tonumber(os.getenv("VID_TILE_WAIT") or "300") + 120 elseif MODE == "delta" and not os.getenv("VID_END") then - -- Same shape as tiles mode plus the keyframe and the three deltas: one - -- ring wait, 23 dictionary chunks and five frames, each bounded by - -- VID_DELTA_WAIT. - DEFAULT_END = AT + 30 * tonumber(os.getenv("VID_DELTA_WAIT") or "300") + 120 + -- Same shape as tiles mode plus the keyframe and the five deltas: one + -- ring wait, 23 dictionary chunks and six frames, each bounded by + -- VID_DELTA_WAIT; the first timeout dumps and exits. + DEFAULT_END = AT + 34 * tonumber(os.getenv("VID_DELTA_WAIT") or "300") + 120 end local STOP_AT = tonumber(os.getenv("VID_END") or tostring(DEFAULT_END)) local ORIGIN = tonumber(os.getenv("VID_ORIGIN") or "0x018C0000") @@ -629,8 +629,9 @@ local STREAM_FILES = { "stream-status.bin", "stream-status.txt" } local TILE_FILES = { "vram-tiles.bin", "dict-tiles.bin", "indices-tiles.bin", "tiles-status.txt" } local DELTA_FILES = { "vram-delta-before.bin", "vram-delta-after.bin", - "vram-delta-reject.bin", "vram-delta-recover.bin", "dict-delta.bin", - "indices-delta.bin", "delta-status.txt" } + "vram-delta-reject.bin", "vram-delta-recover.bin", + "vram-delta-large.bin", "dict-delta.bin", "indices-delta.bin", + "delta-status.txt" } local function clear_dump_files() if MODE == "dict" then @@ -1998,7 +1999,12 @@ end)() -- entries claimed, one carried). Same assertions -- the list may not run -- past the frame; -- 6. recover: one more valid delta at a different tile, so the refusals left --- no latch behind; the assertions are the same shape as step 3. +-- no latch behind; the assertions are the same shape as step 3; +-- 7. large delta: all 1,280 tiles changed. 2 + 3*1,280 = 3,842 payload bytes +-- = THREE datagrams, so the count, the position list and the value list +-- each straddle a fragment boundary -- the path a one-tile delta cannot +-- reach, and the one that would break silently if the fragment walk were +-- off by a byte. Every tile is compared with its new entry. -- -- The active area is screen rows 0..1023, bytes 0..159 of each 256-byte row. -- Bytes 160..255 are outside every tile and belong to the resident OS, exactly @@ -2084,6 +2090,7 @@ local delta_prev = nil -- the screen after the previous accepted frame local delta_after = nil local delta_reject = nil local delta_recover = nil +local delta_large = nil local delta_last_len = 0 local function delta_chunk_frame(off, body) @@ -2280,6 +2287,9 @@ local function delta_dump(timed_out) if delta_recover then ok = write_file(DUMP_DIR .. "/vram-delta-recover.bin", delta_recover) and ok end + if delta_large then + ok = write_file(DUMP_DIR .. "/vram-delta-large.bin", delta_large) and ok + end local lines = { string.format("RX_OK=%d", prog:read_u32(RX_OK)), string.format("RX_BAD=%d", prog:read_u32(RX_BAD)), @@ -2574,6 +2584,72 @@ local function delta_step() delta_check(okv, string.format( "the block is dictionary entry %d (first bad byte %d)", (tile_index(RECOVER) + 2) & 0xFF, first)) + delta_prev = delta_recover + -- Large delta: every tile, a new entry for each. 1,280 entries is a + -- 2 + 3*1,280 = 3,842-byte payload, delivered as THREE datagrams, so + -- the count, the position list and the value list all straddle a + -- fragment boundary -- the one path a one-tile delta never reaches. + local positions, values = {}, {} + for i = 0, TILES_PER_FRAME - 1 do + positions[i + 1] = i + values[i + 1] = (tile_index(i) + 1) & 0xFF + end + f, n = delta_send(positions, values, 0x0095) + if n <= DRAW_CHUNK then + delta_check(false, string.format( + "the large delta is more than one datagram (%d bytes)", n)) + delta_dump(true) + return true + end + if not delta_take(f, n) then + delta_check(false, "the ring accepted the large delta") + delta_dump(true) + return true + end + delta_phase = 7 + delta_phase_at = frames + return false + end + + if delta_phase == 7 then + local bad + if not delta_settled() then + if frames >= delta_phase_at + DELTA_WAIT then + delta_check(false, string.format( + "the payload consumed the large delta (RX_OK=%d TILES_DROP=%d)", + prog:read_u32(RX_OK), prog:read_u32(TILES_DROP))) + delta_dump(true) + return true + end + return false + end + delta_check(delta_last == 3, string.format( + "the large delta arrived as 3 datagrams (%d)", delta_last)) + delta_check(prog:read_u32(RX_OK) == 4, + string.format("the large delta reached the screen (RX_OK=%d)", + prog:read_u32(RX_OK))) + delta_check(prog:read_u32(TILES_DROP) == 2, + string.format("the large delta was not dropped (TILES_DROP=%d)", + prog:read_u32(TILES_DROP))) + delta_check(prog:read_u32(RX_LAST_LEN) == 2 + 3 * TILES_PER_FRAME, + string.format("RX_LAST_LEN is the large delta's payload_len (%d, expected %d)", + prog:read_u32(RX_LAST_LEN), 2 + 3 * TILES_PER_FRAME)) + delta_large = read_vram() + bad = 0 + first = -1 + for i = 0, TILES_PER_FRAME - 1 do + okv = block_equals(delta_large, i % TILE_COLS, i // TILE_COLS, + (tile_index(i) + 1) & 0xFF) + if not okv then + bad = bad + 1 + if first < 0 then + first = i + end + end + end + delta_check(bad == 0, string.format( + "all %d tiles of the large delta match their new entry (%d wrong, first tile %d)", + TILES_PER_FRAME, bad, first)) delta_check(prog:read_u32(DICT_READY) == 1 and prog:read_u32(DICT_BYTES) == TILE_DICT_SIZE and prog:read_u32(DICT_BAD) == 0,