Repository navigation
MP-03: plots (registry, lifecycle, commands) - #8
Open
aurablacklight wants to merge 15 commits into
Open
aurablacklight wants to merge 15 commits into
aurablacklight wants to merge 15 commits into
Conversation
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
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
…t yet reviewed) The real PlotDirectory for a multiplayer server: a player without a plot gets the next free index on first join, the registry and each plot's layout are kept in the world folder, a new plot is built on a later tick, and /agentcraft plot offers info, home, rebuild, list, assign and free. Written by a sandboxed worker and not yet reviewed. The coordinator re-ran the tests outside the sandbox; they pass. The review, the security review and the live checks the worknote lists are still owed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017nnFLfHG1rVtWmS9c6mAQP
Server side: - A plot whose build never ran (the server stopped between allocation and the build) is queued again at start and when its owner joins. - Every multiplayer build, home teleport and respawn point is in the overworld, wherever the caller stands. Singleplayer keeps the caller's level. - Only an allocating join, or the first build finishing while the owner is online, teleports the player; a later join does not move them. - A player who is not an operator may rebuild once a minute. - The plot commands exist only while multiplayer is on (checked at call time). - Freeing a plot whose directory cannot be deleted restores the row, and freezes the registry when that restore cannot be saved; a traversal failure counts as a failed delete. - plots.json: signed coordinate bounds, and a rejected row no longer reserves its index or owner. Tests: 63 JUnit tests and 28 game tests (the lifecycle, the command refusals with whole telemetry lines and unchanged state after each, the frozen registry, the rollback seam). The game tests put back the registry, the files, the rebuild bookkeeping and the mock players they added. An independent security review and the coordinator reviewed the packet; two fix rounds and three re-reviews followed. plot_command keeps the frozen catalog's fields. The live dedicated-server checks and the singleplayer QA compare are still owed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NLkzPTC3pm16WCmLm9dqyp
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Persisted overlapping plots can violate ownership, and failed rebuilds incorrectly consume the player’s throttle window.
Review effort: Balanced
Findings: 1
Open (2)
What changed in this PR
Adds persistent, dedicated-server multiplayer plots, including allocation, building, spawning, management commands, and recovery. The stacked diff also includes the Wave 0 multiplayer foundation and tooling.
Changes:
- Adds persistent plot registry, lifecycle, commands, rebuild throttling, and spawn handling.
- Makes HQ layouts studio-aware and adds extensive JUnit/game-test coverage.
- Includes stacked multiplayer contracts, harness tooling, and Node.js 22.18 baseline changes.
| File | Description |
|---|---|
mod/src/main/java/dev/agentcraft/mp/server/plot/PlotCommands.java |
Implements plot commands and permissions. |
mod/src/main/java/dev/agentcraft/mp/server/plot/PlotFeature.java |
Manages allocation, builds, joins, and shutdown. |
mod/src/main/java/dev/agentcraft/mp/server/plot/PlotRegistry.java |
Stores and allocates plot records. |
mod/src/main/java/dev/agentcraft/mp/server/plot/PlotStore.java |
Persists plots and anchors atomically. |
mod/src/main/java/dev/agentcraft/mp/server/plot/PlotSpawn.java |
Handles plot teleport and respawn. |
mod/src/main/java/dev/agentcraft/mp/server/plot/PlotRebuilds.java |
Implements rebuild throttling. |
mod/src/test/java/dev/agentcraft/mp/plot/PlotRegistryTest.java |
Tests registry and persistence behavior. |
mod/src/test/java/dev/agentcraft/mp/server/plot/PlotReleaseTest.java |
Tests free rollback failures. |
mod/src/gametest/java/dev/agentcraft/gametest/mp/plot/PlotLifecycleGameTest.java |
Tests plot lifecycle in-game. |
mod/src/gametest/java/dev/agentcraft/gametest/mp/plot/PlotCommandGameTest.java |
Tests plot commands in-game. |
mod/src/gametest/java/dev/agentcraft/gametest/mp/plot/PlotGameSupport.java |
Provides plot game-test fixtures. |
mod/src/gametest/resources/data/agentcraft/test_environment/plot_multiplayer.json |
Defines multiplayer plot test environment. |
docs/multiplayer/worknotes/MP-03.md |
Records MP-03 behavior and verification. |
mod/src/main/java/dev/agentcraft/hq/HqFeature.java |
Routes multiplayer builds to plots. |
mod/src/main/java/dev/agentcraft/hq/HqBuilder.java |
Adds origin and studio build options. |
mod/src/main/java/dev/agentcraft/layout/Anchors.java |
Supports per-studio layouts and origins. |
mod/src/main/java/dev/agentcraft/command/AgentCraftCommands.java |
Opens root while preserving subcommand permissions. |
mod/src/main/java/dev/agentcraft/AgentCraft.java |
Wires multiplayer server features. |
mod/src/main/java/dev/agentcraft/mp/Plot.java |
Defines plot bounds and ownership. |
mod/src/main/java/dev/agentcraft/mp/PlotDirectory.java |
Defines plot lookup contract. |
mod/src/main/java/dev/agentcraft/mp/PlotGrid.java |
Maps plot indexes to spiral origins. |
mod/src/main/java/dev/agentcraft/mp/Plots.java |
Holds active plot directory. |
mod/src/main/java/dev/agentcraft/mp/StudioId.java |
Defines studio identity. |
mod/src/main/java/dev/agentcraft/mp/MpServerConfig.java |
Loads multiplayer server configuration. |
mod/src/main/java/dev/agentcraft/mp/MpEvents.java |
Catalogs multiplayer telemetry. |
mod/src/main/java/dev/agentcraft/mp/MpLog.java |
Emits structured multiplayer telemetry. |
mod/src/main/java/dev/agentcraft/mp/MpReasons.java |
Defines rejection reason identifiers. |
mod/src/main/java/dev/agentcraft/mp/RateBucket.java |
Implements shared rate limiting. |
mod/src/main/java/dev/agentcraft/mp/server/StudioRange.java |
Tracks visible studios by range. |
mod/src/main/java/dev/agentcraft/mp/server/world/ServerWorldFeature.java |
Adds server-world feature seam. |
mod/src/main/java/dev/agentcraft/mp/server/layout/LayoutSyncFeature.java |
Adds layout-sync feature seam. |
mod/src/main/java/dev/agentcraft/mp/server/relay/RelayFeature.java |
Adds relay feature seam. |
mod/src/main/java/dev/agentcraft/mp/server/intent/WorldIntentFeature.java |
Adds world-intent feature seam. |
mod/src/main/java/dev/agentcraft/mp/server/protect/PlotProtectionFeature.java |
Adds plot-protection feature seam. |
mod/src/main/java/dev/agentcraft/mp/net/MpProtocol.java |
Defines multiplayer protocol version. |
mod/src/main/java/dev/agentcraft/mp/net/MpPayloads.java |
Registers payloads and handshake. |
mod/src/main/java/dev/agentcraft/mp/net/MpCodecs.java |
Implements bounded payload codecs. |
mod/src/main/java/dev/agentcraft/mp/net/HelloC2S.java |
Defines client handshake response. |
mod/src/main/java/dev/agentcraft/mp/net/HelloS2C.java |
Defines server handshake payload. |
mod/src/main/java/dev/agentcraft/mp/net/LayoutS2C.java |
Defines layout publication payload. |
mod/src/main/java/dev/agentcraft/mp/net/LayoutRemoveS2C.java |
Defines layout removal payload. |
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 upload. |
mod/src/main/java/dev/agentcraft/mp/net/ServerInfo.java |
Defines server capability data. |
mod/src/main/java/dev/agentcraft/mp/net/StudioEventC2S.java |
Defines studio event upload. |
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 upload. |
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 public lamp statuses. |
mod/src/main/java/dev/agentcraft/mp/state/MpText.java |
Sanitizes multiplayer 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 public studio events. |
mod/src/main/java/dev/agentcraft/mp/state/PublicPolicy.java |
Defines public-text policy. |
mod/src/main/java/dev/agentcraft/mp/state/PublicStudioState.java |
Defines redacted 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 public station values. |
mod/src/main/java/dev/agentcraft/mp/state/TaskStatusWire.java |
Defines public task statuses. |
mod/src/main/java/dev/agentcraft/mp/state/WorldIntent.java |
Defines validated world intents. |
mod/src/client/java/dev/agentcraft/client/ClientFeatures.java |
Wires multiplayer client features. |
mod/src/client/java/dev/agentcraft/client/mp/MpMode.java |
Tracks client multiplayer mode. |
mod/src/client/java/dev/agentcraft/client/mp/Studios.java |
Maintains client studio registry. |
mod/src/client/java/dev/agentcraft/client/mp/StudioView.java |
Defines client studio snapshots. |
mod/src/client/java/dev/agentcraft/client/mp/RemoteAgentClicks.java |
Adds remote-agent click seam. |
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-sync seam. |
mod/src/client/java/dev/agentcraft/client/mp/publish/PublishFeature.java |
Adds state-publishing seam. |
mod/src/client/java/dev/agentcraft/client/mp/remote/RemoteStudiosFeature.java |
Adds remote-studio seam. |
mod/src/client/java/dev/agentcraft/client/mp/visitor/VisitorFeature.java |
Adds visitor feature seam. |
mod/src/test/java/dev/agentcraft/mp/AnchorsTest.java |
Tests studio-aware anchors. |
mod/src/test/java/dev/agentcraft/mp/DevFakeTest.java |
Tests development fixtures. |
mod/src/test/java/dev/agentcraft/mp/FoundationTest.java |
Tests multiplayer 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-handler seam. |
mod/src/test/java/dev/agentcraft/mp/StudioRangeTest.java |
Tests visibility calculations. |
mod/src/test/java/dev/agentcraft/mp/StudiosTest.java |
Tests client studio registry. |
mod/src/test/java/dev/agentcraft/mp/WiringTest.java |
Verifies feature wiring. |
mod/src/test/java/dev/agentcraft/client/foreman/ForemanStates.java |
Adds reusable Foreman fixtures. |
mod/src/test/java/dev/agentcraft/client/foreman/ForemanStatesTest.java |
Tests Foreman fixtures. |
mod/src/gametest/java/dev/agentcraft/gametest/HqSmokeGameTest.java |
Adds HQ build smoke test. |
mod/src/gametest/resources/fabric.mod.json |
Registers generated game-test entrypoints. |
mod/build.gradle |
Configures JUnit and game tests. |
tools/mp.mjs |
Adds multiplayer harness CLI. |
tools/lib/mp/config.mjs |
Defines harness options and ports. |
tools/lib/mp/export-launch.gradle |
Exports Loom launch metadata. |
tools/lib/mp/index.mjs |
Adds harness library helpers. |
tools/lib/mp/launch.mjs |
Prepares isolated game launches. |
tools/test/mp-config.test.mjs |
Tests harness configuration. |
tools/test/mp-library.test.mjs |
Tests harness helper APIs. |
tools/test/mp-processes.test.mjs |
Tests process ownership and recovery. |
tools/test/mp-shutdown.test.mjs |
Tests harness cleanup. |
tools/README.md |
Documents multiplayer harness usage. |
tools/package.json |
Requires Node.js 22.18+. |
tools/mac.mjs |
Enforces Node.js baseline on macOS. |
tools/launch.ps1 |
Enforces Node.js baseline on Windows. |
foreman/src/repos.ts |
Parses Node.js 24 test output. |
foreman/test/repos.test.ts |
Tests reporter parsing. |
foreman/package.json |
Requires Node.js 22.18+. |
README.md |
Updates prerequisites. |
docs/multiplayer/README.md |
Updates campaign packet status. |
docs/multiplayer/handoffs/session-resume.md |
Updates campaign handoff state. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+91
to
+94
| if (plot == null || indexes.contains(plot.index()) || owners.contains(plot.owner().owner())) { | ||
| skipped++; | ||
| continue; | ||
| } |
Comment on lines
+118
to
+141
| ServerPlayer actor = source.getPlayer(); | ||
| if (!op(source) && actor != null) { | ||
| int now = source.getServer().getTickCount(); | ||
| if (!PlotRebuilds.allowed(actor.getUUID(), now)) { | ||
| int seconds = PlotRebuilds.remainingSeconds(actor.getUUID(), now); | ||
| return refuse(source, REBUILD, plot.index(), plot.owner().owner(), | ||
| "You can rebuild again in " + seconds + " seconds."); | ||
| } | ||
| PlotRebuilds.note(actor.getUUID(), now); | ||
| } | ||
| Anchors.Layout layout; | ||
| try { | ||
| // Plots always live in the overworld in multiplayer, wherever the caller stands. | ||
| layout = dev.agentcraft.hq.HqFeature.buildPlot(source.getServer().overworld(), builder, force, plot.origin(), plot.owner()); | ||
| } catch (RuntimeException e) { | ||
| AgentCraft.LOGGER.error("Plot {} failed to rebuild", plot.index(), e); | ||
| return refuse(source, REBUILD, plot.index(), plot.owner().owner(), "Rebuild failed."); | ||
| } | ||
| ServerPlayer owner = source.getServer().getPlayerList().getPlayer(plot.owner().owner()); | ||
| if (owner != null) PlotSpawn.applyRespawn(owner, layout); | ||
| else if (source.getPlayer() != null && plot.owner().equals(StudioId.of(source.getPlayer().getUUID()))) { | ||
| PlotSpawn.applyRespawn(source.getPlayer(), layout); | ||
| } | ||
| log(source, REBUILD, plot.index(), true, plot.owner().owner()); |
…l rebuilds (MP-03) plots.json: a row whose box overlaps an already accepted row is skipped like a duplicate index or owner (first row wins), so two owners can no longer hold the same ground after a restart. Rebuild: the one-minute window for a non-operator now starts only after the build succeeded; a build that threw no longer locks the player out of the retry. Found by the pull-request review. 64 JUnit tests and 29 game tests pass. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NLkzPTC3pm16WCmLm9dqyp
An enabled server installed an empty, frozen registry when plots.json was corrupt, and dropped rows that failed validation at the next save: every plot unprotected, and a dropped row's index free for a new player on top of the old build. The server now refuses to start when the file is unreadable, oversized, of the wrong shape, or has any rejected row. It logs which, with counts, and leaves the file as it is. A missing file is still an empty registry. It also refuses to start when a stored plot is not where the configured plotStride puts its index: clients compute origins from the index and the stride, so a changed stride would desync them. No wire change. On a multiplayer server /agentcraft hq builds only the default studio; another builder id built at the world origin over plot 0 and was published as the caller's studio. Found by the pull-request review; the fix had its own security re-review. 69 JUnit tests and 31 game tests pass. Not run: a real server start on a bad file. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NLkzPTC3pm16WCmLm9dqyp
Asked for in the pull-request review: an operator whose server refuses to start on plots.json could see only counts. The loader now records each rejected row by its position in the array and the first check it failed (invalid, duplicate_index, duplicate_owner, overlap), and the refusal names up to eight of them; a stride mismatch names the plot indexes that are off the grid. The text is built from ints and those fixed words only, so no owner and nothing a file can choose reaches the log. What loads is unchanged. 72 JUnit and 31 game tests pass in the sandbox; the security re-review found nothing. 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
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.


