ADR-0017 — Player character uses the stock Citizen model, not custom art¶
Status: Accepted Date: 2026-07-31 Supersedes: — Superseded by: —
Context¶
Assets/Prefabs/PlayerPawn.prefab has never carried any mesh or renderer of any kind — every
component on it (CapsuleCollider, CharacterController, PawnComponent,
PlayerMovementComponent, and the various Character* siblings) is physics or game-state,
confirmed by direct read of the prefab JSON and every Code/Characters/ class (no
Model/Skin/Avatar-shaped property exists anywhere in that namespace). A player connecting
today would move and interact correctly but render as nothing.
This sits alongside ADR-0016's item-art sourcing
work, which explicitly scoped Assets/Prefabs/PlayerPawn.prefab out — "no model-path
convention in their C# at all yet ... wiring cloud assets into those is separate design work,
not decided here." This ADR is that separate design work, for the player body specifically.
Checked directly against ~/Projects/sbox-public (per CLAUDE.md's standing instruction):
the engine ships a complete, ready-to-use rigged humanoid, models/citizen/citizen.vmdl,
referenced by name throughout the engine's own components and tests (e.g.
PlayerController.Animation.cs:44, BaseChair.cs:294, and every SkinnedModelRenderer/
ModelPhysicsTests/GameComponentTests integration test that needs some real model).
Alongside it, a full outfit system already exists:
- Sandbox.Dresser (engine/Sandbox.Engine/Scene/Components/Game/Dresser.cs) — a component
that dresses a SkinnedModelRenderer from one of three ClothingSources (OwnerConnection,
LocalUser, Manual), confirmed via its own [Property] ClothingSource Source.
- Sandbox.Citizen.CitizenAnimationHelper
(game/addons/base/code/Components/Citizen/CitizenAnimationHelper.cs) — drives the citizen's
AnimGraph from real, settable members (WithVelocity, WithWishVelocity, IsGrounded,
look-at/IK targets), confirmed by direct read, not guessed.
This is the standard way essentially every s&box gamemode gives its player character a visible, animated body — no bespoke rig, skeleton, or clothing pipeline is needed to get one.
Decision¶
The player pawn will render using the engine's stock
models/citizen/citizen.vmdl, animated byCitizenAnimationHelper, dressed byDresser(Source = OwnerConnection, so a connecting player shows their own Steam avatar outfit) — not a custom-modeled character.
Concretely, PlayerPawn.prefab gained three sibling components on the existing root
GameObject (alongside CapsuleCollider/CharacterController/PawnComponent/etc.):
Sandbox.SkinnedModelRenderer (Model = "models/citizen/citizen.vmdl"),
Sandbox.Citizen.CitizenAnimationHelper (Target pointed at that renderer), and
Sandbox.Dresser (Source = "OwnerConnection"). PlayerMovementComponent now looks up the
animation helper defensively (nullable — presentation, not part of its movement contract) and
feeds it WithWishVelocity/WithVelocity/IsGrounded every fixed tick, so the citizen
actually animates instead of standing in a T-pose. See Code/Movement/PlayerMovementComponent.cs's
own header comment for the exact engine-member citations.
Scope: this decision covers the reference player body only. Per-character customization
(distinct outfits/heads per Applejack character, beyond whatever Dresser.OwnerConnection
already pulls from a player's own Steam inventory) is not decided here — Code/Characters/
still has no Model/Skin-shaped property, and adding one is future work, not blocked by this
ADR.
Alternatives considered¶
Commission/hand-model a custom Applejack-specific character¶
The "real" long-term answer for a shipping game with its own visual identity, but there is no
art pipeline, no artist, and no character-rig/animation convention of any kind in this
codebase yet (confirmed — no .vmdl, no .blend, no rig anywhere in the repo). Blocking a
visible player on that indefinitely means every other system (inventory, needs, economy —
all already wired to PawnComponent) stays invisible to test in practice. The stock Citizen
is free, already rigged and animated, and does not preclude a custom character body later.
Use a Facepunch sboxassets prop as a stand-in body¶
Rejected outright — sbox.game/facepunch/sboxassets (ADR-0016) is scenery/prop content with
no rig, skeleton, or humanoid animation set. It is the wrong tool for a player character
specifically; citizen.vmdl is the engine's own purpose-built answer to this exact problem.
Consequences¶
Positive¶
The player pawn is visible and animated for the first time. Establishes the
SkinnedModelRenderer + CitizenAnimationHelper + Dresser convention any future NPC/vendor
body can reuse rather than reinventing.
Negative¶
Sandbox.SkinnedModelRenderer, Sandbox.Citizen.CitizenAnimationHelper, and Sandbox.Dresser
in the prefab JSON carry the same "FORMAT UNCERTAINTY" caveat every other component in this
file already does — no real S&box editor has opened this project, so the exact __type
strings and the Model-property JSON shape are unverified guesses (informed by direct engine
source read, not blind guessing) pending a real editor session. See
Assets/Prefabs/README.md's FORMAT UNCERTAINTY section for the full list.
Neutral¶
Player-character art is now "started" the same way ADR-0016 called item art "started" — future ADRs adding per-character customization build on this, they don't re-decide it.
Compliance¶
No automated check enforces this — OfflineTests cannot load a real prefab into an S&box
scene from this machine (same limitation Assets/Prefabs/README.md already documents).
Verify by opening the project in a real editor and confirming the pawn renders as a citizen
and animates while walking.
Notes¶
Open question, same shape as ADR-0016's: whether the Model property on
SkinnedModelRenderer serializes in prefab JSON as a plain string path (the guess made here,
by analogy with every other primitive [Property] value already confirmed in this file) or
some richer wrapper. Resolve in the same real-editor session that resolves ADR-0016's own open
question, and correct both mechanically together if wrong.