Skip to content

SvgMapConverter ignores ancestor SVG transforms: classic_bottleneck* pedestrian group loads 4.37 m off, goal zone inside the boundary wall #8314

Description

@ll7

SvgMapConverter reads x/y/d straight off the SVG attributes and never looks at an ancestor transform. Every classic_bottleneck* map wraps its whole pedestrian group in transform="translate(0,-4.3651647)", so the loaded pedestrian geometry sits 4.3651647 m from where the map file draws it — and the loaded pedestrian goal zone ends up partly inside the northern boundary wall and partly outside the viewBox.

Verified against origin/main 612de6f45 and against e2ac534c9d6bb750346b1e0724638c91306e410a (the commit a downstream figure pins). grep -c transform robot_sf/nav/svg_map_parser.py returns 0 at both.

Where it comes from

_get_svg_info collects elements with self.svg_root.findall(".//svg:rect") (and the same for path / circle), which is a descendant-or-self search that discards the parent chain. _parse_rect_element then does float(rect.attrib.get("x")), float(rect.attrib.get("y")); _parse_path_element reads d directly. Nothing between the XML and the MapDefinition consults transform.

There is no converted-map artifact in between either: each scenario's map_file names the .svg, and SvgMapConverter parses it at run time.

Affected maps

maps/svg_maps/classic_bottleneck.svg, classic_bottleneck_medium.svg, classic_bottleneck_high.svg — all three carry exactly one transform, on the group holding ped_spawn_zone, ped_goal_zone and ped_route_0_0:

transform="translate(0,-4.3651647)"

The robot group, the obstacles and the single_ped_* markers carry none, so only the pedestrian elements move.

Measured consequence (low-tier map, all three identical in drawn geometry)

element as the map file draws it as SvgMapConverter loads it
ped_spawn_zone y 1.635 – 5.635 y 6.000 – 10.000
ped_goal_zone y 33.770 – 37.770 y 38.135 – 42.135
ped_route_0_0 (20, 8.929) → (20, 23.635) (20, 13.294) → (20, 28.000)
robot/pedestrian route crossing (20.00, 16.10) (20.00, 24.56)

Uniform shift (0, −4.3651647) m; the routes' crossing point — where the designed encounter happens — moves 8.4662 m.

