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>
88 lines
4.4 KiB
Markdown
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.
|