The server gives each player a plot: a registry that survives restarts, allocation and the first build on join, and the
/agentcraft plotcommands. All of it runs only on a dedicated server with multiplayer enabled; singleplayer never allocates, writes noplots.jsonand logs nothing. 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
mainthis diff also shows their changes. The packet's own change is 2 commits: compare from the Wave 0 base.What is in it
PlotDirectory: lowest free index on a square spiral, atomicagentcraft/plots.json, a corrupt or oversized file freezes it instead of being overwritten.plot info,home,rebuildfor the owner (a rebuild once a minute for non-operators),list,assign,freeandrebuild <index>for gamemasters. The actor is always the command source.Review history
An independent security review found 11 issues; the coordinator added five. Two fix rounds and three re-reviews followed (8, then 2, then no findings). One finding was declined:
plot_commandkeeps the frozen catalog's fields, so a refusal carries noreason. The worknote (docs/multiplayer/worknotes/MP-03.mdon this branch) has the details.Verification
gradlew test --rerun --no-build-cacheandgradlew build: 63 JUnit tests and 28 game tests, all passing.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 noagentcraft.mpline.Still owed, and not run by anyone:
Merge order and notes
HqFeaturechange described in MP-02's pull request. On this branch alone the builder still ignores the plot's origin, so do not put a second player on a server built from this branch only.🤖 Generated with Claude Code
https://claude.ai/code/session_01NLkzPTC3pm16WCmLm9dqyp