Minecraft-Console-Client/tools/pathing_theory/capabilities.py
BruceChen 418f17b4a1 pathing: add side-wall theory, full-coverage parkour test suite, and live test fixes
Theory simulator:
- Add 2D side-wall jump physics with yaw sweep for worst-case margin
- Generate sidewall theory cases (flat/ascend/descend, wall_offset 0/1)
- Add momentum-capabilities.json with band compression and max_reach
- Extend models, capabilities, canonical, and renderers for sidewall

Full-coverage parkour test suite (tools/test-parkour.py):
- Derive test matrix from momentum-capabilities.json
- Build linear/neo/ceiling courses via RCON with 7-block clear margin
- Use /goto for pathfinding, parse A* and PathMgr log output
- Stop-at-first-failure per (family, subfamily, dy, ceil, wo) group
- Hierarchical --filter (e.g. linear/flat, ceiling/headhitter/ceil2.5)
- Exclude sidewall from default matrix (identical max_reach to linear)

Pathing execution fixes:
- Align parkour contracts and timing budgets with live test results
- Fix jump-entry yaw snapping for grounded handoffs
- Template helper and sprint jump template refinements

Made-with: Cursor
2026-04-15 17:52:47 +00:00

200 lines
5.7 KiB
Python

from collections import defaultdict
from tools.pathing_theory.models import MomentumCapabilityBand, TheoryCase
def _linear_group_key(case: TheoryCase) -> tuple[object, ...]:
return (
case.family,
case.subfamily,
case.movement_mode,
"gap_blocks",
case.delta_y,
None,
None,
)
def _neo_group_key(case: TheoryCase) -> tuple[object, ...]:
return (
case.family,
case.subfamily,
case.movement_mode,
"wall_width",
None,
None,
None,
)
def _ceiling_group_key(case: TheoryCase) -> tuple[object, ...]:
return (
case.family,
case.subfamily,
case.movement_mode,
"gap_blocks",
None,
case.ceiling_height,
None,
)
def _sidewall_group_key(case: TheoryCase) -> tuple[object, ...]:
return (
case.family,
case.subfamily,
case.movement_mode,
"gap_blocks",
case.delta_y,
None,
case.wall_offset,
)
def _case_group_key(case: TheoryCase) -> tuple[object, ...]:
if case.family == "linear":
return _linear_group_key(case)
if case.family == "neo":
return _neo_group_key(case)
if case.family == "ceiling":
return _ceiling_group_key(case)
if case.family == "sidewall":
return _sidewall_group_key(case)
raise ValueError(f"Unsupported theory family for capability bands: {case.family}")
def _case_reach_value(case: TheoryCase) -> int | None:
if not case.expected_reachable:
return None
if case.family in {"linear", "ceiling", "sidewall"}:
return case.gap_blocks
if case.family == "neo":
return case.wall_width
raise ValueError(f"Unsupported theory family for capability bands: {case.family}")
def _compress_mm_ranges(
mm_to_reach: list[tuple[int, int | None]],
family: str,
subfamily: str,
movement_mode: str,
capability_metric: str,
delta_y: float | None,
ceiling_height: float | None,
wall_offset: int | None = None,
) -> list[MomentumCapabilityBand]:
bands: list[MomentumCapabilityBand] = []
current_start = mm_to_reach[0][0]
current_end = current_start
current_reach = mm_to_reach[0][1]
for mm, max_reach in mm_to_reach[1:]:
if max_reach == current_reach and mm == current_end + 1:
current_end = mm
continue
bands.append(
MomentumCapabilityBand(
family=family,
subfamily=subfamily,
movement_mode=movement_mode,
capability_metric=capability_metric,
min_mm=current_start,
max_mm=current_end,
max_reach=current_reach,
delta_y=delta_y,
ceiling_height=ceiling_height,
wall_offset=wall_offset,
)
)
current_start = mm
current_end = mm
current_reach = max_reach
bands.append(
MomentumCapabilityBand(
family=family,
subfamily=subfamily,
movement_mode=movement_mode,
capability_metric=capability_metric,
min_mm=current_start,
max_mm=current_end,
max_reach=current_reach,
delta_y=delta_y,
ceiling_height=ceiling_height,
wall_offset=wall_offset,
)
)
return bands
def build_momentum_capability_bands(
cases: list[TheoryCase],
) -> list[MomentumCapabilityBand]:
grouped_cases: dict[tuple[object, ...], list[TheoryCase]] = defaultdict(list)
for case in cases:
grouped_cases[_case_group_key(case)].append(case)
bands: list[MomentumCapabilityBand] = []
for key in sorted(grouped_cases):
family, subfamily, movement_mode, capability_metric, delta_y, ceiling_height, wall_offset = key
mm_groups: dict[int, list[TheoryCase]] = defaultdict(list)
for case in grouped_cases[key]:
mm_groups[case.momentum_ticks].append(case)
mm_to_reach = [
(
mm,
max(
(
_case_reach_value(case)
for case in mm_groups[mm]
if _case_reach_value(case) is not None
),
default=None,
),
)
for mm in sorted(mm_groups)
]
bands.extend(
_compress_mm_ranges(
mm_to_reach=mm_to_reach,
family=family,
subfamily=subfamily,
movement_mode=movement_mode,
capability_metric=capability_metric,
delta_y=delta_y,
ceiling_height=ceiling_height,
wall_offset=wall_offset,
)
)
return bands
def _format_range(band: MomentumCapabilityBand) -> str:
return f"{band.min_mm}..{band.max_mm}"
def _format_reach(band: MomentumCapabilityBand) -> str:
label = "max_gap" if band.capability_metric == "gap_blocks" else "max_wall_width"
value = "none" if band.max_reach is None else str(band.max_reach)
return f"{label}={value}"
def format_momentum_capability_lines(
bands: list[MomentumCapabilityBand],
) -> list[str]:
lines: list[str] = []
for band in bands:
parts = [band.family, band.subfamily, band.movement_mode]
if band.delta_y is not None:
parts.append(f"dy={band.delta_y}")
if band.ceiling_height is not None:
parts.append(f"ceil={band.ceiling_height}")
if band.wall_offset is not None:
parts.append(f"wo={band.wall_offset}")
parts.append(f"mm={_format_range(band)}")
parts.append(_format_reach(band))
lines.append(" | ".join(parts))
return lines