Add cross-version compatibility harness (T0 + T1) - #190
Closed
jhead wants to merge 30 commits into
Closed
Conversation
PowerShell treats bare -6 as a number, so the flag never reaches the process (#124). Prefer -ipv6 and keep -6 as a legacy alias for other shells. Co-authored-by: Cursor <cursoragent@cursor.com>
LAN pings were refreshing read deadlines while Write consumed ICMP errors, so the server never got marked offline and warnings spammed (#79). Co-authored-by: Cursor <cursoragent@cursor.com>
When -server targets this host's :19132, SO_REUSEADDR can deliver DialUDP traffic back to the ping listener; ignore those sources so each echo does not open another socket until EMFILE. Co-authored-by: Cursor <cursoragent@cursor.com>
Geyser MOTDs omit Port4/Port6, so consoles never learned phantom's proxy port and the Friends/LAN entry failed to show reliably. Co-authored-by: Cursor <cursoragent@cursor.com>
SO_REUSEADDR/PORT cannot share Bedrock discovery across processes, so a second phantom instance stayed idle. Accept multiple -server values in one process and reply for each upstream from a single exclusive discovery listener. Co-authored-by: Cursor <cursoragent@cursor.com>
arm8 is aarch64, so 32-bit Pi OS users who pick it for "ARMv8" hit Illegal instruction / Exec format error — clarify uname -m mapping and add an arm64 alias. Co-authored-by: Cursor <cursoragent@cursor.com>
A 1472-byte receive buffer truncated larger datagrams (often with err=nil), corrupting full-MTU pack transfer traffic. Use a full-size UDP buffer and fail closed on truncation. Co-authored-by: Cursor <cursoragent@cursor.com>
Validates phantom against 51 Minecraft protocol versions, fully automated in CI. Design in test/compat/DESIGN.md. Two tiers, split by testing philosophy and enforced by imports: T0 (white box, ~0.3s, no network) replays real captured pong bytes from all 51 bedrock-protocol versions plus hand-authored shape fixtures through the parser and rewriter, asserting field-level invariants. Adds a fuzz target for the pong parser, which eats untrusted network bytes with no length or magic validation. T1 (black box, ~40s) drives a real phantom subprocess over real UDP against a scriptable fake upstream and, for sampled versions, a real bedrock-protocol server and client. It may not import phantom's implementation packages; TestBlackBoxRuleHolds enforces that with go list -deps rather than trusting convention. Subprocess rather than in-process because serverID is a package global, so "two instances must advertise different ids" is not observable from inside one process. CI shards T1 four ways for isolation as much as speed: phantom binds UDP :19132 unconditionally with SO_REUSEPORT, so two concurrent instances on one host silently load-balance each other's datagrams. One shard per runner gives each its own network stack. Eight invariants XFAIL today. These are the open protocol bugs in TODO.md, now measured against real wire bytes rather than asserted: every real server sends 13 pong fields and phantom drops the 13th. The registry fails the build if a registered case starts PASSING, so fixing a bug forces deleting its entry in the same change. Also: bump go 1.12 -> 1.21 (go:embed needs >=1.16, fuzzing >=1.18) and fix the unkeyed struct literal that made go vet fail. Not verified: the full connect-session tests never execute on darwin/arm64, where bedrock-protocol's native RakNet addon crashes. They use a control connection to the upstream so they skip rather than falsely blame phantom, but their first real run will be on Linux CI. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One flaky i/o timeout was marking the whole proxy offline, so LAN clients got MaxPlayers=0 OfflinePong unless another peer's traffic cleared the flag (#104). Require sustained timeouts, and give Offline pongs placeholder ports so rewrite injects the bind port. Co-authored-by: Cursor <cursoragent@cursor.com>
Unconnected pings no longer reuse gameplay DialUDP sessions, which could stop answering after disconnect while console re-pings kept the stale entry alive. Co-authored-by: Cursor <cursoragent@cursor.com>
iSH needs phantom-linux-x86; SO_REUSEPORT fails with invalid argument so fall back to net.ListenPacket. Co-authored-by: Cursor <cursoragent@cursor.com>
Termux runs standard linux/arm* builds; GOOS=android is unnecessary and needs the NDK. Clarify $HOME/noexec and uname -m mapping. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Combine multi-server discovery hub with prior offline, recv-buffer, discovery-ping, self-echo, and -ipv6 fixes. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
macos-latest is now macos-26, whose dyld rejects Mach-O binaries with no LC_UUID load command. Go's internal linker only began emitting one in 1.26 (golang/go#69987, golang/go#78012), so pinning go.mod's 1.21 minimum made every macOS test binary abort before any phantom code ran: dyld: missing LC_UUID load command in .../proxy.test signal: abort trap This is a toolchain/OS interaction, not a phantom bug - internal/util, which predates this harness, failed identically. go.mod's directive stays at the true language minimum (embed needs >=1.16, fuzzing >=1.18) and is still exercised by the `unit` job on Linux, where LC_UUID is irrelevant. Only the OS matrix needs a current toolchain. Also records in DESIGN.md that the connect-session tests, previously never executed anywhere, now run and pass on Linux CI: a real bedrock-protocol client completes the full RakNet handshake and login through phantom for every sampled version. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Removes the implementation-history and verification-status sections and the per-dimension breakdown, and drops the date and revision markers. The result describes the testing strategy as it stands rather than how it came to be, so it does not need updating every time a test changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The T0 and T1 labels carried no meaning to a reader who had not already read the design document. The tests are unit tests and end-to-end tests, so they are now named that way throughout: known-failure ids, code comments, Makefile targets, the version matrix and the CI job names. CI jobs are now vet, unit, e2e and cross-compile. The job formerly called unit, which runs gofmt/vet/race, is now vet. Also tightens DESIGN.md prose. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The file listing duplicated the tree and went stale on every rename. The Node scripts paragraph it carried moves into the end-to-end section, where it is about design rather than location. Also runs CI on pushes to dev. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Brings in dev's protocol work and drops the XFAIL registry, so every invariant now asserts plainly and failures surface as failures. Test updates required by dev's deliberate behavior changes: - serverID moved from a package global to a per-instance ProxyServer field, so the rewrite tests construct it directly. - The rewrite now always advertises phantom's bind port, injecting Port4/Port6 even when the upstream omitted them. The old test asserted the opposite, which was correct before the Geyser fix. - cmd/phantom.go conflict resolved in favour of dev's multi-server main, which already used keyed struct fields. Known red, to be fixed separately. Co-Authored-By: Claude Opus 5 <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.
Validates phantom against 51 Minecraft protocol versions, fully automated in CI.
Design and rationale:
test/compat/DESIGN.md.Two tiers, split by testing philosophy
T0 — white box, ~0.3s, no network. Replays real captured pong bytes from all
51
bedrock-protocolversions, plus hand-authored shape fixtures (10/12/13/14/15fields, hostile content, malformed frames), through the parser and rewriter. Adds
a fuzz target for the pong parser, which eats untrusted network bytes with no
length or magic validation today.
T1 — black box, ~40s. Drives a real phantom subprocess over real UDP against
a scriptable fake upstream, and for sampled versions a real
bedrock-protocolserver and client. Subprocess rather than in-process because
serverIDis apackage global, so "two instances must advertise different IDs" is not observable
from inside one process.
The tier boundary is enforced, not conventional: T1 may not import
internal/proto,internal/proxyorinternal/clientmap, andTestBlackBoxRuleHoldschecks that withgo list -deps.Eight invariants XFAIL today
These are the open protocol bugs already in
TODO.md, now measured against realwire bytes rather than asserted. The headline: every one of the 51 captured
versions sends 13 pong fields, and phantom drops the 13th.
The registry (
internal/corpus/data/known_failures.json) fails the build if aregistered case starts passing, so fixing a bug forces deleting its entry in
the same change. CI is green on day one and the registry cannot rot.
CI
t0runs on an OS matrix;t1is sharded four ways. The sharding is forisolation as much as speed: phantom binds UDP
:19132unconditionally withSO_REUSEPORT, so two concurrent instances on one host silently load-balanceeach other's datagrams. One shard per runner gives each its own network stack.
A nightly job sweeps every version, fuzzes for 10m, and flags version drift when
Mojang ships a release we don't cover.
Two unavoidable changes to phantom itself
go 1.12→go 1.21—go:embedneeds ≥1.16, native fuzzing ≥1.18.cmd/phantom.go, since CI runsgo vetand it failed on it.
Not yet verified
The full connect-session tests have never executed:
bedrock-protocol'snative RakNet addon crashes on darwin/arm64 (
trace/BPT trap), where this wasdeveloped. They use a control connection straight to the upstream, so they skip
rather than falsely blame phantom — but this PR's CI run is their first real
execution.
🤖 Generated with Claude Code