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>
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. SeeACViewer/Render/TextureCache.cs::IndexToColorfor 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.