Skip to content

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 by CitizenAnimationHelper, dressed by Dresser (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.