The loaded ped_goal_zone is the part that looks like a defect rather than a preference:

  • its viewBox is 0 0 40 40, and the loaded zone spans y 38.135 – 42.135, so 2.135 m of its 4.00 m depth is outside the map;
  • obstacle rect2 is the northern boundary wall at y 39 – 40 across the full width, and the loaded zone overlaps it by 4.00 m² (1.00 m deep over the zone's 4.00 m width) — a quarter of the goal zone is inside a wall.

A goal zone that is partly outside the map and partly inside a wall is not a plausible authoring intent, which is what points at the loader rather than at the maps.

Why this is being reported from outside

A dissertation figure documents the bottleneck archetype's geometry (ll7/diss#2127, ll7/diss#2136). It draws the maps as this loader reads them, because that is the geometry the benchmark campaign ran — but it has to disclose in its caption that the drawing diverges from the map file, which is only correct for as long as this behaviour stands. If the loader is changed to apply transforms, that figure's disclosure and any campaign result that depended on the pedestrian placement both need revisiting.

Possible dispositions

  1. Apply ancestor transforms in SvgMapConverter (walk depth-first accumulating translate, refuse anything that is not a translate rather than ignoring it). This changes the loaded geometry of the three bottleneck maps, so it is behaviour-changing for any existing result on them.
  2. Reject maps that carry transforms, so an authored transform is a loud error instead of a silent 4.37 m displacement.
  3. Flatten the transform in the three map files (bake the offset into the child coordinates) and keep the loader as it is. This also changes the loaded geometry.
  4. Document the current behaviour as the contract and treat the map files as needing to be authored transform-free.

(1) or (2) removes the silent-divergence class; (3) fixes these three maps without closing the class; (4) closes nothing but at least makes the contract explicit. Whichever is chosen, the ped_goal_zone-inside-the-wall geometry deserves a look on its own.

Reproduction

import xml.etree.ElementTree as ET
NS = {"svg": "http://www.w3.org/2000/svg"}
LBL = "{http://www.inkscape.org/namespaces/inkscape}label"
root = ET.parse("maps/svg_maps/classic_bottleneck.svg").getroot()
# what the loader sees
for r in root.findall(".//svg:rect", NS):
    if r.attrib.get(LBL) == "ped_goal_zone":
        print(r.attrib["x"], r.attrib["y"])        # 17.801584 38.135078
# what the file draws
for g in root.iter():
    if g.attrib.get("transform"):
        print(g.attrib["transform"])                # translate(0,-4.3651647)

Ruled implementation contract — 2026-09-03

Objective

Implement ruling option 1: make authored ancestor translate(...) transforms effective in
SvgMapConverter, reject unsupported transform classes, and preserve exact historical/as-run
geometry through an explicit compatibility version rather than silently reinterpreting old
evidence.

Inputs

  • Parser owner: robot_sf/nav/svg_map_parser.py at the implementation branch's exact current-main
    base.
  • Affected fixtures: all repository SVG maps carrying ancestor transforms, including the three
    maps/svg_maps/classic_bottleneck*.svg maps named above.
  • Historical boundary: pinned campaigns and figures that used the legacy transform-ignoring parser.
  • Related truth boundary: ll7/diss#2144 establishes that the low-tier bottleneck cell authored no
    pedestrian markers; transform support must not be described as adding pedestrians to that cell.

Scope

In scope:

  • accumulate nested translations for parsed paths, rectangles, and circles;
  • fail closed on scale, rotate, skew, matrix, malformed transforms, and unsupported mixed
    transform lists;
  • add an explicit legacy/corrected parser or map-geometry contract selector and bind it into
    reproducible configuration/manifests;
  • add analytic parser tests and map-level bottleneck geometry assertions;
  • document the historical-results and corrected-execution compatibility boundary.

Out of scope:

  • rerunning a benchmark campaign in this implementation slice;
  • rewriting, deleting, or reclassifying frozen artifacts or figures;
  • adding missing pedestrian markers or changing scenario density;
  • planner-performance, safety, or paper-facing claims.

Acceptance criteria

  • Nested supported translations produce exact expected coordinates for paths, rectangles, and
    circles.
  • Every unsupported or malformed transform fails with a clear error; none is silently ignored.
  • An explicit legacy route reproduces current transform-ignoring coordinates exactly.
  • Corrected bottleneck geometry matches the authored zone/route coordinates and removes the
    current spawn/robot-goal and pedestrian-goal/wall overlaps.
  • Configs and manifests identify the geometry contract so legacy and corrected rows cannot be
    pooled accidentally.
  • Existing frozen results remain labeled as legacy/as-run and are not retroactively corrected.
  • The low-tier zero-pedestrian finding remains explicit and unchanged.

Verification

  • Add red/green focused parser tests for nested translations and each rejected transform class.
  • Add exact-coordinate tests for all transformed repository maps and the three bottleneck maps.
  • Run the focused navigation/map parser test modules, Ruff, format check, and git diff --check.
  • Run the full repository PR-readiness path because map geometry and benchmark comparability are
    affected.
  • Obtain independent domain-aware review of the exact head before merge; implementation proof is
    not benchmark or paper-grade evidence.

Decision and claim boundary

Ruling token: 1. Confidence: 0.98 that silent transform omission is a loader defect; 0.92 that
bounded translate support with explicit legacy reproduction is the best correction. No campaign,
artifact replacement, historical-result reinterpretation, safety claim, or paper-facing claim is
authorized by this contract.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdomain-approvedDomain approval completed: claim boundary frozenruledAuthor ruling recorded; execution pending or completetype:bug

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions