Commit graph

3784 commits

Author SHA1 Message Date
Erik
df824f366c docs(launcher): close Campaign LA6 LA8 and LA9 2026-08-14 21:18:05 +02:00
Erik
fe63ce186a merge: Campaign LA LA8 - retail character screen review-closed 2026-08-14 21:16:25 +02:00
Erik
1dd5706e15 Make character UI retries transactional 2026-08-14 21:14:08 +02:00
Erik
2198a0cc8e merge: Campaign LA LA9 - verified installer review-closed 2026-08-14 21:06:26 +02:00
Erik
208a70ac83 fix(launcher): guard orphan bake publication 2026-08-14 21:02:27 +02:00
Erik
aeac874dab Harden retail character selection recovery 2026-08-14 20:59:10 +02:00
Erik
2bb8ccb6b6 merge: Campaign LA LA6 - login commands review-closed 2026-08-14 20:48:42 +02:00
Erik
259f0e5ac3 fix(headless): route wire-only chat commands 2026-08-14 20:47:06 +02:00
Erik
3f68895120 fix(launcher): harden installer transactions 2026-08-14 20:36:11 +02:00
Erik
6cfab727f1 Implement retail character management screen 2026-08-14 20:29:19 +02:00
Erik
41b15efd4d feat(runtime): share chat commands and run login sequence 2026-08-14 20:27:45 +02:00
Erik
ff6ebb6a6a feat(launcher): add verified first-run installer 2026-08-14 20:06:37 +02:00
Erik
267804465e docs(launcher): close Campaign LA4 LA5 and LA7 2026-08-14 19:36:00 +02:00
Erik
5535d0adac merge: Campaign LA LA5 - plugin hosting review-closed 2026-08-14 19:33:27 +02:00
Erik
f820eb258d fix(plugins): close LA5 ownership races 2026-08-14 19:28:14 +02:00
Erik
60f627998c merge: Campaign LA LA4 - Avalonia launcher review-closed 2026-08-14 19:14:25 +02:00
Erik
ae2cbbee8c fix(launcher): require executable Linux hosts 2026-08-14 19:12:32 +02:00
Erik
fbe9c8a288 fix(plugins): close LA5 host lifecycle review 2026-08-14 19:05:13 +02:00
Erik
7691cf75e2 merge: Campaign LA LA7b - selection state and flow review-closed 2026-08-14 19:05:03 +02:00
Erik
ff40656293 fix(runtime): isolate Campaign LA7b delete state 2026-08-14 19:03:43 +02:00
Erik
10a712d66b fix(launcher): close LA4 review findings 2026-08-14 19:02:20 +02:00
Erik
1b9e7e41f9 fix(runtime): close Campaign LA7b review findings 2026-08-14 18:55:48 +02:00
Erik
0e82cbf700 feat(runtime): own character selection flow 2026-08-14 18:22:17 +02:00
Erik
d0a9c65d85 feat(launcher): Campaign LA add Avalonia desktop shell 2026-08-14 18:15:14 +02:00
Erik
95f4be94db feat(plugins): complete Campaign LA5 cross-host hosting 2026-08-14 18:12:59 +02:00
Erik
6c4cd2bbc6 docs(launcher): close Campaign LA2 integration 2026-08-14 17:31:33 +02:00
Erik
e01b2cd12f merge: Campaign LA LA2 - probe and idle review-closed
# Conflicts:
#	docs/plans/2026-08-14-launcher-campaign.md
#	src/AcDream.Headless/Hosting/HeadlessSessionHost.cs
2026-08-14 17:30:07 +02:00
Erik
1c5e66c05b fix(launcher): Campaign LA close LA2 review findings 2026-08-14 17:20:02 +02:00
Erik
3313577dcc docs(launcher): Campaign LA close LA1 and LA3 2026-08-14 17:17:56 +02:00
Erik
8a03a25fc3 test(launcher): Campaign LA enforce composer-host contract 2026-08-14 17:16:13 +02:00
Erik
7749545dc4 merge: Campaign LA LA3 - Launcher.Core review-closed 2026-08-14 17:11:25 +02:00
Erik
347a1a5d16 fix(launcher): Campaign LA LA3 narrow review fixes 2026-08-14 17:06:47 +02:00
Erik
890cf267fc docs(launcher): record Campaign LA LA1 fix-round gates 2026-08-14 17:00:46 +02:00
Erik
d511e4c348 fix(launcher): close Campaign LA LA1 review findings 2026-08-14 17:00:09 +02:00
Erik
000ea979d5 test: Campaign LA finish LA2 probe and idle gates
Prove idle play remains passive and live until cancellation, then converges through one truthful status teardown. Keep probe mode string-only so numeric enum aliases cannot expand the pinned v1 contract, and record the Windows/WSL gates.
2026-08-14 16:49:51 +02:00
Erik
4edc122085 docs: Campaign LA handoff — worktree paths, stopped-agent recovery, kickoff prompt
Both remaining agents were stopped for token budget and their partial work
committed as WIP (75a6724d LA1 fix round, c6019424 LA2). The handoff now
carries: full worktree paths with branches and HEADs, exactly what each
stopped agent had finished versus what it still owes, and a paste-ready
kickoff prompt naming all three resumable items plus the two owed merge
items.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 16:34:06 +02:00
Erik
c601942467 wip: Campaign LA LA2 probe mode + idle policy — INCOMPLETE, stopped mid-task
Agent was stopped for token budget. Landed here: probe flag through
LiveSessionConnectOptions + the StartCore short-circuit, the mode field
with JsonRequired-to-semantic-validation move, host exit-code mapping,
and 34 passing tests including 3 new probe tests (agent last reported
green before the stop). NOT DONE: the idle-policy unit tests (next
step), full-suite verification, and the WSL run.

Build/test state UNVERIFIED at this commit. Next session: finish idle
policy tests, run Runtime+Headless Release suites Windows and WSL, then
dispatch the Opus dual-lens review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 16:33:00 +02:00
Erik
75a6724d5b wip: Campaign LA LA1 fix round — INCOMPLETE, stopped mid-task
Agent was stopped for token budget partway through the LA1 review fix
round. Landed here: F1 best-effort SessionStatusWriter, F2 App reader
tolerance (paths/mode), F5 argument-parsing hardening, plus new tests.
NOT DONE: F4 shared-fixture production shape (was the next step), F3
reconnect disconnected edge + recorded limitation, F6 exited
idempotency/reasons, F7 structural redaction test, F8 platform-guard
test + comment fix, optional RuntimeOptions PrintMembers redaction.

Build/test state UNVERIFIED at this commit. Next session: finish the
remaining findings, run the suites, then narrow re-review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 16:32:52 +02:00
Erik
c9fc7f4a66 docs: Campaign LA session handoff — state, in-flight recovery, process, goal
Self-contained handoff for a fresh session: what the campaign is and which
decisions are settled, the slice ledger with commits, the in-flight slices
and how to recover them from git, the two owed merge items (cross-assembly
contract test, Launcher.Core CI lane), the session landmines (index-sweep,
stale agent worktrees, contract-in-prompts), the binding process, and the
goal text to set.

Committed via pathspec so a live implementer agent index in this worktree
is untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 16:30:38 +02:00
Erik
26feba8186 fix(launcher): Campaign LA LA3 review fixes — contract paths omission, probe composition, graceful stop, hygiene
Opus review of LA3 returned FIX FIRST; this addresses every finding in
scope (F1-F5, F7-F12; F6 CI-lane addition excluded per instructions):

- F1 (CRITICAL): SessionProcessSettings.Paths is now nullable and left
  null by SessionConfigComposer unless a caller supplies overrides, so
  the JSON key is entirely absent instead of "paths":{} — the App-side
  loader's strict UnmappedMemberHandling.Disallow would otherwise reject
  every gui/guiSelect session-config document at load.
- F2: added SessionConfigComposer.ComposeProbe and a nullable
  SessionDescriptor.Mode field ("probe", omitted for normal play) per
  the pinned contract — no character/policy/plugins/loginCommands.
- F3: LauncherProcessSupervisor.Stop now tries
  ILauncherChildProcess.TryRequestGracefulStop (Linux: libc SIGINT via
  LibraryImport, K4-proven graceful headless logout) before
  CloseMainWindow. Windows has no reliable no-window-console equivalent
  today; filed docs/ISSUES.md #397 with the CREATE_NEW_PROCESS_GROUP +
  CTRL_BREAK fix direction. Stop()'s blocking-timeout contract is now
  documented for LA4.
