Skip to content

Add Isafdar navigation obstacles and Lletya content - #1237

Open
HarleyGilpin wants to merge 26 commits into
GregHib:mainfrom
HarleyGilpin:area/isafdar
Open

Add Isafdar navigation obstacles and Lletya content#1237
HarleyGilpin wants to merge 26 commits into
GregHib:mainfrom
HarleyGilpin:area/isafdar

Conversation

@HarleyGilpin

Copy link
Copy Markdown
Contributor

Adds the interactable objects needed to navigate Isafdar and the missing Lletya content, plus a crystal bow combat fix found while testing.

Isafdar navigation

  • Dense forest passages (3937-3939, 3998/3999) - squeeze through with rotation-aware movement; the two Elf Camp passages require 56 Agility (data-driven level param). Players line up with the tile nearest the passage centre before squeezing through.
  • Tripwire (3921) / sticks trap (3922) - step-over with an agility success roll; failure deals damage with authentic messages.
  • Leaf traps (3923-3925) - jump across on click; failing the roll or walking onto the trap tile drops the player into the pit below (spikes damage), with the protruding rocks (3927) to climb back out. All four trap sites wired to their pits.
  • Log balances (3931/3932/3933) - 45 Agility, 7.5 xp. Approach is interruptible by walking away; the crossing itself is done at walk speed with the rope balance render emote.
  • Elven Overpass rocks (9296/9297) - three cliffside shortcuts at 59/68/85 Agility.
  • Lletya tree entrance (8742) - gated on Roving Elves.
  • Arandar heavy gates (3944/3945), Underground Pass door pass-through (4006/36000) and Poison Waste caves/sewer ladders (until the full dungeon content exists).

Lletya

  • Teleport crystal - Activate teleports to Lletya using the existing charge degrade chain, with degradation messages.
  • Eluned - re-enchants uncharged crystals for 750 coins, decreasing by 150 per recharge to a floor of 150 (persisted per player).
  • Islwyn - sells new crystal bows/shields for 900,000 and attunes crystal seeds for 900,000 decreasing by 180,000 to a floor of 180,000 (persisted per player).
  • Shopkeeper dialogues (Eudav, Oronwen, Dalldav, Gethin) opening their existing shops, and flavour dialogue for Goreu, Ysgawyn, Mawrth, Arvel, Kelyn, Eoin, Iona and Gwir.
  • Elf musician (8723) no longer turns to face players (interacts = false), staying locked facing south.

Fixes

  • Crystal bow dropped itself as ammo: new_crystal_bow didn't match the startsWith("crystal_bow") check in ammo/Ammo.kt, so firing it removed the bow from the weapon slot and dropped it at the target like a thrown weapon. Now maps to the same no-ammo handling as the rest of the chain.
  • objectApproach supports arrive = false (mirroring objectOperate): approach handlers can now run without waiting for the already-queued route to finish. Needed because the log balance's cache reach tile is south of the log - without this the player always detoured a tile south before crossing.

