Skip to content

MP-06: relay and presence - #11

Open
aurablacklight wants to merge 12 commits into
mainfrom
mp/MP-06-relay
Open

aurablacklight wants to merge 12 commits into
mainfrom
mp/MP-06-relay

Conversation

@aurablacklight

Copy link
Copy Markdown

The server keeps the latest public state of each studio and relays it, the studio's events and its owner's presence to the mod-equipped players in range. The client applies them to its studio registry only in multiplayer and never for its own studio. This is one of the packets marked (sec): it sits on a trust boundary and had an independent security review.

Stacked on Wave 0. This branch starts from the four open Wave 0 pull requests (#1, #2, #4, #5) merged together, so until they are on main this diff also shows their changes. The packet's own change is 1 commit: compare from the Wave 0 base.

What is in it

  • A state or event is attributed to the connection that sent it, never to a field of the payload. It is refused without a plot, without an accepted hello, past the rate limit, and (an event) when no state is stored or the agent is not in it.
  • States and events never go back to their owner. A state whose content did not change is stored with its new revision and not sent again.
  • Speech text is cleared unless the studio's stored policy shares it, and always when no state is stored.
  • Presence is the owner's connectivity as the player list knows it. The retained state is sent to a viewer who enters range whether or not the owner is online; a disconnect stores and sends an offline copy.

Review history

An independent security review found 13 issues; the coordinator added one. Two fix rounds and three re-reviews followed (5, then 2, then no findings). The worknote (docs/multiplayer/worknotes/MP-06.md on this branch) has the details.

Verification

  • Tests on this branch: gradlew test --rerun --no-build-cache and gradlew build: 94 JUnit tests and 4 game tests, all passing.
  • All fourteen Wave 1 branches together (mp/integration): 238 JUnit tests and 102 game tests pass, and the singleplayer QA compare (ten shots against a reference on the same display) moved no shot; the singleplayer client logged no agentcraft.mp line.

Still owed, and not run by anyone:

  • The two-client harness checks in the worknote (they need MP-03's plots and MP-12's dev.mp.send).
  • Every check that needs a dedicated server with a real player or two clients belongs to MP-I; none was run for this packet.

Merge order and notes

  • Contract change request: studio_event_rejected needs the reason bad_version in the telemetry catalog (an event from a sender without an accepted hello is refused with it). The catalog is in work-packets.md, which a packet may not edit.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NLkzPTC3pm16WCmLm9dqyp

aurablacklight and others added 10 commits October 3, 2026 23:23
The packet worker could not run the live checks in its sandbox. Add what the
coordinator observed when running them outside it, including what failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UTWx6KTPBs4koTLg8uZMHX
…r review

- A client whose DevBridge cannot bind its port is detected from its log,
  and relaunched once after the TIME_WAIT window, bounded by --timeout.
- Server and client telemetry is read from logs/debug.log, so debug-level
  agentcraft.mp events are visible to harness tests.
- Gradle preparation is bounded by --timeout, the launch export ensures
  client assets, and the process identity no longer depends on the
  caller's time zone or locale.
- Fall back to the checkout's .gradle-home and check for Java 25, like the
  other launchers. Document and pin the --json summary shape.
- Windows: portable test fixture paths, -ExecutionPolicy Bypass, and the
  wrapper started through Start-Bg. None of this was run on Windows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017nnFLfHG1rVtWmS9c6mAQP
main gained the CI workflow (.github/workflows/ci.yml). Nothing else changes
on this branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017nnFLfHG1rVtWmS9c6mAQP
Local integration branch for Wave 1 (not pushed, not for main).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017nnFLfHG1rVtWmS9c6mAQP
Local integration branch for Wave 1 (not pushed, not for main).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017nnFLfHG1rVtWmS9c6mAQP
Local integration branch for Wave 1 (not pushed, not for main).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017nnFLfHG1rVtWmS9c6mAQP
Local integration branch for Wave 1 (not pushed, not for main).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017nnFLfHG1rVtWmS9c6mAQP
The server keeps the latest public state of each studio and relays it, the
studio's events and its owner's presence to the mod-equipped players in range.
A state or event is attributed to the connection that sent it, never to a
field of the payload; it is refused without a plot, without an accepted hello,
past the rate limit, and (an event) when no state is stored or the agent is not
in it. States and events never go back to their owner. A state whose content
did not change is stored with its new revision and not sent again. Speech text
is cleared unless the studio's stored policy shares it. Presence is the
owner's connectivity as the player list knows it; the retained state is sent
to a viewer who enters range whether or not the owner is online, and a
disconnect stores and sends an offline copy.

The client applies remote states, presence and events to the studio registry
only in multiplayer and never for its own studio.

An independent security review and the coordinator reviewed the packet; two
fix rounds and three re-reviews followed. 94 JUnit tests and all 4 game tests
pass. The two-client harness checks are still owed. Contract change request:
studio_event_rejected gains the reason bad_version.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NLkzPTC3pm16WCmLm9dqyp

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Invalid studio events bypass the event rate bucket, allowing authenticated clients to generate unbounded rejection telemetry.

Review effort: Balanced
Findings: 1 High severity

Open (1)
What changed in this PR

Implements multiplayer relay and presence on top of the Wave 0 foundation, game-test infrastructure, Node baseline, and two-client harness.

Changes:

  • Adds authenticated, plot-scoped state/event relay with redaction, presence, range filtering, and client registry updates.
  • Establishes multiplayer payloads, public-state contracts, visibility, rate limiting, and feature wiring.
  • Adds extensive JUnit/game-test coverage, tooling, fixtures, and campaign documentation.
File Description
README.md Requires Node 22.18+.
foreman/​package.json Updates Node engine requirement.
foreman/​src/​repos.ts Parses Node spec-reporter output.
foreman/​test/​repos.test.ts Tests output parsing.
tools/​package.json Updates Node engine requirement.
tools/​README.md Documents prerequisites and harness.
tools/​launch.ps1 Adds early Node validation.
tools/​mac.mjs Enforces Node 22.18+.
tools/​mp.mjs Adds harness CLI.
tools/​lib/​mp/​config.mjs Defines harness configuration.
tools/​lib/​mp/​export-launch.gradle Exports Loom launch metadata.
tools/​lib/​mp/​harness.mjs Orchestrates multiplayer processes.
tools/​lib/​mp/​index.mjs Provides harness helper APIs.
tools/​lib/​mp/​launch.mjs Builds isolated launch environments.
tools/​lib/​mp/​processes.mjs Safely manages owned processes.
tools/​test/​mp-config.test.mjs Tests CLI and JVM configuration.
tools/​test/​mp-library.test.mjs Tests harness helpers.
tools/​test/​mp-processes.test.mjs Tests process ownership.
tools/​test/​mp-review.test.mjs Covers review-found harness cases.
tools/​test/​mp-shutdown.test.mjs Tests cleanup and recovery.
docs/​multiplayer/​README.md Updates campaign status.
docs/​multiplayer/​handoffs/​session-resume.md Updates coordinator handoff.
docs/​multiplayer/​worknotes/​MP-B.md Records Node baseline work.
docs/​multiplayer/​worknotes/​MP-F.md Records foundation work.
docs/​multiplayer/​worknotes/​MP-H.md Records harness work.
docs/​multiplayer/​worknotes/​MP-T.md Documents game-test infrastructure.
docs/​multiplayer/​worknotes/​MP-06.md Documents relay implementation.
mod/​build.gradle Configures JUnit and game tests.
mod/​src/​gametest/​resources/​fabric.mod.json Registers generated game-test entrypoints.
mod/​src/​gametest/​java/​dev/​agentcraft/​gametest/​HqSmokeGameTest.java Verifies HQ construction.
mod/​src/​gametest/​java/​dev/​agentcraft/​gametest/​mp/​relay/​RelayRefusalGameTest.java Tests relay refusal paths.
mod/​src/​main/​java/​dev/​agentcraft/​AgentCraft.java Wires server multiplayer features.
mod/​src/​main/​java/​dev/​agentcraft/​hq/​HqBuilder.java Adds studio-aware build options.
mod/​src/​main/​java/​dev/​agentcraft/​layout/​Anchors.java Supports per-studio layouts.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​StudioId.java Defines studio identity.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​Plot.java Defines plot bounds.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​PlotDirectory.java Defines plot lookup contract.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​PlotGrid.java Implements plot-grid mapping.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​Plots.java Provides active plot directory.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​RateBucket.java Implements token-bucket limiting.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​MpEvents.java Defines telemetry catalog.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​MpLog.java Adds structured multiplayer logging.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​MpReasons.java Defines refusal reasons.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​MpServerConfig.java Loads multiplayer server configuration.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​state/​AgentStateWire.java Defines public agent states.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​state/​CiSlot.java Defines public CI slots.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​state/​CiStatusWire.java Defines public CI statuses.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​state/​Counts.java Defines public task counts.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​state/​GoalStatusWire.java Defines public goal statuses.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​state/​GoalSummary.java Defines public goal summary.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​state/​LampStatusWire.java Defines lamp status values.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​state/​MpText.java Sanitizes public text.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​state/​PublicAgent.java Defines public agent data.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​state/​PublicEvent.java Defines relayed events.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​state/​PublicJson.java Implements strict public JSON.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​state/​PublicPolicy.java Defines sharing policy.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​state/​PublicStudioState.java Defines public studio state.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​state/​PublicTask.java Defines public task data.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​state/​StationWire.java Defines station values.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​state/​TaskStatusWire.java Defines task statuses.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​state/​WorldIntent.java Defines bounded world intents.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​net/​HelloC2S.java Defines client hello.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​net/​HelloS2C.java Defines server hello.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​net/​LayoutRemoveS2C.java Defines layout removal.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​net/​LayoutS2C.java Defines layout synchronization.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​net/​MpCodecs.java Implements bounded codecs.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​net/​MpPayloads.java Registers payloads and handshake.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​net/​MpProtocol.java Defines protocol version.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​net/​PresenceS2C.java Defines presence payload.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​net/​PublicStateC2S.java Defines public-state publication.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​net/​ServerInfo.java Defines server capabilities.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​net/​StudioEventC2S.java Defines outbound studio events.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​net/​StudioEventS2C.java Defines relayed studio events.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​net/​StudioStateS2C.java Defines relayed studio state.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​net/​WorldIntentC2S.java Defines world-intent publication.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​server/​StudioRange.java Tracks visible studios.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​server/​relay/​StudioRelay.java Implements relay and presence logic.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​server/​relay/​RelayFeature.java Connects relay to Fabric networking.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​server/​intent/​WorldIntentFeature.java Adds intent feature seam.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​server/​layout/​LayoutSyncFeature.java Adds layout feature seam.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​server/​plot/​PlotFeature.java Adds plot feature seam.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​server/​protect/​PlotProtectionFeature.java Adds protection feature seam.
mod/​src/​main/​java/​dev/​agentcraft/​mp/​server/​world/​ServerWorldFeature.java Adds server-world seam.
mod/​src/​client/​java/​dev/​agentcraft/​client/​ClientFeatures.java Wires client multiplayer features.
mod/​src/​client/​java/​dev/​agentcraft/​client/​mp/​MpMode.java Manages multiplayer mode.
mod/​src/​client/​java/​dev/​agentcraft/​client/​mp/​RemoteAgentClicks.java Adds remote-click seam.
mod/​src/​client/​java/​dev/​agentcraft/​client/​mp/​Studios.java Implements client studio registry.
mod/​src/​client/​java/​dev/​agentcraft/​client/​mp/​StudioView.java Defines client studio view.
mod/​src/​client/​java/​dev/​agentcraft/​client/​mp/​dev/​MpDevFake.java Adds local multiplayer fixtures.
mod/​src/​client/​java/​dev/​agentcraft/​client/​mp/​layout/​LayoutSyncClient.java Adds client layout seam.
mod/​src/​client/​java/​dev/​agentcraft/​client/​mp/​publish/​PublishFeature.java Adds publisher seam.
mod/​src/​client/​java/​dev/​agentcraft/​client/​mp/​remote/​RemoteStudiosFeature.java Applies relayed data client-side.
mod/​src/​client/​java/​dev/​agentcraft/​client/​mp/​visitor/​VisitorFeature.java Adds visitor feature seam.
mod/​src/​test/​java/​dev/​agentcraft/​client/​foreman/​ForemanStates.java Provides state fixtures.
mod/​src/​test/​java/​dev/​agentcraft/​client/​foreman/​ForemanStatesTest.java Tests state fixtures.
mod/​src/​test/​java/​dev/​agentcraft/​client/​mp/​remote/​RemoteStudiosFeatureTest.java Tests remote registry updates.
mod/​src/​test/​java/​dev/​agentcraft/​mp/​AnchorsTest.java Tests per-studio anchors.
mod/​src/​test/​java/​dev/​agentcraft/​mp/​CodecTest.java Tests payload codecs.
mod/​src/​test/​java/​dev/​agentcraft/​mp/​DevFakeTest.java Tests development fixtures.
mod/​src/​test/​java/​dev/​agentcraft/​mp/​FoundationTest.java Tests foundation contracts.
mod/​src/​test/​java/​dev/​agentcraft/​mp/​RateBucketTest.java Tests rate limiting.
mod/​src/​test/​java/​dev/​agentcraft/​mp/​RemoteAgentClicksTest.java Tests click dispatch.
mod/​src/​test/​java/​dev/​agentcraft/​mp/​StudioRangeTest.java Tests range calculation.
mod/​src/​test/​java/​dev/​agentcraft/​mp/​StudiosTest.java Tests studio registry and mode.
mod/​src/​test/​java/​dev/​agentcraft/​mp/​WireBoundaryTest.java Tests wire boundaries.
mod/​src/​test/​java/​dev/​agentcraft/​mp/​WiringTest.java Tests feature wiring.
mod/​src/​test/​java/​dev/​agentcraft/​mp/​relay/​StudioRelayAcceptTest.java Tests relay decisions.
mod/​src/​test/​java/​dev/​agentcraft/​mp/​relay/​StudioRelayTelemetryTest.java Tests relay telemetry and privacy.
mod/​src/​test/​resources/​dev/​agentcraft/​fixtures/​showcase.json Adds showcase fixture.
mod/​src/​test/​resources/​dev/​agentcraft/​fixtures/​showcase-late.json Adds late-state fixture.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +139 to +145
PublicStudioState latest = STORED.get(studio);
if (latest == null) {
MpLog.event(MpEvents.STUDIO_EVENT_REJECTED,
"player", player, "studio", studio.owner(),
"plot", plot.get().index(),
"reason", MpReasons.UNKNOWN_AGENT);
return EventResult.UNKNOWN_AGENT;
A packet without a hello, without a plot or naming an unknown agent was
refused before the rate bucket was consulted, and every refusal logged a line,
so the limit did not stop a flood. Every state and event now takes a token
first, whoever sends it, and refusal lines are throttled to one per player,
event and reason in five seconds. A sender's bucket survives a reconnect; idle
buckets are dropped.

Also: the stored state of a studio that no longer has a plot is dropped
wherever the relay looks the plot up; Say.to is relayed only when it is
"user" or an agent of the sender's state; the play receivers no longer hop to
the server thread a second time.

From the pull-request review; the fix had two security re-reviews. 101 JUnit
tests and all 4 game tests pass. Left for the integration packet: a plot freed
and allocated again with no relay call in between keeps its old state.

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

2 participants