- F4: LauncherProfileStore.Save chmods the Linux temp file to 0600
  immediately after creation, before any credential is serialized;
  failure paths and Load() clean up a stale .tmp.
- F5: added LauncherCoreDependencyBoundaryTests asserting Launcher.Core
  references exactly AcDream.Platform and no packages.
- F7: StatusEventParser.Parse no longer throws on a whitespace/null
  line; StatusFileTailer.ReadNewEvents swallows the File.Exists/open
  TOCTOU window (FileNotFoundException/DirectoryNotFoundException/
  IOException) instead of throwing.
- F8: Start() now kills (entire process tree) and disposes a child that
  started successfully but failed while being fed its stdin password,
  instead of orphaning it.
- F9: SetState is monotonic — once Exited, no later transition applies
  or fires StateChanged, closing a Start()-path race where a
  synchronously-exiting child could be "resurrected" to Running.
- F10: CharacterIdFormat.TryParse now requires the "0x" prefix (an
  unprefixed hand-typed decimal id is also valid hex and was silently
  misread); a parsed id of 0 is treated as unusable and falls back to
  the name selector; LauncherProfileStore.MergeRoster normalizes both
  sides through TryParse/ToHexString instead of raw string equality, so
  a legacy unprefixed-hex row self-heals via name match instead of
  duplicating.
- F11: StatusCharacterEntry.SecondsGreyedOut is now uint, matching
  CharacterRosterEntry and the host writer.
- F12: added MalformedStatusEvent, returned for a recognized `e` whose
  payload doesn't match its shape, distinguished from UnknownStatusEvent
  (an unrecognized `e`).

AllowUnsafeBlocks was added to AcDream.Launcher.Core.csproj — required
by the LibraryImport source generator's function-pointer marshalling
stub for F3's Linux SIGINT P/Invoke.

