Skip to content

Commit 022d2a2

Browse files
trekawekclaude
andcommitted
Resolve the tile-map base at the fetcher's VRAM read
Probing m3_lcdc_bg_map_change to the T-cycle: a mid-line write to the LCDC map-select bit lands one tile too late because the map base was baked into the address at GET_TILE_T1 and the read happened at T2, while the hardware fetch cycle ends with a two-dot push that places its reads one dot later than our back-to-back restart. The SCX/SCY map offset stays sampled at GET_TILE_T1 (m3_scx_high_5_bits pins that); the map-select bit is now resolved at the VRAM read in GET_TILE_DATA_LOW_T1, and the tile data low read moved to GET_TILE_DATA_HIGH_T1 accordingly. bg_map_change 2002 -> 962 diff pixels, win_map_change 1924 -> 1404, tile_sel_win_change 2294 -> 1718; the rest unchanged. Full battery (mooneye, blargg, dmg-acid2, units) stays green. CLAUDE.md now carries the measured-but-unimplemented findings: the hardware dot machine runs ~4 T later than ours (tile data reads +5), the line-0 mode-2 interrupt quirk, SCX%8 mid-line alignment, and the SameBoy conflict-map pointers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J1wLWscyUGS7CFwc3CjJR8
1 parent 48805d2 commit 022d2a2

3 files changed

Lines changed: 62 additions & 5 deletions

File tree

CLAUDE.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,49 @@ rising edge) — see `StatRegister`.
239239
- Mappers now implemented: MBC1(+M), MBC2, MBC3(+RTC), MBC5, MBC6(+flash), MBC7(+EEPROM,
240240
accelerometer), MMM01, HuC1, HuC3(+RTC), TAMA5(+TAMA6 RTC).
241241

242+
## Mealybug Tearoom status (mid-mode-3 register writes, DMG)
243+
244+
`mvn -pl core test -Ptest-mealybug` — 24 DMG tests in
245+
`core/src/test/resources/roms/mealybug/` (expected images = DMG-CPU B photos,
246+
DMG-blob where no CPU B image exists; the `*2` ROM variants are CGB-only).
247+
Compare via `ImageTestRunner` (stops at `LD B,B`). Not in CI yet — most still fail.
248+
249+
**Verified-to-the-dot model (implemented):**
250+
- The DMG resolves pixels at the LCD interface, `OUTPUT_DELAY = 7` T after the
251+
FIFO pop (`DmgPixelFifo` delay line): palettes (BGP/OBP0/OBP1), LCDC bg/obj
252+
enable bits and the obj/bg mux are all read at *output* time. During the single
253+
T-cycle in which a CPU write commits, the output stage reads `old|new` for
254+
palettes and `old|(new&1)` for LCDC (SameBoy PALETTE_DMG / DMG_LCDC conflicts).
255+
This makes m3_bgp_change / m3_lcdc_bg_en_change / m3_window_timing pixel-exact
256+
except line 0.
257+
- The fetcher samples the SCX/SCY-derived map offset at GET_TILE_T1, but resolves
258+
the LCDC map-select bit at the VRAM read, which happens at GET_TILE_DATA_LOW_T1
259+
(one T later than the state name suggests — the hardware fetch cycle ends with a
260+
two-dot push, placing its reads one dot later than our back-to-back restart).
261+
262+
**Measured but NOT yet implemented (next steps):**
263+
- All remaining line-0 diffs: our line-0 mode-2 STAT interrupt fires ~4 T earlier
264+
than hardware (the test source compensates line 0 by 4 cycles; our rows 0 render
265+
shifted). Suspect the line-0 OAM int is delayed 4 T on hardware.
266+
- Tile DATA reads (tile_sel): probing m3_lcdc_tile_sel_change shows hardware's
267+
data reads land ~5 T after our GET_TILE_DATA_HIGH_T1/T2, i.e. *inside the
268+
tile's own pop window* in our frame — the hardware dot machine (fetch+pop) runs
269+
~4 T later than ours with the LCD delay being ~3 (our pop+7 == hw pop+3). A full
270+
machine retiming (mode 3 starting 4 T later, E moving 248→252) would need the
271+
CPU-visible mode-0/lock events to fire *before* the machine finishes (they are
272+
calibrated at E+1/E+4 with E=248) — blocked on early-completion bookkeeping
273+
because sprite stalls can still lengthen the line after the crossing.
274+
- m3_scx_low_3_bits: mid-line SCX%8 changes must affect the pop alignment
275+
(hardware artifact at x=154..157); our alignment is line-start-only.
276+
- Sprite-related conflicts (m3_bgp_change_sprites 992, m3_obp0_change 432 diffs
277+
at x=0..1, obj_en/size 146-390): SameBoy's DMG_LCDC obj_en special cases
278+
(position_in_line==0, during_object_fetch) not implemented.
279+
- Window tests (m2_win_en_toggle, m3_lcdc_win_en_change_multiple*, wx_*): not
280+
yet analysed; disable_window_pixel_insertion_glitch in SameBoy is the model.
281+
- SameBoy's conflict maps live in Core/sm83_cpu.c (dmg_conflict_map + the
282+
GB_CONFLICT_* cases); their per-register apply offsets are relative to a
283+
baseline 4 T before our CPU write commit tick.
284+
242285
## Possible future work
243286

