PUBLIC ACCOUNT, FIXED LAYOUT
Appearance is a separate public record, not a private model file or finished mesh
For owner wallet o, the Player Program derives one Appearance PDA from the literal seed appearance-v1 and o. The account is public like other Solana program data. Its program owner controls which instructions may write it, while its stored owner field identifies the player relationship. Neither kind of ownership encrypts the display name, title, model code, timestamps, or treasury key.
The account has exactly 9,612 bytes. A 256-byte header stores magic, version, bump, initialized state, owner, linked PlayerProfile, GlobalConfig, treasury, modelKind, flags, three string lengths, the declared equipment count, and four timestamps. Fixed regions then reserve 300 bytes for display name, 96 for title, 2,048 for modelCode, and 6,912 for twelve 576-byte rich-equipment records.
That large reserved equipment region is about 71.9 percent of the account. Reserved capacity is not proof of a working feature: the current packer writes every rich-equipment state as zero and clears each payload. The fields exposed by the SDK decoder describe a format, not a current contract path that fills it.
The four user-facing fields follow independent rules. Name is at most 32 characters and 300 UTF-8 bytes using underscore, ASCII letters or digits, or U+4E00 through U+9FFF. Title may be empty and is at most 96 UTF-8 bytes. modelKind is only 1 or 2. modelCode is valid UTF-8 from 3 through 2,048 bytes beginning with the exact uppercase characters NCM. No rule connects the kind byte to the model text.
One deterministic public address per owner under the Player Program.
256 + 300 + 96 + 2,048 + 12 × 576.
Current packing still leaves every record empty.
It is not an NCM structure or rendering validation.
ONE INSTRUCTION, THREE IDENTITY RECORDS
One upsert maintains Profile, name claim, and Appearance—then clears every reserved equipment payload
Tag 8 requires exactly six accounts: writable owner signer and payer, writable PlayerProfile, writable PlayerAppearance, GlobalConfig, System Program, and writable UsernameIndex. The current website supplies all six. The processor validates the variable instruction lengths, field rules, signer, writable flags, program identities, and the owner-derived Profile and Appearance addresses before changing state.
Within that one instruction, the program ensures or reuses PlayerProfile, writes its name, ensures or verifies the current UsernameIndex for a nonempty name, then creates or reuses Appearance. These are separate accounts changed atomically by the transaction. A later browser cache write is not part of that atomic unit.
On reuse, a valid Appearance keeps its original createdSlot and createdAt. updatedSlot and updatedAt become the current Clock values. The program then calls the complete fixed-layout packer rather than editing only changed strings. Flags, padding, reserved bytes, and all twelve rich-equipment records are rewritten.
Each 576-byte rich record is shaped for state, slot, flags, item public key, mass, grip point, grip rotation, and up to 512 model-code bytes. Current pack writes state zero, preserves only the ordinal slot byte, and zeroes the other 574 bytes. The Player Program exposes no separate Appearance instruction that fills them. Current held tools instead come through Backpack and NCF1 reconstruction, not these records.
- Owner approves the six-account instruction
The wallet is signer and storage payer; Profile, Appearance, and UsernameIndex remain separate writable destinations.
- Profile and canonical name claim are handled first
The current name is written to Profile and its current index is created or checked before Appearance packing.
- Appearance is completely packed again
Creation metadata survives valid reuse, update metadata advances, and every rich-equipment payload returns to empty.
CHAIN ENVELOPE, CLIENT DECODERS
A code can fit the chain's storage rule and still be invalid, incomplete, or expensive to render
The Player contract stops after UTF-8, length, and uppercase NCM-prefix checks. It does not require a colon, recognize a version, decode Base64URL, verify a checksum, count cuboids, inspect bones, or estimate reconstruction work. Values such as NCM, NCM3:anything, or a nonexistent alias can therefore be stored even though the avatar loader cannot build them.
After exact alias resolution, the current shared avatar loader dispatches NCM4 to its strict character codec and sends other avatar text through the NCM2 decoder. NCM4 requires the exact uppercase NCM4: envelope, canonical unpadded Base64URL, valid version and flags, bounded counts and coordinates, twenty fixed bones, valid actions, no trailing ambiguity, a maximum 1,532-byte raw payload, and a matching CRC32C checksum. Those checks occur on the player's device, not inside the Player Program.
The current avatar NCM2 path is materially looser. It checks positive dimensions, scale, and palette count, but applies no cuboid-count, palette-count, dimension, canonical-encoding, trailing-byte, or reconstruction resource budget. Its bit reader zero-fills out-of-range bits. In focused read-only tests, heavily truncated standard girl text could still produce seventy boxes, and a 19-byte synthetic text declaring 10,000 cuboids produced 10,000 overlapping boxes. That is a current client memory or denial-of-service hardening gap; the 2,048-byte chain limit alone is not a render-cost guarantee.
The repository's general source NCM2 codec is stricter and throws when its bitstream ends. Passing that utility's fixture checks does not prove the actual avatar decoder rejects the same damaged text. Documentation and tests must name the consumer whose behavior they measured.
No version, geometry, checksum, or cost validation.
No current decoder resource budget or canonical check.
Strict client validation still does not turn the code into a chain-verified mesh.
CREATION, LOGIN, AND PLAY ARE SEPARATE HANDOFFS
The creation page, browser cache, and Play refresh can briefly hold different model sources
The creation page currently offers exactly two checked-in NCM2 fixtures. It embeds the complete 353-character standard boy code and complete 451-character standard girl code, previews the selected bytes, and submits the full selected code—not a boy-or-girl alias—to Appearance. The female model therefore works without any built-in girl alias.
After the transaction helper returns, creation writes gender, full code, label, and timestamp to both legacy global localStorage keys and wallet-scoped keys. Those sequential cache writes are not part of the Solana transaction. A storage exception can occur after chain confirmation and leave only an earlier prefix of browser values; the confirmed public account cannot be rolled back by that local error.
Login first uses a wallet-scoped character cache when a username is already present. Without that shortcut, it reads Appearance and copies the chain model into global and wallet-scoped storage. Current Play can start from the local profile and then asynchronously fetch Profile and Appearance again. These latency choices allow a short interval where the creation page, login gateway, local profile, and fresh chain read do not yet agree.
For the local visible avatar, current precedence is chain Appearance.modelCode, then the local profile modelCode, then the built-in boy default. Name precedence is related but separate. A cache value is a presentation source; it is not evidence that Appearance contains the same bytes.
- Choose one complete standard fixture
Creation uses the full male or female NCM2 text and sends it with name, title, and modelKind.
- Confirm chain work before local handoffs
Browser caches, invite receipt, and candidate spawn are later local or separate steps; none extends the atomic scope of Appearance upsert.
- Let Play refresh authority asynchronously
A cached model can appear first, then a successful chain Appearance read becomes the preferred source for the next mesh load.
RESOLUTION, RECONSTRUCTION, AND SURVIVAL
Aliases, NCM2, and NCM4 reach different runtime capabilities before fallback keeps play usable
The built-in alias map recognizes the empty string, NCM:peasant_guy:v1, NCM:peasant_guy_blackhair:v1, and peasant_guy. All four resolve to the same embedded standard boy NCM2. There is no current NCM:peasant_girl:v1 alias. Such text would pass the contract's NCM prefix but miss the alias table and fail the avatar decoder.
NCM2 geometry has no stored skeleton. The renderer centers its boxes above the ground, infers head, torso, arms, and legs from names and spatial position, creates pivots and hand anchors, applies procedural walking and mining motion, and can append the current basic or restored forged tool, blueprint, or held block. The character uses vertex colors and realtime light; the baked world TextureArray visible in the teaching references is not sampled as the character's own texture.
NCM4 carries bones, groups, and actions, and the shared renderer can decode a valid model, build its mesh, and sample an explicitly selected action. Current Play integration is incomplete: its normal frame state supplies moving and mining values rather than an NCM4 action or clip selection. The NCM4 mesh currently returns equipment: [] and handAnchors: {}, so the established NCM2 held-tool attachment path is bypassed. Current collision and hidden-group behavior also lack complete Play-level coverage.
When a requested local model fails, Play does not erase a mesh that already works. It tries the last active code when one exists; with no usable mesh it tries the default boy. If fallback also fails, it returns the existing mesh or null. Guardian remote avatars and Profile preview have related but separate default-boy recovery paths and caches, so one successful fallback does not repair the stored Appearance text.
The empty key is included; no standard girl alias exists.
Procedural moving and mining are derived after geometry decode.
Normal moving and mining do not select clips; equipment and handAnchors are empty.
CURRENT CLIENT, OLDER SDK, DATED EVIDENCE
The production browser shape and older SDK differ, while Devnet proves a real modelKind mismatch
The current website chain adapter constructs the tag-8 browser instruction with six accounts, including writable UsernameIndex. The checked-in TypeScript SDK still constructs an older five-account instruction, and its older test asserts that shape. Because the current processor rejects any account count other than six, passing that test is evidence of SDK drift, not production parity. Integrators should compare any builder with the processor and deployed artifact they intend to call.
A retained confirmed Devnet snapshot at slot 476,758,441 contains ten canonical PlayerAppearance accounts. Every account is 9,612 bytes, version 1, carries the configured treasury, holds 67,790,400 lamports, and has twelve zero-state rich-equipment records. Raw model text matches the standard creation fixtures exactly: five boy codes and five girl codes, with no NCM4 in that snapshot. Eight titles are empty and two are Explorer.
The stored kind distribution is four kind 1 and six kind 2. One kind-2 account named kali contains the exact 353-character boy code. That live byte-level mismatch is direct evidence that modelKind does not select or enforce the rendered body. It is a dated observation, not a claim that every future account has this distribution.
The 67,790,400-lamport amount also matches a separate finalized 16 July 2026 minimum-balance query for 9,612 bytes. The player pays or tops up Appearance during upsert, but only the configured treasury can call tag 9. Close requires the recipient to be that same treasury, clears the data, and transfers the account's full balance. There is no current player close-and-reclaim path.
The retained deployed ProgramData allocation matched a saved ELF at the snapshot, but current Rust, Play, Chunk.js, and documentation worktrees contain later modifications. The snapshot supports its captured account and artifact claims; it does not prove every current source line is already deployed.
The sixth current account is writable UsernameIndex.
Confirmed slot 476,758,441; zero NCM4 in that snapshot.
Raw bytes disprove any automatic kind-to-model binding.
Dated schedule observation, not a permanent SOL quote or player refund promise.
READ THE IMPLEMENTATION
Six equations and twelve literal excerpts connect stored text to the visible villager
The formulas separate address, layout, field checks, upsert, renderability, source precedence, and close authority. Each code block is a continuous repository substring, so the plain-language explanation can be checked against the exact implementation.
One owner has a separate fixed-size Appearance address
Appearance(o)=PDA_Player([UTF8("appearance-v1"),o]); LEN=256+300+96+2048+(12×576)=9612; richShare=6912/9612≈71.9%Derivation identifies the expected address. Program ownership, initialization, magic, version, internal relationships, and validated contents remain separate checks.
- o
- The player's public wallet key.
- PDA_Player
- Program-address derivation under the Player Program ID.
- 12×576
- Twelve fixed rich-equipment regions, currently packed empty.
Four fields pass four independent rules
Valid=Name(chars≤32,bytes≤300,allowedRange) ∧ Title(validUTF8,bytes≤96) ∧ modelKind∈{1,2} ∧ ModelCode(validUTF8,3≤bytes≤2048,startsWith("NCM"))The expression contains no NCM version parser, geometry decoder, checksum, reconstruction budget, or relation between modelKind and modelCode because the current contract contains none.
- allowedRange
- Underscore, ASCII letters or digits, or code points U+4E00 through U+9FFF.
- ∧
- Every listed independent condition must hold.
- bytes
- UTF-8 byte length, which can exceed visible character count.
Upsert preserves birth metadata but replaces the record body
Upsert_8=Ensure(Profile)+WriteName+Ensure(UsernameIndex)+CreateOrReuse(Appearance)+PackAll; created'=storedCreated∨now; updated'=now; ∀s∈[0,11],richState'_s=0The plus signs show ordered work inside one instruction. Browser cache writes happen later and are not rolled back by this transaction.
- created'
- Creation slot and time after the operation.
- updated'
- The current Clock slot and timestamp.
- s
- One reserved rich-equipment slot from zero through eleven.
Contract storage and local renderability are different predicates
Stored(c)≠Renderable(c); Renderable(c)=ExactBoyAlias(c) ∨ AvatarNCM2Decode(c) succeeds ∨ StrictNCM4Decode(c) succeedsNCM2 success here means the behavior of the actual avatar decoder, including its current zero-fill and missing resource-budget boundary. NCM4 success means its stricter canonical and structural checks passed.
- Stored
- Passes the Player Program's narrow modelCode envelope.
- Renderable
- The current shared avatar loader produces a mesh without throwing.
- ∨
- At least one route succeeds.
The requested source and the survival fallback have separate orders
RequestedCode=Appearance.modelCode || LocalProfile.modelCode || DefaultBoy; VisibleMesh=requestedSuccess || CurrentValidMesh || defaultBoySuccessThe double bars mean choose the first usable value. A fallback changes the local visible mesh only; it does not rewrite the public Appearance code.
- LocalProfile
- The browser's current local player profile object.
- CurrentValidMesh
- The last mesh that finished construction and was uploaded successfully.
- DefaultBoy
- The built-in standard boy route used when no better code is available.
Storage payer and close authority are intentionally different
UpsertPayer=owner; CloseAllowed⇔signer=recipient=configuredTreasury; close⇒data=0^9612 ∧ TreasuryLamports+=AppearanceLamports ∧ AppearanceLamports=0The observed 67,790,400 lamports was a dated Devnet rent-exempt balance, not a source constant. No current player reclaim path exists.
- 0^9612
- All 9,612 data bytes are filled with zero.
- configuredTreasury
- The treasury public key compiled into the current Player build.
- ⇔
- The stated signer and recipient equality is required.
Fixed offsets make the 9,612-byte layout explicit
Rustprograms/nicechunk_player/src/state.rs pub const HEADER_LEN: usize = 256;
pub const DISPLAY_NAME_OFFSET: usize = Self::HEADER_LEN;
pub const TITLE_OFFSET: usize = Self::DISPLAY_NAME_OFFSET + PLAYER_NAME_MAX_BYTES;
pub const MODEL_CODE_OFFSET: usize = Self::TITLE_OFFSET + APPEARANCE_TITLE_MAX_BYTES;
pub const EQUIPMENT_OFFSET: usize = Self::MODEL_CODE_OFFSET + APPEARANCE_MODEL_CODE_MAX_BYTES;
pub const LEN: usize =
Self::EQUIPMENT_OFFSET + APPEARANCE_EQUIPMENT_SLOT_COUNT * APPEARANCE_EQUIPMENT_SLOT_LEN;
Each variable string owns a fixed reserved region. The twelve equipment records begin only after the full 2,048-byte model-code region.
- Lines 1–5
Build each offset from the preceding fixed-size region.
- Lines 6–7
Add twelve 576-byte records to reach the total length.
Kind and model text are validated independently
Rustprograms/nicechunk_player/src/state.rs pub fn validate_model_kind(model_kind: u8) -> Result<(), NicechunkPlayerError> {
if model_kind != CHARACTER_MODEL_KIND_MALE && model_kind != CHARACTER_MODEL_KIND_FEMALE {
return Err(NicechunkPlayerError::InvalidCharacterModelKind);
}
Ok(())
}
pub fn validate_title(title: &[u8]) -> Result<&str, NicechunkPlayerError> {
if title.len() > APPEARANCE_TITLE_MAX_BYTES {
return Err(NicechunkPlayerError::InvalidAppearanceTitle);
}
core::str::from_utf8(title).map_err(|_| NicechunkPlayerError::InvalidAppearanceTitle)
}
pub fn validate_model_code(code: &[u8]) -> Result<&str, NicechunkPlayerError> {
if code.is_empty() || code.len() > APPEARANCE_MODEL_CODE_MAX_BYTES {
return Err(NicechunkPlayerError::InvalidCharacterCode);
}
let value =
core::str::from_utf8(code).map_err(|_| NicechunkPlayerError::InvalidCharacterCode)?;
if !value.starts_with("NCM") {
return Err(NicechunkPlayerError::InvalidCharacterCode);
}
Ok(value)
}
The kind function receives no model text, and the code function receives no kind. The code validator stops after length, UTF-8, and uppercase prefix.
- Lines 1–6
Accept only numeric kind one or two.
- Lines 8–24
Validate the optional title, then accept bounded model UTF-8 beginning with uppercase NCM without decoding a format.
Tag 8 rejects any account list other than six
Rustprograms/nicechunk_player/src/lib.rsfn upsert_player_appearance(
program_id: &Pubkey,
accounts: &[AccountInfo],
payload: &[u8],
) -> ProgramResult {
if accounts.len() != 6 || payload.len() < 7 {
return Err(NicechunkPlayerError::InvalidInstruction.into());
}
A five-account SDK instruction fails this first shape check before the processor can perform the remaining Appearance work.
- Lines 1–5
Enter the Appearance processor with supplied accounts and bytes.
- Lines 6–8
Require exactly six accounts and at least the seven-byte variable-length header.
The sixth processor account is writable UsernameIndex
Rustprograms/nicechunk_player/src/lib.rs let account_info_iter = &mut accounts.iter();
let owner = next_account_info(account_info_iter)?;
let player_profile = next_account_info(account_info_iter)?;
let appearance = next_account_info(account_info_iter)?;
let global_config = next_account_info(account_info_iter)?;
let system_program_account = next_account_info(account_info_iter)?;
let username_index = next_account_info(account_info_iter)?;
if !owner.is_signer || !owner.is_writable {
return Err(NicechunkPlayerError::InvalidPayer.into());
}
if !player_profile.is_writable || !appearance.is_writable || !username_index.is_writable {
return Err(NicechunkPlayerError::InvalidWritableAccount.into());
}
The ordered roles are owner, Profile, Appearance, GlobalConfig, System Program, and UsernameIndex. Three data accounts plus the payer must be writable as shown.
- Lines 1–7
Assign each supplied AccountInfo to its fixed semantic role.
- Lines 9–14
Require a writable signing payer and writable Profile, Appearance, and UsernameIndex.
Every full pack clears all twelve rich-equipment payloads
Rustprograms/nicechunk_player/src/state.rs for slot in 0..APPEARANCE_EQUIPMENT_SLOT_COUNT {
writer.u8(0)?;
writer.u8(slot as u8)?;
writer.bytes(&[0_u8; APPEARANCE_EQUIPMENT_SLOT_LEN - 2])?;
}
For each record, state becomes zero, the slot ordinal is stored, and all other 574 bytes become zero. Earlier rich-equipment data is not preserved.
- Lines 1–5
Repeat the empty-record representation for slot indexes zero through eleven.
Close clears data and transfers the entire balance
Rustprograms/nicechunk_player/src/lib.rs {
let mut data = appearance.try_borrow_mut_data()?;
data.fill(0);
}
let reclaimed_lamports = appearance.lamports();
**recipient.try_borrow_mut_lamports()? = recipient
.lamports()
.checked_add(reclaimed_lamports)
.ok_or(NicechunkPlayerError::InvalidAppearanceData)?;
**appearance.try_borrow_mut_lamports()? = 0;
Ok(())
Earlier checks require the configured treasury as both signer and recipient. This block zeroes the account and transfers whatever balance is present, not only a quoted minimum.
- Lines 1–4
Clear all Appearance data bytes.
- Lines 5–10
Add the full balance to the validated recipient and drain Appearance.
- Line 11
Return success after both changes.
The current website builder supplies UsernameIndex
JavaScriptsrc/chain/nicechunkChain.js keys: [
{ pubkey: authority, isSigner: true, isWritable: true },
{ pubkey: playerProfile, isSigner: false, isWritable: true },
{ pubkey: appearance, isSigner: false, isWritable: true },
{ pubkey: deriveGlobalConfigPda(), isSigner: false, isWritable: false },
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
{ pubkey: usernameIndex, isSigner: false, isWritable: true },
],
data: Buffer.concat([header, name, title, code]),
The browser order matches the processor and includes writable UsernameIndex as account six. Instruction data follows after those account metas.
- Lines 1–8
Declare the signing owner and five non-signing accounts in the current order.
- Line 9
Append tag, kind, lengths, and full string bytes.
Creation submits full model text before writing local caches
JavaScriptplayer_creat/player_creat.js const result = await createPlayerAppearanceOnChain({
playerName: name,
gender: model.gender,
title: "",
ncmCode: model.code,
});
if (result?.submitted === false) throw new Error(result.reason || "player-appearance-not-saved");
persistUsername(name);
localStorage.setItem(characterStorageKeys.gender, model.gender);
localStorage.setItem(characterStorageKeys.code, model.code);
localStorage.setItem(characterStorageKeys.label, t(model.labelKey));
localStorage.setItem(characterStorageKeys.createdAt, String(Date.now()));
localStorage.setItem(walletCharacterStorageKey(characterStorageKeys.gender), model.gender);
localStorage.setItem(walletCharacterStorageKey(characterStorageKeys.code), model.code);
localStorage.setItem(walletCharacterStorageKey(characterStorageKeys.label), t(model.labelKey));
localStorage.setItem(walletCharacterStorageKey(characterStorageKeys.createdAt), String(Date.now()));
The selected object contains the complete boy or girl NCM2. Chain submission is awaited first; the later global and wallet-scoped writes are independent browser operations.
- Lines 1–7
Submit full selected text and reject an explicit skipped result.
- Lines 8–16
Persist name and character fields locally after the helper returns.
Every built-in alias resolves to the standard boy
JavaScriptchunk.js/renderer/avatar-mesh.jsexport const BUILTIN_AVATAR_MODEL_CODES = new Map([
["", DEFAULT_PEASANT_GUY_NCM],
["NCM:peasant_guy:v1", DEFAULT_PEASANT_GUY_NCM],
["NCM:peasant_guy_blackhair:v1", DEFAULT_PEASANT_GUY_NCM],
["peasant_guy", DEFAULT_PEASANT_GUY_NCM],
]);
The map contains no girl alias. Creation makes the girl work by storing her full NCM2 text instead.
- Lines 1–6
Map four accepted keys, including empty text, to one embedded standard boy code.
The avatar NCM2 bit reader zero-fills missing bytes
JavaScriptchunk.js/renderer/avatar-mesh.jsfunction createBitReader(bytes, offset = 0) {
let bitOffset = offset * 8;
return {
read(bits) {
let value = 0;
for (let i = 0; i < bits; i += 1) {
const byte = bytes[bitOffset >> 3] ?? 0;
const bit = (byte >> (bitOffset & 7)) & 1;
value |= bit << i;
bitOffset += 1;
}
return value >>> 0;
},
};
}
When geometry asks for a byte beyond the decoded payload, nullish fallback supplies zero. The read continues instead of signaling truncated data.
- Lines 1–4
Create a bit-positioned reader.
- Lines 5–11
Read each requested bit; missing source bytes explicitly become zero.
- Lines 12–15
Return the accumulated unsigned value without an end-of-stream check.
NCM4 enforces canonical text, payload size, and CRC32C
JavaScriptchunk.js/ncm/character-codec.jsfunction readEnvelope(code) {
const text = String(code ?? "");
if (!text.startsWith(NCM4_PREFIX)) throw new Error("Expected a canonical NCM4 payload.");
const encoded = text.slice(NCM4_PREFIX.length);
if (!encoded || !/^[A-Za-z0-9_-]+$/.test(encoded) || encoded.length % 4 === 1) {
throw new Error("Invalid canonical NCM4 Base64URL payload.");
}
let raw;
try {
raw = base64UrlDecode(encoded);
} catch {
throw new Error("Invalid canonical NCM4 Base64URL payload.");
}
if (base64UrlEncode(raw) !== encoded) throw new Error("Invalid canonical NCM4 Base64URL payload.");
if (raw.length < 8) throw new Error("NCM4 payload is truncated.");
if (raw.length > NCM4_MAX_PAYLOAD_BYTES) throw new Error("NCM4 payload exceeds the on-chain safety limit.");
const body = raw.subarray(0, raw.length - 4);
const storedChecksum = new DataView(raw.buffer, raw.byteOffset, raw.byteLength).getUint32(raw.length - 4, true);
const actualChecksum = ncm4Crc32c(body);
if (storedChecksum !== actualChecksum) throw new Error("NCM4 CRC32C checksum mismatch.");
return { text, raw, body };
}
These are local codec checks after chain storage. Exact prefix, canonical re-encoding, minimum and maximum bytes, and checksum must all agree.
- Lines 1–7
Require exact NCM4 prefix and canonical Base64URL characters.
- Lines 8–16
Decode, re-encode, and enforce the bounded payload length.
- Lines 17–21
Separate the checksum, recompute CRC32C, and reject a mismatch.
Play chooses chain, local, then default model text
JavaScriptplay/play-profile-session.js function currentAvatarModelCode() {
const chainAppearance = getChainPlayer()?.snapshot?.()?.appearance || null;
return normalizeProfileModelCode(chainAppearance?.modelCode)
|| normalizeProfileModelCode(gameState.playerProfile?.modelCode)
|| defaultModelCode;
}
The first acceptable nonempty code comes from the asynchronously refreshed chain Appearance, followed by local profile text and the configured default route.
- Lines 1–2
Read the current chain Appearance snapshot when available.
- Lines 3–5
Choose chain model text, then local profile text, then default.
IMPLEMENTATION EVIDENCE
Where these claims come from
Each claim is intentionally scoped to a concrete implementation path. These references are for verification, not decoration.
programs/nicechunk_player/src/state.rs
Defines Appearance seed, magic, version, 9,612-byte offsets, field validators, complete packing, empty rich-equipment records, and creation metadata reads.
programs/nicechunk_player/src/lib.rs
Implements tag-8 six-account upsert, Profile and UsernameIndex ordering, rent funding, creation-time preservation, full repack, and treasury-only tag-9 close.
programs/nicechunk_player/src/cluster_config.rs
Pins the configured Player treasury authority used in new Appearance data and close authorization.
src/chain/nicechunkChain.js
Builds the current website's six-account instruction, applies browser field checks, fetches and decodes Appearance, and derives the name index.
sdk/nicechunk-player.ts
Retains the older five-account Appearance builder and a rich-equipment decoder, exposing both integration drift and the difference between a readable schema and an active contract writer.
tests/nicechunk_player_chunk.ts
Asserts the older SDK account shape and contains a hand-authored nonzero equipment decode fixture; it is not proof that the current program writes rich equipment.
player_creat/player_creat.js
Embeds the full standard boy and girl NCM2 codes, submits selected complete text, and writes global plus wallet-scoped browser cache values after the chain helper returns.
login/login.js
Uses a wallet-scoped character shortcut, otherwise reads Appearance and restores chain model text into global and wallet-scoped browser storage.
play/play-profile-session.js
Defines chain-Appearance, local-profile, and default model-code precedence for the local avatar.
play/play-avatar-session.js
Builds and replaces the local mesh, restores Backpack and NCF1 equipment for the established path, keeps a working mesh on failure, and attempts the default when needed.
play/play-chain-player.js
Fetches Profile and Appearance asynchronously and keeps modelKind as decoded state rather than renderer model selection.
play/play-guardian-appearance.js
Implements the separate five-minute wallet cache, bounded mesh cache, Appearance fetch, and default-boy fallback used for remote Guardian avatars.
play/player-motion-controller.js
Supplies normal moving, time, mining progress, and mining aim state without selecting an NCM4 action or clip.
chunk.js/renderer/avatar-mesh.js
Defines the boy-only alias map, NCM2 and NCM4 dispatch, loose NCM2 zero-filled bit reader, heuristic NCM2 rig and tools, and current NCM4 empty equipment and hand-anchor fields.
chunk.js/renderer/avatar-action.js
Resolves and samples explicitly selected NCM4 actions, bone rotations, and visible groups.
chunk.js/ncm/character-codec.js
Defines NCM4's 1,532-byte payload ceiling, twenty bones, bounded geometry and actions, canonical Base64URL envelope, and CRC32C validation.
src/vox/ncm.js
Provides the stricter general source NCM2 decoder whose truncated-bit rejection must not be confused with the avatar renderer's zero-fill behavior.
chunk.js/renderer/webgl2-renderer.js
Shows avatar vertex color and realtime lighting behavior separately from the world's baked terrain texture array.
public/media/vox/chr_peasant_guy_blackhair.ncm
The checked-in 353-character standard boy NCM2 fixture used by current creation and built-in fallback.
public/media/vox/chr_peasant_girl_orangehair.ncm
The checked-in 451-character standard girl NCM2 fixture submitted as full text because no girl alias exists.
chunk.js/tests/ncm-character.test.mjs
Exercises shared character format behavior; its passing scope does not add Player Program validation.
chunk.js/tests/ncm4-avatar-renderer.test.mjs
Exercises NCM4 decode, mesh construction, groups, and explicit action sampling outside the incomplete normal Play mapping.
docs/audits/character-creation-devnet-2026-07-16.json
Retains finalized Devnet rent queries, standard model hashes and dimensions, deployed ProgramData evidence, and source-versus-deployment limitations.
docs/audits/player-profile-devnet-2026-07-16.json
Retains confirmed slot-476,758,441 raw accounts used to re-derive ten Appearance addresses, balances, field distributions, model bytes, titles, and 120 empty rich states.
docs/visual-provenance/player-appearance-codes.json
Records exact imagegen prompts and edit lineage, source and output hashes, processing decisions, and the one-to-one teaching role of all seven visuals.