acdream/CLAUDE.md
Erik 733f8ff601 feat(net+app): SubPalette overlays applied to palette-indexed textures (Phase 5b)
Implements the other half of ObjDesc: SubPalettes (palette-range
overlays) that repaint palette-indexed textures with per-entity color
schemes. Ported algorithm from ACViewer Render/TextureCache.IndexToColor
after the user pointed out I was prematurely implementing from scratch
instead of checking all the reference repos.

The Nullified Statue of a Drudge sends (setup=0x020007DD with a drudge
GfxObj animPart replacing part 1, plus 2 texChanges targeted at part 1,
plus 1 subpalette id=0x04001351 offset=0 length=0). The TextureChanges
swap fine detail surfaces; the SubPalette with length=0 ("entire palette"
per Chorizite docs) remaps the drudge's flesh-tone palette to stone.
Without this commit, the statue looked like a normal flesh drudge
because palette-indexed textures decoded with the base flesh palette.

Added:
  - Core/World/PaletteOverride.cs: per-entity record carrying
    BasePaletteId + a list of (SubPaletteId, Offset, Length) range
    overlays. Documents the "offset/length are wire-scaled by 8"
    convention and the "length=0 means whole palette" sentinel.
  - WorldEntity.PaletteOverride nullable field. Per-entity (same across
    all parts), in contrast to MeshRef.SurfaceOverrides which is per-part.
  - TextureCache.GetOrUploadWithPaletteOverride: new entry point that
    composes the effective palette at decode time. Composite cache key
    is (surfaceId, origTexOverride, paletteHash) so entities with
    equivalent palette setups share the GL texture.
  - ComposePalette: ports ACViewer's IndexToColor overlay loop:
      for each subpalette sp:
          startIdx = sp.Offset * 8             // multiply back from wire
          count = sp.Length == 0 ? 2048 : sp.Length * 8   // sentinel
          for j in [0, count):
              composed[j + startIdx] = subPal.Colors[j + startIdx]
    Critical detail: copies from the SAME offset in the sub palette, not
    from [0]. Both base and sub are treated as full palettes sharing an
    index space.
  - StaticMeshRenderer.Draw: three-way switch on (entity.PaletteOverride,
    meshRef.SurfaceOverrides) picks the right TextureCache path:
      - Both → palette override (it handles origTex override internally)
      - Only tex override → GetOrUploadWithOrigTextureOverride
      - Neither → plain GetOrUpload
  - GameWindow.OnLiveEntitySpawned: builds PaletteOverride from
    spawn.BasePaletteId + spawn.SubPalettes when the server sent any.

Reference note: the user asked "but I mean THIS MUST BE IN WORLDBUILDER"
which was the right push. WorldBuilder is actually a dat VIEWER and its
ClothingTableBrowserViewModel is a 10-line stub — it doesn't apply
palette overlays because it doesn't need to. The actual algorithm lives
in ACViewer (a MonoGame character viewer), which I should have checked
earlier. CLAUDE.md updated with a standing rule: always cross-reference
all four of references/ACE, ACViewer, WorldBuilder, Chorizite.ACProtocol,
plus holtburger. A single reference can be misleading; the intersection
is usually the truth.

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

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 16:30:08 +02:00

88 lines
4.4 KiB
Markdown

# acdream — project instructions for Claude
## Goal
Build **acdream**, a modern open-source C# .NET 10 Asheron's Call client. The
end state is a working client that:
- Loads the retail AC dat files and renders the world (terrain, static meshes,
dynamic entities, characters)
- Connects to an ACE server and plays as a character
- Exposes a **first-class plugin API** so players can write native scripts and
macros to automate gameplay — this is a core architectural requirement, not
a bolt-on
The codebase is organized by phase. Current phase state lives in memory
(`memory/project_phase_*_state.md`), current phase plans live in `docs/plans/`,
and the long-term vision lives in `memory/project_acdream.md`.
## How to operate
**Function as the lead developer on this project.** Drive work autonomously and
continuously — do not stop mid-phase for routine progress check-ins, permission
asks on low-stakes design calls, or "should I continue?" confirmations. Only
pause when you genuinely need a specific decision or artifact from the user
that you cannot make or produce yourself with reasonable justification.
Things to still stop and ask for:
- Architectural direction where multiple defensible paths exist and the
tradeoffs depend on product intent the user hasn't expressed
- Visual iteration where "does this look right?" is the actual acceptance test
- Destructive or hard-to-reverse actions outside the normal commit workflow
- When memory or commit history clearly indicates the user has a preference
you should ask about before diverging from
Things you should just do without asking:
- Continue to the next planned sub-step of a phase after the previous one
lands clean
- Pick between two roughly equivalent implementations; justify the choice in
the commit message
- Refactor small amounts of surrounding code when genuinely needed to land a
change cleanly (but not "while I'm here" scope creep)
- Run the test suite, build the project, commit to main with co-author
attribution — the project's established workflow is direct-to-main and the
user has repeatedly authorized it
Before claiming a phase or sub-step is done: run `dotnet build` and
`dotnet test` green, commit with a message that explains the "why", update
memory if there's a durable lesson, and move to the next todo item.
## Reference repos: check ALL FOUR, not just one
When researching a protocol detail, dat format, rendering algorithm, or
any "how does AC do X" question, **check all four of the vendored
references in `references/`** before committing to an approach. Do not
settle on the first hit and move on — cross-reference at least two of
these, ideally all four:
- **`references/ACE/`** — ACEmulator server. Authority on the wire
protocol (packet framing, ISAAC, game message opcodes, serialization
order). The things a server has to know to parse and produce bytes.
- **`references/ACViewer/`** — MonoGame-based dat viewer that actually
renders characters + world. Authority on the client-side visual
pipeline: ObjDesc application, palette overlays, texture decoding
for the palette-indexed formats. See
`ACViewer/Render/TextureCache.cs::IndexToColor` for the canonical
subpalette overlay algorithm.
- **`references/WorldBuilder/`** — C# + Silk.NET dat editor. Exact-stack
match to acdream for rendering approaches: terrain blending, texture
atlases, shader patterns. Most useful for "how do I do this GL thing
with Silk.NET on net10 idiomatically?" Less useful for protocol or
character appearance (dat editor, not game client).
- **`references/Chorizite.ACProtocol/`** — clean-room C# protocol
library generated from a protocol XML description. Useful sanity check
on field order, packed-dword conventions, type-prefix handling. The
generated Types/*.cs files have accurate field comments (e.g. "If
it is 0, it defaults to 256*8") that ACE's server-side code doesn't.
- **`references/holtburger/`** — Rust AC client crate. Cross-references
handshake quirks, race delays, and per-message encoding decisions
that ACE doesn't document because it's server-side.
Pattern: when you encounter an unknown behavior, grep all four for the
relevant term, read each hit, and compose a multi-source understanding
BEFORE writing acdream code. A single reference can be misleading; the
intersection of all four is almost always the truth. The user has
repeatedly had to remind me about this when I narrowly searched one ref
and missed obvious answers in another.