Verification: dotnet build AcDream.slnx -c Release green (0 errors);
dotnet test tests/AcDream.Launcher.Core.Tests -c Release green at 94/94
on native Windows and under WSL (Ubuntu, verified across multiple runs
for the timing-sensitive SIGINT/sharing-violation tests, no flakes
observed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 16:29:39 +02:00
Erik
498f1c1182 docs: Campaign LA ledger — LA7a DONE+merged (fa2de1c4); LA1 implemented, review dispatching
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 16:08:45 +02:00
Erik
fa2de1c46e merge: Campaign LA LA7a — character wire messages (review-closed)
CharacterDelete 0xF655 (account+slot), CharacterRestore 0xF7D9/0xF643
(guid-only adaptation, register AD-97), CharacterError 0xF659 (retail
26-member enum). Opus retail-lens review PASS, narrow re-review MERGE:
6a32f375 + 4338b1c1 + 0c8643a7.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 16:07:58 +02:00
Erik
0c8643a7f2 docs: AD-97 wording nit from LA7a narrow re-review — correct artifact-appearance count
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 16:05:51 +02:00
Erik
e1322a06ae note: db9ad53c is a MIXED commit — docs + LA1 in-progress files
The running LA1 implementer had pre-staged its work-in-progress; git
commit takes the whole index regardless of what git add named, so the
docs commit swept in 37 LA1 files mid-implementation. Content is intact;
LA1 completion commit(s) follow with the remainder, and the LA1 slice
review covers the combined range. Process rule adopted: no orchestrator
commits in a worktree while an implementer agent is live in it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 16:05:17 +02:00
Erik
db9ad53c1c docs: Campaign LA — pinned launch-contract schema COMMITTED into plan LA1
The LA3 Opus review process note was right: the contract both sides
implement lived only in orchestrator prompts, which is exactly the drift
mode the pin exists to prevent (and it produced the paths-key CRITICAL).
The schema, field rules, probe-mode discriminator, and status vocabulary
are now a binding plan section; amendments change this text first,
implementations second. Ledger: LA3 fix round dispatched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 16:04:32 +02:00
Erik
0bcc7ba3a3 docs: Campaign LA — spec §11.4 corrected per LA7a review; LA7b hazards recorded
The spec seeded the wrong claim (restore extra strings = decompiler
artifact, no register row needed); the LA7a Opus review decoded the
PDB-paired binary and showed the two constant-string arguments are real,
making our guid-only request an adaptation — AD-97 filed on the LA7a
branch. Plan LA7 now carries the review-surfaced LA7b hazards (ACE
silent no-reply restore path, SendToLogon/SendToControl routing,
NumErrors sentinel).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 16:02:20 +02:00
Erik
4338b1c1f3 fix(net): Campaign LA LA7a review fixes — AD-97 register row, corrected restore justification
The Opus retail-lens review decoded the PDB-paired binary at
CPlayerSystem::RestoreCharacter@0x0055d760 and refuted the
uninitialized-edx justification: the two extra arguments are real
push imm32 of a constant PStringBase (BN mis-renders them, but they
pack to >=4 bytes each), so retail 0xF7D9 is >=16 bytes where ours
is 8. The guid-only CODE stands (ACE reads only the guid; holtburger
consensus) but it is an adaptation, not a corrected decompile — filed
as divergence register AD-97 and the doc comment now states the true
mechanism.

Also from the review: the 0xF643 conditional-parse doc now names BOTH
ACE flag-only failure branches (NameInUse + Corrupt); CharacterError
0x08 doc corrected (ACE misnames it ServerCrash2 — the port corrects
an ACE misnaming; ACE omits three values, not four); LA7b hazard notes
added (ACE silent no-reply on unknown restore guid; retail SendToLogon
vs SendToControl routing; NumErrors never rendered); two review-nit
tests (flag=0 Undef flag-only, non-Ok body with trailing bytes
ignored).

Core.Net suite: 953 passed / 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 16:01:52 +02:00
Erik
205b09cf32 docs: Campaign LA ledger — LA3 implemented (37d74e44), LA7a implemented (6a32f375), both reviews in flight
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 15:50:41 +02:00
Erik
37d74e4402 feat(launcher): Campaign LA LA3 — AcDream.Launcher.Core profile store, composer, supervisor, status tailer
New AcDream.Launcher.Core (BCL-only, ProjectReference: AcDream.Platform
ONLY) plus tests/AcDream.Launcher.Core.Tests, both registered in
AcDream.slnx. This is the file-contract orchestrator core the Avalonia
launcher (LA4) will bind to — the game solution (Core/Runtime/App/
Headless) stays entirely out of this dependency graph, so the launcher
can never accidentally grow a game-protocol coupling.

- Profiles/: LauncherProfileStore owns launcher-profiles.json (spec §5
  schema: version 1, servers[]/accounts[]/characters[]), strict
  camelCase System.Text.Json (UnmappedMemberHandling.Disallow), typed
  CRUD (add/edit/remove server; add/edit/remove account; edit character
  settings), and MergeRoster (fold a reported roster into an account's
  characters[] while preserving user-owned launchMode/plugins/
  loginCommands, adding new rows with default guiSelect, and retaining
  rows absent from the roster — they may be pending-delete). 0600 on
  Linux via File.SetUnixFileMode after save.
- Launching/: SessionConfigComposer builds the pinned session-config
  contract (Headless K1 shape + plugins/loginCommands/
  loginCommandDelayMs/statusFile) from a profile character + install
  record — character selector omitted entirely for guiSelect, policy
  {id:"idle"} only for headless, credential always standardInput/
  session. Passwords never enter this document (proven by a dedicated
  test). LauncherProcessSupervisor spawns a host, feeds the password to
  stdin then closes it, and exposes Starting/Running/Exited lifecycle;
  Stop calls CloseMainWindow falling back to Kill after a timeout, both
  reachable through an injectable ILauncherChildProcess/factory seam so
  the state machine is unit-testable without real OS process timing.
- Status/: StatusEventParser decodes the v1 status.jsonl vocabulary
  (started/connected/characterList/enteredWorld/pluginLoaded/
  pluginFailed/disconnected/exited); an unrecognized "e" or a malformed
  line degrades to a typed Unknown event rather than throwing.
  StatusFileTailer incrementally reads new lines, tolerating a
  not-yet-existing file and a partial trailing line (only advances its
  read position past confirmed '\n' boundaries; a truncated tail is
  simply re-read next poll, never parsed early).
- Integrity/: streaming SHA-256 + hex verify for later pak/download
  checks (LA9/LA10).

Tests: 71 passed (profile CRUD + roster-merge matrix + strict-schema
rejection; composer golden-shape tests for gui/guiSelect/headless +
password-absence; supervisor tests against both an injected fake child
(state-machine determinism) and a real spawned `dotnet --version`
child (genuine cross-platform stdin/exit-code proof); tailer tests
incl. partial-line and not-yet-existing-file; SHA-256 tests). Verified
green on Windows (Release) and native WSL/Linux (Release) — the Linux
0600 test executes its real assertion body under WSL rather than
early-returning.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 15:49:13 +02:00
Erik
6a32f37589 feat(net): Campaign LA LA7a — CharacterDelete/CharacterRestore/CharacterError wire messages
Ports the three character-management wire messages LA7 (design spec §7,
plan §11 item 4) identified as missing before the character-select
screen (LA8) can be built: delete, restore, and the server error channel.
Message types + tests only — no WorldSession/Runtime/UI wiring, that is
LA7b.

CharacterDelete (0xF655): outbound account+SLOT-INDEX request per
Proto_UI::SendDeleteCharacter@0x00546b30 (retail packs the account as
String16L then writes the trailing u32 directly after — NOT the character
guid; CPlayerSystem::DeleteCharacter@0x0055f830 resolves that slot via
CharacterSet::GetSlot before sending). The server's ack reuses the same
opcode with an empty body (ACE GameMessageCharacterDelete.cs); a fresh
CharacterList follows separately per CharacterHandler.cs:322 — that
refresh flow is explicitly out of scope here (LA7b).

CharacterRestore (0xF7D9 request / 0xF643 response): guid-only request,
per ACE (CharacterHandler.cs:331-385, ReadUInt32 only) and holtburger
(CharacterRestoreRequestData, guid-only) independent consensus. The
decompiled call site (Proto_UI::SendAdminRestoreCharacter@0x00546cf0)
appears to pack two extra strings, but its only caller
(CPlayerSystem::RestoreCharacter@0x0055d760) passes an uninitialized
local (`class PStringBase<char>* edx;`, never assigned) as the second
argument and `this` (a CPlayerSystem*, not a string) as the third —
textbook decompiler register-corruption, not real arguments. No
divergence-register row: this follows the correct reading of a corrupted
decompile, not a deviation from retail (spec §11 item 4). The response
reuses opcode 0xF643, a genuine retail collision with
CharacterCreateResponse (ACE's own comment: "This is a duplicate...",
GameMessageOpcode.cs:42); GameMessageCharacterRestore.cs always writes a
success shape (flag=1 + guid + name + secondsGreyedOut), but retail's
CharacterRestore handler can also reply via the CharacterCreateResponse
path on failure (e.g. NameInUse) with a flag-only body and no trailing
fields — the parser mirrors that conditionality instead of assuming the
four fields are always present.

CharacterError (0xF659): u32 error code, confirmed directly from retail's
inbound dispatcher UIQueueManager::ProcessNetBlobData@0x0055b000 ->
CPlayerSystem::Handle_CharacterError@0x0055d5d0, which reads
`enum charError` straight off the wire. The Code enum is a verbatim port
of retail's own enum charError (docs/research/named-retail/acclient.h:
4038-4067, 26 members incl. CHAR_ERROR_NUM_ERRORS) rather than a subset
filtered through ACE — retail's header names four members ACE's C#
CharacterError enum omits (LoggedOn, NoPremade, AccountInUse,
CharacterIsBooted) because ACE's server never sends them, though a
genuine retail server could. The 32-bit storage-width compiler sentinel
FORCE_charError_32_BIT is deliberately excluded (not a real value).
Unknown codes never throw — RawErrorCode always preserves the wire value.
Today acdream cannot surface any character-stage server error; this is
the first parser for the family.

46 new tests (byte-exact builder assertions, ACE-serializer-shaped
parser fixtures via the existing AceWireWriter test helper, all 26
retail error codes round-tripped, unknown/truncated/wrong-opcode
handling). Full Core.Net.Tests suite: 951 passed, 0 failed, 0 skipped.
Release build green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 15:46:48 +02:00