Skip to content

engine: Position is an integer cell — rounds and clamps to i16 - #22

Open
Morgandri1 wants to merge 2 commits into
mainfrom
sdk/position-integer-clamp
Open

engine: Position is an integer cell — rounds and clamps to i16#22
Morgandri1 wants to merge 2 commits into
mainfrom
sdk/position-integer-clamp

Conversation

@Morgandri1

Copy link
Copy Markdown
Owner

Summary

  • Implements the operator ruling in ARCHITECTURE.md ("Position is an integer cell", 2026-09-06): Position{x,y} changes from f64 to i16, with a custom Deserialize that accepts any JSON number (an in-flight drag can still send a float), rounds to the nearest cell, and clamps to i16::MIN..=i16::MAX before the value is ever stored, compared, or serialized back out.
  • Regenerated docs/schema/*.json (position.json's field type is now integer/int16; additionalProperties: false preserved via #[serde(deny_unknown_fields)] on the struct even though Deserialize is hand-written).
  • Updated the two contract-pinning tests that asserted the old float shape (node.rs, json_shape.rs) and added four new tests for round/clamp/JSON-parse/deny-unknown-fields, mutation-checked (reverted clamp_cell to a bare cast, confirmed the round/clamp tests fail).

Why

qa/integration/test_node_position.py's POS-is-an-integer/POS-rounds-and-clamps cases are currently failing on main's own tip — confirmed on a fresh CI run of main (not caused by any of my other open PRs). wheel-core::Position had no rounding/clamping logic anywhere; only the web-side UI clamp had landed.

Test plan

  • cargo test -p wheel-core (55 lib + 63 integration tests, including the new Position tests) — green
  • cargo test -p wheel-engine -p wheel-cli -p wheel-api — green, no code changes needed outside wheel-core (Position flows through Position::new(f64, f64) unchanged at every call site)
  • cargo build --workspace — clean
  • cargo fmt --check -p wheel-core, cargo clippy -p wheel-core --lib --tests — clean
  • CI: qa/integration/test_node_position.py POS-* cases should go green

@vercel

vercel Bot commented Sep 6, 2026

Copy link
Copy Markdown

Deployment failed for project wheel with the following error:

Resource is limited - try again in 24 hours (more than 100, code: "api-deployments-free-per-day").

Learn More: https://vercel.com/morgandri1s-projects?upgradeToPro=build-rate-limit

@Morgandri1
Morgandri1 force-pushed the sdk/position-integer-clamp branch from 052e08f to 303cfdb Compare September 6, 2026 18:12
Morgandri1 added a commit that referenced this pull request Sep 7, 2026
Rust's f64::round is half-away-from-zero; JS Math.round is half-up. They agree
everywhere except a negative exact half: Math.round(-10.5) is -10, and the
engine will store -11. Board coordinates go negative the moment you pan, so
that is reachable, and it is one cell of permanent disagreement on every
dragged node — invisible on screen, and exactly the drift the position gate is
meant to catch.

Fixed before #22 lands rather than after, so the engine's arithmetic and ours
are written from the same rule instead of converging on a bug report.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hw5v8WJBYWzZ1SYaD5xctn
Morgandri1 added a commit that referenced this pull request Sep 7, 2026
Rounding cannot -- 0.5 cells/axis, one cell is one CSS px, canvas.tsx caps
zoom at 1.8, so 1.27 px worst case. That is geometry and holds for any board.
Clamping is unbounded and is a question about the DATA, not the code, so it
can only be answered by looking at the rows that exist.

Takes a sqlite path or a live engine URL, names any node outside the i16
bound and prints how far it will jump in pixels. Verified in both directions:
exit 0 on a clean board, exit 1 naming a runaway node.

Written because I asked PM for a SQL query and a request someone has to
translate is a request that waits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F1nLndrG8rrdr5ar7P6WkN
SDK/Engine added 2 commits September 7, 2026 00:20
… way in

Implements the operator ruling in ARCHITECTURE.md (position cell size and
clamping, 2026-09-06): Position{x,y} is now i16, with a custom Deserialize
that accepts any JSON number, rounds to the nearest cell, and clamps to
i16::MIN..=i16::MAX before a value is ever stored or compared. Fixes the
POS-is-an-integer/POS-rounds-and-clamps failures currently red on main
(qa/integration/test_node_position.py).
Position's exported wire type became integer/int16 in the previous commit
(the operator ruling). JSON Schema's type: integer keyword correctly
rejects a non-whole number like -1234.5 by spec, even though the engine's
Deserialize impl still accepts and rounds it. That's an engine-level
behavior the static schema check can't express, so this fixture is marked
_pending the same way tool_source_postman.json already is, pointing at the
wheel-core unit test that actually covers rounding.
@Morgandri1
Morgandri1 force-pushed the sdk/position-integer-clamp branch from 303cfdb to 7b411ee Compare September 7, 2026 00:21
Morgandri1 pushed a commit that referenced this pull request Sep 7, 2026
PM approved landing this: additive to QA's docker-based ENG-starts-
without-shm gate (qa/integration/test_engine_shm.py), not a duplicate --
same failure mode, different technique, no docker required. QA's own
comment says the directory-at-the-shm-path fixture "verified to
reproduce the production symptom exactly," and that fixture is a
filesystem fact, not a container one, so it reproduces here too.

Seeds a database already in WAL (the shape that actually crash-looped
production -- journal_mode persists in the header, so every boot
rediscovers it), blocks the -shm path with a directory, then asserts
open_configured(path, false) -- the engine's own mode -- both returns Ok
AND accepts a real write, with the pre-existing row intact. Mutation-
checked: reverting mode_holds to a bare read-back (dropping the `BEGIN
IMMEDIATE; COMMIT` proof) reproduces the exact symptom -- settles back
on "wal", the mode that just failed.

Does not close qa/BUGS.md #22 -- that stays QA's call once they confirm
1163cc9 live against a fresh image (PM is asking them directly).
Morgandri1 pushed a commit that referenced this pull request Sep 7, 2026
PM approved landing this: additive to QA's docker-based ENG-starts-
without-shm gate (qa/integration/test_engine_shm.py), not a duplicate --
same failure mode, different technique, no docker required. QA's own
comment says the directory-at-the-shm-path fixture "verified to
reproduce the production symptom exactly," and that fixture is a
filesystem fact, not a container one, so it reproduces here too.

Seeds a database already in WAL (the shape that actually crash-looped
production -- journal_mode persists in the header, so every boot
rediscovers it), blocks the -shm path with a directory, then asserts
open_configured(path, false) -- the engine's own mode -- both returns Ok
AND accepts a real write, with the pre-existing row intact. Mutation-
checked: reverting mode_holds to a bare read-back (dropping the `BEGIN
IMMEDIATE; COMMIT` proof) reproduces the exact symptom -- settles back
on "wal", the mode that just failed.

Does not close qa/BUGS.md #22 -- that stays QA's call once they confirm
1163cc9 live against a fresh image (PM is asking them directly).
Morgandri1 added a commit that referenced this pull request Sep 7, 2026
…raction

Content review of the rescued tree, per PM. All four pieces were GENUINELY
NEW — none of this text is on main — and the classification is per piece
rather than per commit, because the histories diverged and replaying them
would have carried a claim its own author withdrew.

CODE (still needed, verified against main): coverage_gate printed
`r.stderr[-2000:] or r.stdout[-2000:]` on failure — stderr only when stderr
is non-empty, and only its last 2000 chars. cargo puts the failing test's
name and panic in the MIDDLE of a long stream, so the slice reliably cut the
one thing worth reading. I hit this myself tonight: I could not read a
coverage failure from CI and dug through the whole log twice. Main still had
the bad slice; it now finds where the failure starts.

BUG-022 CLOSED, by two people who each had half the proof. That session
proved the mechanism at the crate level (mode_holds before the fast path,
directory blocking -shm) and wrote down honestly that it could NOT verify
through the docker fixture, having no docker binary. That gap is closed from
the other side: ENG-starts-without-shm is green through the fixture,
engine-shm 12/12. Neither session could have produced both halves.

BUG-026 CLOSED by measurement — POS-* is 34/34 since #22 landed. Main's entry
still said "17 red", true when filed and not any more.

BUG-034, THE COLLISION, resolved by renumbering: that session filed the
table-columns finding as 026; main had already given 026 to the position
ruling. Different bugs, same number. Renumbering is the whole fix.

AND THE PART THAT WAS ACTUALLY IRREPLACEABLE — the uncommitted file. Commit
c9f0f6f claims the columns bug CONFIRMED, citing a CI run. The uncommitted
correction RETRACTS it: PR #12 predates PR #20 and tested a route that 404s
unconditionally, so neither run was evidence about columns at all. Landing
the commits without the uncommitted file would have put a CONFIRMED claim on
main that its own author had already withdrawn, and sent the next person
hunting a bug the evidence never supported. All three states are recorded —
claimed, retracted, then settled green by measurement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F1nLndrG8rrdr5ar7P6WkN
Morgandri1 added a commit that referenced this pull request Sep 7, 2026
…elete

Content review complete. All four pieces were genuinely new; none of the text
was on main. Classified per piece rather than per commit, because replaying
the commits alone would have landed a CONFIRMED claim its own author had
already retracted in the uncommitted file.

  coverage_gate  the blind stderr tail-slice that hid the failing test's name
  BUG-022        CLOSED — crate-level proof from that session, fixture-level
                 proof from this one; neither had both halves
  BUG-026        CLOSED by measurement — POS-* 34/34 since #22
  BUG-034        the 026 collision, renumbered; claim, retraction, and the
                 measurement that finally settled it

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F1nLndrG8rrdr5ar7P6WkN
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.

1 participant