Notes

  • Trap fail damages and success rates are placeholder values flagged with TODOs; exact 2011 numbers are unverified.
  • Which pit belongs to the west/centre/north leaf traps is a best guess (the south trap's pit is verified); flagged in code.
  • Quest gates use questCompleted("roving_elves"), relying on quests.requirements.skipMissing until the elf quests are implemented.

Testing

  • IsafdarTest (17 tests): every obstacle type, level gates, trap failure damage, pit fall/climb-out, log pathing regression (7 start positions asserting no south detour), approach interruption.
  • LletyaTest (6 tests): teleport crystal charge chain, Eluned/Islwyn price ladders and persistence, shopkeeper dialogue opening shops.
  • CrystalBowTest: bow stays equipped and degrades correctly when fired.
  • Full :game: and :engine: suites pass.

Isafdar:
- Dense forest passages (56 Agility for the two Elf Camp entrances),
  lining up with the passage centre before squeezing through
- Tripwire and sticks traps with agility roll, fail damage and messages
- Leaf traps: jump over, fall into the pit below on failure or when
  walking onto the trap tile, climb out via the protruding rocks
- Log balances (45 Agility) at the hunting grounds and Arandar,
  interruptible while approaching, walked at walk speed while crossing
- Elven Overpass cliffside rocks (59/68/85 Agility)
- Arandar heavy gates, Underground Pass door pass-through and
  Poison Waste cave entrances, exits and sewer ladders

Lletya:
- Teleport crystal Activate with charge degradation
- Eluned recharges teleport crystals (750 coins, decreasing to 150)
- Islwyn sells and attunes crystal bows and shields (900k, decreasing)
- Shopkeeper and resident dialogues
- Elf musician locked facing south

Fixes:
- New crystal bow no longer dropped at the target as ammo when fired
- objectApproach supports arrive = false to skip waiting for the
  pending route, matching objectOperate
@Ebp90

Ebp90 commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

I can convert Regicide, Roving Elves and MEP1 to compliment this at some point.

@GregHib GregHib left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dense forests:

  • anims don't seem to be correct
  • if you stand to the side of one and enter you can clip yourself into the forest on the other side
  • Anim finishes too early and force walk slides the last tick

Sticks:

  • Can move diagonal
  • Anim finishing too early and slides at end
  • You can step onto them without setting them off

Tripwire:

  • A little janky with teleports/anims but generally okay

Leaf traps:

  • Also diagonal movement issue
  • Doesn't trigger when walking over without interaction
  • Also slide at end of movement

Log balance:

  • Run orb disables when walking across (see other log balances)

- Dense forest: correct animations per passage type (839 climb-over,
  749 double squeeze, 1237 side squeeze) with sounds, 94cs move with
  30cs start delay to stop the end-of-move slide, and a collision-aware
  line-up walk so entering from the side can no longer clip through
- Tripwire: step-over is now a lined-up single-tile exact move over the
  wire; failing fires the flanking arrow launcher animations and
  projectiles
- Sticks trap: crossing walks along the trap at walk speed; failing
  springs the trap and flings the player off in the direction fixed by
  the trap's rotation, and walking onto the trap without interacting
  now sets it off
- Leaf traps: trigger areas cover the full 3x3 leaf patches so walking
  (or cutting diagonally) over any leaf tile drops the player into the
  pit with the stumble and getup animations; jumps line up with the
  trap first and use the proper timings; centre pit tile corrected
- Log balance: no longer toggles the run setting while crossing (run
  orb stays lit) and plays the balance sound each step
@HarleyGilpin

HarleyGilpin commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review in bf7c86f, using a packet capture from OSRS as reference:

Dense forests: animations were wrong for most ids: 3937/3999 use 839 (climb-over), 3938/3998 use 749 (double squeeze, cleared after the move), 3939 uses 1237 (side squeeze), each with their forest sound. The exact move is now 94cs with a 30cs start delay which removes the early anim finish/slide. The line-up walk is now collision-aware (walkToDelay instead of a forced walk-over) and aborts if the lane tile isn't reached, so entering from the side can't clip through anymore.

Tripwire: reworked to the captured shape: walk onto the near wire tile, single-tile exact move over the wire with anim 1236, then step off. Failing now animates the flanking arrow launchers (3920) and fires arrow projectiles at the player.

Sticks: crossing walks straight along the trap at walk speed (lined up first, so no diagonals). Failing springs the trap (obj anim + stumble-back 846) and flings the player off in the fixed direction given by the trap's rotation. Walking onto the trap without interacting now sets it off the same way, with a regression test.

Leaf traps: trigger areas now cover the full 3x3 leaf patches, so walking or cutting a diagonal over any leaf tile drops the player into the pit (stumble 764 → getup 534 + "Ouch!"), with a test. Jumps line up with the trap centre first and use the captured timings, which also fixes the end slide. Scripted crossings suppress the walk-on trigger via a var.

Log balance: the run setting is no longer touched; crossing relies on force-walk like the other log balances so the run orb stays lit, and the balance sound plays each step.

One deviation: in our map data the tripwire midline carries wall flags, so walking through the wire without interacting is collision-blocked, the capture's walk-through arrow volley can't trigger and I left that walk-on handler out rather than firing it from the approach tile.

Tag the tripwire tiles as border areas so a walk click at the wire
force-walks the player through the wall-flagged midline at walk speed,
with the arrow launcher volley, damage and poison firing on the way
through. Interacted step-overs suppress the volley and roll as before.
@HarleyGilpin

HarleyGilpin commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up in e4b6395: the walk-through deviation from the previous comment is resolved, the tripwire tiles are now tagged as border areas, so walking at the wire crosses it the same way the border guard gates work (forced walk through the wall-flagged midline), with the arrow volley, damage and poison firing on the way through. Interacted step-overs still suppress the volley and roll as before.

Interacting turns the player to face the clicked object, so once the
exact move carried them past it they turned back towards it. Re-face
the crossing direction when the move completes (dense forest and leaf
trap jumps).
749 is not the squeeze animation in this revision; 3303 is the standard
squeeze-through used for dense forest. It self-terminates so the
explicit anim clear after the move is no longer needed.
Only 3937 climbs over; every other dense forest passage squeezes
through with animation 1237. Remove the separate double squeeze
animation entry.
A full region scan found five tripwires and six sticks traps; wires at
(2251,3168) and (2294,3243) and sticks at (2181,3209), (2257,3227) and
(2295,3214) had no trigger areas. The last two sticks traps are the
vertical variants.
A successful pass is one exact move from the player to two tiles past
the trap anchor in the crossing direction with the walk animation,
rather than stepping across tile by tile.
Tag the sticks strips as border areas so a walk click at them force
walks the player through at walk speed, always springing the trap on
the way. Successful interacted passes now land one tile past the strip
so the player never ends a crossing standing inside the border area.
846 shows a crawl in this revision; 1441 is the era's recoil animation
used for sprung traps. Also replace the placeholder fail damage and
rate with the known values: 80 damage and a 31-156/256 agility roll.
The forced move with the walk animation looked wrong; go back to
lining up and stepping across at walk speed, ending just past the
strip.
Forcing the jump onto the patch's centre column made the client blend
the sidestep into the jump as a glide. The patch is three tiles wide,
so keep the player's lane (coerced into the patch) and only walk when
they genuinely need to line up.
@HarleyGilpin

Copy link
Copy Markdown
Contributor Author

@GregHib - All adjustments have been made.

Comment thread game/src/main/kotlin/content/area/tirannwn/isafdar/IsafdarTraps.kt
Comment on lines +43 to +47
objectApproach("Cross", "isafdar_log_balance,isafdar_log_balance_2,arandar_log_balance", arrive = false) { (target) ->
approachRange(10)
steps.clear()
crossLog(target)
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think approach is needed for these (when I tested it gave me a message saying can't reach that)

@HarleyGilpin HarleyGilpin Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "can't reach that" comes from clicking the far log object, which the pathfinder can't reach, the original game gives the same message there. The approach handler is still needed for the near log though: without it the interaction pathfinds to the melee reach tile diagonally behind the log (2258,3249) and walks the whole route before operating, so the player dips a tile south before crossing. The Crossing the log never paths south around it test reproduces it if the approach is removed. Happy to rework it if you'd prefer a different shape for skipping the arrival delay.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

without it the interaction pathfinds to the melee reach tile diagonally behind the log (2258,3249) and walks the whole route before operating

This doesn't seem to be the case, without an approach it walks to 2259, 3249 or 2258, 3250

@HarleyGilpin HarleyGilpin Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, retested and the reach tile lands beside the log, so the approach handler and the arrive parameter engine addition are both removed in 5846c34. The far-end "can't reach that" matches the original game, and there's a test covering it now.

Comment on lines +105 to +113
// Approach the log at normal speed; walking away cancels
walkTo(from)
var count = 0
while (tile != from && count++ < 50) {
pause(1)
}
if (tile != from) {
return
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

walkOverDelay should be fine for this

@HarleyGilpin HarleyGilpin Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

walkOverDelay's delay is non-cancellable, and it sets the "delay" var which makes the Walk instruction ignore input, so the player can't cancel by walking away while still approaching the log (they'd be dragged the rest of the way and across). The pause loop keeps the approach interruptible; there's a test covering it (Walking away while approaching the log cancels the crossing). The crossing itself still uses walkOverDelay.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah but as the approach is removed it shouldn't be an issue as it won't activate until right next to the log (and you can't cancel once on the log)

@HarleyGilpin HarleyGilpin Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 5846c34, approach removed and the whole thing is walkOverDelay now. One engine fix fell out of it: Movement.calculate was queueing steps with only the equipment-based forceWalk check and discarding the noRun bit that walkTo packs into the target step, so walkOverDelay(forceWalk = true) crossings actually ran at run speed (this affected the other log balances too). It now honours the packed bit.

Comment thread game/src/main/kotlin/content/skill/agility/shortcut/LogBalance.kt Outdated
- Store per-site sticks trap landing tiles in a tables.toml so crossing
  or being flung never overshoots into scenery; the trap at (2200,3169)
  now walks the player out to the south past the tree
- Simplify the log balance approach to walkOverDelay and drop the
  manual balance sound; the rope_balance render emote carries its own
walkOverDelay's non-cancellable delay blocks Walk instructions, so a
player couldn't change their mind while walking up to the log. Walk
with a pause loop instead so stepping away cancels the crossing;
covered by a test.
Drop the approach registration and the arrive parameter engine
addition; the interaction's own pathing stops right next to the log
and stays cancellable, and clicking the unreachable far end gives
"I can't reach that" like the original game.

The crossing itself ran instead of walked: Movement.calculate queued
steps with only the equipment-based forceWalk check, discarding the
noRun bit walkTo packs into the target step, so walkOverDelay's
forceWalk never applied. Honour the packed bit so forced walks
actually walk.
Comment on lines -53 to 56
character.steps.queueRoute(route, tile, tile.noCollision, noRun = strategy.forceWalk(character))
character.steps.queueRoute(route, tile, tile.noCollision, noRun = tile.noRun || strategy.forceWalk(character))
} else if (tile != Tile.EMPTY) {
character.steps.queueStep(tile, tile.noCollision, noRun = strategy.forceWalk(character))
character.steps.queueStep(tile, tile.noCollision, noRun = tile.noRun || strategy.forceWalk(character))
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems redundant, noRun isn't set on strategy.destination (which are tiles not steps) and if you were, you can do it with strategy.forceWalk

@HarleyGilpin HarleyGilpin Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the check into TileTargetStrategy.forceWalk as suggested in f5820e0 so Movement is untouched.

walkTo packs noRun into the strategy's target step, so let the
strategy's forceWalk report it instead of unpacking the bit in
Movement.
Comment on lines +17 to +22
val direction = if (tile.x < target.tile.x) Direction.EAST else Direction.WEST
val dest = Tile(target.tile.x + direction.delta.x * 2, target.tile.y, tile.level)
face(direction)
delay()
anim("dense_forest_squeeze")
exactMoveDelay(dest, delay = 90, direction = direction)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't the correct anim, the objects are animated and open up and then the player walkOverDelay's to the other side

@HarleyGilpin HarleyGilpin Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked in 29a03c7, both tree gate objects play the open animation with the door sounds.

Comment on lines +12 to +44
objectOperate("Enter", "poison_waste_cave_entrance,poison_waste_cave_entrance_2,poison_waste_cave_entrance_3") {
message("You enter the murky cave...")
delay(2)
tele(Tile(1989, 4174))
}

objectOperate("Exit", "poison_waste_cave_exit,poison_waste_cave_exit_2,poison_waste_cave_exit_3,poison_waste_cave_exit_4") {
delay(2)
tele(Tile(2321, 3100))
}

objectOperate("Climb-up", "poison_waste_sewer_ladder_up") { (target) ->
anim("climb_up")
delay(2)
val dest = if (target.tile.equals(2041, 4189) || target.tile.equals(2041, 4172)) {
target.tile.add(1, 0, 1)
} else {
target.tile.add(0, 1, 1)
}
tele(dest)
}

objectOperate("Climb-down", "poison_waste_sewer_ladder_down") { (target) ->
anim("climb_down")
delay(2)
val dest = if (target.tile.equals(2041, 4189) || target.tile.equals(2041, 4172)) {
target.tile.add(-1, 0, -1)
} else {
target.tile.add(0, -1, -1)
}
tele(dest)
}
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These can all be .teles.toml

@HarleyGilpin HarleyGilpin Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 29a03c7, script deleted, entrances, exits and all three ladders are entries in poison_waste.teles.toml.

return@objectOperate
}
}
if (Level.success(levels.get(Skill.Agility), 128..250)) { // TODO unknown rate

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://oldschool.runescape.wiki/w/Leaves_(trap)

Suggested change
if (Level.success(levels.get(Skill.Agility), 128..250)) { // TODO unknown rate
if (Level.success(levels.get(Skill.Agility), 180..255)) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 29a03c7.

player.anim("pit_getup")
player.say("Ouch!")
player.sound("fall_land")
player.damage(random.nextInt(20, 41)) // TODO unknown damage

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
player.damage(random.nextInt(20, 41)) // TODO unknown damage
player.damage(random.nextInt(20, 181)) // TODO unknown min damage

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 29a03c7.

Comment on lines +60 to +61
// Interacting faced the object, which is now behind; keep facing the way travelled
face(direction)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automatic due to how interactions work

Suggested change
// Interacting faced the object, which is now behind; keep facing the way travelled
face(direction)

@HarleyGilpin HarleyGilpin Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Player faces behind them unless i set the face direction.

Comment on lines +43 to +44
if (inventory.remove("coins", price) && inventory.remove("crystal_teleport_seed_uncharged")) {
inventory.add("crystal_teleport_seed_4")

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Transaction

@HarleyGilpin HarleyGilpin Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 29a03c7, the recharge is a single transaction (coins + uncharged seed out, charged seed in) with the deficient case falling back to the apology line.

walkOverDelay(entry)
anim("tripwire_step_over")
exactMoveDelay(exit, delay = 30, direction = direction)
if (Level.success(levels.get(Skill.Agility), 128..250)) { // TODO unknown rate

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://oldschool.runescape.wiki/w/Tripwire

Suggested change
if (Level.success(levels.get(Skill.Agility), 128..250)) { // TODO unknown rate
if (Level.success(levels.get(Skill.Agility), 30..155)) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 29a03c7.

this["crossing_trap"] = true
walkToDelay(entry.add(direction.inverse()), forceWalk = true)
walkOverDelay(entry)
if (Level.success(levels.get(Skill.Agility), 31..156)) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

? https://oldschool.runescape.wiki/w/Sticks_(trap)

Suggested change
if (Level.success(levels.get(Skill.Agility), 31..156)) {
if (Level.success(levels.get(Skill.Agility), 30..155)) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 29a03c7 (and matched the sticks operate roll to it).

class ElvenOverpass : Script {

init {
objectOperate("Climb", "elven_overpass_rocks_up,elven_overpass_rocks_down") { (target) ->

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The timing/anims on these are off.

Dump:
elven-overpass.txt

@HarleyGilpin HarleyGilpin Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked from the dump in 29a03c7, climbing down is the exact move facing the cliff (delay 120/90 per site) with human_climbing_down and the climbing_loop sound, climbing up walks the cliff path tile by tile under the climbing render emote, and the level gate uses the dump's message. One note: the dump's objects are 16514/16515 but our cache has 9296/9297 at those exact tiles (16514/16515 don't exist in 634 here), so the ids stay, renamed the defs top/bottom to match. The dump's overpass gates (3944/3945) are also reworked to swing open to their open variants, walk the player through and close five ticks later.

- Trap success rates from the wiki: tripwire and sticks 30..155, leaf
  jump 180..255; leaf fall damage up to 180
- Drop the redundant re-face after the dense forest squeeze
- Eluned's recharge is a single inventory transaction
- Poison Waste entrances, exits and ladders are data-only teleports
- Lletya tree entrance animates both gates open, walks the player
  through and closes them, instead of a squeeze animation
- Elven overpass rocks: climbing down is an exact move facing the cliff
  with the climbing-down animation; climbing up walks the cliff path
  under the climbing render emote; custom level message; defs renamed
  top/bottom to match which end they sit at
- Arandar heavy gates swing open to their open variants with the big
  door sound, walk the player through and close five ticks later
- Restore facing the direction of travel after the dense forest squeeze
- Animate the Lletya tree gate with its own tree-parting animation
  (12109) instead of an unrelated climb animation
- Swing the Arandar heavy gate halves a full 90 degrees when opening
Each gate half swings across to the opposite side: the right gate
opens as 3947 at (2384,3333) and the left as 3946 at (2387,3333).
Walking through on any column but 2385 or 2386 clips the player
through the gate, so clamp the crossing tile to the opening.
Passing on any row but 3194 or 3195 clips the player through the
trees, so clamp the crossing tile to the opening.
@HarleyGilpin
HarleyGilpin requested a review from GregHib September 9, 2026 22:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants