Commit graph

11 commits

Author SHA1 Message Date
Erik
86c0a7e0ee feat(runtime,net): Campaign OP slice OP1 — full character-option table, dirty model, real 0x01A1 blob builder
The retail Options panel (Campaign OP) needs a Runtime-owned option map
covering all 53 PlayerOption ids and the real batched SetCharacterOptions
(0x01A1) blob before any UI can be built on top of it. Today's surface only
modeled 6 ListenTo*Chat ids and the 0x01A1 builder was a malformed 16-byte
stub (deleted at Campaign CH slice CH3, docs/research/2026-08-09-chat-side-
channels-vs-ace.md).

- CharacterOptionTable.cs: the ONE typed table, PlayerOption id (0x00..0x34)
  -> (Options1/Options2 word, mask, IsAutoSave, ClientDefault), transcribed
  from acclient.h's verbatim CharacterOption/CharacterOptions2/PlayerOption
  enums and byte-verified against IsAutoSaveOption @0x0059A600 (the 21-id
  auto-save table) and GetDefaultOptionValue @0x005D2A30 (the Defaults-
  button table). Reconstructing CharacterOptions1/2 defaults from the
  ClientDefault column independently reproduces 0x50C4A54A / 0x00008700,
  cross-confirming the id-mask mapping. CharacterOptionId (SocialActions.cs)
  widened from 6 to all 53 ids to match.
- RuntimeCharacterOptionsState: SetOptionBit now resolves through the full
  table (was a 6-case switch). New TrySetOption is the ONE shared local-
  write-then-send/dirty seam — mirrors CPlayerModule::OnChanged exactly:
  write the bit locally first, then either send 0x0005 immediately (auto-
  save ids) or MarkDirty for the batched blob, no-op on an unchanged value
  (retail's own early-return) or an unmodeled id. New dirty model (IsDirty/
  FirstDirtiedAt/MarkDirty/TryFlush/TryFlushIfAutoSaveDue) uses an injected
  TimeProvider so it's fully unit-testable without a live clock.
- Both IRuntimeCharacterCommands.SetSingleOption adapters (Direct + Current)
  now route through TrySetOption instead of duplicating the write; this
  fixes the headless local-write gap the OP1 research flagged (the direct
  adapter previously sent the wire message without writing the bit first,
  same class of bug CH4 fixed for the graphical host). Both also reject an
  id outside the table instead of silently accepting it. LiveSessionRuntime
  Factory's SendSingleCharacterOption closure now delegates to the same
  seam instead of duplicating write-then-send inline.
