THE PLAYER LOOP
One mining swing crosses four unequal layers
Imagine the boy in the illustration striking one stone block. Chunk.js first decides which voxel is under the pointer, whether the tool can reach it, and whether the moving tool box actually touches it. The tool animation, target outline, contact-count state, and locally recorded tool wear can update at frame speed on this device; during the current mining submission path, the target block itself remains present until the action is accepted. The current Play runtime does not produce a mining contact sound or contact-chip particle effect.
For a supported shared action, the browser then submits an integer coordinate and the required on-chain data accounts with a wallet or an authorized temporary session key. Think of the wallet as the main authority and the session key as a limited game key. The Chunk program derives and reads the relevant records, independently recalculates the generated block, checks rules and protection, and writes only if every instruction in the transaction succeeds. The current client waits for confirmed rather than finalized, applies or rolls back the local pending action, then reads the affected PDA records again.
The system still has four jobs even though the list below has five moments: reconstruction happens once before the action to draw the starting world and again after validation to show newly read shared records.
- Reconstruct the starting world
Chunk.js combines the configured seed, version, integer coordinates, model bytes, baked materials, and fetched rule or state accounts into a playable scene.
- React locally
Movement, aiming, collision, tool contact, the target outline, and durability updates keep mining responsive while its target remains. Other actions, such as ordinary placement, can also show a reversible pending delta without claiming a shared write.
- Relay nearby activity
Guardian may deliver a fast regional hint so another player can see motion, chat, equipment, or an action cue without waiting for an on-chain round trip.
- Validate the supported action
A Solana program checks the exact signer, accounts, PDAs, rules, and action data that belong to its instruction; it does not validate the whole rendered frame.
- Read shared records again
After a successful confirmed result, the browser starts new reads for the affected PDA data and rebuilds the scene from the responses it accepts. Starting a new request does not guarantee the newest possible answer. A failure removes the mining pending state and returns the tool durability that was deducted locally for that rejected attempt.
FAST MULTIPLAYER, LIMITED AUTHORITY
Guardian lets nearby players react without becoming the judge
Play discovers nearby Guardian endpoints from registered Region accounts. Within a covered area, Guardian accepts only bounded message shapes, forwards activity near the player, rejects stale or out-of-order sequences, and limits how quickly messages can arrive. It then relays temporary activity such as movement, chat, equipment presentation, digging cues, or building announcements.
That speed solves a different problem from Solana. The current Guardian greeting includes a wallet public key but does not challenge the sender to sign with that wallet, so the greeting alone does not prove control of the matching private key. A relayed digging action can update the receiver's render delta before an RPC read. Treat the message as timely news; treat a successfully executed instruction followed by a validated PDA read as evidence for persistent shared state.
Owns input, camera, physics, animation, pending edits, turning blocks into drawable surfaces, materials, and most presentation.
Moves bounded nearby messages and discovery hints; it is not a replacement for program execution.
One instruction validates its supplied accounts and signers, then stores only the fields defined by that program.
Public inputs and validated records become terrain, models, materials, water, buildings, and inventory presentation.
PROGRAMS AND PDAS
There is no single NiceChunk save file
A program is a public rule engine. A PDA is a predictable on-chain data address that the relevant program can use for one kind of record; it is not a login account and has no private key. Ordinary players can think of the programs as separate clerks: each clerk accepts a particular form, checks particular signatures and records, and cannot rewrite another clerk's notebook merely because the screen places their results side by side.
Core holds the root world settings. Player holds identity, appearance, lookup indexes, compact equipment identities, separate PlayerEquipment custody, and temporary sessions. Chunk holds generated-world removals and world rule tables. The Game program routes Backpack, Smelting, and Market instructions, including immutable ForgedItem model accounts. Building holds foundations and the bytes needed to reconstruct buildings. Guardian holds registry and Region endpoint discovery, while Civilization holds governance rules, signatures, vote counts, receipts, and power records.
Profile, Appearance, name and invite indexes, compact equipment identities, PlayerEquipment custody, and temporary PlayerSession records.
Broken generated coordinates, mining progress, mutable drop or decoration rules, and foundation Chunk indexes.
Routes the current Backpack, Smelting, and Market namespaces, each with its own instruction and account rules.
Building bytes and protection live in Building accounts; realtime endpoint discovery lives in Guardian Region accounts.
CHECK IT YOURSELF
Trust the exact handoff, not a reassuring label
Words such as connected, confirmed, active, verified, or on chain are useful only when the page says who produced the label and what was checked. For a real mining result, the strongest current check combines a successful Chunk instruction on Devnet with newly requested confirmed RPC responses for the correctly derived ChunkBroken and Backpack PDAs, checking each address, owning program, byte format, and claimed value. A new request is not automatically the newest possible answer or proof of which transaction caused the bytes.
The same discipline reveals honest limits. The current client stops waiting at Solana commitment confirmed rather than finalized. The manual Confirm control can move local pending data into an internal collection named chainDeltas, and Guardian can apply a digging hint to that same render layer. Ordinary block placement currently returns chain-placement-disabled. None of those convenient labels or collection names should be silently upgraded into stronger proof.
- Rebuild an untouched place
Entry: open /play/ in two private or incognito windows so neither reuses the other's in-memory edits, and use the minimap coordinate readout to move both players to the same untouched integer x, y, z area. Action: compare the terrain steps, trees, shoreline, and water. Success: both clients draw the same untouched scene. A mismatch means the inputs, version, coordinate, or loaded rules differ; a match demonstrates deterministic reconstruction only, not an account write.
- Verify one real mine
Entry: in /play/, connect a wallet, create or equip a Backpack, and fund the authorized game session shown by the Session control. Action: mine one supported generated block and wait for the event feed to report ON CHAIN, then fully reload the page. Success at the player-UI level: the coordinate remains missing and the reward returns from the chain Backpack after reload. For an independent program check, open a full transaction signature from approved diagnostic tooling in Solana Explorer with cluster=devnet and verify the configured Chunk Program ID GnVKn442KDTDgCyjVG7SEtCQQLjaCiLvrEZDWSU13wbj. Current limitation: the event feed displays only a shortened signature, and a temporary session—not necessarily the connected wallet—signs mining, so wallet activity or a wallet-address search may not expose that transaction. Without the full signature and PDA addresses, reload persistence is useful evidence but not a complete independent audit.
- Test the placement boundary
Entry: select an ordinary placeable block in /play/ while the wallet is connected and keep the chain event feed visible. Action: place it, observe chain-placement-disabled, and optionally press the manual Confirm control. Success for the documented boundary: this screen can keep the local result, but the wallet has no matching placement signature, a second clean client cannot read a placement PDA, and a reload removes the local-only placement. If a matching Devnet transaction or recoverable placement PDA appears, this page is stale and should be reported.
- Compare Guardian with RPC
Entry: open two clients in one Guardian-covered Region and confirm that both show the regional connection. Action: move or dig in one client, watch the other react quickly, and then fully reload the receiving client after its RPC refresh. Success: movement can arrive without any transaction, while a mined coordinate persists after the full reload only when chain-backed data is read again. The current session can retain an unobserved Guardian digging gap even when an ordinary account refresh does not contain it, so the full reload matters. If approved diagnostic tooling supplies the full signature, compare it and the affected PDA reads in Devnet Explorer; the ordinary event feed's shortened signature is not enough for that independent check. A relay cue that disappears after the reload was news, not a receipt.
- Check the protected-foundation guard
Entry: aim at a foundation voxel that the loaded Building records mark as protected. Action: press the mining control. Success: the normal client reports that the block is protected and stops before starting the pickaxe animation, so there is no contact spark, missing voxel, reward, or signature. This local guard improves feedback but is not the authority: if stale data or a modified client submits the request anyway, the Chunk program independently reads the foundation protection and must reject it.
- The currently configured Solana cluster is Devnet, even though the file name and world seed contain mainnet wording.
- The transaction helper waits for commitment confirmed; this page does not claim finalized or irreversible settlement.
- Non-tree surface decorations remain disabled until Play fetches a valid SurfaceDecorationTable PDA; bundled defaults are not production authority.
- A successful instruction proves only its exact program, signers, supplied accounts, checks, and writes—not the camera, hand path, particles, or every other program's state.
- For a foundation already known to this client, Play blocks mining before the swing animation; the Chunk program repeats the protection check so stale or modified clients cannot rely on bypassing that local guard.
ONE COORDINATE, READ HONESTLY
The screen has an order of precedence; proof needs an extra check
The formula describes which block the current Chunk.js scene draws. The short source excerpts then show local render precedence, independent mining checks, Guardian's unverified hint path, program-specific PDAs, confirmed commitment, and the disabled ordinary-placement boundary.
The visible block at one integer coordinate
B_screen(p) = first_defined(P_pending(p), D_render(p), G(worldInputs, p))first_defined means 'take the first value that exists.' For example, at one coordinate a placement preview can temporarily cover a stored removal; without either edit, Chunk.js draws the generated base block. D_render is deliberately not named chain proof: the current internal chainDeltas map can also receive a manual local confirmation or a Guardian action hint. A shared claim needs a successful signed instruction plus a new confirmed RPC response whose expected account address, owning program, byte format, and claimed value are validated; merely starting a new request does not guarantee the newest possible answer.
- B_screen(p)
- The block that this client currently draws at coordinate p.
- p
- One integer world coordinate, written as x, y, z.
- first_defined(...)
- Read left to right and use the first available block answer.
- P_pending(p)
- A local proposed change that can still be confirmed or rolled back.
- D_render(p)
- The delta currently used by ChunkState when drawing the coordinate; its internal collection name is not independent proof of where the data came from.
- worldInputs
- The public seed, generation version, Chunk size, sea level, height limits, and other generator configuration needed to reproduce the base world.
- G(worldInputs, p)
- The untouched base block reconstructed from all required public world inputs. Non-tree surface decorations are a separate PDA-selected layer.
Chunk.js draws the first block source that exists
JavaScriptchunk.js/chunk/chunk-state.js getFinalBlock(localX, localY, localZ) {
if (!containsLocal(localX, localY, localZ, this)) return BLOCK_ID.air;
if (!this.pendingDeltas.size && !this.chainDeltas.size) return this.getBaseBlock(localX, localY, localZ);
const key = deltaKey(localX, localY, localZ, this.chunkSize);
const pending = this.pendingDeltas.get(key);
if (pending) return pending.blockId;
const chain = this.chainDeltas.get(key);
if (chain) return chain.blockId;
return this.getBaseBlock(localX, localY, localZ);
}
Read this from top to bottom. A pending local edit hides everything below it. If there is no pending edit, the runtime tries its stored render delta; if neither edit source exists, it falls back to the generated block. The variable named chain does not verify its own origin, so the client must separately obtain and validate a current PDA snapshot before calling that result shared state.
- 1
The function asks for the final block at one local x, y, z coordinate inside a Chunk.
- 2
A coordinate outside this Chunk is treated as air here; another Chunk is responsible for its own cells.
- 3
When there are no pending edits and no stored render deltas, the generated base block can be returned immediately.
- 4-6
The coordinate is converted to a lookup key. If a reversible local edit exists at that key, its block wins first.
- 7-9
Otherwise the function tries the stored render delta, then falls back to the generated base block if no delta exists.
- 10
The closing brace ends this one-coordinate decision.
The mining program independently recalculates the block
Rustprograms/nicechunk_chunk/src/lib.rs let surface = generated_surface_height(global_config_view, args.world_x, args.world_z);
let block_id = generated_block_id_at_surface(global_config_view, &generated_args, surface);
if args.expected_block_id != block_id {
return Err(NicechunkChunkError::GeneratedBlockMismatch.into());
}
if matches!(block_id, BLOCK_AIR | BLOCK_WATER | BLOCK_BEDROCK) {
return Err(NicechunkChunkError::UnmineableBlock.into());
}
The browser may submit, in effect, 'I mined this kind of block here.' The program computes its own answer for that coordinate. A mismatch fails, and air, water, or bedrock fails even when the browser asks to mine it. The complete instruction also checks protection, account addresses, sessions, duplicates, rule tables, the Backpack, and state writes; some of those checks occur before this excerpt and some occur after it.
- 1-2
The program calculates the surface and generated block for the coordinate from validated world configuration and instruction arguments.
- 3-5
The browser's expected block ID must equal the program's answer. If it does not, the instruction stops with GeneratedBlockMismatch and writes nothing.
- 6-8
Even a matching answer is rejected when the generated block is air, water, or bedrock, because those targets are not mineable.
A Guardian digging message can change rendering before RPC
JavaScriptplay/play-guardian.js if (event.action !== 3 || !chunks?.applyChainDelta) return;
const worldX = Math.trunc(event.x);
const worldY = Math.trunc(event.y);
const worldZ = Math.trunc(event.z);
chunks.applyChainDelta([{ worldX, worldY, worldZ, blockId: BLOCK_ID.air }]);
onWorldChanged({ type: "guardian-dig", worldX, worldY, worldZ, localPlayerId: event.localPlayerId });
This receiver treats Guardian action 3 as a fast digging hint and applies air to the render-delta layer. The callback lets Play invalidate that Chunk and request an independent RPC refresh. Nothing in this excerpt verifies a transaction signature or PDA owner, so the hint remains news rather than settlement.
- 1
Ignore messages that are not the digging action, or stop if the loaded world cannot accept a render delta.
- 2-4
Convert the message's coordinates to integer world cells.
- 5
Temporarily draw air at that cell through the same internal render-delta collection used for other resolved-looking changes. The collection name does not prove the source.
- 6
Notify Play that a Guardian hint changed the world view; Play separately schedules a Chunk PDA refresh.
The same progress label stays separate under different programs
JavaScriptsrc/chain/nicechunkChain.jsfunction derivePlayerProgressPdaForContext(owner, context = gameContext) {
return derivePlayerProgressPdaForProgram(owner, context.chunkProgramId);
}
function deriveSmeltingPlayerProgressPdaForContext(owner, context = gameContext) {
return derivePlayerProgressPdaForProgram(owner, context.smeltingProgramId);
}
Both callers use the same derivation helper and player-progress label, but one supplies the Chunk Program ID and the other supplies the Smelting Program ID. Because a program ID is part of PDA derivation, the two results are different accounts rather than one automatically merged progress total.
- 1-3
The mining-side helper derives the player's progress address under the configured Chunk Program ID.
- 4
The blank line separates two independent address choices; it does not join their data.
- 5-7
The smelting-side helper derives progress under the configured Smelting Program ID, producing a separate PDA even for the same player and label bytes.
The transaction helper stops waiting at confirmed
JavaScriptsrc/chain/nicechunkChain.jsasync function signAndSendKeypairTransaction(signer, transaction, conn = getNicechunkConnection()) {
transaction.feePayer = signer.publicKey;
const { blockhash, lastValidBlockHeight } = await conn.getLatestBlockhash("confirmed");
transaction.recentBlockhash = blockhash;
transaction.lastValidBlockHeight = lastValidBlockHeight;
transaction.sign(signer);
const signature = await sendRawTransactionWithLogs(conn, transaction.serialize(), "keypair");
await confirmTransactionByHttpPolling(conn, { signature, blockhash, lastValidBlockHeight }, "confirmed");
return signature;
}
The helper signs and sends a transaction, then polls until Solana reports confirmed. It does not ask this path to wait for finalized, so the page keeps those commitment stages separate.
- 1-2
Start the helper and make the authorized session key pay this transaction's network fee.
- 3-5
Fetch a recent confirmed blockhash and its expiry height, then attach both to the transaction.
- 6-7
Sign with the authorized key and send the serialized transaction to Solana, receiving a transaction signature.
- 8
Poll status until the signature reaches confirmed. The requested word is visibly confirmed, not finalized.
- 9-10
Return the signature to the caller and end the helper.
The normal client stops a protected swing before animation
JavaScriptplay/mining-controller.js if (miningPlan.blocks?.some?.((block) => isBlockProtected(block)) || isBlockProtected(targetHit)) {
onStatus("This block is protected by a foundation.");
return null;
}
const duration = Math.max(1, swingDurationMs);
Before creating the active swing, the normal client checks the target and every block in a multi-block mining plan against its loaded foundation protection data. A protected result returns immediately, so the animation-duration line is reached only for a locally allowed target.
- 1
Ask whether the direct target or any block in the planned mining action is inside a foundation the client currently knows is protected.
- 2-3
Show the player a protected-foundation message and return null, which stops this action before an active pickaxe swing is created.
- 4-5
Only an unprotected result leaves the condition and proceeds to calculate the swing duration.
The Chunk program independently repeats foundation protection
Rustprograms/nicechunk_chunk/src/lib.rs let (chunk_x, chunk_z, local_x, local_z) = args.chunk_coords(global_config_view)?;
reject_foundation_protected_block(
program_id,
foundation_chunk,
global_config,
chunk_x,
chunk_z,
&args,
)?;
Client feedback is never the authority. If stale local data or a modified client still submits a mine, the Chunk instruction derives the target's Chunk coordinates and calls its own foundation-protection check before accepting the generated block or writing a removal.
- 1
Derive the target's Chunk and local coordinates from already validated instruction arguments and world configuration.
- 2-9
Pass the program, supplied foundation index, global configuration, Chunk coordinates, and mining arguments to the independent protection check. The question mark stops the instruction on any protection error.
Ordinary block placement currently stops before submission
JavaScriptsrc/chain/nicechunkChain.jsexport async function recordBlockPlacementOnChain(_target, _renderType, _toolSlot = 0) {
return { submitted: false, reason: "chain-placement-disabled" };
}
The exported function exists so the client can call one stable interface, but its current result explicitly says that no ordinary placement transaction was submitted. A local preview or manual Confirm action cannot turn this return value into a placement PDA.
- 1
Play can call the ordinary placement adapter with a target, render type, and selected tool slot. The leading underscores show that this implementation does not currently use them.
- 2
Return submitted false with the exact reason chain-placement-disabled; no signed placement transaction is built here.
- 3
The closing brace ends the disabled adapter.
IMPLEMENTATION EVIDENCE
Where these claims come from
Each claim is intentionally scoped to a concrete implementation path. These references are for verification, not decoration.
public/mainnet.json
Defines the current world inputs, Devnet cluster, and configured program addresses used by the client.
play/main.js
Connects the wallet entry, Chunk.js runtime, scene inputs, player model, Guardian, chain-backed controllers, and post-hint RPC refreshes.
chunk.js/world/world-generator.js
Reconstructs the base block answer from the world seed, integer coordinates, and generator configuration.
chunk.js/chunk/chunk-state.js
Defines base, pending, and render-delta precedence plus local confirm and rollback behavior.
chunk.js/renderer/avatar-mesh.js
Loads canonical NCM villager boxes, centers the model, builds body groups, and attaches held tools.
chunk.js/renderer/texture-array-manager.js
Bakes deterministic material tiles and uploads the texture array used by the Chunk.js renderer.
play/play-chain-session.js
Separates local preview, wallet-needed, submitting, automatic confirmation, skipped submission, and rollback paths.
src/chain/nicechunkChain.js
Builds signed gameplay transactions, waits for confirmed commitment, and currently disables ordinary block placement submission.
programs/nicechunk_chunk/src/lib.rs
Validates mining accounts and sessions, recalculates the block, checks protection and duplicates, resolves rules, and writes accepted results.
play/play-chain-chunk-deltas.js
Derives, owner-checks, decodes, and applies RPC snapshots of ChunkBroken accounts to the loaded world.
play/play-guardian-registry.js
Discovers current regional Guardian endpoints from canonical Region PDA scans.
play/play-guardian.js
Handles nearby transient activity and shows that a Guardian digging hint can affect rendering before RPC refresh.
Guardian/src/app.cpp
Implements Guardian connection, region, sequence, area-of-interest, rate, and message forwarding boundaries.
play/play-surface-decoration-sync.js
Keeps non-tree surface decorations disabled until a valid PDA-backed rule table has been fetched.
docs/audits/global-config-genesis-devnet-2026-07-15.json
Records dated Devnet observations used to separate source claims from deployment claims.