Skip to content

MP-03: plots (registry, lifecycle, commands) - #8

Open
aurablacklight wants to merge 15 commits into
mainfrom
mp/MP-03-plots
Open

aurablacklight wants to merge 15 commits into
mainfrom
mp/MP-03-plots

Conversation

@aurablacklight

Copy link
Copy Markdown

The server gives each player a plot: a registry that survives restarts, allocation and the first build on join, and the /agentcraft plot commands. All of it runs only on a dedicated server with multiplayer enabled; singleplayer never allocates, writes no plots.json and 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 main this diff also shows their changes. The packet's own change is 2 commits: compare from the Wave 0 base.

What is in it

  • The registry is the real PlotDirectory: lowest free index on a square spiral, atomic agentcraft/plots.json, a corrupt or oversized file freezes it instead of being overwritten.
  • A joining player is allocated a plot from the connection's UUID; the studio is built two ticks later; a plot whose build never ran is queued again at start and when its owner joins.
  • Commands: plot info, home, rebuild for the owner (a rebuild once a minute for non-operators), list, assign, free and rebuild <index> for gamemasters. The actor is always the command source.
  • Every build, home teleport and respawn point is in the overworld. Only an allocating join, or the first build finishing while the owner is online, moves the player.
  • Freeing a plot whose directory cannot be deleted restores the row, and freezes the registry when that restore cannot be saved.

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_command keeps the frozen catalog's fields, so a refusal carries no reason. The worknote (docs/multiplayer/worknotes/MP-03.md on this branch) has the details.

Verification

  • Tests on this branch: gradlew test --rerun --no-build-cache and gradlew build: 63 JUnit tests and 28 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 live dedicated-server checks in the worknote: a join allocates and builds, the commands, a restart keeps the plots.
  • 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

  • Merge after MP-02, with the one-line HqFeature change 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.
  • The other networking packets' harness checks need an owned plot, so this one goes in before them.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NLkzPTC3pm16WCmLm9dqyp

aurablacklight and others added 11 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
…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

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

Persisted overlapping plots can violate ownership, and failed rebuilds incorrectly consume the player’s throttle window.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

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());
aurablacklight and others added 2 commits October 4, 2026 10:27
…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
aurablacklight and others added 2 commits October 4, 2026 11:59
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
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