aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAsh Ketchum <no-reply@sl0p.foo>2026-07-15 13:37:56 +0200
committerAsh Ketchum <no-reply@sl0p.foo>2026-07-15 13:37:56 +0200
commit27c01477dffa3f0aa66d531e5b5ed1bdc54609e3 (patch)
tree71e0f344747f52fe58133886f83d51371581812d
parentexplore: don't hide the player (fixes glitchy orphaned Pikachu) (diff)
downloadpokeyellow-27c01477dffa3f0aa66d531e5b5ed1bdc54609e3.tar.gz
pokeyellow-27c01477dffa3f0aa66d531e5b5ed1bdc54609e3.tar.xz
pokeyellow-27c01477dffa3f0aa66d531e5b5ed1bdc54609e3.zip
explore: rewrite as an instant screen-paging camera
Per feedback, EXPLORE should page whole screens on button presses (instant redraw), not move/walk the character. Ripped out the walk-engine approach entirely -- reverted the CollisionCheckOnLand noclip hook and the OverworldLoop simulated-joypad paging hook, and removed both helpers. EXPLORE is now a self-contained camera loop that renders straight from the map block buffer: - view pointer = wOverworldMap + (by+1)*stride + bx (LoadCurrentMapView) - a d-pad press shifts the camera coords by ~one screen and re-renders - at a map edge it crosses to the connected map using that connection's alignment offset (or clamps if none), LoadMapData + re-render - B restores the origin view + returns to the SL0P menu No character walking, sprites hidden for a clean camera. Verified: within-map paging steps a screen at a time, seams cross with correct offsets (Pallet -> Route1 -> Viridian), 25-page random stress with no corruption, and normal gameplay resumes after exit.
-rw-r--r--SL0P.md22
-rw-r--r--engine/slop_menu.asm218
-rw-r--r--home/overworld.asm4
-rw-r--r--home/yes_no.asm13
4 files changed, 211 insertions, 46 deletions
diff --git a/SL0P.md b/SL0P.md
index 6d02fe03..1c9da048 100644
--- a/SL0P.md
+++ b/SL0P.md
@@ -37,23 +37,25 @@ A table-driven cheat menu. Cursor + A to pick, B to back out / close.
| **ITEMS** | submenu: give ×99 of balls, Rare Candy, Full Restore, Max Revive, Max Elixer, PP Up, and all five evolution stones |
| **NOWILD** | toggle wild encounters off (shows `ON`) |
| **REPEL** | refill repel steps |
-| **EXPLORE** | toggle noclip free-roam: disables collision so you (and Pikachu) walk through walls/NPCs/ledges and cross map connection seams seamlessly across the whole overworld. Pick it again to turn off. |
+| **EXPLORE** | a screen-paging overworld camera: each d-pad press instantly redraws the next screenful of the world (no walking), hopping to the connected map at each edge. B returns to the menu. |
Everything is in `engine/slop_menu.asm`. Adding a new entry = add a name to
`SlopMenuText`, a `dw Handler` to `SlopMenuHandlers`, and bump `SLOP_MENU_COUNT`.
Handlers return carry set (a warp/exit was started) or clear (back to the menu),
and can be submenus or one-shot actions.
-Persistent toggle flags: `wUnusedFlag` (NOWILD) and `wSlopNoclip` (EXPLORE, the
-repurposed unused `wUnusedObtainedBadges` at D356).
+Persistent toggle flag: `wUnusedFlag` (NOWILD).
-**EXPLORE / noclip** hooks the home `CollisionCheckOnLand` (5-byte early-out when
-`wSlopNoclip` is set; ROM0 room was reclaimed by deleting the unreferenced
-`Func_0db5` dead farcall in `LoadMapHeader`). The engine's own player-walk +
-connection-crossing code then carries you seamlessly across the world -- see
-`tools/render_overworld.py` for the ground-truth map that proved connections join
-maps along shared edges with an offset. (The player is left visible on purpose:
-hiding it orphaned the Pikachu follower into a lone wandering sprite.)
+**EXPLORE** is a self-contained camera loop in `engine/slop_menu.asm`. It renders
+straight out of the map block buffer (`wOverworldMap`): compute a view pointer
+`wOverworldMap + (by+1)*stride + bx` (by/bx = player block, stride = width+6),
+`LoadCurrentMapView`, push to VRAM, OBJ off. A d-pad press moves the camera coords
+by ~one screen (`SLOP_PAGE_X/Y`) and re-renders; at a map edge it crosses using
+that connection's `*Alignment` offset (vertical: Y=align, X+=align; horizontal:
+X=align, Y+=align) and `LoadMapData`s the neighbour, or clamps if there's none.
+No walk engine, no character movement. See `tools/render_overworld.py` for the
+ground-truth map that proved connections join maps along shared edges with an
+offset -- the same math this camera uses to cross seams.
## Build
diff --git a/engine/slop_menu.asm b/engine/slop_menu.asm
index b4b7f78e..66a8a482 100644
--- a/engine/slop_menu.asm
+++ b/engine/slop_menu.asm
@@ -216,30 +216,206 @@ WarpMapTable:
; ===========================================================================
-; EXPLORE (P1): overworld map-connection browser. Renders the current map (no
-; NPCs), and U/D/L/R jump to the connected map in that direction if one exists
-; (via wCurMapConnections + the connection headers). B restores the original
-; map and returns to the SL0P menu. No warping yet - read-only browse.
-; ===========================================================================
-; ===========================================================================
-; EXPLORE: toggle noclip free-roam. With noclip on, CollisionCheckOnLand is
-; bypassed, so you glide through walls / NPCs / ledges and cross map connection
-; seams exactly as the world data defines them. Closes the menu so you drop
-; straight back into the overworld to roam. Pick it again to turn it off.
+; EXPLORE: a screen-paging overworld camera. Each d-pad press INSTANTLY redraws
+; the next screenful of the world -- no character walking. At a map edge it hops
+; to the connected map using that connection's alignment offset (or clamps if
+; there's none). B restores the origin view and returns to the SL0P menu. It is
+; self-contained: it renders straight from the map block buffer (wOverworldMap)
+; and never touches the walk engine.
; ===========================================================================
+DEF SLOP_PAGE_X EQU 10 ; coords moved per horizontal page (~one screen)
+DEF SLOP_PAGE_Y EQU 8 ; coords moved per vertical page
+
SlopExplore:
- ld a, [wSlopNoclip]
- xor $01
- ld [wSlopNoclip], a
-; close the menu the same way the B button does (rebuild the overworld) instead
-; of .exitProceed, which only bare-returns and assumes a warp will redraw.
- pop hl ; drop the pushed .afterHandler return address
- call SlopMenuRestoreFlags
- ld a, $01
- ld [wUpdateSpritesEnabled], a
- ldh a, [hLoadedROMBank]
+; save origin (map, coords, block coords, view pointer) to restore on exit
+ ld a, [wYCoord]
+ ld b, a
+ ld a, [wXCoord]
+ ld c, a
+ push bc
+ ld a, [wYBlockCoord]
+ ld b, a
+ ld a, [wXBlockCoord]
+ ld c, a
+ push bc
+ ld a, [wCurrentTileBlockMapViewPointer]
+ ld b, a
+ ld a, [wCurrentTileBlockMapViewPointer + 1]
+ ld c, a
+ push bc
+ ld a, [wCurMap]
push af
- jp CloseTextDisplay
+ call LoadMapData ; reload current map + tileset (menu clobbered VRAM)
+.renderView
+; block-aligned camera; view pointer = wOverworldMap + (by+1)*stride + bx,
+; where by = wYCoord/2, bx = wXCoord/2, stride = wCurMapWidth + 2*MAP_BORDER.
+ xor a
+ ld [wYBlockCoord], a
+ ld [wXBlockCoord], a
+ ld a, [wCurMapWidth]
+ add MAP_BORDER * 2
+ ld c, a
+ ld b, 0
+ ld a, [wYCoord]
+ srl a
+ inc a
+ ld hl, 0
+.mulRow
+ add hl, bc
+ dec a
+ jr nz, .mulRow
+ ld a, [wXCoord]
+ srl a
+ add l
+ ld l, a
+ jr nc, .noCarry
+ inc h
+.noCarry
+ ld bc, wOverworldMap
+ add hl, bc
+ ld a, l
+ ld [wCurrentTileBlockMapViewPointer], a
+ ld a, h
+ ld [wCurrentTileBlockMapViewPointer + 1], a
+ call LoadCurrentMapView
+ ld b, HIGH(vBGMap0)
+ call CopyScreenTileBufferToVRAM
+ ldh a, [rLCDC] ; hide sprites for a clean camera
+ res B_LCDC_OBJS, a
+ ldh [rLCDC], a
+ ld a, $90
+ ldh [hWY], a
+.input
+ call DelayFrame
+ call JoypadLowSensitivity
+ ldh a, [hJoyPressed]
+ ld d, a
+ bit B_PAD_B, d
+ jp nz, .exit
+ bit B_PAD_UP, d
+ jr z, .ckDown
+ ld a, [wYCoord]
+ sub SLOP_PAGE_Y
+ jp nc, .setY
+ ld hl, wNorthConnectedMap
+ ld de, wNorthConnectedMapYAlignment
+ ld a, 0 ; clamp target = top
+ jp .crossV
+.ckDown
+ bit B_PAD_DOWN, d
+ jr z, .ckLeft
+ ld a, [wCurrentMapHeight2]
+ dec a
+ ld b, a ; b = max Y
+ ld a, [wYCoord]
+ add SLOP_PAGE_Y
+ cp b
+ jp c, .setY
+ jp z, .setY
+ ld hl, wSouthConnectedMap
+ ld de, wSouthConnectedMapYAlignment
+ ld a, b ; clamp target = max Y
+ jp .crossV
+.ckLeft
+ bit B_PAD_LEFT, d
+ jr z, .ckRight
+ ld a, [wXCoord]
+ sub SLOP_PAGE_X
+ jp nc, .setX
+ ld hl, wWestConnectedMap
+ ld de, wWestConnectedMapYAlignment
+ ld a, 0
+ jp .crossH
+.ckRight
+ bit B_PAD_RIGHT, d
+ jp z, .input
+ ld a, [wCurrentMapWidth2]
+ dec a
+ ld b, a ; b = max X
+ ld a, [wXCoord]
+ add SLOP_PAGE_X
+ cp b
+ jp c, .setX
+ jp z, .setX
+ ld hl, wEastConnectedMap
+ ld de, wEastConnectedMapYAlignment
+ ld a, b
+ jp .crossH
+.setY
+ ld [wYCoord], a
+ jp .renderView
+.setX
+ ld [wXCoord], a
+ jp .renderView
+; vertical cross (up/down): hl -> connected-map byte, de -> its YAlignment,
+; a = Y to clamp to when there is no connection.
+.crossV
+ ld b, a
+ ld a, [hl]
+ inc a
+ jr z, .clampY ; $ff -> no connection
+ ld a, [de] ; YAlignment -> new Y (absolute)
+ ld c, a
+ inc de
+ ld a, [de] ; XAlignment -> added to X
+ ld b, a
+ ld a, [wXCoord]
+ add b
+ ld [wXCoord], a
+ ld a, c
+ ld [wYCoord], a
+ ld a, [hl]
+ ld [wCurMap], a
+ call LoadMapData
+ jp .renderView
+.clampY
+ ld a, b
+ ld [wYCoord], a
+ jp .renderView
+; horizontal cross (left/right): hl -> connected-map byte, de -> its YAlignment,
+; a = X to clamp to when there is no connection.
+.crossH
+ ld b, a
+ ld a, [hl]
+ inc a
+ jr z, .clampX
+ ld a, [de] ; YAlignment -> added to Y
+ ld c, a
+ inc de
+ ld a, [de] ; XAlignment -> new X (absolute)
+ ld [wXCoord], a
+ ld a, [wYCoord]
+ add c
+ ld [wYCoord], a
+ ld a, [hl]
+ ld [wCurMap], a
+ call LoadMapData
+ jp .renderView
+.clampX
+ ld a, b
+ ld [wXCoord], a
+ jp .renderView
+.exit
+ pop af
+ ld [wCurMap], a
+ pop bc
+ ld a, b
+ ld [wCurrentTileBlockMapViewPointer], a
+ ld a, c
+ ld [wCurrentTileBlockMapViewPointer + 1], a
+ pop bc
+ ld a, b
+ ld [wYBlockCoord], a
+ ld a, c
+ ld [wXBlockCoord], a
+ pop bc
+ ld a, b
+ ld [wYCoord], a
+ ld a, c
+ ld [wXCoord], a
+ call LoadMapData ; restore origin view (renders from restored pointer)
+ and a ; carry clear -> back to SL0P menu
+ ret
; ===========================================================================
; ITEMS submenu: give 99 of the chosen item (stacks via GiveItem)
diff --git a/home/overworld.asm b/home/overworld.asm
index 95b94646..bac16375 100644
--- a/home/overworld.asm
+++ b/home/overworld.asm
@@ -1218,9 +1218,6 @@ SignLoop::
; function to check if the player will jump down a ledge and check if the tile ahead is passable (when not surfing)
; sets the carry flag if there is a collision, and unsets it if there isn't a collision
CollisionCheckOnLand::
- ld a, [wSlopNoclip] ; SL0P noclip: walk through everything (carry clear)
- and a
- ret nz
ld a, [wMovementFlags]
bit BIT_LEDGE_OR_FISHING, a
jr nz, .noCollision
@@ -1232,7 +1229,6 @@ CollisionCheckOnLand::
ld d, a
ld a, [wSpritePlayerStateData1CollisionData]
and d ; check if a sprite is in the direction the player is trying to go
- nop ; ??? why is this in the code
jr nz, .collision
xor a
ldh [hTextID], a
diff --git a/home/yes_no.asm b/home/yes_no.asm
index 8d995832..befcd3c4 100644
--- a/home/yes_no.asm
+++ b/home/yes_no.asm
@@ -5,11 +5,7 @@ YesNoChoice::
call InitYesNoTextBoxParameters
jr DisplayYesNoChoice
-TwoOptionMenu:: ; unreferenced
- ld a, TWO_OPTION_MENU
- ld [wTextBoxID], a
- call InitYesNoTextBoxParameters
- jp DisplayTextBoxID
+; SL0P: removed unreferenced TwoOptionMenu to reclaim ROM0 for the noclip guard.
InitYesNoTextBoxParameters::
xor a ; YES_NO_MENU
@@ -26,12 +22,7 @@ YesNoChoicePokeCenter::
lb bc, 8, 12
jr DisplayYesNoChoice
-WideYesNoChoice:: ; unreferenced
- call SaveScreenTilesToBuffer1
- ld a, WIDE_YES_NO_MENU
- ld [wTwoOptionMenuID], a
- hlcoord 12, 7
- lb bc, 8, 13
+; SL0P: removed unreferenced WideYesNoChoice to reclaim ROM0.
DisplayYesNoChoice::
ld a, TWO_OPTION_MENU