Commit graph

4 commits

Author SHA1 Message Date
Erik
af381ac6fb feat(net): Phase 4.9 — send ACK_SEQUENCE for every received server packet
Root cause of the still-purple-haze symptom AND the ACE-side
"Network Timeout" drop after ~60s. acdream was never sending
acknowledgement packets back to the server, so the server's
reliability layer saw a one-way stream and eventually dropped the
session. During the 60s window the player rendered to other clients
as the stationary purple loading haze (AC's "this client is in
portal-space transition" indicator).

Pattern ported from
references/holtburger/crates/holtburger-session/src/session/
{send.rs::send_ack, receive.rs::finalize_ordered_server_packet}.
The proper holtburger pattern is per-packet acks, NOT a periodic
heartbeat: every received server packet with sequence > 0 and no
ACK_SEQUENCE flag of its own gets a bare control packet sent back
with:

  PacketHeader {
    Flags    = ACK_SEQUENCE (0x4000),
    Sequence = current_client_sequence (= last issued, no increment),
    Id       = session client id,
  }
  Body = u32 little-endian server sequence being acked

Acks are cleartext control packets (no EncryptedChecksum) and
re-use the most recently issued client sequence rather than
consuming a new one — they aren't part of the reliable stream the
server tracks for retransmits.

Wired into ProcessDatagram so both Tick (post-InWorld) and PumpOnce
(during Connect/EnterWorld) trigger acks on every received non-ack
server packet.

Also (per user request) upgrades the CLAUDE.md description of the
holtburger reference repo from "Rust AC client crate" to "almost-
complete Rust TUI AC client — the most authoritative reference for
client-side behavior in the project, look here FIRST for anything
WorldSession or message-builder related." This was the third time
in two days I would have saved hours by checking holtburger first
instead of guessing at the protocol from ACE alone.

220 tests green.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 23:42:41 +02:00
Erik
50de6b5de4 docs(claude): tighten operating instructions + roadmap discipline + subagent policy
Three additions / changes to CLAUDE.md after a brainstorming session
that produced a new strategic roadmap and Foundation phase spec:

1. "How to operate" rewritten to be more explicit that the agent is
   the lead engineer and should stop only for visual verification.
   Everything else — picking phases, jumping across commit boundaries,
   shipping whole multi-step phases in one session, spawning subagents,
   adding and stripping diagnostic logging — is the agent's call. The
   closing line is "if you catch yourself about to ask 'should I
   continue?', the answer is always yes."

2. New "Subagent policy" section. Default is Sonnet for all execution
   work — implementers, researchers, spec-followers. Opus is reserved
   for load-bearing quality review at phase boundaries. This codifies
   what the memory files already said (feedback_subagent_models.md)
   but is binding in CLAUDE.md so it applies to every new session
   including ones that haven't read memory yet.

3. New "Roadmap discipline" section. Points at
   docs/plans/2026-04-11-roadmap.md as the single source of truth and
   docs/superpowers/specs/*.md as the per-phase detailed specs. Five
   rules: re-read before starting new work, brainstorm when reality
   diverges, update the shipped table when a phase lands, don't invent
   phase numbers mid-session, name the phase in every commit message.
   Directly addresses the "Phase 11 / Phase 9.3 mid-sentence" process
   smell the agent hit in this session.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 21:48:19 +02:00
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
Erik
8b940bd038 docs(claude): project goal + lead-developer operating instructions
CLAUDE.md captures the project goal (modern C# AC client with
first-class plugin support) and sets Claude's operating mode to
"lead developer" — drive phases continuously and only pause for
decisions that genuinely need the user's input. Reduces check-in
overhead on the long tail of phase work.

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