Conversation
…RFC 0021)
RFC 0021 gave a mode back the bag on the link cable: opts.items makes an
item the turn, and `{ type = "action", kind = "item", item, index, move }`
rides the wire so the peer -- and a spectator -- can apply the same effect
to their copies before the moves. The peer never got the item.
Wire.sanitize rebuilds every inbound message field by field from
SCHEMAS[type] and drops anything the schema does not name. SCHEMAS.action
names kind, slot and index; `item` and `move` are not among them, so they
were stripped on the way in. Added beside the others, clamped the same
way (a string id, a move slot in 1..MAX_MOVES). SCHEMAS.spectate calls
the same sanitize, so a spectator is fixed by the same line.
Why the tests did not see it. tests/engine/link_items.lua -- and every
in-process link test -- pairs the two sides with Net.loopbackPair, which
hands the table straight over; sanitize only runs on the way through
Session, which is every REAL transport. So the failure needed two
machines to show up, and when it did it was the quietest kind: the peer
receives an item action with no item in it, LinkItems.apply returns
nothing, the turn is still spent, nothing is printed, and the two
simulations part by one heal with nothing on either screen to say so --
until a hash several turns later blames the wrong turn. Found by a
downstream mod's two-client harness on its first real duel: the guest
healed 12 -> 18 and the host watched its copy of that mon go 12 -> 6.
link_items.lua now pins the schema directly (an item action through
sanitize, through a spectate wrapper, and a move action carrying no
item), which is a check a loopback pair cannot make. docs/rfcs/0021
gains the section saying a new field on an existing message type is
invisible until SCHEMAS knows about it.
tests/run_link_tests.lua green; the engine tier matches stock's reds
(572/584, the same twelve).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
CI build artifacts
Commit: #e3ee1f7 This comment was automatically generated. View workflow run |
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.
Follow-up to #2284 (RFC 0021). The feature works in-process and does not work between two machines; this is the one line that was missing, plus the test that would have caught it.
The bug
opts.itemsputs the turn's item on the wire as{ type = "action", kind = "item", item, index, move }.Wire.sanitizerebuilds every inbound message field by field fromSCHEMAS[type]and drops anything the schema does not name, andSCHEMAS.actionnames onlykind,slotandindex. Soitemandmovewere stripped on the way in.What the peer then receives is an item action with no item in it:
LinkItems.applyreturns{}, the turn is still spent, nothing is printed, and the two simulations part by one heal with nothing on either screen to say so — until a per-turn hash several turns later blames the wrong turn.SCHEMAS.spectatecalls the samesanitize, so a spectator was drifting for the same reason and is fixed by the same line.Why #2284's tests are green
tests/engine/link_items.lua, and every other in-process link test, pairs the two sides withNet.loopbackPair, which hands the message table straight across.sanitizeruns only on the way throughSession, which is every real transport. The gap is invisible to a loopback pair by construction.Found by a downstream mod's two-client harness on the first real duel with items on: the guest healed its lead 12 → 18 and the host watched its copy of that same Pokémon go 12 → 6.
The change
src/link/Wire.lua—SCHEMAS.actiongainsitem(a string id) andmove(clamped to1..MAX_MOVES), besideslotandindex.tests/engine/link_items.lua— pins the schema directly: an item action throughsanitize, the same through aspectatewrapper, and a move action that carries no item. A check a loopback pair cannot make.docs/rfcs/0021-link-battle-items.md— a section saying a new field on an existing message type is invisible untilSCHEMASnames it, and describing the failure mode, so the next person adding one does not repeat it.Verification
luajit tests/run_link_tests.lua— ALL LINK TESTS PASSEDluajit tests/engine/link_items.lua— 28/28luajit tests/run_engine.lua— 572/584, the same twelve reds asorigin/devwith this branch stashed (audio device, shell-script scan, curl env, version dev identity and friends; none link-related)