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

4.4 KiB

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 2568") 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.