Kzeroko KTM2

Humanoid architecture and extension points

Profile ownership, authoritative mutation, shared presentation and safe extension of the humanoid system.

Updated Sep 26, 2026 6 min read

Players and NPCs share the humanoid appearance model and rendering pipeline. This reference identifies the internal owners of that state and the entry points for changing it.

What it does

The humanoid system gives players and NPCs the same appearance model: identity, body build, face shape, palette, selected parts and an optional origin ID. HumanoidProfile is a versioned value object; the current version is 1. JSON authoring belongs in preset data and part authoring.

The Java types described here live in internal.humanoid or client implementation packages. They are internal integration points, not a HumanoidAPI facade.

Runtime model

Owner / stageResponsibility
HumanoidAppearanceComponentPlayer profile, creation stage, saved profiles and revision; component ID isekaiexpansion:humanoid_appearance. Registered with ALWAYS_COPY for respawn.
HumanoidSubjectNonplayer contract: get/set profile and expose a revision.
BaseNpcEntityBase character is stored separately from its outfit-composed presentation.
HumanoidAppearanceAccessUniform player/subject reads and internal direct assignment.
HumanoidAppearanceServiceServer player sanitisation, origin resolution, unlock/paint validation and creation-stage commits.
HumanoidPartCatalogRegistryServer-authored part definitions, compatibility sanitisation and catalogue revision.
HumanoidPartAssetRegistryClient resource discovery; distinct from server catalogue membership.
HumanoidRenderResolverResolves body, visible layers, styles, tint/paint textures and armour/trait hiding into a snapshot.
HumanoidProfileCodec / HumanoidProfileTrackerShared compact wire format for component and tracked-entity presentation.

A player’s live profile, revision and creation stage synchronise to recipients; personal saved slots are included only for the owner. Component setters skip equal replacements, recalculate dimensions when needed and bump revision on accepted changes. Revision is a cache invalidation counter, not an authorisation token.

NPC presets resolve an authored or generated base profile; the selected outfit overlays it for display. Read getBaseHumanoidProfile() when editing/exporting the character and getHumanoidProfile() when rendering. Citizen generation and NPC preset hooks are covered in NPC assets.

Client creation editing keeps a detached working profile and up to 48 undo entries. It does not edit the live player component directly. Submission crosses a server scope merge followed by sanitisation; screen visibility alone is not the authority boundary.

Contracts

Read through the common access point

HumanoidAppearanceAccess.profileOrNull(entity) returns no profile for unsupported entities; profileOf(entity) returns HumanoidProfile.DEFAULT instead. revisionOf returns 0 for unsupported entities. Player lookup takes precedence over the nonplayer subject interface.

displayName returns an enabled character name: translated text for a selected name preset or literal text for a typed nickname. Otherwise it returns the entity’s own name. It does not rename the entity itself. buildOf reads the same profile path.

applyProfile directly calls the player’s component setter or the subject setter. It does not perform server player sanitisation. For untrusted player submissions, use the scoped packet/service path. Direct component setters are appropriate only when the caller deliberately owns validation.

Validate on the server

sanitizeForPlayer resolves the actual Origins origin, sanitises catalogue compatibility, removes locked parts, removes unknown paint references, then fills declared starting parts while character creation is incomplete. An empty catalogue is treated as unavailable data and preserves existing parts.

Catalogue sanitisation checks slot, gender and origin compatibility, repairs styles, fills mandatory body/origin traits, then removes unmet companion requirements until stable. These checks do not prove client models or textures exist. Unlock checks and resource resolution remain separate stages.

Creation-stage changes normally advance monotonically; explicit reset is separate. HumanoidCreationFlow.owedScope decides the owed step, and OpenHumanoidCreationPacket.send grants a scoped session before asking the client to display it. The exact scope matrix and current request-handler limitations belong in the runtime reference.

Give body size one owner

HumanoidBodyScale.of(player) uses height / 1.8 only when a player component has a body reference and no active morph entity. Otherwise it returns 1. Player dimensions, eye height and the outer player render scale use that policy.

The player feature marks HumanoidRigRenderState.setBodyScaleApplied(true); the rig renderer then skips applying body scale again. Detached/NPC rendering has its own presentation context. Do not multiply both the outer player transform and the rig by the player’s height ratio.

Client body rendering additionally requires the shared rig resource and yields to an active morph presentation. Server size policy does not depend on client resource availability. A missing resource can therefore affect rendering without removing the server’s body reference.

Extending it

Add a channel, slot or chapter

ChangeRequired audit
Colour channelAdd ID/default to HumanoidColorChannel, expose it in CreationChapter, add translations and review catalogue/plugin channel bindings.
Part slotAdd ID, kind, render order and armour defaults to HumanoidSlot; expose its chapter, update showsOnArm and trait mapping where relevant, and update the plugin’s explicit slot list.
Screen chapterDefine slots, palette channels and camera focus in CreationChapter; include it in allowed scopes and add custom controls where the generic slot builder is insufficient.
Nonplayer subjectImplement HumanoidSubject, own persistence/synchronisation, and bump the reported revision when presentation changes.
NPC-specific generationOverride resolveInitialHumanoidProfile; use getNpcPresetId to share a preset across entity types.

Palette NBT stores an array by channel ordinal. The wire format uses ordinal bits for palette channels and ordinals for slots. Preserve existing enum order and audit compatibility when extending it; render ordering is the slot’s explicit numeric order, not its ordinal. The palette wire encoding uses an integer mask, so adding channels also requires reviewing mask capacity.

The Blockbench plugin contains explicit slot/channel tables; adding an enum constant is not sufficient to update the whole authoring pipeline. Keep the Java declarations, screen exposure, exporter, language entries and data definitions aligned.

Add display strings through language_assemble. Chapter keys use screen.isekaiexpansion.creation.chapter.<id> and .hint; part/channel/slot keys follow the authoring reference and existing language category.

Integrate a screen into creation

Use ClientHumanoidScreenSequencer.hold(screen, gate) for screens that must wait until AFTER_CHARACTER_BASE or AFTER_CHARACTER_COMPLETE. If it returns false, the gate is already open and the caller may show the screen. A new held screen replaces the previous held screen for the same gate.

Server grants are queued, and the client releases held screens and grants when no screen is active. Keep new stage decisions in HumanoidCreationFlow; adding a UI chapter does not by itself grant mutation authority or change stage progression.

Preserve render ownership

The body supplies shared bone poses; part-specific animation is evaluated before matching body poses overwrite shared names. Shape/face changes belong in HumanoidBoneShaper, texture composition in HumanoidTextureComposer, and asset discovery in its reload listener.

The render snapshot cache includes profile/revision, catalogue and asset revisions, paint revision, armour/trait masks and composed-texture generation. A new visual input needs a corresponding invalidation input. Do not keep a parallel cache that survives catalogue or resource reload without versioning it.

Verifying

  • Compare the same profile on its player owner, another client, an NPC and a detached preview.
  • Submit fields outside each granted scope and compare the stored profile, not only the screen preview.
  • Confirm respawn retains profile/stage/saved slots and only the owner receives saved profiles.
  • Change height, body presence and morph state; check hitbox, eye height, held items and rendered scale.
  • Reload catalogue and client assets independently; check saved profiles and render-cache invalidation.
  • For a new slot/channel, round-trip NBT and network state, check enum compatibility, then export it through the plugin.
  • Test an interrupted first-join sequence and a held screen without replacing an active screen.

Names and culture selection are documented in Naming API.