Commit graph

2 commits

Author SHA1 Message Date
Erik
b69d776179 feat(net+app): TextureChanges applied via Surface→OrigTex resolution (Phase 5a)
Finishes the TextureChange half of ObjDesc. Characters' clothing now
renders with correct per-part textures (user-verified "looks good"
after previous "partial coverage" / "wrong clothes"). The Nullified
Statue still looks like a flesh-colored drudge because the statue's
color comes from SubPalettes (palette-indexed texture recoloring),
which is the remaining major Phase 5 piece.

The first attempt at TextureChange application was silently broken by
an ID-type mismatch: the server encodes OldTexture/NewTexture as
SurfaceTexture (0x05XXXXXX) ids, but my sub-meshes are keyed by
Surface (0x08XXXXXX) ids. The override dict was keyed by one type
and looked up by the other, so TryGetValue never hit and no override
actually applied.

Diagnosed via Phase 1 systematic debugging with resolve-level logging:

  live: spawn +Acdream texChanges=20
  live:   texChange part=0 old=0x05000BB0 new=0x0500025D
  ...
  live:   resolve part=0 surface=0x08000519 origTex=0x05000BB0 [MATCH]
  live:   resolve part=0 surface=0x0800051C origTex=0x05000CBE [MATCH]
  ... 10/10 lines [MATCH]

The [MATCH] lines proved the server's OldTexture IS reachable via a
Surface→OrigTextureId lookup, just needed keying by the right value.

Fix:
  - TextureCache.GetOrUploadWithOrigTextureOverride(surfaceId, origTexOverride):
    loads the base Surface dat for its color/flags/palette, but
    substitutes the override SurfaceTexture id in the decode chain.
    Caches under a (surfaceId, origTexOverride) composite key.
  - MeshRef.SurfaceOverrides is now Dictionary<uint, uint> keyed by
    Surface id, value = replacement OrigTextureId. Null means no
    overrides.
  - GameWindow.OnLiveEntitySpawned now does TWO passes when texture
    changes are present:
      1. Group the raw server changes by PartIndex into (oldOrigTex →
         newOrigTex) dicts
      2. For each affected part's post-animPartChange GfxObj, iterate
         its Surfaces list, resolve each Surface → OrigTextureId, and
         if that matches a raw change's oldOrigTex, write an entry
         Surface id → newOrigTex into the final override map
  - StaticMeshRenderer.Draw: when sub-mesh surface id has an override,
    call GetOrUploadWithOrigTextureOverride instead of GetOrUpload.

Verified live: +Acdream's clothing renders correctly, NPCs are
"much better" (characters previously naked are now dressed). Statue
has the full mechanical pipeline working (resolve diagnostic shows
2/2 Surfaces [MATCH] for the statue's override dict) but its visible
color comes from the separate SubPalette overlay that isn't wired yet.

Also added a statue-targeted diagnostic block that dumps its full
ObjDesc contents (texChanges + subPalettes + animPartChanges) by
name match, which is how I traced the Nullified Statue of a Drudge's
specific ObjDesc. Lives under `if (isStatue && ...)` so normal logins
aren't spammed.

Cross-referenced against two new references this session:
  * references/Chorizite.ACProtocol (cloned from github.com/Chorizite/
    Chorizite.ACProtocol.git on user's suggestion) — confirms the
    ObjDesc field order and PackedDword-of-known-type convention.
  * references/WorldBuilder/... (already in repo) — confirms the
    Surface→OrigTexture→SurfaceTexture→RenderSurface chain and the
    P8/INDEX16 palette decode path.

Tests: 77 core + 83 net = 160, all green.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 16:22:23 +02:00
Erik
473a06c534 feat(core): add LandblockLoader with Stab+Building → WorldEntity mapping 2026-04-10 17:58:30 +02:00