244287
- CGB PPU rendering in DMG-compat mode (palettes via KEY0 path) — IO regs are

core/src/main/java/eu/rekawek/coffeegb/core/gpu/Fetcher.java

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ public class Fetcher implements Serializable, Originator<Fetcher> {
4949

5050
private int tileMapAddress;
5151

52+
private int tileMapOffset;
53+
5254
private int tileId;
5355

5456
private TileAttributes tileAttributes = TileAttributes.EMPTY;
@@ -104,7 +106,12 @@ public int getState() {
104106
public void advance(int position, boolean window, int windowY, boolean duringObjectFetch) {
105107
switch (state) {
106108
case GET_TILE_T1: {
107-
int map = window ? lcdc.getWindowTileMapDisplay() : lcdc.getBgTileMapDisplay();
109+
// the map offset (SCX/SCY, window position) is sampled at the start of
110+
// the fetch; the map-select and tile-data-select LCDC bits are resolved
111+
// at the respective VRAM reads, one T-cycle later than the state names
112+
// suggest - the hardware fetch cycle ends with a two-dot push, placing
113+
// its reads one dot later than a back-to-back restart would (mealybug
114+
// m3_lcdc_bg_map_change pins the map read to the T-cycle)
108115
int y;
109116
int x;
110117
if (window) {
@@ -119,17 +126,18 @@ public void advance(int position, boolean window, int windowY, boolean duringObj
119126
}
120127
}
121128
fetcherY = y;
122-
tileMapAddress = map + (y / 8) * 0x20 + x;
129+
tileMapOffset = (y / 8) * 0x20 + x;
123130
state++;
124131
break;
125132
}
126133

127-
case GET_TILE_DATA_LOW_T1:
128-
case GET_TILE_DATA_HIGH_T1:
134+
case GET_TILE_T2:
129135
state++;
130136
break;
131137

132-
case GET_TILE_T2:
138+
case GET_TILE_DATA_LOW_T1: {
139+
int map = window ? lcdc.getWindowTileMapDisplay() : lcdc.getBgTileMapDisplay();
140+
tileMapAddress = map + tileMapOffset;
133141
tileId = videoRam0.getByte(tileMapAddress);
134142
if (gbc) {
135143
tileAttributes = TileAttributes.valueOf(videoRam1.getByte(tileMapAddress));
@@ -138,8 +146,13 @@ public void advance(int position, boolean window, int windowY, boolean duringObj
138146
}
139147
state++;
140148
break;
149+
}
141150

142151
case GET_TILE_DATA_LOW_T2:
152+
state++;
153+
break;
154+
155+
case GET_TILE_DATA_HIGH_T1:
143156
tileData1 = getTileData(tileId, effectiveY(window, windowY) & 7, 0,
144157
lcdc.getBgWindowTileData(), lcdc.isBgWindowTileDataSigned(), tileAttributes, 8);
145158
state++;

roms

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/mnt/nas/emu/roms/gb

0 commit comments

Comments
 (0)