Character creation: UO-style first-login wizard (hair/beard + colours + name) #108
Labels
No labels
alpha:wave-0
alpha:wave-1
alpha:wave-2
alpha:wave-3
area:assets
area:combat
area:ecology
area:infra
area:render
area:scripting
area:ui
area:world
enhancement
epic
migration
post-alpha
roadmap
tech-debt
type:bug
type:chore
type:design
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
marco/IsoMmo#108
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Part of the Alpha epic (pillar 1 — small-world intimacy). Design agreed:
docs/game-design/systems/character-creation.md. This issue is the player-facing design; technical breakdown to follow (separatecritical-design-review).The system, from the player's chair
On first login (an account with no character yet), instead of dropping into the world the player gets a short UO-style creation wizard: pick a hair style and beard style (each with an explicit "None"), pick hair / beard / skin colour from swatch palettes, and type a name with a live availability check. A live front paperdoll preview updates with every choice, and a "Randomize" button fills a complete valid look in one click for players who just want to get in. Confirm → the character is born with that look and enters the world. That look is then what nearby players see on the sprite and paperdoll — the face friends recognize.
Gender is male-only for now (female deferred). One character per account still holds; a returning player skips the wizard.
Six-lens summary
POST /me/character) with appearance fields, and extends the #18 server-authoritative appearance state so others render it. Cosmetic-only → no fairness/economy risk. Persist stable semantic keys, not positional art indices (placeholder UO art will be replaced).CharacterCreationScreen(not bolted ontoLoginScreen— Screen HARD GATE), viewport-derived layout, explicit "None", typed inline errors (too-short / taken / invalid-chars) that preserve appearance picks on retry, and a preview that matches the in-world sprite.charclaim.Scope (what to build)
CreateCharacterRequest+POST /me/characterwith validated appearance choices (hair/beard style id, hair/beard/skin colour id) as semantic keys; server rejects out-of-range.Character(Auth EF migration).ProtocolVersion.Currentin the same change.CharacterCreationScreenwith style selectors, swatch palettes, live paperdoll preview, live name-availability, and a Randomize button; decomposed into focused components.SystemMessageId), no loose literals.Out of scope
Definition of Done (player-observable)
Base DoD applies on top (tests green, whole solution builds, zero warnings, multi-platform preserved).
Technical design (agreed —
critical-design-review)Verdict: go-with-changes. Two architectural forks decided by the owner:
Characterrow, minted into a signedappearanceclaim (at login + create), read by the GameServer on connect, applied toPlayerMobile, surfaced viaToState()/PlayerAppearance. NoPlayerMobileblob version bump — appearance is immutable for now (barber deferred); when an in-game look-change lands it migrates to the blob +AppearanceDirtyreconcile (theIsGhostpattern). Preserves the strict JWT-only Auth↔GameServer contract (GameServer never queries Auth).Color; skin = a tint on the body layer. Colours travel as stable semantic keys; the client maps key →Colorvia a table inClient.Core. One texture set per style (no per-colour variants) → pack stays small, adding/removing a colour is a table edit with zero repack. Extract-time hue baking is the documented upgrade path if full UO fidelity is ever needed.Item), soEquippedItemis unchanged. Hair/beard/skin are their own fields onCharacterAppearanceinsidePlayerAppearance. Skin is a body-layer tint.Invariants Check (walked against CLAUDE.md
## Design checklist)Color. Creation not spammable (one-char/account guard +DbUpdateExceptionbackstop).CommandRegistry/CanExecuteentry).sub/ displayschar.PlayerAppearanceextended withCharacterAppearance= wire change → bumpProtocolVersion.Current8→9 same change; new wire types inShared/Protocol.SystemMessageCatalogin Client.Core or a dedicated home) → possible CLAUDE.md index update.PlayerMobileinside the existingAddPlayer/RestorePlayersingleInvokeAsync; claim read on the connection thread and passed in as data. No new lock.AddPlayer/RestorePlayergain an appearance parameter passed to thePlayerMobilector (trivial state set); no logic inWorld.CharacterCreationScreendecomposed into components (selector/swatch,PaperdollPreview, name/validation); coordinates only; not bolted ontoLoginScreen.PlayerMobile; claim read +PlayerAppearancebroadcast in Networking; meet only inGameSessionHandler/WorldTick.PlayerMobileinstance (likename); no side-collection mirroring it; a future barber = intrinsic setter +AppearanceDirty.PlayerMobileversion bump, no tick file access.Character→ one migration (just migrate-auth AddCharacterAppearance);Database.Migrate()at startup; SQLite-only; neverEnsureCreated.NameMaxLength) = a const declared once in Shared / a field on an options record; noIConfigurationread in a service.PlayerAppearance, already reconciled withinAoiRadius(join on enter, inWelcome/PlayerState), absolute; hair/beard/skin static per session → part of the join snapshot, no new transient event.Color/tint cross-platform; art extraction on the dev machine, output committed; no OS-specific dep._equipArtfilter pattern); skin is aColor(no art dep); repo runnable withoutassets.isoa.architecture.md+ this DoD; nogm-commands.md/spells.mdtouched.No
✗, no HARD GATE tripped. Index-maintenance flag: the String-catalog invariant doesn't cover client-local UI copy (login/wizard over HTTP) — if we formalize a client-UI catalog, update that CLAUDE.md bullet.Implementation breakdown (sequenced)
CharacterAppearancerecord (keys) + valid-key catalog; extendPlayerAppearance; bump ProtocolVersion 8→9; claim encode/decode.Character+ migration; extendCreateCharacterRequest+ key validation; name hardening (max length, whitelist, NFKC + strip zero-width, reserved words); mintappearanceclaim (login + create); name-availability endpoint.GameSessionHandler; thread intoAddPlayer/RestorePlayer; set onPlayerMobile;ToState()includes it.hair/<style>/…beard/<style>/…+ gumps; rebuildassets.isoa; verify grey-ramp assumption.AssetPackLoaderbuilds hair/beard sets;Playerdraws hair/beard layers with ownColor; skin = body tint; key→Color table in Client.Core;PaperdollGumpper-layer tint + hair/beard gumps.CharacterCreationScreen— dedicated screen + components; live preview; swatches; Randomize; live name-check; typed errors preserving picks; screen-flow wiring.Character appearance: creation screen (gender, skin hue, hair) + appearance modelto Character creation: UO-style first-login wizard (hair/beard + colours + name)Delivered in #134 (merged to
main) — closing (the PR did not auto-close it).UO-style first-login wizard: hair/beard style + hair/beard/skin colours + name with live availability, live front-paperdoll preview, Randomize. Appearance is GameServer-owned game state (
SetAppearanceintent); Auth stays identity-only.