Repository navigation
Pin what frenet promises on any road with property tests - #47
Merged
Merged
Conversation
The unit tests in path.rs check frenet on four roads drawn by hand. This adds proptest as the workspace's first property-testing dependency and six properties over generated roads, turned and shifted anywhere in the plane: a point on the road reads back its own arc length, a lane point round trips while it stays within the bend's reach, the offset is the distance to the road, the offset changes no faster than a walking point does, a loop's arc length stays within one lap, and an open road's arc length runs on past both ends with the lane offset holding. Every road is generated so that it cannot cross itself, since a crossing puts a point on two stretches at once and rounding then picks between them. An open road steps at least a meter along one axis per point; a closed one is a star-shaped polygon walked in angular order, with no angular gap reaching half a turn. That last bound was the one lesson the stress runs taught: a wider gap sends a segment across the center and through the far side, which showed up as an offset changing sign a kilometer from the road and was the generator's fault rather than frenet's. The property tests reach only the public API, so they live as an integration test. Proptest's default regression-file location assumes a crate root the test does not have, so the config places it beside the source. The tests cost the actors crate's clean test build under a second and run in a few hundredths of a second. The demo world hash is unchanged, since nothing here reaches a fingerprint. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
edufford
commented
Sep 3, 2026
The property tests' oracle counted a road's segments from its points and whether it closes, which the path already knew and counted in three places of its own: the builder, the refusal check and a private method reading it off the arc-length table. One associated function now answers all of them, the method is public with a unit test pinning both counts, and the oracle calls it. Inert by intent: the demo world hash is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
An open road was a walk that always advanced along one axis, which cannot cross itself but cannot double back either. It is now a random walk turning by up to 150 degrees at each vertex, held to one rule: any two segments that do not meet stay three lane widths apart, since closer than that they would share lane space and a lane point beside one would be beside the other. Closed roads keep the same clearance. About a third of open roads are redrawn for it, so proptest's redraw allowance is raised to suit a stress run. Roads that double back showed the continuity property was overstated. Half a kilometer from a hairpin, a point switches from the right of the outbound leg to the outside of a right turn, and the offset flips sign with no zero crossing; no signed offset can be continuous far from a road facing both ways. The property now holds the sign only beside the road, within half the clearance, where any two segments a point is nearest to are close enough that they must meet at a vertex, and holds the magnitude everywhere. The one pair it leaves unchecked is a walk changing stretch and nearest segment at once, where the line a road holds past its end gives way to a nearer segment, which a short return leg ending within lane distance of the outbound leg can bring beside the road. The same sentences carry "rotated" for "turned", a wider point range with its reason, and "segment" wherever a stretch of road was meant, so that the word names only the parts of an open road's arc length. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Two tests each worked out, in their own words and with a bare cap of fifty or a hundred meters, how far a point could sit off a segment before another segment was nearer. One helper now answers both: half the distance to the nearest other segment, and never more than the widest road, ten lanes. The tests that recover a point from its arc length and offset say so in their names rather than claiming an inverse the docs deny, the distance test names its three stretches outright instead of matching the last with a wildcard, and the past-the-ends test calls the offset it places the expected one and says a negative one lies to the right. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The docs on frenet said the offset goes on measuring across the line the road held past its end, and left unsaid what happens where the road bends back toward that line: any segment nearer than the line still wins, so the offset steps from the line's distance to the road's as a car crosses between them. Review settled that this stays as it is, so the docs now say it. The property tests' notion of a stretch is trimmed to what it classifies, an arc length, and the function naming one binds its result before returning it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The property that a closed road's arc length stays within one lap allows the total itself, and its comment explained why in a sentence nobody could read. Outside the corner where the road closes, the last segment and the first are the same distance away in exact arithmetic, rounding decides which answers, and the last answers with the total where the first answers with zero. The test and its neighbor now say closed road rather than loop, as the rest of the file does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The config comment said a failure's seed lands in a directory beside the test, where proptest's WithSource setting writes a sibling file with the test's name and a new extension. Saying the real name is what lets the file be found, committed, or cleaned up on purpose. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A persisted proptest seed replays its case only while proptest and the generators that drew it stay as they are, and nothing says when that stops. So the seed file is committed as proptest asks, the fix it led to lands as a unit test with the shrunk case written out, and the file is deleted whenever proptest moves in the lock or a generator changes. The config comment says so. Proptest stays at a caret requirement pinned through Cargo.lock like libm, and CI now fetches the dependency crates with the lock held before its first build. A stale lock used to be updated quietly and built against whatever it resolved, which for libm is a different fingerprint and for proptest a seed that no longer means its case; now it fails the job at the cause. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
edufford
marked this pull request as ready for review
September 3, 2026 06:47
edufford
commented
Sep 3, 2026
The pull request description was carrying the reasons, which is DECISIONS.md's job. One dated entry now holds them: the offset steps where a nearer segment takes over from the line an open road holds past its end, the signed offset is continuous beside the road rather than everywhere, and a persisted proptest seed is committed with its fix landing as a unit test and the file deleted once proptest or a generator moves. CLAUDE.md's list of barriers names the CI step that fetches the dependency crates with the lock held. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The locked fetch sat after every install step, so a stale lock was reported only once the packager and the Python environment were set up. It now follows the rust-cache restore directly, the first moment cargo can run, and the crates it fetches land in the restored cache. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The dev-dependency comment tied proptest to the property tests on the road, which would read as stale the day a second file of property tests appeared. It names the kind of test now and nothing more. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The unit tests in path.rs check frenet on four roads drawn by hand. This
adds proptest as the workspace's first property-testing dependency and
six properties over generated roads lying anywhere in the plane facing
any way: a point on the road reads back its own arc length, a lane point
is recovered while the lane has room, the offset is the distance to the
road, the offset changes no faster than a point moving beside the road,
a closed road's arc length stays within one lap, and an open road's arc
length runs on past both ends with the lane offset holding.
An open road is a random walk turning by up to 150 degrees at each
vertex, and a closed one a star-shaped polygon walked in angular order.
Both keep any two segments that do not meet three lane widths apart,
since closer than that they would share lane space and a lane point
beside one would be beside the other. That clearance is what the
properties lean on: a lane has room beside a segment up to half the
distance to the nearest other segment, and beside the road, within half
the clearance, any two segments a point is nearest to must meet at a
vertex and so agree on which side of the road it is.
Two things the stress runs taught, recorded in DECISIONS.md: a
star-shaped polygon needs every angular gap under half a turn or it is
not simple, and no signed offset can be continuous far from a road that
comes back around in another direction, so the continuity property holds
the sign beside the road and the magnitude everywhere. The one boundary
it leaves unchecked is where the line a road holds past its end gives
way to a nearer segment, and frenet's docs now say that step is there
and why it stays.
The tests reach only the public API, so they live as an integration
test, and num_segments is public for the oracle's sake with one count
shared by the builder and the refusal check. A failure's seed is
persisted beside the test, committed as proptest asks, and the fix lands
with the shrunk case as a unit test, since a seed draws the same case
only while proptest and the generators stay put; CI now fetches the
dependency crates with the lock held before its first build, so a stale
lock fails the job rather than quietly building something else. The
tests cost the actors crate's clean test build under a second and run
in a tenth of a second. The demo world hash is unchanged.