Files
my-botty/test/test_pather_heading.py
alexpolo1 2162046dac fix(pather): abort after repeated heading rejections instead of guessing
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>
2026-08-28 01:25:55 +02:00

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"