Bug 25 added a heading gate that refuses a low-confidence node match implying a
>90 degree reversal. The gate works, but rejection returned None and the
traverse loop fell through to its anti-stuck path, which force-moves along
last_direction — and when the character is already wedged against the Harrogath
wall, that shoves it further in.
Observed 2026-08-28 01:22:
rejecting low-confidence A5_TOWN_1 (65.3%) for node 3 - implies reversal
rejecting low-confidence A5_TOWN_1 (66.3%) for node 3 - implies reversal
rejecting low-confidence A5_TOWN_1 (67.0%) for node 3 - implies reversal
rejecting low-confidence A5_TOWN_1 (67.7%) for node 3 - implies reversal
Pather: taking a random guess towards (-423, 247)
Wanted to select A5_RED_PORTAL, but could not find it
The error screenshot shows the character outside the battlements in the dark
void with the portal a faint occluded glow. Declaring a position untrustworthy
and then moving on an arbitrary 488px vector are contradictory.
Three consecutive rejections now abort the traverse so the caller re-anchors
from a fresh game (~40s) rather than wedging the character somewhere that
poisons the rest of the run. A confident match clears the counter, and it
resets per traverse so a stale count cannot abort the next one.
The abort MUST precede the anti-stuck block; a test asserts that ordering.
Nothing about the thresholds or the gate itself changed.
Note on the test: its first version searched the source for "random guess" and
matched the explanatory COMMENT above the abort, reporting the ordering
backwards. It now strips comments — the same mistake as slicing source on a
branch name and matching a comment that merely mentioned it. Verified in both
directions: removing the abort makes it fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
60 lines
2.5 KiB
Python
60 lines
2.5 KiB
Python
"""The heading gate must not be followed by a random guess.
|
|
|
|
Bug 25 added a heading check that refuses a low-confidence node match implying a
|
|
>90 degree reversal, because such matches are phantoms scored off unrelated
|
|
scenery. But rejection returned None and the traverse loop fell through to its
|
|
anti-stuck path, which force-moves along last_direction — and when the character
|
|
is already wedged against the Harrogath wall, that shoves it further in.
|
|
|
|
Measured 2026-08-28: four rejections of A5_TOWN_1 at 65-68%, then
|
|
"random guess towards (-423, 247)", then the red portal was unreachable for the
|
|
rest of the run. Declaring a position untrustworthy and then moving on an
|
|
arbitrary vector are contradictory.
|
|
"""
|
|
import inspect
|
|
|
|
|
|
def test_repeated_rejections_abort_rather_than_guess():
|
|
from pather import Pather
|
|
|
|
# Ignore comments. An earlier version of this test matched the phrase
|
|
# "random guess" inside the explanatory comment above the abort check and
|
|
# reported the ordering backwards — the same mistake as slicing source on a
|
|
# branch NAME and matching a comment that merely mentioned it.
|
|
code = [ln for ln in inspect.getsource(Pather.traverse_nodes).splitlines()
|
|
if not ln.strip().startswith("#")]
|
|
|
|
abort_at = next((i for i, ln in enumerate(code) if "_MAX_HEADING_REJECTS" in ln), None)
|
|
guess_at = next((i for i, ln in enumerate(code) if "taking a random guess" in ln), None)
|
|
|
|
assert abort_at is not None, "traverse never gives up on repeated rejections"
|
|
if guess_at is not None:
|
|
assert abort_at < guess_at, (
|
|
"the abort check must run BEFORE the anti-stuck force-move; that path "
|
|
"moves along last_direction and drives a wall-wedged character further in"
|
|
)
|
|
|
|
|
|
def test_rejection_counter_resets_per_traverse():
|
|
"""A stale count would abort the next traverse before it starts."""
|
|
from pather import Pather
|
|
|
|
src = inspect.getsource(Pather.traverse_nodes)
|
|
assert "self._heading_rejects = 0" in src, "counter is never reset per traverse"
|
|
|
|
|
|
def test_a_confident_match_clears_the_counter():
|
|
from pather import Pather
|
|
|
|
src = inspect.getsource(Pather.find_abs_node_pos)
|
|
assert "self._heading_rejects = 0" in src, (
|
|
"a good match must clear the counter, or unrelated rejections accumulate "
|
|
"across an otherwise healthy traverse"
|
|
)
|
|
|
|
|
|
def test_threshold_is_small_enough_to_act_before_the_guess():
|
|
from pather import Pather
|
|
|
|
assert Pather._MAX_HEADING_REJECTS <= 4, "four rejections preceded the observed wall-walk"
|