- New IRuntimeCharacterCommands.SaveOptions(generation) — the explicit
  blob-flush verb (retail's SaveToServer(force: 0)) — wired end-to-end in
  both adapters, including a new SaveCharacterOptionsRuntimeCmd on the
  graphical router.
- SocialActions.BuildSetCharacterOptions + WorldSession.SendSetCharacterOptions:
  the real PlayerModule::Pack body per the wire research's field-by-field
  layout — header always 0x460 OR'd with 0x001/0x008 when shortcuts/desired
  comps are non-empty, favorite spells always 8 lists, never sets 0x100 or
  0x200. Echoes last-parsed shortcuts/favorites/desired-comps/spellbook
  filters (via new CharacterOptionsBlobSource) instead of zeroing them.
  Conformance: a hand-computed golden byte vector (not generated by the
  builder under test — the CH3 builder died of tests that pinned a wrong
  shape and looked green) plus a round-trip through PlayerDescriptionParser.

Contract deviation: the 480 s auto-save timer and the flush-before-logout
trigger are implemented as fully-tested pure state-machine logic
(TryFlushIfAutoSaveDue) but are NOT wired into either host's live per-frame
loop or graceful-shutdown sequence in this slice — only the explicit
SaveOptions verb is production-wired. Wiring the timer touches App's
UpdateFrameOrchestrator graph and Headless's tick loop (outside this
slice's Runtime/wire-layer scope); wiring logout risks the already-fragile
graceful-shutdown sequence CLAUDE.md flags. Filed as TS-71 per the plan's
own escape valve ("target: not deferred" with a register row if deferred).
Also filed: AP-193 (the 0x34 HearPKDeathMessages id/mask is ACE-sourced,
unverifiable against the 2013 binary) and AP-194 (GetDefaultOptionValue's
table disagrees with the constructor default for ConfirmVolatileRareUse/
ShowHelm/ShowCloak — retail's own quirk, reproduced not fixed).

Tests: table completeness x53, auto-save/client-default split pinned
id-by-id against the byte-verified tables, unknown/reserved-id rejection
(0x35/0x36 landmines), local-write-then-send on both adapters + the router,
the dirty/flush state machine, SaveOptions, and the wire golden vector +
PlayerDescriptionParser round-trip. Full Release suite: 12,745 passed / 4
skipped / 0 failed (baseline 12,611/4/0 — slice adds 134 passing tests,
zero regressions).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 23:31:34 +02:00
Erik
614a1e055f feat(chat): Campaign CH slice CH3 — side-channel membership, wire, and echo parity
Ports retail's SendTurbineChat (@0x0057db10) local pre-send membership gate
so Roleplay/Society/Olthoi stop silently swallowing outbound chat: a new
TurbineChatMembershipGate checks Turbine availability and the player's own
Hear*Chat option before sending, raising "Turbine chat is not available."
or the 0x0551 YouAreNotListeningTo_Channel refusal through the CH2 AddText
chokepoint instead. Wired into both the graphical (LiveSessionCommandRouter)
and headless (DirectGameRuntimeCommandAdapter) send paths so they can't
diverge. Retracts the 26-day-old false "ACE doesn't run a TurbineChat
server" claim from ISSUES.md, the roadmap, and project_chat_pipeline.md —
ACE's TurbineChat implementation is complete and on by default; the real
bug was treating Hear*Chat as a display filter instead of room membership.

Also: implements SetSingleCharacterOption (0x0005), the only wire message
that actually joins/leaves a Turbine room, and wires the five Settings Chat
toggles to it (publish on Save, changed bits only) plus seeds ChatSettings
from the server's own CharacterOptions2 on every PlayerDescription. Fixes
the legacy-channel double-print (Fellow/Vassals/Patron/Monarch/CoVassals
skip the local echo now that ChatChannelInfo.IsSelfEchoChannel is finally
consulted). Routes /a to Turbine unconditionally (retail's @a never falls
back to the legacy bitflag) and adds /ab for the legacy AllegianceBroadcast
verb retail actually has. Surfaces a nonzero TurbineChat ack HResult instead
of discarding it silently. Deletes the malformed, callerless SetCharacterOptions
(0x01A1) and AddChannel/RemoveChannel (0x0145/0x0146) builders.

Files every AC-specific algorithm change cites the named retail decomp
(SendTurbineChat 0x0057db10, StartupTurbineChatSystem 0x0057EFB0,
GameActionSetSingleCharacterOption) plus ACE/holtburger cross-checks.
Register rows AP-181 (no client-side spam throttle) and UN-9 (an
incidentally-discovered CharacterOptions1.Default literal mismatch, not
investigated further) filed per the divergence-register rule.

11,957 passed / 4 skipped / 0 failed (full Release suite, up from the
11,916/4/0 baseline).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 19:39:44 +02:00
Erik
902076c0a4 refactor(runtime): own world environment state 2026-07-26 16:45:04 +02:00
Erik
20df9d155d refactor(runtime): own combat and magic intent
Move attack build/repeat state, combat-mode policy, authoritative auto-target transitions, and spell-cast intent beneath RuntimeActionState. Keep App as the input, world-query, DAT-policy, transport, and presentation adapter while preserving retail request and busy ordering. Add direct/graphical parity, reset, failure, and instance-isolation coverage.

Co-authored-by: Codex <noreply@openai.com>
2026-07-26 11:56:40 +02:00
Erik
b298f99f91 refactor(runtime): own canonical action state
Move selection, combat, and interaction target mode under one Runtime owner; make plugins, retained UI, session routing, and typed runtime views borrow its exact children; and add failure-safe reset, instance isolation, source ownership, and normalized checkpoint coverage without changing retail ordering.

Co-authored-by: Codex <codex@openai.com>
2026-07-26 10:44:09 +02:00
Erik
dcb61efb5a refactor(runtime): expose canonical gameplay state
Move character options and movement skills into the Runtime-owned character graph, expose borrowed inventory, character, and social views, and route retained UI state commands through generation-gated typed Runtime contracts. Preserve the existing synchronous wire path while deleting the App-owned option and skill mirrors and extending normalized parity checkpoints.

Co-authored-by: Codex <codex@openai.com>
2026-07-26 09:12:30 +02:00
Erik
011efbeaa7 refactor(runtime): own inventory transaction state
Move the retail one-request-at-a-time gate, shared use busy references, external-container state, item mana, shortcuts, and desired-component snapshots into one Runtime-owned graph over J3's exact ClientObjectTable. Retained UI and session routing now borrow that owner; reset and shutdown preserve the existing order while failure/reentrancy tests protect the transaction boundary.

Co-authored-by: Codex <codex@openai.com>
2026-07-26 08:20:32 +02:00
Erik
420e5eea70 refactor(app): key live projections by runtime identity
Move materialized live-object sidecars and presentation worksets to exact RuntimeEntityKey ownership. Runtime remains the only GUID/incarnation/local-ID authority while hydration, animation, effects, lights, equipped children, renderer resources, visibility, liveness, and teardown resolve exact projection identities. Preserve synchronous callbacks, local-ID allocation order, and current rendering behavior.
2026-07-25 21:50:58 +02:00
Erik
bca4148739 test(app): add canonical connected soak snapshots
Make scripted lifecycle checkpoints acknowledged post-diagnostics render barriers, capture the exact frame outcome beside canonical resource ownership, and harden the nine-stop route with ordered same-location cache and lifetime gates without weakening process residency thresholds.

Co-authored-by: Codex <codex@openai.com>
2026-07-22 20:01:06 +02:00
Erik
826f9ea9b5 refactor(app): complete session startup composition
Move the live-session reset and routing graph, combat and diagnostic command targets, and the sole gameplay input subscriber into Phase 7 before frame publication. Add exact retryable ownership for late bindings so partial startup cannot strand session or component teardown edges.

Co-authored-by: Codex <codex@openai.com>
2026-07-22 18:49:31 +02:00
Erik
aa6ffa5176 refactor(app): compose interaction and retained UI startup 2026-07-22 17:20:47 +02:00