OXYGENHUB
◆ main enhanced-x16-emulator / tests / test_code_map.c
62.8 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
1
// Checks for the anchored disassembler / code map (src/code_map.c).
2
//
3
// code_map exists because a linear 65C02/65C816 disassembler drifts: start one
4
// byte off, or assume the wrong operand width, and every following line is
5
// garbage. These checks pin the three things that keep it aligned -- recorded
6
// instruction starts, the bank context they are keyed by, and the backward
7
// anchor search -- plus the recovery path for when the forward walk and the
8
// backward walk disagree.
9
//
10
// code_map.c leans on only disasm(), dbg_info and two core symbols, so the test
11
// supplies its own memory and register file and links no emulator core.
12
13
#include "code_map.h"
14
#include "cpu/registers.h"
15
16
#include <stdbool.h>
17
#include <stdint.h>
18
#include <stdio.h>
19
#include <string.h>
20
21
// ---- Stand-ins for the emulator core --------------------------------------
22
// The real ones live in fake6502.c and memory.c and would drag in SDL, video
23
// and the whole machine; code_map and disasm only ever reach for these.
24
25
struct regs regs;
26
bool        is_gen2;
27
28
// The memory model has to be real enough to tell banks apart, or the bank
29
// arguments code_map computes are unobservable and cm_x16bank_for() is checked
30
// by nothing: a stub returning g_mem[address] gives the same answer for every
31
// bank, so any window selection -- or none -- passes. This mirrors the decode
32
// in real_read6502() closely enough to make a wrong window show up as the
33
// wrong bytes:
34
//   - gen2 with a non-zero CPU bank: the whole 64K is that bank's flat RAM,
35
//     and the window registers select nothing;
36
//   - $C000-$FFFF: banked ROM, x16Bank if given, else the live ROM register;
37
//   - $A000-$BFFF: banked RAM, x16Bank if given, else the live RAM register;
38
//   - below that: flat, unbanked, and x16Bank is not consulted at all.
39
#define TEST_RAM_BANKS  256
40
#define TEST_ROM_BANKS  32
41
#define TEST_GEN2_BANKS 4
42
43
static uint8_t g_mem[0x10000];                            // flat low memory
44
static uint8_t g_bram[TEST_RAM_BANKS][0x2000];            // $A000-$BFFF
45
static uint8_t g_brom[TEST_ROM_BANKS][0x4000];            // $C000-$FFFF
46
static uint8_t g_gen2[TEST_GEN2_BANKS][0x10000];          // gen2 flat CPU banks
47
static uint8_t g_ram_bank = 0;
48
static uint8_t g_rom_bank = 0;
49
50
// Set whenever a read below $A000 arrives with an explicit window bank. Low
51
// memory is not banked, so code_map is supposed to pass "current bank" there;
52
// anything else is it inventing a window that does not exist.
53
static int g_low_mem_windowed = 0;
54
55
uint8_t
56
real_read6502(uint16_t address, uint8_t bank, bool debugOn, int16_t x16Bank)
57
{
58
	(void)debugOn;
59
60
	if (is_gen2 && bank != 0) {
61
		if (bank < TEST_GEN2_BANKS) {
62
			return g_gen2[bank][address];
63
		}
64
		return (uint8_t)((address >> 8) & 0xFF); // open bus, as in memory.c
65
	}
66
	if (address >= 0xC000) {
67
		int rb = x16Bank >= 0 ? (uint8_t)x16Bank : g_rom_bank;
68
		if (rb >= TEST_ROM_BANKS) {
69
			return (uint8_t)((address >> 8) & 0xFF); // open bus, as in memory.c
70
		}
71
		return g_brom[rb][address - 0xC000];
72
	}
73
	if (address >= 0xA000) {
74
		int rb = x16Bank >= 0 ? (uint8_t)x16Bank : g_ram_bank;
75
		return g_bram[rb % TEST_RAM_BANKS][address - 0xA000];
76
	}
77
	if (x16Bank >= 0) {
78
		g_low_mem_windowed++;
79
	}
80
	return g_mem[address];
81
}
82
83
uint8_t
84
memory_get_ram_bank(void)
85
{
86
	return g_ram_bank;
87
}
88
89
uint8_t
90
memory_get_rom_bank(void)
91
{
92
	return g_rom_bank;
93
}
94
95
// ---- Test harness ----------------------------------------------------------
96
97
static int failures = 0;
98
99
static void
100
check(bool cond, const char *what)
101
{
102
	if (!cond) {
103
		failures++;
104
		printf("FAIL: %s\n", what);
105
	} else {
106
		printf("ok  : %s\n", what);
107
	}
108
}
109
110
// `recorded` is derivable: a row is a verified whole instruction exactly when
111
// it is an instruction row AND its start address is a recorded anchor. The
112
// field predates the other two and is kept for callers, so the relationship is
113
// checked rather than left as a comment -- if it ever stops holding, one of the
114
// three is being set wrong.
115
static void
116
check_line_flags(const code_map_line_t *lines, int n, const char *what)
117
{
118
	for (int i = 0; i < n; i++) {
119
		bool expect = (lines[i].kind == CM_LINE_INSTRUCTION) && lines[i].start_recorded;
120
		if (lines[i].recorded != expect) {
121
			printf("      line %d at $%04X: recorded=%d kind=%u start_recorded=%d\n",
122
			       i, (unsigned)lines[i].addr, (int)lines[i].recorded,
123
			       (unsigned)lines[i].kind, (int)lines[i].start_recorded);
124
			check(false, what);
125
			return;
126
		}
127
		if (lines[i].kind == CM_LINE_DATA && lines[i].eff_addr != -1) {
128
			printf("      line %d at $%04X: data row with eff_addr $%04X\n",
129
			       i, (unsigned)lines[i].addr, (unsigned)lines[i].eff_addr);
130
			check(false, what);
131
			return;
132
		}
133
	}
134
	check(n >= 1, what);
135
}
136
137
// The property that makes a disassembly window renderable at all: the emitted
138
// lines must TILE the range they cover. Every line starts exactly where the
139
// previous one ended -- an overlap claims the same byte for two instructions, a
140
// gap describes no instruction at all for the bytes in between, and nothing
141
// downstream can draw either. Asserted directly, rather than against golden
142
// text, because this is the actual invariant; golden strings pass for the wrong
143
// reasons and fail for cosmetic ones.
144
static void
145
check_tiles(const code_map_line_t *lines, int n, const char *what)
146
{
147
	for (int i = 0; i < n; i++) {
148
		if (lines[i].size < 1) {
149
			printf("      line %d at $%04X has size %u\n",
150
			       i, (unsigned)lines[i].addr, (unsigned)lines[i].size);
151
			check(false, what);
152
			return;
153
		}
154
		if (i == 0) {
155
			continue;
156
		}
157
		uint16_t end = (uint16_t)((lines[i - 1].addr + lines[i - 1].size) & 0xFFFF);
158
		if (lines[i].addr != end) {
159
			printf("      %s: line %d $%04X+%u ends at $%04X, line %d starts at $%04X\n",
160
			       (uint16_t)(lines[i].addr - end) < 0x8000 ? "gap" : "overlap",
161
			       i - 1, (unsigned)lines[i - 1].addr, (unsigned)lines[i - 1].size,
162
			       (unsigned)end, i, (unsigned)lines[i].addr);
163
			check(false, what);
164
			return;
165
		}
166
	}
167
	check(n >= 1, what);
168
}
169
170
static void
171
reset_all(void)
172
{
173
	code_map_reset();
174
	memset(g_mem, 0xEA, sizeof(g_mem)); // NOP everywhere by default
175
	memset(g_bram, 0xEA, sizeof(g_bram));
176
	memset(g_brom, 0xEA, sizeof(g_brom));
177
	memset(g_gen2, 0xEA, sizeof(g_gen2));
178
	g_ram_bank         = 0;
179
	g_rom_bank         = 0;
180
	g_low_mem_windowed = 0;
181
	memset(&regs, 0, sizeof(regs));
182
	regs.status = FLAG_MEMORY_WIDTH | FLAG_INDEX_WIDTH; // 8-bit A/X/Y
183
}
184
185
// Write into a specific banked window, so a test can put different code at the
186
// same address in different banks. Addresses are absolute; which array they
187
// land in follows the same split real_read6502() uses.
188
static void
189
poke_banked(uint16_t addr, uint8_t window_bank, const uint8_t *bytes, size_t n)
190
{
191
	for (size_t i = 0; i < n; i++) {
192
		uint16_t a = (uint16_t)((addr + i) & 0xFFFF);
193
		if (a >= 0xC000) {
194
			g_brom[window_bank % TEST_ROM_BANKS][a - 0xC000] = bytes[i];
195
		} else if (a >= 0xA000) {
196
			g_bram[window_bank % TEST_RAM_BANKS][a - 0xA000] = bytes[i];
197
		} else {
198
			g_mem[a] = bytes[i];
199
		}
200
	}
201
}
202
203
// Write through whichever window the live bank registers currently select, so
204
// a plain poke and a plain read agree without the test naming a bank.
205
static void
206
poke(uint16_t addr, const uint8_t *bytes, size_t n)
207
{
208
	for (size_t i = 0; i < n; i++) {
209
		uint16_t a = (uint16_t)((addr + i) & 0xFFFF);
210
		poke_banked(a, a >= 0xC000 ? g_rom_bank : g_ram_bank, &bytes[i], 1);
211
	}
212
}
213
214
static void
215
poke_gen2(uint8_t cpu_bank, uint16_t addr, const uint8_t *bytes, size_t n)
216
{
217
	for (size_t i = 0; i < n; i++) {
218
		g_gen2[cpu_bank % TEST_GEN2_BANKS][(addr + i) & 0xFFFF] = bytes[i];
219
	}
220
}
221
222
int
223
main(void)
224
{
225
	// ── Coverage recording ──────────────────────────────────────────────────
226
	{
227
		reset_all();
228
		check(!code_map_is_recorded_start(0x8000, 0, 0, 0),
229
		      "reports nothing recorded before anything runs");
230
231
		code_map_record(0x8000, 0, 0, 0, 0x34);
232
		check(code_map_is_recorded_start(0x8000, 0, 0, 0),
233
		      "remembers a recorded instruction start");
234
		check(code_map_recorded_status(0x8000, 0, 0, 0, 0xFF) == 0x34,
235
		      "returns the status captured at that instruction");
236
		check(code_map_recorded_status(0x8001, 0, 0, 0, 0xFF) == 0xFF,
237
		      "falls back for an address that never executed");
238
		check(!code_map_is_recorded_start(0x8001, 0, 0, 0),
239
		      "does not claim neighbouring addresses");
240
	}
241
242
	// ── Bank context is part of the key ─────────────────────────────────────
243
	// Coverage recorded in one bank context must not be visible in another:
244
	// the same address holds completely different code in each, so leaking a
245
	// recorded start across banks is exactly how the disassembler goes wrong.
246
	{
247
		reset_all();
248
		code_map_record(0xA000, 0, 5, 0, 0x20);
249
250
		check(code_map_is_recorded_start(0xA000, 0, 5, 0),
251
		      "finds the record in its own RAM bank");
252
		check(!code_map_is_recorded_start(0xA000, 0, 6, 0),
253
		      "keeps RAM banks apart");
254
255
		code_map_record(0xC000, 0, 0, 1, 0x20);
256
		check(code_map_is_recorded_start(0xC000, 0, 0, 1),
257
		      "finds the record in its own ROM bank");
258
		check(!code_map_is_recorded_start(0xC000, 0, 0, 2),
259
		      "keeps ROM banks apart");
260
261
		// The 65C816 program bank belongs in the key on a Gen2 machine, where a
262
		// non-zero program bank maps its whole 64K as flat RAM: $01:8000 and
263
		// $00:8000 are then different memory, and sharing coverage between them
264
		// would hand the disassembler another bank's boundaries and widths.
265
		reset_all();
266
		is_gen2 = true;
267
		code_map_record(0x8000, 0, 0, 0, 0x30);
268
		check(code_map_is_recorded_start(0x8000, 0, 0, 0),
269
		      "finds the record in program bank 0");
270
		check(!code_map_is_recorded_start(0x8000, 1, 0, 0),
271
		      "keeps Gen2 program banks apart");
272
273
		code_map_record(0x8000, 1, 0, 0, 0x00);
274
		check(code_map_recorded_status(0x8000, 0, 0, 0, 0xFF) == 0x30 &&
275
		          code_map_recorded_status(0x8000, 1, 0, 0, 0xFF) == 0x00,
276
		      "keeps a separate status per Gen2 program bank");
277
278
		// A non-zero Gen2 program bank maps flat RAM, so the window registers
279
		// select nothing there and must not fragment the key.
280
		check(code_map_is_recorded_start(0x8000, 1, 7, 3),
281
		      "ignores the window banks inside a Gen2 program bank");
282
		code_map_record(0xC000, 1, 0, 0, 0x30);
283
		check(code_map_is_recorded_start(0xC000, 1, 0, 9),
284
		      "ignores the ROM bank inside a Gen2 program bank");
285
286
		// On gen1 the program bank selects nothing at all -- read6502() forces
287
		// it to zero even with -c816 -- so the same address is the same memory
288
		// and must share coverage rather than being filed twice.
289
		reset_all();
290
		is_gen2 = false;
291
		code_map_record(0x8000, 0, 0, 0, 0x30);
292
		check(code_map_is_recorded_start(0x8000, 1, 0, 0),
293
		      "shares coverage across program banks when they select nothing");
294
	}
295
296
	// ── Coverage follows the memory, not the bank registers ─────────────────
297
	// Only part of the address space is banked. Filing unbanked low memory
298
	// under the live (RAM, ROM) bank pair would record the same code once per
299
	// bank combination -- exhausting the context cache during ROM boot -- and
300
	// then fail to find it again after the program switched banks, throwing
301
	// away the ground truth the whole file exists to collect.
302
	{
303
		reset_all();
304
		code_map_record(0x0801, 0, 0, 0, 0x30);
305
306
		check(code_map_is_recorded_start(0x0801, 0, 7, 3),
307
		      "finds low-memory coverage under different banks");
308
		check(code_map_recorded_status(0x0801, 0, 7, 3, 0xFF) == 0x30,
309
		      "keeps the recorded status for low memory across banks");
310
311
		// A whole sweep of bank settings must not create a context each.
312
		for (int b = 0; b < 64; b++) {
313
			code_map_record(0x0900, 0, (uint8_t)b, (uint8_t)(b & 31), 0x30);
314
		}
315
		check(code_map_is_recorded_start(0x0801, 0, 0, 0),
316
		      "does not evict low-memory coverage while banks churn");
317
318
		// Banked windows still have to be told apart, though.
319
		reset_all();
320
		code_map_record(0xA000, 0, 5, 0, 0x30);
321
		check(code_map_is_recorded_start(0xA000, 0, 5, 9),
322
		      "ignores the ROM bank for a RAM-window address");
323
		check(!code_map_is_recorded_start(0xA000, 0, 6, 0),
324
		      "still separates RAM banks in the RAM window");
325
326
		code_map_record(0xC000, 0, 0, 2, 0x30);
327
		check(code_map_is_recorded_start(0xC000, 0, 9, 2),
328
		      "ignores the RAM bank for a ROM-window address");
329
		check(!code_map_is_recorded_start(0xC000, 0, 0, 3),
330
		      "still separates ROM banks in the ROM window");
331
	}
332
333
	// ── Reset ───────────────────────────────────────────────────────────────
334
	// Anchors describe the code that *was* there. After a reset it may not be,
335
	// so stale coverage has to go or the disassembly aligns to a dead program.
336
	{
337
		reset_all();
338
		code_map_record(0x8000, 0, 0, 0, 0x24);
339
		code_map_record(0x9000, 0, 3, 0, 0x24);
340
		code_map_reset();
341
		check(!code_map_is_recorded_start(0x8000, 0, 0, 0) &&
342
		          !code_map_is_recorded_start(0x9000, 0, 3, 0),
343
		      "forgets all coverage on reset");
344
	}
345
346
	// ── Context recycling ───────────────────────────────────────────────────
347
	// Only a bounded number of bank contexts are kept. Once that is exceeded the
348
	// least-recently-used one is recycled, and it must come back *empty* rather
349
	// than carrying the previous tenant's coverage.
350
	{
351
		reset_all();
352
		for (int b = 0; b < 40; b++) {
353
			code_map_record((uint16_t)(0xA000 + b), 0, (uint8_t)b, 0, 0x20);
354
			// A marker at the same address in every context, so the check below
355
			// does not depend on guessing which slot gets recycled.
356
			code_map_record(0xA0FF, 0, (uint8_t)b, 0, 0x20);
357
		}
358
		check(code_map_is_recorded_start(0xA027, 0, 39, 0),
359
		      "keeps the most recent context after recycling");
360
361
		// Whichever early context was evicted must not report stale coverage.
362
		int stale = 0;
363
		for (int b = 0; b < 40; b++) {
364
			if (code_map_is_recorded_start((uint16_t)(0xA000 + b), 0, (uint8_t)b, 0)) {
365
				continue;
366
			}
367
			stale++;
368
		}
369
		check(stale > 0, "recycles contexts once the cap is reached");
370
371
		// Re-recording into a recycled context must see a clean slate: the
372
		// marker its previous tenant left behind must be gone.
373
		code_map_record(0xB000, 0, 0, 0, 0x20);
374
		check(code_map_is_recorded_start(0xB000, 0, 0, 0),
375
		      "records into a recycled context");
376
		check(!code_map_is_recorded_start(0xA0FF, 0, 0, 0),
377
		      "a recycled context does not resurrect old coverage");
378
	}
379
380
	// ── Backward anchoring ──────────────────────────────────────────────────
381
	// Finding the instruction *before* an address is the hard direction: you
382
	// cannot decode backwards. Recorded coverage is the ground truth that makes
383
	// it exact.
384
	{
385
		reset_all();
386
		// $8000: LDA #$00   (A9 00)   2 bytes
387
		// $8002: NOP        (EA)      1 byte
388
		const uint8_t code[] = { 0xA9, 0x00, 0xEA };
389
		poke(0x8000, code, sizeof(code));
390
391
		code_map_record(0x8000, 0, 0, 0, regs.status);
392
		code_map_record(0x8002, 0, 0, 0, regs.status);
393
394
		check(code_map_prev_instruction(0x8002, 0, 0, 0) == 0x8000,
395
		      "walks back onto a recorded instruction start");
396
397
		// With no coverage at all it still has to produce something usable, by
398
		// finding the offset that decodes to land exactly on the anchor.
399
		code_map_reset();
400
		check(code_map_prev_instruction(0x8002, 0, 0, 0) == 0x8000,
401
		      "self-syncs backwards without any coverage");
402
403
		// Coverage must actually OUTRANK self-sync, not merely agree with it.
404
		// Here two candidates both decode onto the anchor at $8003:
405
		//   $8001 LDA #$EA (A9 EA) -- 2 bytes, and what really executed
406
		//   $8002 NOP     (EA)     -- 1 byte, the operand byte misread as code
407
		// Self-sync tries the smallest backoff first and so picks $8002; only
408
		// the recorded start can get this right.
409
		reset_all();
410
		const uint8_t amb[] = { 0xA9, 0xEA, 0xEA };
411
		poke(0x8001, amb, sizeof(amb));
412
		check(code_map_prev_instruction(0x8003, 0, 0, 0) == 0x8002,
413
		      "self-sync alone picks the nearer decoy");
414
		code_map_record(0x8001, 0, 0, 0, regs.status);
415
		check(code_map_prev_instruction(0x8003, 0, 0, 0) == 0x8001,
416
		      "recorded coverage outranks the self-sync guess");
417
	}
418
419
	// ── Forward disassembly ─────────────────────────────────────────────────
420
	{
421
		reset_all();
422
		// $8000: LDA #$12   (A9 12)
423
		// $8002: STA $1234  (8D 34 12)
424
		// $8005: NOP        (EA)
425
		const uint8_t code[] = { 0xA9, 0x12, 0x8D, 0x34, 0x12, 0xEA };
426
		poke(0x8000, code, sizeof(code));
427
428
		code_map_line_t lines[8];
429
		uint16_t        next = 0;
430
		int             n    = code_map_disasm_forward(0x8000, 0, 0, 0, 3, lines, 8, &next);
431
432
		check(n == 3, "disassembles the requested number of instructions");
433
		check(n == 3 && lines[0].addr == 0x8000 && lines[0].size == 2,
434
		      "sizes an immediate load");
435
		check(n == 3 && lines[1].addr == 0x8002 && lines[1].size == 3,
436
		      "sizes an absolute store");
437
		check(n == 3 && lines[2].addr == 0x8005 && lines[2].size == 1,
438
		      "sizes an implied instruction");
439
		check(next == 0x8006, "reports the address just past the last instruction");
440
		check(n == 3 && lines[0].bytes[0] == 0xA9 && lines[0].bytes[1] == 0x12,
441
		      "captures the raw instruction bytes");
442
		// The text must be a disassembly, not a hex dump: pin the mnemonic that
443
		// belongs to the opcode. (Asserting only that "8d" is absent cannot
444
		// fail -- the mnemonics are alphabetic and the operand here is $1234.)
445
		check(n == 3 && strstr(lines[1].text, "sta") != NULL &&
446
		          strstr(lines[1].text, "$1234") != NULL,
447
		      "renders the mnemonic and operand for the opcode it decoded");
448
449
		// Coverage is reported so a UI can distinguish "known good" from "guess".
450
		check(n == 3 && !lines[0].recorded, "marks unexecuted lines as unrecorded");
451
		code_map_record(0x8000, 0, 0, 0, regs.status);
452
		n = code_map_disasm_forward(0x8000, 0, 0, 0, 1, lines, 8, &next);
453
		check(n == 1 && lines[0].recorded, "marks executed lines as recorded");
454
	}
455
456
	// ── Windowed disassembly ────────────────────────────────────────────────
457
	{
458
		reset_all();
459
		const uint8_t code[] = { 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA };
460
		poke(0x8000, code, sizeof(code));
461
		for (uint16_t a = 0x8000; a < 0x8008; a++) {
462
			code_map_record(a, 0, 0, 0, regs.status);
463
		}
464
465
		code_map_line_t lines[16];
466
		int             center_index = -99;
467
		int             n = code_map_disasm_window(0x8004, 0, 0, 0, 3, 3, lines, 16, &center_index);
468
469
		check(n == 6, "returns the requested window size");
470
		check(center_index >= 0 && center_index < n, "reports where the center landed");
471
		check(center_index >= 0 && center_index < n && lines[center_index].addr == 0x8004,
472
		      "centers the window on the requested address");
473
		check(n >= 1 && lines[0].addr == 0x8001, "starts the window before the center");
474
475
		// A window that cannot fit must still be safe and still locate center.
476
		center_index = -99;
477
		n = code_map_disasm_window(0x8004, 0, 0, 0, 3, 3, lines, 2, &center_index);
478
		check(n == 2, "honours the output capacity");
479
		check(center_index == 1 && lines[center_index].addr == 0x8004,
480
		      "still locates the center in a window that cannot fit");
481
		check(n == 2 && lines[0].addr == 0x8003,
482
		      "spends a too-small window on the lines nearest the center");
483
	}
484
485
	// ── Recovering from a disagreement about instruction width ──────────────
486
	// The backward walk sizes candidates using the status recorded *at* each
487
	// one; the forward pass carries a propagated estimate. Propagation cannot
488
	// model PLP -- it restores a status pulled from the stack -- so here the two
489
	// disagree, and a naive forward walk steps straight over the center address
490
	// and renders every following line at the wrong offset.
491
	//
492
	//   $8000: PLP            (28)        recorded with 8-bit A
493
	//   $8001: LDA #$A9EA     (A9 EA A9)  3 bytes, because PLP cleared M
494
	//   $8004: NOP            (EA)        the center, recorded with 16-bit A
495
	//
496
	// Read with 8-bit A the LDA is only 2 bytes, so the walk lands on $8003,
497
	// then $8005 -- jumping the center entirely.
498
	{
499
		reset_all();
500
		regs.is65c816 = true;
501
		regs.e        = 0;
502
503
		const uint8_t code[] = { 0x28, 0xA9, 0xEA, 0xA9, 0xEA };
504
		poke(0x8000, code, sizeof(code));
505
506
		const uint8_t st_8bit  = FLAG_MEMORY_WIDTH | FLAG_INDEX_WIDTH;
507
		const uint8_t st_16bit = FLAG_INDEX_WIDTH; // PLP cleared M
508
509
		regs.status = st_8bit;
510
		code_map_record(0x8000, 0, 0, 0, st_8bit);
511
		code_map_record(0x8004, 0, 0, 0, st_16bit);
512
513
		code_map_line_t lines[16];
514
		int             center_index = -99;
515
		int             n = code_map_disasm_window(0x8004, 0, 0, 0, 2, 2, lines, 16, &center_index);
516
517
		check(n > 0 && center_index >= 0, "does not lose the center line to width drift");
518
		check(center_index >= 0 && center_index < n && lines[center_index].addr == 0x8004,
519
		      "re-aligns onto the center address after drifting");
520
		check(n == 4, "returns exactly the requested line count despite the drift");
521
		check(center_index == 2, "puts exactly the requested number of lines before center");
522
523
		// The width the backward walk settled on is the whole point: read with
524
		// the 8-bit status the LDA is 2 bytes and the walk lands on $8003, then
525
		// $8005, jumping the center. Only the 16-bit reading reaches $8004.
526
		check(n == 4 && lines[1].addr == 0x8001 && lines[1].size == 3,
527
		      "sizes the drifting instruction from the status that reaches center");
528
529
		// The real damage from drift is contradictory output: a line claiming
530
		// bytes that the next line also claims. Nothing downstream can render
531
		// that sensibly, so the window must never emit it.
532
		check_tiles(lines, n, "never emits overlapping instructions");
533
534
		regs.is65c816 = false;
535
	}
536
537
	// ── The one-byte fallback must not overlap the line after it ────────────
538
	// When no backoff decodes cleanly onto the anchor, the backward walk gives
539
	// up and backs off a single byte so the caller still makes progress. That
540
	// is a guess about where the previous instruction STARTS -- it is not a
541
	// decode -- so the byte sitting there can still decode wider than the one
542
	// byte the walk allowed for, and the rendered line then runs straight into
543
	// the line the caller actually asked for.
544
	//
545
	//   $7FFF: A9 ..   LDA #imm -- 2 bytes, so its operand IS $8000
546
	//   $8000: the requested center
547
	//
548
	// Everything else is NOP, so no backoff of 2..4 decodes onto $8000 either;
549
	// the walk has nothing to align to and falls back.
550
	{
551
		reset_all();
552
		g_mem[0x7FFF] = 0xA9; // LDA #imm: 2 bytes, operand at $8000
553
554
		code_map_line_t lines[8];
555
		int             center_index = -99;
556
		int             n = code_map_disasm_window(0x8000, 0, 0, 0, 1, 2, lines, 8, &center_index);
557
558
		check(n >= 2 && lines[0].addr == 0x7FFF,
559
		      "backs off a single byte when nothing decodes onto the anchor");
560
		check(center_index >= 0 && center_index < n && lines[center_index].addr == 0x8000,
561
		      "keeps the center where the caller asked despite the fallback");
562
		check_tiles(lines, n, "the one-byte fallback tiles the range instead of overlapping");
563
	}
564
565
	// ── Truncating onto an interior anchor must not leave a hole ────────────
566
	// Overlapping-but-legal instruction starts are ordinary on this CPU. The
567
	// `.byte $2C` skip idiom hides a real entry point inside a BIT abs operand,
568
	// so the BIT and the byte after it are BOTH genuine instruction starts, and
569
	// live execution can record either.
570
	//
571
	//   $8002: 2C 8D 12   BIT $128D -- 3 bytes, lands exactly on $8005
572
	//   $8003: 8D ..      STA abs   -- the hidden entry point, recorded
573
	//
574
	// The inner start is not itself a candidate (STA abs from $8003 runs to
575
	// $8006, past the anchor), so the backward walk picks the BIT. The fill then
576
	// truncates the BIT onto the recorded start at $8003 -- correctly, an anchor
577
	// outranks a decode -- and the two operand bytes end up described by no line
578
	// at all unless the truncation is followed up.
579
	{
580
		reset_all();
581
		const uint8_t skip_idiom[] = { 0x2C, 0x8D, 0x12 };
582
		poke(0x8002, skip_idiom, sizeof(skip_idiom));
583
		code_map_record(0x8003, 0, 0, 0, regs.status);
584
585
		code_map_line_t lines[8];
586
		int             center_index = -99;
587
		int             n = code_map_disasm_window(0x8005, 0, 0, 0, 1, 2, lines, 8, &center_index);
588
589
		check(n >= 2 && lines[0].addr == 0x8002,
590
		      "walks back onto the wider of two overlapping starts");
591
		check(center_index >= 0 && center_index < n && lines[center_index].addr == 0x8005,
592
		      "still centers on the requested address after truncating");
593
		check_tiles(lines, n, "truncating onto an interior anchor leaves no gap");
594
		check_line_flags(lines, n, "keeps recorded/kind/start_recorded consistent");
595
596
		// Tiling alone would also be satisfied by simply not truncating, so
597
		// pin the intent: the recorded start at $8003 is ground truth and must
598
		// begin a line of its own, and the line cut short to reach it must not
599
		// still claim to be a whole verified instruction.
600
		bool starts_at_8003 = false;
601
		for (int i = 0; i < n; i++) {
602
			if (lines[i].addr == 0x8003) {
603
				starts_at_8003 = true;
604
			}
605
		}
606
		check(starts_at_8003, "begins a line at the recorded start inside the BIT");
607
		check(n >= 1 && lines[0].size == 1 && !lines[0].recorded && lines[0].eff_addr == -1,
608
		      "a line cut short is not reported as a whole verified instruction");
609
		// size, bytes and text must agree: a row cut short is rendered as the
610
		// data bytes it actually owns, not as an instruction whose mnemonic
611
		// describes bytes the next row claims.
612
		check(n >= 1 && lines[0].bytes[0] == 0x2C &&
613
		          strcmp(lines[0].text, ".byte $2c") == 0,
614
		      "renders a cut-short line as the bytes it owns");
615
		// The row kind says what the row IS, so a consumer need not parse the
616
		// text to find out; start_recorded still says the address was executed,
617
		// which `recorded` cannot express once the row became data.
618
		check(n >= 1 && lines[0].kind == CM_LINE_DATA,
619
		      "marks a cut-short line as data rather than an instruction");
620
		check(center_index >= 0 && center_index < n &&
621
		          lines[center_index].kind == CM_LINE_INSTRUCTION,
622
		      "marks a whole line as an instruction");
623
624
		// Capacity is the case that used to lose the center outright: phase B
625
		// now needs two lines where the walk resolved one, and with room for
626
		// only two lines a naive fill spends both and drops $8005 entirely.
627
		center_index = -99;
628
		int tight = code_map_disasm_window(0x8005, 0, 0, 0, 1, 2, lines, 2, &center_index);
629
		check(tight >= 1 && center_index >= 0 && center_index < tight &&
630
		          lines[center_index].addr == 0x8005,
631
		      "keeps the center line when phase B cannot fit as well");
632
		check_tiles(lines, tight, "tiles what fits when capacity runs out");
633
634
		// ...and it must spend the one remaining slot on the row NEAREST the
635
		// center. A group that needs two lines and has room for one can keep
636
		// its last line ($8003) and simply start the window later; dropping the
637
		// whole group instead throws away context the caller has room for.
638
		check(tight == 2 && center_index == 1 && lines[0].addr == 0x8003,
639
		      "keeps the part of a too-large group closest to the center");
640
		// That row is the recorded start at $8003 cut short by the boundary, so
641
		// it reports as data while still saying its address was executed --
642
		// "never ran" and "ran, but cannot be shown whole here" are different
643
		// things, and `recorded` alone cannot say which.
644
		check(tight == 2 && lines[0].kind == CM_LINE_DATA && lines[0].start_recorded &&
645
		          !lines[0].recorded,
646
		      "a row cut by a boundary still reports its start as executed");
647
	}
648
649
	// ── Two overlapping starts that are BOTH ground truth ───────────────────
650
	// The `.byte $2C` skip idiom really can have both starts executed: entering
651
	// at the BIT swallows the next two bytes, entering at the hidden address
652
	// runs the instruction they spell. When only the inner one is recorded, the
653
	// outer decode is a guess and gives way to it (above). When BOTH are
654
	// recorded they are evidence of the same rank, and the caller asked for one
655
	// of them specifically -- clipping it there would throw away a whole known
656
	// instruction to show a different execution path than the one requested.
657
	//
658
	//   $8002: 2C 8D 12   BIT $128D   recorded -- the skip
659
	//   $8003:    8D 12 EA  STA $EA12  recorded -- the hidden entry point
660
	{
661
		reset_all();
662
		const uint8_t skip_idiom[] = { 0x2C, 0x8D, 0x12 };
663
		poke(0x8002, skip_idiom, sizeof(skip_idiom));
664
		code_map_record(0x8002, 0, 0, 0, regs.status);
665
		code_map_record(0x8003, 0, 0, 0, regs.status);
666
667
		code_map_line_t lines[8];
668
		uint16_t        next = 0;
669
670
		int n = code_map_disasm_forward(0x8002, 0, 0, 0, 2, lines, 8, &next);
671
		check(n == 2 && lines[0].addr == 0x8002 && lines[0].size == 3 &&
672
		          lines[0].recorded,
673
		      "emits a recorded instruction whole over a recorded start inside it");
674
		check(n == 2 && strstr(lines[0].text, "bit") != NULL,
675
		      "keeps the mnemonic of a recorded instruction spanning another start");
676
		check(n == 2 && lines[1].addr == 0x8005,
677
		      "continues past a swallowed start on the path the caller asked for");
678
		check_tiles(lines, n, "tiles the path entered at the outer start");
679
680
		// Entering at the hidden address gives the other real path, whole.
681
		n = code_map_disasm_forward(0x8003, 0, 0, 0, 2, lines, 8, &next);
682
		check(n == 2 && lines[0].addr == 0x8003 && lines[0].size == 3 &&
683
		          lines[0].recorded && strstr(lines[0].text, "sta") != NULL,
684
		      "emits the hidden entry point whole when entered there");
685
		check_tiles(lines, n, "tiles the path entered at the hidden start");
686
687
		// A guessed width still gives way, so the protection above cannot be
688
		// used to step over ground truth.
689
		code_map_reset();
690
		code_map_record(0x8003, 0, 0, 0, regs.status);
691
		n = code_map_disasm_forward(0x8002, 0, 0, 0, 2, lines, 8, &next);
692
		check(n == 2 && lines[0].size == 1 && !lines[0].recorded,
693
		      "an unrecorded decode still gives way to a recorded start inside it");
694
695
		// A row forced to data by a window boundary still reports that its
696
		// address was executed -- "never ran" and "ran, but cannot be shown
697
		// whole on this path" are different things, and `recorded` alone
698
		// cannot say which.
699
		reset_all();
700
		poke(0x8002, skip_idiom, sizeof(skip_idiom));
701
		code_map_record(0x8002, 0, 0, 0, regs.status);
702
		code_map_record(0x8003, 0, 0, 0, regs.status);
703
704
		int             center_index = -99;
705
		code_map_line_t win[8];
706
		int             wn = code_map_disasm_window(0x8005, 0, 0, 0, 1, 1, win, 8, &center_index);
707
		check(wn >= 2 && win[0].addr == 0x8002 && win[0].size == 3 &&
708
		          win[0].kind == CM_LINE_INSTRUCTION && win[0].start_recorded,
709
		      "the window shows the outer instruction whole when both are recorded");
710
		check_tiles(win, wn, "tiles when both overlapping starts are recorded");
711
712
		// Centering on each overlapping start gives that start's own path.
713
		center_index = -99;
714
		wn = code_map_disasm_window(0x8002, 0, 0, 0, 0, 2, win, 8, &center_index);
715
		check(wn >= 1 && center_index == 0 && win[0].addr == 0x8002 &&
716
		          win[0].size == 3 && win[0].kind == CM_LINE_INSTRUCTION,
717
		      "centering on the outer start shows the outer instruction whole");
718
		center_index = -99;
719
		wn = code_map_disasm_window(0x8003, 0, 0, 0, 0, 2, win, 8, &center_index);
720
		check(wn >= 1 && center_index == 0 && win[0].addr == 0x8003 &&
721
		          win[0].size == 3 && win[0].kind == CM_LINE_INSTRUCTION,
722
		      "centering on the inner start shows the inner instruction whole");
723
	}
724
725
	// ── KNOWN LIMITATION: a stale anchor that kept its opcode byte ──────────
726
	// Anchors are validated by comparing one byte -- the opcode that was
727
	// executing -- against memory. Replacement code that happens to repeat that
728
	// byte at the same address keeps the old anchor, and with it the old
729
	// recorded STATUS. On a 65C816 that status can imply a different operand
730
	// width, so the stale line decodes wider than the new code really is, and
731
	// the exemption above then lets it swallow a genuinely fresh anchor inside
732
	// it.
733
	//
734
	//   old: $8000  A9 11 22   LDA #$2211  recorded with 16-bit A (3 bytes)
735
	//   new: $8000  A9 EA      LDA #$EA    8-bit A, and $8002 freshly executed
736
	//
737
	// The opcode byte is still $A9, so the stale anchor survives and wins.
738
	//
739
	// This pins the CURRENT behaviour so the limitation is visible rather than
740
	// folklore. Note carefully what it is NOT: the recorded state here is
741
	// indistinguishable from a routine legitimately executed at two different
742
	// widths, where emitting the 3-byte LDA is exactly right. Nothing derivable
743
	// from the anchors AS RECORDED -- one opcode byte -- can separate the two,
744
	// which is why a "prefer the newer anchor" rule would not fix this; it
745
	// would only trade this case for that one. A wider staleness check would
746
	// catch this particular fixture, whose operand bytes do differ, but not the
747
	// case where the replacement bytes are identical; only invalidating anchors
748
	// on the write that replaced the code closes it in general. See
749
	// docs/code-map-width-propagation.md. If you fix it, this check should
750
	// fail -- but only for a fix INSIDE code_map (a wider staleness check, a
751
	// stored length, an epoch). A write-invalidation fix would not be reached
752
	// from here at all, because poke() writes the test's memory directly and
753
	// code_map has no write-notification entry point; route poke() through
754
	// whatever hook you add, or this tripwire will stay green while the note
755
	// above it goes stale. Update these checks, do not delete them.
756
	{
757
		reset_all();
758
		regs.is65c816 = true;
759
		regs.e        = 0;
760
761
		// The old program runs here with a 16-bit accumulator, so its LDA is
762
		// three bytes and the anchor records both that opcode and that status.
763
		const uint8_t old_code[] = { 0xA9, 0x11, 0x22, 0x33 };
764
		poke(0x8000, old_code, sizeof(old_code));
765
		code_map_record(0x8000, 0, 0, 0, FLAG_INDEX_WIDTH);
766
767
		// An overlay replaces the code. The replacement happens to begin with
768
		// $A9 as well, so the one-byte staleness check cannot see the change
769
		// and the old anchor -- with its 16-bit status -- survives.
770
		const uint8_t new_code[] = { 0xA9, 0xEA, 0xEA, 0xEA };
771
		poke(0x8000, new_code, sizeof(new_code));
772
		code_map_record(0x8002, 0, 0, 0, FLAG_MEMORY_WIDTH | FLAG_INDEX_WIDTH);
773
		regs.status = FLAG_MEMORY_WIDTH | FLAG_INDEX_WIDTH;
774
775
		code_map_line_t lines[4];
776
		uint16_t        next = 0;
777
		int             n    = code_map_disasm_forward(0x8000, 0, 0, 0, 2, lines, 4, &next);
778
779
		check(n == 2 && lines[0].size == 3,
780
		      "KNOWN LIMITATION: a stale same-opcode anchor still swallows a fresh one");
781
		check(n == 2 && lines[1].addr == 0x8003,
782
		      "KNOWN LIMITATION: the line after it starts past the fresh anchor");
783
784
		regs.is65c816 = false;
785
	}
786
787
	// The same swallowing happens on a 65C02, where no width is involved at
788
	// all: it needs only a stale anchor whose opcode byte survived. Here the
789
	// old code's BIT is still $2C, so the anchor is believed, is exempt from
790
	// the interior clamp, and covers the fresh start the new code has at
791
	// $8001. Only the WRONG-WIDTH flavour above is 65C816-specific.
792
	{
793
		reset_all(); // regs.is65c816 stays false
794
795
		const uint8_t old_bit[] = { 0x2C, 0x8D, 0x12 }; // bit $128d
796
		poke(0x8000, old_bit, sizeof(old_bit));
797
		code_map_record(0x8000, 0, 0, 0, regs.status);
798
799
		// Overlay: same $2C opcode byte, but the new program really starts an
800
		// instruction at $8001.
801
		const uint8_t new_bit[] = { 0x2C, 0xA9, 0xEA };
802
		poke(0x8000, new_bit, sizeof(new_bit));
803
		code_map_record(0x8001, 0, 0, 0, regs.status);
804
805
		code_map_line_t lines[4];
806
		uint16_t        next = 0;
807
		int             n    = code_map_disasm_forward(0x8000, 0, 0, 0, 2, lines, 4, &next);
808
809
		check(n == 2 && lines[0].size == 3 && lines[1].addr == 0x8003,
810
		      "KNOWN LIMITATION: stale-anchor swallowing is not 65C816-specific");
811
	}
812
813
	// ── Recorded starts are respected while decoding forward ────────────────
814
	// A wrong width estimate can size an instruction so that it swallows an
815
	// address live execution proved was an instruction start. The recorded
816
	// anchor is the stronger evidence, so the decode has to give way to it.
817
	{
818
		reset_all();
819
		regs.is65c816 = true;
820
		regs.e        = 0;
821
822
		// $8000: LDA #$xx -- 2 bytes with 8-bit A, 3 bytes with 16-bit A.
823
		// $8002 is recorded, so a 3-byte read would step straight over it.
824
		const uint8_t code[] = { 0xA9, 0xEA, 0xEA, 0xEA };
825
		poke(0x8000, code, sizeof(code));
826
		regs.status = FLAG_INDEX_WIDTH; // 16-bit A: LDA # decodes as 3 bytes
827
		code_map_record(0x8002, 0, 0, 0, FLAG_INDEX_WIDTH);
828
829
		code_map_line_t lines[4];
830
		uint16_t        next = 0;
831
		int             n    = code_map_disasm_forward(0x8000, 0, 0, 0, 2, lines, 4, &next);
832
		check(n == 2 && lines[0].size == 2,
833
		      "truncates an instruction that would swallow a recorded start");
834
		check(n == 2 && lines[1].addr == 0x8002,
835
		      "lands on the recorded start instead of stepping over it");
836
837
		regs.is65c816 = false;
838
	}
839
840
	// ── Anchors do not outlive the code they describe ───────────────────────
841
	// Nothing removes an anchor when memory changes, and on this machine memory
842
	// under code changes constantly: a second program is loaded over the first,
843
	// an overlay is swapped in, a decompressor unpacks over its own loader. A
844
	// stale anchor is worse than none, because live-execution evidence outranks
845
	// the .dbg spans that would have been right -- it can confidently place a
846
	// boundary in the middle of a real instruction.
847
	{
848
		reset_all();
849
		// Program 1 runs: $8000 is genuinely an instruction start.
850
		const uint8_t prog1[] = { 0xA9, 0x00, 0xEA };
851
		poke(0x8000, prog1, sizeof(prog1));
852
		code_map_record(0x8000, 0, 0, 0, regs.status);
853
		code_map_record(0x8002, 0, 0, 0, regs.status);
854
		check(code_map_is_recorded_start(0x8002, 0, 0, 0),
855
		      "trusts an anchor while its code is still there");
856
857
		// Program 2 is loaded over it. $8002 is now the middle of a 3-byte
858
		// instruction, not a start.
859
		const uint8_t prog2[] = { 0xEA, 0x8D, 0x34, 0x12 };
860
		poke(0x8000, prog2, sizeof(prog2));
861
		check(!code_map_is_recorded_start(0x8002, 0, 0, 0),
862
		      "drops an anchor once the code under it changed");
863
		check(code_map_recorded_status(0x8002, 0, 0, 0, 0xC3) == 0xC3,
864
		      "does not report a stale anchor's status");
865
866
		// $8000 still holds an instruction start, but a different instruction,
867
		// so its anchor must go too rather than claim the old width.
868
		check(!code_map_is_recorded_start(0x8000, 0, 0, 0),
869
		      "drops an anchor whose opcode was replaced");
870
871
		// A stale anchor must not truncate a correct instruction either: the
872
		// STA at $8001 is 3 bytes and spans the dead anchor at $8002.
873
		code_map_line_t lines[4];
874
		uint16_t        next = 0;
875
		int             n    = code_map_disasm_forward(0x8001, 0, 0, 0, 1, lines, 4, &next);
876
		check(n == 1 && lines[0].size == 3,
877
		      "a stale anchor does not cut a real instruction short");
878
879
		// Re-running the new code re-establishes trust.
880
		code_map_record(0x8001, 0, 0, 0, regs.status);
881
		check(code_map_is_recorded_start(0x8001, 0, 0, 0),
882
		      "trusts the new code once it has actually run");
883
	}
884
885
	// ── A width guess cannot run on past the next anchor ────────────────────
886
	// PLP restores a status byte from the stack, so what it does to the M/X
887
	// widths is unknowable until it runs. The estimate after it is therefore
888
	// wrong, and the point of anchoring is that it stays wrong only until the
889
	// next piece of live evidence -- which re-establishes both the instruction
890
	// boundary and the width. This pins that self-correction.
891
	{
892
		reset_all();
893
		regs.is65c816 = true;
894
		regs.e        = 0;
895
896
		//   $8000: PLP        (28)        recorded with 8-bit A
897
		//   $8001: LDA #$A9EA (A9 EA A9)  really 3 bytes: PLP cleared M
898
		//   $8004: NOP        (EA)        recorded with 16-bit A -- the anchor
899
		const uint8_t code[] = { 0x28, 0xA9, 0xEA, 0xA9, 0xEA };
900
		poke(0x8000, code, sizeof(code));
901
		const uint8_t st_8bit  = FLAG_MEMORY_WIDTH | FLAG_INDEX_WIDTH;
902
		const uint8_t st_16bit = FLAG_INDEX_WIDTH;
903
		regs.status = st_8bit;
904
		code_map_record(0x8000, 0, 0, 0, st_8bit);
905
		code_map_record(0x8004, 0, 0, 0, st_16bit);
906
907
		code_map_line_t lines[8];
908
		uint16_t        next = 0;
909
		int             n    = code_map_disasm_forward(0x8000, 0, 0, 0, 4, lines, 8, &next);
910
911
		// The guessed line is reported as a guess, so a UI can say so.
912
		check(n >= 2 && lines[0].recorded, "reports the anchored line as recorded");
913
		check(n >= 2 && !lines[1].recorded,
914
		      "reports a line decoded from a guessed width as unrecorded");
915
916
		// And the guess does not run on: the anchor at $8004 is reached rather
917
		// than stepped over, so decoding is right again from there.
918
		check(n == 4 && lines[3].addr == 0x8004,
919
		      "re-syncs onto the next anchor after a bad width guess");
920
		check(n == 4 && lines[3].recorded && lines[3].status == st_16bit,
921
		      "recovers the real width from the anchor it landed on");
922
923
		regs.is65c816 = false;
924
	}
925
926
	// ── The same bound applies to RTI ───────────────────────────────────────
927
	// RTI restores a status byte the interrupt pushed, so like PLP it changes
928
	// the M/X widths by an amount no static analysis recovers -- and unlike
929
	// PLP there is not even a matching push in the instruction stream to walk
930
	// back to. cm_propagate() therefore leaves the estimate alone for it.
931
	//
932
	// This pins the two things that make that safe, and it is deliberately
933
	// written so that "fixing" RTI by inventing a plausible width would fail
934
	// it: the line decoded from the stale estimate must be reported as a guess,
935
	// and the next anchor must be reached rather than stepped over.
936
	//
937
	//   $8000: 40         RTI       recorded with 8-bit A
938
	//   $8001: A9 EA A9   LDA #...  2 bytes on the stale estimate, 3 in truth
939
	//   $8004: EA         NOP       recorded with 16-bit A -- the anchor
940
	{
941
		reset_all();
942
		regs.is65c816 = true;
943
		regs.e        = 0;
944
945
		const uint8_t code[] = { 0x40, 0xA9, 0xEA, 0xA9, 0xEA };
946
		poke(0x8000, code, sizeof(code));
947
		const uint8_t st_8bit  = FLAG_MEMORY_WIDTH | FLAG_INDEX_WIDTH;
948
		const uint8_t st_16bit = FLAG_INDEX_WIDTH;
949
		regs.status = st_8bit;
950
		code_map_record(0x8000, 0, 0, 0, st_8bit);
951
		code_map_record(0x8004, 0, 0, 0, st_16bit);
952
953
		code_map_line_t lines[8];
954
		uint16_t        next = 0;
955
		int             n    = code_map_disasm_forward(0x8000, 0, 0, 0, 4, lines, 8, &next);
956
957
		check(n >= 2 && lines[0].recorded && lines[0].size == 1,
958
		      "decodes RTI itself from its own anchor");
959
960
		// The contract: cm_propagate leaves the estimate alone for RTI, so the
961
		// next line is decoded on the pre-RTI width. Pinned explicitly, because
962
		// substituting a plausible width is the tempting "fix" and it would be
963
		// a guess dressed up as knowledge.
964
		check(n >= 2 && lines[1].addr == 0x8001 && lines[1].size == 2 &&
965
		          lines[1].status == st_8bit,
966
		      "leaves the width estimate alone across RTI instead of guessing");
967
968
		// The two properties that make leaving it alone acceptable: the guess
969
		// is labelled as one, and it cannot run on -- the next anchor is
970
		// reached rather than stepped over, and it restores the real width.
971
		check(n >= 2 && !lines[1].recorded,
972
		      "reports a line decoded across RTI as a guess, not as known");
973
		bool resynced = false;
974
		for (int i = 0; i < n; i++) {
975
			if (lines[i].addr == 0x8004 && lines[i].recorded && lines[i].status == st_16bit) {
976
				resynced = true;
977
			}
978
		}
979
		check(resynced, "reaches the anchor after RTI and recovers the real width");
980
		check_tiles(lines, n, "tiles the range either side of an RTI");
981
982
		regs.is65c816 = false;
983
	}
984
	// ── Reads go through the window that backs each address ─────────────────
985
	// Every read code_map makes has to name the bank that actually backs the
986
	// address: the ROM bank above $C000, the RAM bank in $A000-$BFFF, and the
987
	// live registers below that. Get it wrong and the disassembler decodes some
988
	// other bank's bytes, which is indistinguishable from garbage.
989
	//
990
	// The live bank registers are deliberately pointed somewhere else here, with
991
	// different code in them, so selecting the wrong window shows up as the
992
	// wrong instruction rather than passing by coincidence.
993
	{
994
		reset_all();
995
		g_ram_bank = 0x11; // live registers disagree with the requested banks
996
		g_rom_bank = 0x17;
997
998
		// What the caller asks for.
999
		const uint8_t ram5[]  = { 0xA9, 0x12 };       // lda #$12
1000
		const uint8_t rom2[]  = { 0x8D, 0x34, 0x12 }; // sta $1234
1001
		// Decoys of a different length sitting in the live banks.
1002
		const uint8_t decoy[] = { 0xEA, 0xEA, 0xEA }; // nop nop nop
1003
		poke_banked(0xA000, 5, ram5, sizeof(ram5));
1004
		poke_banked(0xC000, 2, rom2, sizeof(rom2));
1005
		poke_banked(0xA000, 0x11, decoy, sizeof(decoy));
1006
		poke_banked(0xC000, 0x17, decoy, sizeof(decoy));
1007
1008
		code_map_line_t lines[4];
1009
		uint16_t        next = 0;
1010
1011
		int n = code_map_disasm_forward(0xA000, 0, 5, 2, 1, lines, 4, &next);
1012
		check(n == 1 && lines[0].size == 2 && lines[0].bytes[0] == 0xA9 &&
1013
		          lines[0].bytes[1] == 0x12,
1014
		      "reads a $A000 address through the requested RAM bank");
1015
1016
		n = code_map_disasm_forward(0xC000, 0, 5, 2, 1, lines, 4, &next);
1017
		check(n == 1 && lines[0].size == 3 && lines[0].bytes[0] == 0x8D &&
1018
		          lines[0].bytes[2] == 0x12,
1019
		      "reads a $C000 address through the requested ROM bank");
1020
1021
		// Low memory is not banked at all, so code_map must ask for "current
1022
		// bank" there rather than invent a window for it.
1023
		g_low_mem_windowed = 0;
1024
		n = code_map_disasm_forward(0x0801, 0, 5, 2, 4, lines, 4, &next);
1025
		check(n == 4 && g_low_mem_windowed == 0,
1026
		      "does not put a window bank on an unbanked low-memory read");
1027
1028
		// An instruction may straddle the $BFFF/$C000 boundary, and then each
1029
		// half lives behind a different window. The bytes shown have to follow
1030
		// the address, not the opcode.
1031
		reset_all();
1032
		g_ram_bank = 0x11;
1033
		g_rom_bank = 0x17;
1034
		const uint8_t head[] = { 0x8D };       // sta $xxxx, opcode in RAM bank 5
1035
		const uint8_t tail[] = { 0x34, 0x12 }; // its operand, in ROM bank 2
1036
		poke_banked(0xBFFF, 5, head, sizeof(head));
1037
		poke_banked(0xC000, 2, tail, sizeof(tail));
1038
		const uint8_t straddle_decoy[] = { 0x00, 0x00 };
1039
		poke_banked(0xC000, 0x17, straddle_decoy, sizeof(straddle_decoy));
1040
1041
		n = code_map_disasm_forward(0xBFFF, 0, 5, 2, 1, lines, 4, &next);
1042
		check(n == 1 && lines[0].size == 3 && lines[0].bytes[0] == 0x8D &&
1043
		          lines[0].bytes[1] == 0x34 && lines[0].bytes[2] == 0x12,
1044
		      "shows each byte of a window-straddling instruction from its own bank");
1045
		// NOT COVERED, and not fixed here: this pins the displayed BYTES only.
1046
		// The mnemonic text and effective address are still wrong for a
1047
		// straddling instruction -- cm_decode() hands disasm() a single window
1048
		// (the one backing the START address), and disasm() reads every operand
1049
		// byte through it, so the operand above $C000 comes from ROM bank 5
1050
		// rather than 2 and the text reads "sta $eaea". Fixing it means giving
1051
		// disasm() a per-byte reader, which is shared with the classic debugger
1052
		// and the DAP server. See docs/code-map-width-propagation.md.
1053
1054
		// The width propagation, by contrast, is code_map's own and must read
1055
		// its operand through the right window. A REP whose opcode is the last
1056
		// byte of the RAM window takes its operand from the ROM window, and
1057
		// getting that wrong mis-sizes the following instructions until an
1058
		// accurate anchor is reached -- exactly the drift this file exists to
1059
		// prevent.
1060
		//
1061
		//   $BFFF: C2 20   REP #$20   opcode in RAM bank 5, operand in ROM 2
1062
		//   $C001: A9 ..   LDA #imm   3 bytes once REP has cleared M
1063
		//
1064
		// ROM bank 5 holds $00 at $C000, so reading the operand through the
1065
		// opcode's own window clears nothing and sizes the LDA at 2.
1066
		reset_all();
1067
		regs.is65c816 = true;
1068
		regs.e        = 0;
1069
		regs.status   = FLAG_MEMORY_WIDTH | FLAG_INDEX_WIDTH; // 8-bit A to start
1070
		g_ram_bank    = 0x11;
1071
		g_rom_bank    = 0x17;
1072
1073
		const uint8_t rep_op[]    = { 0xC2 };       // REP, in the RAM window
1074
		const uint8_t rep_arg[]   = { 0x20 };       // its operand: clear M
1075
		const uint8_t rep_decoy[] = { 0x00 };       // same address, wrong bank
1076
		const uint8_t lda_imm[]   = { 0xA9, 0xEA, 0xEA };
1077
		poke_banked(0xBFFF, 5, rep_op, sizeof(rep_op));
1078
		poke_banked(0xC000, 2, rep_arg, sizeof(rep_arg));
1079
		poke_banked(0xC000, 5, rep_decoy, sizeof(rep_decoy));
1080
		poke_banked(0xC001, 2, lda_imm, sizeof(lda_imm));
1081
1082
		n = code_map_disasm_forward(0xBFFF, 0, 5, 2, 2, lines, 4, &next);
1083
		// A REP is two bytes whatever its operand says, so this only pins that
1084
		// the straddling instruction is sized at all -- the check below is the
1085
		// one that exercises the operand's window.
1086
		check(n == 2 && lines[0].addr == 0xBFFF && lines[0].size == 2,
1087
		      "sizes a REP that straddles the window boundary");
1088
		check(n == 2 && lines[1].addr == 0xC001 && lines[1].size == 3,
1089
		      "reads a REP operand through the window backing the operand");
1090
1091
		// The same split exists at $9FFF/$A000: an opcode below $A000 follows
1092
		// the live bank while its operand sits in the caller's RAM window.
1093
		reset_all();
1094
		regs.is65c816 = true;
1095
		regs.e        = 0;
1096
		regs.status   = FLAG_MEMORY_WIDTH | FLAG_INDEX_WIDTH;
1097
		g_ram_bank    = 0x11;
1098
		const uint8_t sep_op[]  = { 0xC2 };
1099
		const uint8_t sep_arg[] = { 0x20 };
1100
		const uint8_t sep_bad[] = { 0x00 };
1101
		poke_banked(0x9FFF, 0, sep_op, sizeof(sep_op));   // below $A000: flat
1102
		poke_banked(0xA000, 4, sep_arg, sizeof(sep_arg)); // requested RAM bank
1103
		poke_banked(0xA000, 0x11, sep_bad, sizeof(sep_bad)); // live bank decoy
1104
		poke_banked(0xA001, 4, lda_imm, sizeof(lda_imm));
1105
1106
		n = code_map_disasm_forward(0x9FFF, 0, 4, 0, 2, lines, 4, &next);
1107
		check(n == 2 && lines[1].addr == 0xA001 && lines[1].size == 3,
1108
		      "reads a REP operand across the $9FFF/$A000 boundary correctly");
1109
1110
		// And at the very top of memory the operand wraps to $0000. That is low
1111
		// memory, which is unbanked, so this pins the wrapped ADDRESS rather
1112
		// than a window choice: reading at `addr` instead of `addr + 1` leaves
1113
		// M set and sizes the LDA at 2. (There is deliberately no decoy here --
1114
		// no window selection can be observed below $A000.)
1115
		reset_all();
1116
		regs.is65c816 = true;
1117
		regs.e        = 0;
1118
		regs.status   = FLAG_MEMORY_WIDTH | FLAG_INDEX_WIDTH;
1119
		poke_banked(0xFFFF, 3, sep_op, sizeof(sep_op)); // REP in ROM bank 3
1120
		poke(0x0000, sep_arg, sizeof(sep_arg));         // operand wraps to $0000
1121
		poke(0x0001, lda_imm, sizeof(lda_imm));
1122
1123
		n = code_map_disasm_forward(0xFFFF, 0, 0, 3, 2, lines, 4, &next);
1124
		check(n == 2 && lines[1].addr == 0x0001 && lines[1].size == 3,
1125
		      "wraps the REP operand address past $FFFF instead of reading past the end");
1126
1127
		regs.is65c816 = false;
1128
	}
1129
1130
	// ── Anchor staleness is judged in the right bank ────────────────────────
1131
	// An anchor is only believed while the opcode it recorded is still in
1132
	// memory. That comparison is a read, so it too has to name the right
1133
	// window: check it against the wrong bank and a live anchor looks stale
1134
	// (or, worse, a stale one looks live) purely because another bank happens
1135
	// to hold a different byte at the same address.
1136
	{
1137
		reset_all();
1138
		const uint8_t lda[] = { 0xA9, 0x12 };
1139
		const uint8_t nop[] = { 0xEA, 0xEA };
1140
		poke_banked(0xA000, 5, lda, sizeof(lda));
1141
		poke_banked(0xA000, 6, nop, sizeof(nop));
1142
1143
		g_ram_bank = 6; // live register points at the other bank throughout
1144
		code_map_record(0xA000, 0, 5, 0, regs.status);
1145
		check(code_map_is_recorded_start(0xA000, 0, 5, 0),
1146
		      "validates an anchor against the bank it was recorded in");
1147
1148
		// Overwrite bank 5 only. The anchor must die even though the live bank
1149
		// register still points at untouched memory.
1150
		poke_banked(0xA000, 5, nop, sizeof(nop));
1151
		check(!code_map_is_recorded_start(0xA000, 0, 5, 0),
1152
		      "drops the anchor when its own bank's code changes");
1153
	}
1154
1155
	// ── Gen2 program banks are flat memory ──────────────────────────────────
1156
	// On a Gen2 machine a non-zero CPU bank maps its whole 64K as flat RAM, so
1157
	// the window registers select nothing there -- including above $C000, where
1158
	// they otherwise would.
1159
	{
1160
		reset_all();
1161
		is_gen2 = true;
1162
		g_rom_bank = 0x17;
1163
		const uint8_t code[] = { 0xA9, 0x12 };
1164
		const uint8_t decoy[] = { 0x8D, 0x34, 0x12 };
1165
		poke_gen2(1, 0xC000, code, sizeof(code));
1166
		poke_banked(0xC000, 0x17, decoy, sizeof(decoy));
1167
		poke_banked(0xC000, 3, decoy, sizeof(decoy));
1168
1169
		code_map_line_t lines[4];
1170
		uint16_t        next = 0;
1171
		int n = code_map_disasm_forward(0xC000, 1, 0, 3, 1, lines, 4, &next);
1172
		check(n == 1 && lines[0].size == 2 && lines[0].bytes[0] == 0xA9 &&
1173
		          lines[0].bytes[1] == 0x12,
1174
		      "reads a Gen2 program bank as flat RAM, ignoring the ROM window");
1175
1176
		is_gen2 = false;
1177
	}
1178
1179
	// ── KNOWN LIMITATION: the carry feeding XCE is not tracked through data ──
1180
	// XCE swaps the carry and emulation flags, so predicting it needs the
1181
	// incoming CARRY as well as the incoming E. cm_propagate carries the carry
1182
	// through CLC, SEC and whatever REP/SEP/XCE themselves do to it, but every
1183
	// data-dependent change (ADC, SBC, CMP/CPX/CPY, the shifts and rotates) and
1184
	// the stack restores (PLP, RTI) leave the estimate untouched. A stale carry
1185
	// therefore mispredicts the emulation flag, and with it the widths, WITHOUT
1186
	// any PLP or RTI being involved.
1187
	//
1188
	//   $8000: 4A       LSR A   really clears C; the estimate keeps it set
1189
	//   $8001: FB       XCE     estimate: C set -> enter emulation, force 8-bit
1190
	//                           reality:  C clear -> stay native, A stays 16-bit
1191
	//   $8002: A9 xx xx LDA #   3 bytes in reality, 2 on the estimate
1192
	//
1193
	// Pinned so the gap is visible, and paired with the recovery that bounds
1194
	// it. If XCE's inputs are ever tracked properly this first check should
1195
	// fail: update it, do not delete it.
1196
	{
1197
		reset_all();
1198
		regs.is65c816 = true;
1199
		regs.e        = 0;
1200
		// 16-bit A, and carry set going in.
1201
		regs.status = FLAG_INDEX_WIDTH | FLAG_CARRY;
1202
1203
		const uint8_t code[] = { 0x4A, 0xFB, 0xA9, 0xEA, 0xEA, 0xA9, 0xEA, 0xEA };
1204
		poke(0x8000, code, sizeof(code));
1205
1206
		code_map_line_t lines[8];
1207
		uint16_t        next = 0;
1208
		int             n    = code_map_disasm_forward(0x8000, 0, 0, 0, 3, lines, 8, &next);
1209
1210
		check(n == 3 && lines[2].addr == 0x8002 && lines[2].size == 2,
1211
		      "KNOWN LIMITATION: a stale carry into XCE mispredicts the widths");
1212
		check(n == 3 && !lines[2].recorded,
1213
		      "the mispredicted line is still reported as a guess");
1214
1215
		// What an anchor buys here is its OWN line, and no more. It supplies
1216
		// the status for that line, so the line is sized correctly -- but no
1217
		// anchor re-establishes E, so propagating past it folds the widths back
1218
		// to 8-bit and the next unanchored line is wrong again. (E is not
1219
		// beyond repair in general: a later XCE decoded with an accurate carry
1220
		// writes a correct E, as the real instruction does. There is no XCE
1221
		// after this point in the fixture, so nothing repairs it here.)
1222
		const uint8_t st_16 = FLAG_INDEX_WIDTH;
1223
		code_map_record(0x8002, 0, 0, 0, st_16);
1224
		n = code_map_disasm_forward(0x8000, 0, 0, 0, 4, lines, 8, &next);
1225
		check(n == 4 && lines[2].addr == 0x8002 && lines[2].size == 3 &&
1226
		          lines[2].recorded,
1227
		      "an anchor recovers the width of its own line after a bad XCE");
1228
		check(n == 4 && lines[3].addr == 0x8005 && lines[3].size == 2 &&
1229
		          !lines[3].recorded,
1230
		      "KNOWN LIMITATION: past that anchor a stale E mis-sizes again");
1231
1232
		regs.is65c816 = false;
1233
	}
1234
1235
	// ── KNOWN LIMITATION: "emulation mode is always right" is not true of the
1236
	//    ESTIMATE ──────────────────────────────────────────────────────────────
1237
	// The real CPU in emulation mode forces 8-bit widths, so its widths are
1238
	// always right. code_map's fold-in (`if (e) status |= INDEX|MEMORY`) uses
1239
	// the ESTIMATED e, though, and no ordinary anchor repairs that estimate:
1240
	// run_e is seeded once per forward run and thereafter written only by XCE,
1241
	// from the ESTIMATE's carry. (A later XCE decoded with an accurate carry
1242
	// would put it back in step -- that is what the real instruction does --
1243
	// but an anchor overrides the status and never the E, so nothing here
1244
	// guarantees it.) So the estimate can believe it is in native mode while
1245
	// the machine is really in emulation, and size operands accordingly.
1246
	//
1247
	//   real:  A bit 7 set and C=0, so ASL A really sets C; XCE then sees
1248
	//          C=1,E=1 and STAYS in emulation, where widths are forced 8-bit
1249
	//   model: the carry is not tracked through ASL, so XCE sees C=0,E=1 and
1250
	//          predicts NATIVE
1251
	//   then:  REP #$20 clears the model's M; the machine still forces 8-bit
1252
	//   so:    the LDA # is really 2 bytes and the estimate sizes it 3
1253
	//
1254
	// regs.a is set below only to make that premise concrete: this harness
1255
	// never executes the CPU, so what the real ASL/XCE would do is reasoning
1256
	// about the machine, while the assertion pins what the ESTIMATE produces.
1257
	// (The ASL is load-bearing for reality, not for the model -- the model's
1258
	// seed carry is already clear, so it would predict native either way.)
1259
	{
1260
		reset_all();
1261
		regs.is65c816 = true;
1262
		regs.e        = 1; // the machine really IS in emulation mode
1263
		regs.a        = 0x80; // so a real ASL A would set the carry
1264
		regs.status   = FLAG_MEMORY_WIDTH | FLAG_INDEX_WIDTH; // as emulation forces
1265
1266
		const uint8_t code[] = {
1267
			0x0A,             // $8000 ASL A -- really sets C; not modelled
1268
			0xFB,             // $8001 XCE   -- really stays in emulation
1269
			0xC2, 0x20,       // $8002 REP #$20
1270
			0xA9, 0xEA, 0xEA, // $8004 LDA # -- really 2 bytes in emulation
1271
		};
1272
		poke(0x8000, code, sizeof(code));
1273
1274
		code_map_line_t lines[8];
1275
		uint16_t        next = 0;
1276
		int             n    = code_map_disasm_forward(0x8000, 0, 0, 0, 4, lines, 8, &next);
1277
1278
		check(n == 4 && lines[3].addr == 0x8004 && lines[3].size == 3,
1279
		      "KNOWN LIMITATION: a stale carry mis-sizes even in emulation mode");
1280
		check(n == 4 && !lines[3].recorded,
1281
		      "that mis-sized line is still reported as a guess");
1282
1283
		regs.is65c816 = false;
1284
		regs.e        = 0;
1285
	}
1286
1287
	// ── KNOWN LIMITATION: a stale anchor can also decode too SHORT ───────────
1288
	// The stale-anchor cases above all decode too WIDE and swallow a fresh
1289
	// start. The opposite is just as reachable and is quieter: an anchor
1290
	// recorded when the accumulator was 8 bits sizes its LDA # at two bytes, so
1291
	// if the replacement code is about to run with a 16-bit accumulator the row
1292
	// stops one byte early and the NEXT row starts inside the real
1293
	// instruction's operand. Nothing is swallowed, so nothing looks wrong --
1294
	// the rows still tile.
1295
	{
1296
		reset_all();
1297
		regs.is65c816 = true;
1298
		regs.e        = 0;
1299
1300
		// Old code, 8-bit A: LDA #$11 is two bytes.
1301
		const uint8_t old_code[] = { 0xA9, 0x11 };
1302
		poke(0x8000, old_code, sizeof(old_code));
1303
		code_map_record(0x8000, 0, 0, 0, FLAG_MEMORY_WIDTH | FLAG_INDEX_WIDTH);
1304
1305
		// Replacement keeps the $A9 opcode but is about to run with a 16-bit
1306
		// accumulator, so the real instruction there is three bytes. (It has
1307
		// not run yet -- executing at $8000 would re-record the anchor and
1308
		// refresh its status, which is exactly what fixes this.)
1309
		const uint8_t new_code[] = { 0xA9, 0xEA, 0xEA, 0xEA };
1310
		poke(0x8000, new_code, sizeof(new_code));
1311
		regs.status = FLAG_INDEX_WIDTH;
1312
1313
		code_map_line_t lines[4];
1314
		uint16_t        next = 0;
1315
		int             n    = code_map_disasm_forward(0x8000, 0, 0, 0, 2, lines, 4, &next);
1316
1317
		check(n == 2 && lines[0].size == 2,
1318
		      "KNOWN LIMITATION: a stale anchor can size an instruction too short");
1319
		check(n == 2 && lines[1].addr == 0x8002,
1320
		      "KNOWN LIMITATION: the next row then starts inside a real operand");
1321
		check_tiles(lines, n, "the too-short reading still tiles, so it looks correct");
1322
1323
		regs.is65c816 = false;
1324
	}
1325
	{
1326
		reset_all();
1327
		code_map_line_t lines[4];
1328
		int             center_index = -99;
1329
1330
		check(code_map_disasm_window(0x8000, 0, 0, 0, 2, 2, NULL, 4, &center_index) == 0,
1331
		      "declines a NULL output buffer");
1332
		check(center_index == -1, "clears the center index when it declines");
1333
		check(code_map_disasm_window(0x8000, 0, 0, 0, 2, 2, lines, 0, NULL) == 0,
1334
		      "declines a zero-capacity buffer");
1335
1336
		uint16_t next = 0x1234;
1337
		check(code_map_disasm_forward(0x8000, 0, 0, 0, 4, NULL, 4, &next) == 0,
1338
		      "forward disassembly declines a NULL buffer");
1339
		check(next == 0x8000, "leaves next_addr at the start when it declines");
1340
		check(code_map_disasm_forward(0x8000, 0, 0, 0, -1, lines, 4, &next) == 0,
1341
		      "treats a negative count as nothing to do");
1342
1343
		// Negative "lines before" must be clamped, not used as an array size.
1344
		// Asserting only that a line comes back proves nothing -- phase C
1345
		// always emits the center -- so pin where the center actually lands.
1346
		center_index = -99;
1347
		int n = code_map_disasm_window(0x8000, 0, 0, 0, -5, 1, lines, 4, &center_index);
1348
		check(n == 1 && center_index == 0 && lines[0].addr == 0x8000,
1349
		      "treats a negative lines_before as none at all");
1350
1351
		// An absurd one must be capped too: the backward walk collects into a
1352
		// fixed 256-entry buffer, so an uncapped count writes off the end of
1353
		// it. 255 preceding lines is the ceiling, and the cap is only worth
1354
		// asserting if the count it produces is actually pinned -- a bare
1355
		// "did not overrun" passes for any cap at all.
1356
		static code_map_line_t wide[300];
1357
		center_index = -99;
1358
		n = code_map_disasm_window(0x8000, 0, 0, 0, 100000, 1, wide, 300, &center_index);
1359
		check(n == 256 && center_index == 255,
1360
		      "caps an absurd lines_before at the backward-walk ceiling");
1361
		check_tiles(wide, n, "tiles a window capped by the backward-walk ceiling");
1362
	}
1363
1364
	// ── Wrapping at the top of the address space ────────────────────────────
1365
	// Decoding off the end of memory must wrap to $0000 and read the bytes that
1366
	// are really there. Asserting only that the address stayed below $0100
1367
	// proves nothing: code_map_line_t::addr is a uint16_t, so no arithmetic in
1368
	// the file can produce anything else.
1369
	{
1370
		reset_all();
1371
		// $FFFE: A9 12     LDA #$12   -- 2 bytes, ends exactly on the wrap
1372
		// $0000: 8D 34 12  STA $1234  -- must be decoded from the bottom of RAM
1373
		const uint8_t tail[] = { 0xA9, 0x12 };
1374
		const uint8_t head[] = { 0x8D, 0x34, 0x12 };
1375
		poke(0xFFFE, tail, sizeof(tail));
1376
		poke(0x0000, head, sizeof(head));
1377
1378
		code_map_line_t lines[4];
1379
		uint16_t        next = 0;
1380
		int             n = code_map_disasm_forward(0xFFFE, 0, 0, 0, 2, lines, 4, &next);
1381
1382
		check(n == 2 && lines[0].addr == 0xFFFE && lines[0].size == 2,
1383
		      "decodes the instruction at the very top of memory");
1384
		check(n == 2 && lines[1].addr == 0x0000 && lines[1].size == 3 &&
1385
		          lines[1].bytes[0] == 0x8D && lines[1].bytes[2] == 0x12,
1386
		      "wraps to $0000 and decodes the bytes actually there");
1387
		check(next == 0x0003, "reports the wrapped next address");
1388
		check_tiles(lines, n, "tiles across the $FFFF wrap");
1389
	}
1390
1391
	if (failures) {
1392
		printf("\nFAILED (%d failures)\n", failures);
1393
		return 1;
1394
	}
1395
	printf("\nPASSED (0 failures)\n");
1396
	return 0;
1397
}