Skip to content

refactor(xodr,osc): one enum table and one XML helper set instead of two (p-ponytail-a) - #564

Merged
JArmandoAnaya merged 1 commit into
mainfrom
refactor/ponytail-core-dedup
Aug 12, 2026
Merged

refactor(xodr,osc): one enum table and one XML helper set instead of two (p-ponytail-a)#564
JArmandoAnaya merged 1 commit into
mainfrom
refactor/ponytail-core-dedup

Conversation

@JArmandoAnaya

Copy link
Copy Markdown
Contributor

Part 1 of 2 for #563 (ponytail audit remediation). Kernel-side duplication only;
the docs-site half follows in its own PR.

No behaviour change — and the evidence for that is byte-level, not "the
tests pass". See Acceptance below, including the part where my first gate
turned out to be blind.

What collapsed

before after
6 OpenDRIVE enums an if-chain in xodr/reader.cpp and a switch in xodr/writer.cpp, in different files, per enum one constexpr table each in the new core/src/xodr/enum_names.hpp, serving both directions
5 pugixml scalar helpers one private copy per format (to_double, num, set_num, set_optional_num, node_to_string) core/src/xml/xml_common.hpp
linked_end byte-identical in edit/operations.cpp and mesh/junction_maneuvers.cpp core/src/road/junction_adjacency.hpp
is_one_of a hand-rolled loop over an initializer_list std::ranges::find
@orientation the same three-way if-chain written out twice in the reader one value_of call each
Environment::procedural_sky set, asserted by two tests, never read by GLRenderer deleted

The enums are the point. Two tables maintained separately, in opposite
directions, is the exact shape #476 came in — the writer re-spelled parsed lane
and mark enums into different semantics on save. One table cannot disagree with
itself.

Three invariants the table deliberately does not absorb, each documented in
the header: *::Other never gets a row (it is the writer's lossy fallback
argument — as a row, "solid" would parse back as RoadMarkType::Other); the
empty string never gets a row (absent-attribute policy is per-attribute and
stays at the call site with the paragraph that mandates it); and value_of
returns nullopt rather than guessing.

Not shared, deliberately

append_fragment looks like a sixth duplicated helper and is not one: the
OpenSCENARIO writer passes pugi::parse_fragment and the OpenDRIVE writer takes
pugixml's defaults. That pair is a real divergence, so it stays per-format —
unlike the five helpers above, whose copies carried comments defending them
against a divergence that never happened.

Also left alone, and why: point_segment_distance (identical in two
subsystems that share no internal header — a new one to save 13 lines is
break-even), and the OpenSCENARIO PhaseSemantics twin (one enum, six values,
not worth a file).

Acceptance

1. Byte-identical output. A throwaway probe reads and re-emits all 143
tracked .xodr/.xosc fixtures — 128 produce documents, 63 produce diagnostics
— and diffs bytes and diagnostic text against the same probe built on main.
Zero differences.

2. That probe is structurally blind to these tables, which I found by
sabotaging it.
With {Solid,"broken"}, {Broken,"solid"} compiled in it still
reported zero differences, for two compounding reasons:

The tables are reached by networks the editor, the bindings and the importers
build, which no fixture round trip exercises. So the probe proves the
preserved tier, the diagnostics and everything non-enum are untouched — and
nothing about the tables.

3. core/tests/test_enum_names.cpp is the gate that does cover them, with
literals transcribed from the ASAM tables rather than derived from the code.
The obvious test — value_of(name_of(v)) == v — is a tautology for exactly
reason (1) above, and is not what this file does. Four sabotages, all caught,
none of which the byte probe noticed:

sabotage probe test_enum_names
swap solid/broken clean 2 failures
invert @orientation +/- clean 2 failures
promote the read-only walking alias to canonical clean 2 failures
add curb as a real RoadMarkType row clean 2 failures

4. ci-macos (-Werror): build clean, 3329/3329 ctest green.
git ls-files '*.cpp' '*.hpp' | xargs clang-format --dry-run --Werror clean —
run after staging, since CI's command skips untracked files and all three new
files were untracked.

Size

−99 lines of executable code (+214 / −313, comments and blanks excluded);
+26 lines net counting the two new headers' comments, which carry the
"never make this a row" rules and the spec citations. The audit predicted −240
for these items; it had counted the deletions without the shared implementation
that replaces them.

Closes #563 is deliberately not claimed here — the docs-site half is still
outstanding.

…two (#563)

Six OpenDRIVE enums each had a read table (an if-chain in xodr/reader.cpp) and
a write table (a switch in xodr/writer.cpp), in different files, with nothing
tying the two directions together. That is the shape #476 came in: the writer
re-spelled parsed lane and mark enums into different semantics on save. They
now share one constexpr table each in core/src/xodr/enum_names.hpp.

Three things stay out of the tables, each preserving today's behaviour:
*::Other never gets a row (it is the writer's lossy fallback argument — as a
row, "solid" would parse back as RoadMarkType::Other); the empty string never
gets a row (absent-attribute policy is per-attribute and stays at the call site
with the paragraph that mandates it); and value_of returns nullopt rather than
guessing.

Five pugixml scalar helpers the two formats each kept a private copy of —
to_double, num, set_num, set_optional_num, node_to_string — collapse into
core/src/xml/xml_common.hpp. append_fragment does NOT: OpenSCENARIO passes
pugi::parse_fragment and OpenDRIVE takes the defaults, so that pair is a real
divergence rather than a copy defended against a hypothetical one.

Also: linked_end was byte-identical in edit/operations.cpp and
mesh/junction_maneuvers.cpp and moves to road/junction_adjacency.hpp; the
@orientation if-chain was written out twice in the reader; is_one_of hand-rolled
std::ranges::find; and Environment::procedural_sky was set, asserted by two
tests, and never read by GLRenderer.

Re-emitting all 143 tracked .xodr/.xosc fixtures is byte-identical, diagnostics
included. That probe cannot see these tables, though — a round trip is invariant
under a consistent permutation, and the preserved tier (*::type_str) means a
parsed file never consults them — so core/tests/test_enum_names.cpp pins the
spellings against literals transcribed from the ASAM tables. Four sabotages the
byte probe called clean all fail it.

-99 lines of executable code.
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.

Collapse the duplicated enum tables and XML helpers the persistence layer maintains twice (ponytail audit)

1 participant