THE RULEBOOK
A PDA is an addressed account, not an invisible promise
NiceChunk derives the SurfaceDecorationTable from the literal seed surface-decor-v1 and the GlobalConfig public key under the chunk program. The client requests that exact address, checks that the chunk program owns it, then validates the fixed length, magic, version, count, and row fields before compiling the rules.
The 16-byte header stores magic, format version, bump, rule count, one reserved byte, and a u32 revision. Each following 20-byte row stores eleven compact fields. The table has no authority field: initialization checks the GlobalConfig development wallet, while later full-table replacements are authorized through the Civilization receipt path.
Four identity and block IDs, two roll bounds, two height bounds, salt, variant, and flags.
A 16-byte header reserves capacity for at most 128 fixed-size rows.
At the current verification snapshot, CHtBvv7ggCe5PAJKhDL84sdEZRwwJZ4U82kMNESJzend matches the checked-in fixture. This is observed devnet state, not an immutable protocol constant.
ONE ROW, ONE POSSIBILITY
Every rule answers the same small set of questions
A row carries nonzero ruleId and decorationId values, a supporting surface block ID, a candidate drop block ID, an inclusive height range, and a half-open roll band. ruleId must be unique only within the current table; the program does not enforce immutable identities across revisions. A salt selects a repeatable stream, while variant and flags carry compact presentation and behavior choices.
The on-chain matcher does not recognize species. Chunk.js maps supported decoration IDs to efficient models, and the current backpack client maps supported drop IDs to registered resources. A syntactically valid unknown ID is not proof that either client mapping exists.
- surfaceBlockId filters the canonical top block of one x, z column. The resolved object sits at y = surfaceY + 1; vertical and newly exposed faces are not candidates.
- rollStartBps and rollEndBps select a half-open band in a 0-9,999 roll space. Overlap is allowed by validation, and the first matching row in stored order wins.
- minY and maxY include both endpoints when they restrict the canonical surface altitude.
- dropBlockId selects the first lossy secondary reward after the required base-block unit. It is written only when it can merge into a compatible stack below quantity ninety-nine or open a free Backpack record.
- Reward metadata stores (ruleId << 16) | decorationId. It does not pin the table revision, variant, flags, probability band, or original row contents.
DETERMINISTIC MATCHING
Only the canonical top face can match a row
The program calculates the canonical surface at x and z, rejects the decoration path when water covers it, and rejects it when a generated tree voxel occupies the position immediately above. It then considers rows whose supporting block and inclusive height range match that top surface.
Rust hashes the chunk program's fixed canonical seed, the surface coordinate, and each active salt. The GlobalConfig public key anchors the PDA, but the current Rust generator does not read terrain seed or bounds from that account. The browser must use the corresponding canonical inputs when it predicts the same ordered bands.
- Find the canonical surface
Recalculate surface height h and supporting block b for the requested integer x, z column.
- Protect occupied top faces
Return no decoration when canonical water is above h or a generated tree voxel exists at h + 1.
- Filter generic rows
Keep rows with surfaceBlockId equal to b and minY <= h <= maxY.
- Resolve the seeded band
Hash seed, x, h + 1, z, and salt into 0-9,999, then return the first row whose half-open band contains the roll.
CURRENT VERIFIED SNAPSHOT
Revision 2 adds cotton and five separate flower colors
The currently verified devnet table is revision 2 with 43 rows and is byte-for-byte equal to the checked-in initialization fixture. Its final six rows use grass as the support block, salt 201, the full canonical height range, variant zero, and SHADOW plus MINEABLE flags.
These values describe the current account snapshot, not constants embedded in the core matcher. A future valid full-table revision can change them without adding a cotton or flower branch to Rust.
- Cotton: rule 74, decoration 12, drop block 48, band [425,455), conditional probability 0.30%.
- White flower: rule 75, decoration 13, drop block 49, band [455,475), conditional probability 0.20%.
- Yellow flower: rule 76, decoration 14, drop block 50, band [475,495), conditional probability 0.20%.
- Red flower: rule 77, decoration 15, drop block 51, band [495,515), conditional probability 0.20%.
- Blue flower: rule 78, decoration 16, drop block 52, band [515,535), conditional probability 0.20%.
- Pink flower: rule 79, decoration 17, drop block 53, band [535,555), conditional probability 0.20%.
TWO CONSUMERS, TWO READ TIMES
The renderer may predict; current chain state decides
After the world becomes visible, the play runtime fetches the derived PDA, checks its owner and payload, compiles rows by supporting block, and installs them in the Chunk.js workers. It begins empty, fetches once for that startup sync, and may therefore render an older cached revision after a later table update.
Reward mining derives and validates the current program-owned table, recalculates the canonical surface, and accepts a decoration only when the mined block is that support and the MINEABLE bit is set. The transaction supplies no expected table revision, rule ID, or decoration ID, so a local flower model cannot force a reward.
The base mined-block unit is stored first, either by merging into compatible headroom or by opening a record. A matching decoration is attempted ahead of any exploration bonus among lossy secondary rewards. It is skipped only when it has neither compatible stack headroom nor a free record; that skip does not prevent the later exploration candidate from attempting its own storage.
CHANGE WITHOUT CORE BRANCHES
A threshold-met Civilization receipt can replace the whole table
After initialization, the chunk program accepts a full-table replacement through its Civilization adapter. It validates the exact target program and PDA, the hash of the complete payload, a threshold-met tally, and the executed receipt, then repacks the rows with revision.saturating_add(1). The table itself stores no authority, and a valid update executor is a signer and payer rather than a hard-coded table owner.
The existing devnet revision-2 migration is an inspectable 1-of-1 record: total active power, required power, and yes power are all one. That is evidence about this migration, not a protocol requirement or proof of independent multi-party review.
The checked-in synchronization script is a one-off revision-1 to revision-2 migration tool. It only accepts the exact 37-row prefix and 43-row target, is dry-run by default, and writes only with --execute. Its publish and sign builders use the legacy Civilization account layout; the current Civilization source requires PowerSnapshot and CitizenPower accounts, so this script is not a generic future updater.
ACCOUNT AND MATCHING MODEL
One derived account feeds an ordered top-face resolver
The first equation fixes the only valid account address. The second describes the deterministic row selection used after the canonical surface, covering water, and generated-tree occupancy have been calculated.
Surface-decoration table address
T = find_program_address(["surface-decor-v1", G], P_chunk)The table is fetched at the address derived from the literal table seed and the GlobalConfig public key under the chunk program. Account ownership is checked before its fixed-size payload is decoded.
- T
- The unique SurfaceDecorationTable PDA for this GlobalConfig and chunk program.
- G
- The GlobalConfig account public key used as an address anchor.
- P_chunk
- The chunk program ID that owns and validates the table.
First matching row
roll_i = H3(S, x, h+1, z, 1200+salt_i) mod 10000; match = first i in table order where surface_i = b AND minY_i <= h <= maxY_i AND start_i <= roll_i < end_iThe resolver runs only when water does not cover h and the canonical tree block at x, h+1, z is air. Rows are evaluated in stored order, so the first eligible half-open band wins; i is not chosen by the numeric rule ID.
- S
- The canonical seed used by the matching implementation.
- h
- The canonical surface height at column x, z.
- b
- The canonical block ID on that column's top surface.
- i
- A row position in stored table order.
The play renderer clears a missing table
JavaScriptplay/play-surface-decoration-sync.js if (!state.found || !state.ruleCount) {
compiledRules = compileSurfaceDecorationRules([]);
state.applied = Boolean(chunks?.setSurfaceDecorationRules?.([], { revision: 0 }));
onRulesChanged({ rules: [], revision: 0 });
return { ok: true, found: false, applied: false, ruleCount: 0 };
}
Production rendering explicitly installs an empty compiled rule set when the derived account is absent or supplies no rows. The checked-in defaults are not installed as a runtime fallback.
Rust rechecks the support and mineable bit
Rustprograms/nicechunk_chunk/src/lib.rs surface_decoration_from_table_at_surface(
global_config_view,
&data,
args.world_x,
args.world_z,
surface,
)?
.filter(|entry| {
entry.surface_block_id == block_id
&& entry.flags & SURFACE_DECORATION_FLAG_MINEABLE != 0
})
Reward mining resolves the current program-owned table and accepts a decoration only when the mined block is its canonical support and the MINEABLE bit is set. This filter does not guarantee compatible block-stack headroom or a free Backpack record for the secondary reward.
IMPLEMENTATION EVIDENCE
Where these claims come from
Each claim is intentionally scoped to a concrete implementation path. These references are for verification, not decoration.
chunk.js/world/surface-decoration-rules.js
Defines the generic client row schema, empty compiled state, ordered deterministic resolver, supported visual IDs, flags, and current initialization fixture.
play/play-surface-decoration-sync.js
Starts the renderer with an empty table, installs fetched PDA rows, and preserves fail-closed rendering when startup synchronization fails.
src/chain/nicechunkChain.js
Derives the SurfaceDecorationTable address, checks program ownership, validates the fixed payload, exposes revision, and requires the account for reward mining.
programs/nicechunk_chunk/src/state.rs
Defines the 20-byte rule, fixed-capacity table, generic ordered top-surface matcher, water and tree suppression, and returned match record.
programs/nicechunk_chunk/src/lib.rs
Reads the current program-owned table during reward mining, enforces canonical support and MINEABLE, queues a lossy decoration reward, and applies receipt-backed revisions.
programs/nicechunk_backpack/src/state.rs
Shows that secondary reward appends stop successfully when backpack capacity has been reached.
programs/nicechunk_civilization/src/lib.rs
Defines the current PowerSnapshot, CitizenPower, signature, tally, adapter-authority, and execution-receipt flow used by governable targets.
scripts/sync-surface-decoration-rules.ts
Audits the exact 37-row revision-1 to 43-row revision-2 legacy migration, with dry-run inspection and explicit execution.
