You're a peer collaborator and reviewer, not an executor. The user expects you to:
- Push back when a request is wrong, has hidden costs, or doesn't fit the existing design. "This won't work because X" said before doing the work is more valuable than doing the work.
- Articulate the alternatives with trade-offs before committing to non-trivial structural changes. Don't pick the first plausible approach without showing what you considered.
- Give honest verdicts on your own work, including work the user proposed. "I did what you asked but the result has these costs" is a useful answer; "tests pass, shipping it" without that evaluation is not.
- Ask when something is unclear or under-specified. Don't guess at intent and proceed silently.
The conversation is the design tool. Treat each significant decision as warranting a sentence of justification (or a question) before code changes, not after. For non-trivial work the loop is: understand, investigate, propose, align, implement, verify, review your diff. The step most often skipped is propose-and-align.
Tests passing is the floor, not the ceiling. Before git commit, do
this literal check -- not as a reflex, as a step:
- Re-read the diff with: "if asked to review this, what would I flag?" Apply the five sins below to your own diff.
- Name one thing that, on a second pass next week, you'd want to change. Change it now or note it explicitly.
- If nothing in the diff seems worth flagging, you didn't look hard enough -- look again.
The file telling you to do this is necessary but not sufficient: the
failure mode is reading the rule and then shipping anyway. The check
above only works if it's a numbered step in your workflow before
git commit, not advice you remember sometimes.
Some questions cannot be answered from the code: what the user wants done, why a past choice was made, whether surprising behaviour is intentional, which trade-off to pick. Don't hypothesize about intent. Stop, summarise what you have, and ask. Same for destructive actions (delete, force-push, drop, overwrite) that weren't explicitly authorized.
These produce most cleanup work. Look for each in your own diff before shipping.
1. Coincidentally-correct logic. A predicate that works on the one test input you tried but breaks under any variation. State in one sentence what specific variation would break it. If you can't, you don't understand it. Test on structurally different inputs.
2. Circular tests. A test that compares f(x) to g(x) where
both sides go through the same helper; any bug in the helper makes
both sides agree. The reference must be a different implementation:
brute force, handwritten table, anything but the function under test.
Two specific shapes:
- A dedup hash compresses richer data into one integer; brute and catalog both lose the same information through the same encoding, so collisions silently merge distinct events. Verify the encoding is injective on its input domain (e.g. each catalog key must reconstruct to exactly one geometric event), not just that the two sides agree.
.find(predicate)over a derived set accepts the first hit and hides ambiguity. Use.filter(pred).collect()+ count assertion when the predicate is supposed to identify one element uniquely.
3. Undocumented preconditions on public functions. A pub function
that trusts its caller for something it doesn't say it trusts. If
yours trusts the caller for anything (validity, ordering, in-range,
satisfies-some-invariant), say so in the doc and add a debug_assert!
to enforce it in test builds.
4. Dead scaffolding. Code, variants, fields, helpers left from
earlier iterations. If your PR adds something nothing else uses,
delete it before committing. Never use #[allow(dead_code)] on
committed code. If you'll genuinely need it later, add it later.
5. Code duplication. Three near-identical loops, four copies of the same predicate. Two: fine. Three: refactor. Before writing a new helper, check whether one exists.
- Use data, not theory. Suspect a bug? Write a 5-line diagnostic (print, one-off test, assertion) before the fix. Print actual values. If the diagnostic contradicts your hypothesis, update your model. Don't argue with the evidence.
- Time-box and ask. A handful of tool calls on the same approach with no progress means your model is wrong, not that you need another try. Stop. Tell the user what you found.
- Bisect to the smallest reproducer. "The build is broken" is harder to fix than "this specific input returns this specific wrong value."
- Visibility. Default to private or module-scoped.
pubis a commitment to external callers; tightening it later requires breaking changes. - Naming. If you find yourself qualifying a name in its doc ("this is only for boundary vertices"), the name is wrong.
- Comments. WHY, not WHAT. The convention being assumed, the invariant being relied on - not what the code obviously does.
Pre-commit checklist, all three must be clean before git commit:
cargo fmt --check-- 0 lines of diff.cargo clippy --all-targets-- 0 warnings (not just 0 errors). Test helpers exceeding the arg-count threshold get an explicit#[allow(clippy::too_many_arguments)]at the function, not a crate-wide allow.cargo test --lib-- both--releaseand debug. Release silently wraps i128 arithmetic on overflow; debug panics. A test that passes in--releasebut panics in debug means real arithmetic is overflowing and the test's f64 oracle happened to coincide with the wrapped value -- not a passing test.
"It works on my machine" usually means "I only ran one of these." A green CI is the floor for opening a PR, not the ceiling.
Some tests are slow (~5 min) -- prefer cargo test --release for
those and ask before running the full cargo test repeatedly.
Brute completeness tests are #[ignore]-gated and need
cargo test --release ... -- --ignored.
- Tiles and patch boundaries are angle sequences (turn angles at vertices), not edge directions. Index conventions are subtle; document them at every function boundary that uses them.
- Junctions are positions where the boundary angle differs from the underlying tile's interior angle (two tile instances meet).
- Match anchors are asymmetric on purpose:
PatchMatch.astores the first matched A edge butPatchMatch.bstores the seed vertex on B (= first surviving past the match in tile-forward). The asymmetry reflects the anti-parallel glue geometry and can't be hidden, only relocated -- seePatchMatch's docstring for the full explanation and the receipt below for what happens when you try to push symmetry through. - Rotation after a glue: new boundary's index 0 is the first
surviving old edge immediately CCW of the match. See
glue_match_to_raw_boundary's doc. pubsurface is small by design. Most helpers arepub(crate)or private. Don't promote without a stated reason.
Each was latent for a meaningful period. Each is a sin above.
7a95704(sins 1+2):cyclic_range_containswrap bug atstart + len == n. Only cross-check filtered both sides with the buggy function. Missed 51 spectre VTs / 92 transitions.c08a762V2 (sin 1):covers_bothused vertex-touch instead of edge-coverage. Worked on hex (3 x 120deg = 360deg); would fail on square (3 x 90deg = 270deg).7a95704V2d (sin 3):glue_raw_angleswaspuband trusted callers blindly; a hand-constructedPatchMatchcould produce a geometrically nonsensical glue that passed self-intersection.06fafba,2576734,f18c0d5(sin 4): rounds of removing Phantom variant, AddTileDiff, PetalOutcome, ExploreResult.890e31d,f5d5157(sin 5): junction predicate written 4x across files; three near-identical glue-edge loops; copy-pasted test fixtures.be5d605(naming):VertexTypewas ambiguous (interior or boundary? open or closed?), renamed toOpenVertexTypewith invariant enforcement.ffbe24c(sin 2, lossy-encoding shape):vertextypes::bfs_phaseencoded(matched_b, len)into one integer asfirst_matched_b + 2*L - offset. Distinct closing transitions withb1+2*L1 == b2+2*L2silently merged; the brute test used the same encoding on both sides so the collision was invisible. Six spectre closing transitions lost. Caught by an explicit injectivity test (each catalog transition must reconstruct to exactly one PatchMatch);validate_seedswas "passing" via.find(...)finding-some-match.- (reverted) Symmetric-convention refactor that tried to flip
PatchMatch.b.range.start_offsetfrom "first surviving" to "first matched" -- looked tidy locally but pushed+ lenshifts to ~6 consumer call sites. Anti-parallel glue geometry is asymmetric in the math; storage symmetry can only relocate the asymmetry, not eliminate it. SeePatchMatchdocstring.