docs(#822): three tracked files told an API reader to read a pos_up key the API does not serve - #841
Conversation
…`pos_up` key that is not served Position is served as one array — `"pos": [east, north, up]`. `pos_east`/`pos_north`/`pos_up` are `PlayerState` field names that `get_debug` folds into that array; no body this API serves has a `pos_*` key. Three sentences told an agent to read one anyway: * `docs/http-api.md` levitating table — now names `player.pos[2]`. * `PlayerState::levitating` rustdoc — the sentence is about what an AGENT reads, so it now names `player.pos[2]` and says plainly that the `pos_up` field beside it is internal. * `eqoxide_core::coord::WIRE_Z_OFFSET` datum paragraph — found by grepping the CONCEPT, not the identifier. It named a `/player` endpoint that does not exist alongside `pos_up`, and claimed "every agent-facing field"; both are gone. Adds `position_is_served_as_one_pos_array_with_no_pos_up_key_822`, which pins the wire fact those sentences now assert (through the real router, not a hand-serialised struct). The prose itself has no test behind it — measured, see the PR body. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HQVEpaaKeXsZcW9VT2roeV
Independent adversarial review — PR #841I did not write this PR. I re-verified its central claims against source directly, ran an 1. The three replacement sentences — checked TRUE at source, not just "less false"
2. Central wire fact — independently reproduced, not just read
3. Independent concept-grep sweep (my own predicate, not the author's)Ran 4. Spot-checked "left alone" rulings — all correctChecked the ones most likely to matter (agent-facing docs / public rustdoc): 5. Mutation evidence — spot-checked at the call site, RED confirmedBacked up Result — RED, as claimed: This matches the PR's description almost exactly, including that the failure message prints the Mutation 2 (add a second The test does pin the claim the docs make (one 6.
|
Fixes the
agent-honestydefect in #822: tracked prose telling an API reader to look up apos_upkey that no body this API serves contains.
Verified at source, not transcribed from the issue.
get_debughand-builds itsplayerobjectand emits
"pos": [player.pos_east, player.pos_north, player.pos_up](crates/eqoxide-http/src/observe.rs:982).PlayerStatederivesSerializebut no handler serialises it whole — the same fact #801/#810already recorded on
afloat_stall— so its field names never reach the wire. Corroborated by themutation run below, whose failure message printed the endpoint's real 55-key set: it contains
posand no
pos_east/pos_north/pos_up.Two line numbers in #822 have drifted since it was filed, and one attribution was off; both stated
here rather than silently worked around:
observe.rs:982, not:926;pos_east/pos_north/pos_upatcrates/eqoxide-http/src/lib.rs:137-139arePlayerStatefields, not
ControllerView's.ControllerViewlives ineqoxide-ipcand has nopos_upfieldat all (
grep -rn pos_upreturns no hit in that crate). The substance of the issue is unchanged —they are internal field names either way.
Concept-grep inventory
Per #822: grep the concept, not one identifier. The concept searched was "prose that tells a
reader of the HTTP API where to read the vertical/up coordinate". Searches run over the whole
worktree (
*.rs,*.md,*.py,*.sh,*.toml):pos_up;pos_east|pos_north;pos\[[012]\];player\.pos|pos|pos array|\[east; and, scoped todocs/,\bpos\b|position|height|vertical|up coordinate|z-?coord|altitudeintersected withread|agent|key|field|report|serve|expose|check.Every hit, and the ruling on each:
docs/http-api.md:316levitatingtable: "pos_upis a height the character will not fall from"crates/eqoxide-http/src/lib.rs:198PlayerState::levitating: "an agent that readspos_up…"crates/eqoxide-core/src/coord.rs:29WIRE_Z_OFFSETdatum paragraph: "every agent-facing field (/playerpos_up,/observe,/observe/entities)"/playerendpoint that does not exist: the routers are nested atcrates/eqoxide-http/src/lib.rs:1140-1155and there is no/playernamespace among them.crates/eqoxide-http/src/lib.rs:137-139PlayerStatefield declarationscrates/eqoxide-http/src/lib.rs:278-288from_game_stateconstruction (pos_up: gs.player_z)crates/eqoxide-http/src/lib.rs:339pos_east/north/up"crates/eqoxide-http/src/lib.rs:667PlayerAfloatStallViewdoc: "served as the singleposarray … NOT aspos_east/pos_north/pos_upkeys"crates/eqoxide-http/src/lib.rs:689anchor_*doc: "there are nopos_east/pos_north/pos_upkeys in any body this API serves"crates/eqoxide-http/src/observe.rs:982crates/eqoxide-http/src/observe.rs:2019-2021zone_exits: comment +let pos = [...]localcrates/eqoxide-http/src/lib.rs:1233,1241-1246pos_up_reports_foot_not_wiretestdocs/http-api.md:435anchor_*row: "(Position is served as that one array; there are nopos_east/pos_north/pos_upkeys…)"docs/http-api.md:36[east,north,up]"docs/http-api.md:404,:940,:943-944,:1330player.pos/posmoving, provisional, frozenpos, none names apos_*keycrates/eqoxide-http/src/move_api.rs:50/v1/observe/debugpos:east= +server_x"crates/eqoxide-nav/src/diagnostics.rs:123,412,462[east, north, up]/[east, north, floor_z]array docseqoxide-navis off-limits this wave)crates/eqoxide-core/src/afloat.rs:312,328,physics.rs:113-169,game_state.rs:984,1410,2891,2950,zone_assets.rs:443,assets/src/lib.rs:543,ui/windows/map.rs:98,191,renderer/*,walker.rs:235,1950,interact.rs:191,name_match.rs:53,persist.rs:38,docs/architecture.md:69-70,docs/collision-system.md:92,136,docs/autonomous-play.md:181,docs/plan-standalone-gm-client.md:192,272poslocals, mesh/GLB axis-order notes,gs.player_zcaveatsNo other site was found that tells an API reader to read a
pos_*key. That is the result ofthe searches listed above, not a claim that no such sentence can exist anywhere in the repo.
Exact before/after
1.
docs/http-api.md:316Before:
After:
2.
crates/eqoxide-http/src/lib.rs:198(rustdoc onPlayerState::levitating)Before:
After:
#822 offered a fork here ("fix the referent or make it explicit that the name is internal"). Both
are taken: the sentence is about what an agent reads, so it names
player.pos[2], and it alsosays outright that the adjacent field is internal — so the next reader of this struct does not have
to re-derive which of the two things the sentence meant.
3.
crates/eqoxide-core/src/coord.rs:29(the third site)Before:
After:
The old sentence also carried an "every agent-facing field … is FOOT" completeness claim that
nothing here establishes. Since the clause was being rewritten anyway, it is replaced by a named,
checkable pair plus an explicit statement that the pair is not exhaustive, rather than re-asserting
a universal.
Mutation checks
The prose fix has no test behind it, and this was MEASURED, not assumed. All three wording edits
were reverted to their pre-#822 text (the new test kept) and the full workspace suite was re-run:
So reverting any of the three sentences goes unnoticed by the suite. Said plainly rather than
implied. A doc-key guard of the kind #822 floats ("check the doc's claimed response keys all exist
in a served body") would close that gap for the whole class; it is not attempted here.
Note for whoever tries it:
crates/eqoxide-net/src/action_loop.rs:4688doesinclude_str!("../../../docs/http-api.md"), so a markdown-only edit to that file is not inert —it recompiles
eqoxide-net. The only assertion that parsesDOCreads the| \idle` |row of the nav_state table; the edit above is in thelevitating` table, so that test is untouched.What IS mutation-checked is the new test,
position_is_served_as_one_pos_array_with_no_pos_up_key_822in
crates/eqoxide-http/src/observe.rs. It pins the wire fact the corrected sentences assert, soa future change that adds a
pos_*key or renamesposgoes red and the prose stops being true onlyby luck. It drives the real router through
debug_json, notserde_json::to_value(&player_state)—the dead end #810 round 1 found. Fixture uses three distinct values with a negative
up(
[812.5, 43.0, -119.75]), so a transposed or flattened axis cannot pass.Both mutations run on the remote builder,
-p eqoxide-http --lib --locked --no-fail-fast, filerestored from an
md5sum-verified copy between runs. Control: 269 passed; 0 failed."pos"key inget_debugto"pos_up"→ 268 passed; 1 failed, at the arrayassertion. Its message printed the endpoint's real key set, which is where the "no
pos_*key"claim in this PR comes from rather than from reading the source twice.
"pos"and additionallyplayer.insert("pos_up".into(), json!(-119.75))→ 268 passed;1 failed, at the
contains_keyloop. This is the half that only the negative assertion covers;without it the test would pass a body that served both.
Recorded so the next person does not re-run it: adding a fourth key inside the
json!literal doesnot compile — that object is already at
serde_json's macro recursion limit (error: recursion limit reached while expanding $crate::json_internal!), which is why mutation 2 goes throughplayer.insert. That limit is also whyget_debughand-builds and patches the object at all, whichis the root reason
PlayerState's field names never reach the wire.Full workspace run — five-figure log standard
rbuild <worktree> test --workspace --locked --no-fail-fast, stdout and stderr captured toseparate files.
Finished `test` profile [unoptimized + debuginfo] target(s) in 53m 15sexit status 0, and a complete final
test result:line(
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s).There is no
fetchedline to check on atestinvocation —rbuildonly rsyncs a binary back forbuild/b.running [0-9]+ tests?headers vstest result:lines: 55 / 55, EQUAL.(Regex allows the singular
running 1 test.)test result:lines: 0. Every one matches^test result: (ok|FAILED)\. N passed; N failed; N ignored; N measured; N filtered out; finished in Ns$.0 passed; 0 failed; 0 ignored.running Nheader sum (1879). Balances.
test observe::tests::position_is_served_as_one_pos_array_with_no_pos_up_key_822 ... okappears inthe log. A stale-tree build could not print it.
bash scripts/check-no-local-detail.sh→ exit 0, "no forbidden patterns in tracked files".NOT verified, and not claimed
runtime behaviour to observe. The new test exercises the real router in-process, which is the
ceiling here.
workspace suite green. The new test guards the wire fact, never the wording.
listed; a sentence phrased in words none of those patterns match would not have been found.
coord.rsis inherited, not re-verified here. This PR corrected theendpoint and key names in that sentence; whether
/v1/observe/entitiespositions are FOOT wasnot independently re-measured in this change.
docs/http-api.mdwas not audited end-to-end for other claimed-but-unserved keys. That is theclass-wide check Two docs tell an agent to read
pos_up; the API servesposas an array and has no such key #822 suggests and explicitly does not require.Closes or addresses?
Closes #822. Its acceptance has two bullets and one instruction:sentence found by the concept grep is fixed too.
plainly that the fix is prose with no test behind it" — the disjunction's second branch is taken,
measured rather than asserted, and stated in its own section above.
marked in the issue as "worth considering, but not required to close this". It is not done
here. If it should block, say so and this becomes
Addresses #822; a follow-up issue for thatclass-wide guard would be the cleaner home for it either way.
🤖 Generated with Claude Code
https://claude.ai/code/session_01HQVEpaaKeXsZcW9VT2roeV