acdream/docs/plans/2026-08-14-launcher-campaign.md
Erik a49e92df3a fix(platform): Campaign LA LA0 review fixes — CI Linux lanes, arch doc, self-guard
Opus dual-lens review of cb6502c8 passed with six findings; this lands
the fix round:
1. headless-portability.yml: AcDream.Platform src/tests join both path
   triggers and the presentation-free build/test arrays — the moved XDG
   tests run on ubuntu-latest again (they had fallen out of every Linux
   lane).
2. acdream-architecture.md: AcDream.Platform gets its own layer block;
   Runtime may-reference clause updated (the guard changed in cb6502c8,
   its human-readable twin had not).
3. PlatformDependencyBoundaryTests: the BCL-only contract (zero
   project/package references) is now enforced, not just observed.
4. memory/project_linux_graphical.md canonical seam renamed.
5. Plan LA0 recon corrected: the K0 Headless guard was never the guard
   needing amendment (it asserts Headless own refs); Runtime own-refs
   guard was — the commit did the right thing, the plan text now says so.
6. App declares its AcDream.Platform reference explicitly per its own
   convention instead of riding transitivity.

Platform.Tests: 4 passed (3 moved + the new guard).

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

418 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Campaign LA — launcher / installer / updater + retail character-select
**Status:** ACTIVE (started 2026-08-14)
**Spec (approved):** `docs/superpowers/specs/2026-08-14-launcher-campaign-design.md`
**Memory crib:** `claude-memory/project_launcher_direction.md`
**Branch:** `claude/acdream-launcher-credentials-4d2f7c` (merge to main at coherent checkpoints)
Campaign LA ships the alpha launcher (Avalonia, Windows + Linux): triple-duty
launcher + installer + updater, ThwargLauncher-model profiles with full in-UI
CRUD, plaintext credential file (user-decided), file-contract orchestration of
`AcDream.App` and `AcDream.Headless`, plugins + login commands on both hosts,
the headless character probe, and the retail character-select screen (no
Create). All architectural decisions live in the spec — this plan sequences
the work.
## Process (binding)
- **Fable plans/sequences/integrates. Sonnet implements bounded slices. Opus
reviews at every slice boundary, dual-lens:** (a) architectural — ownership,
layering, dependency-guard integrity, seams; (b) retail fidelity vs
`docs/research/named-retail/` wherever the slice touches retail behavior.
Findings → fixes → narrow re-review.
- Max 34 agents in parallel including children; subagents never spawn
subagents; implementer prompts carry spec+plan paths, files-to-read,
acceptance criteria, commit style.
- `dotnet build` + `dotnet test` green before a slice is DONE; ≥1 commit per
slice tagged `Campaign LA`; retail deviations add their
`docs/architecture/retail-divergence-register.md` row in the same commit;
no workarounds without explicit user approval.
- Connected/visual gates are the ONLY stop-and-wait points; each gets an
exact script under `docs/research/` and non-blocked slices keep moving.
## Slice map
| Slice | Deliverable | Depends on |
|---|---|---|
| LA0 | `AcDream.Platform` extraction (`ApplicationPathSet`) + guard amendments | — |
| LA1 | Launch contract: App `--session-config` + stdin credential; status.jsonl writer both hosts; roster plumbing | LA0 |
| LA2 | Headless probe mode + `idle` policy | LA1 |
| LA3 | `AcDream.Launcher.Core`: profile store CRUD, config composition, spawn/supervise, status reader | LA0 (LA1 contract shapes) |
| LA4 | `AcDream.Launcher` Avalonia UI: CRUD views, per-char settings, sessions, probe action | LA3 |
| LA5 | Plugin hosting: headless `IPluginHost` + capability flag; session-driven plugin set both hosts | LA1 |
| LA6 | Login commands: parser-core extraction + execution on both hosts | LA1, LA5 |
| LA7 | Character-select: Runtime selection state + wire (delete/restore/error) + no-selector flow | LA1 |
| LA8 | Character-select authored retail screen (flat listbox — NO 3D preview, recon-corrected) | LA7 |
| LA9 | Installer: first-run wizard (DAT locate/validate, bake w/ progress, SHA record) | LA3, LA4 |
| LA10 | Updater: GitHub Releases manifest, download/verify/install/swap, self-update | LA3, LA4 |
| LA11 | Closeout: connected-gate script, roadmap/CLAUDE.md/memory, program ledger | all |
Parallelism guide: LA3/LA4 (launcher side) proceed alongside LA5LA8 (client
side) — different assemblies, no shared files. LA9/LA10 close the launcher
side; LA11 closes the campaign.
## Linux posture (binding — user decision 2026-08-14)
Everything the launcher does must WORK ON LINUX in this campaign, except
GUI client launches: the Linux graphical client is Slice L, parked at L1,
resuming later ("ok we will do it later"). Concretely:
- **Linux-shipping in LA:** the Avalonia launcher UI, profile CRUD +
0600-permission file, installer (manual DAT picker — the auto-detect
paths are Windows-only; `acdream-bake` is GL-free and runs on Linux),
updater (staged swap; Linux can replace a running binary but keep the
same staged-atomic flow), headless launches with plugins + login
commands, and the character probe.
- **Launcher UX on Linux:** the `gui` / `guiSelect` launch modes render
disabled with an explicit "requires the Linux graphical client (Slice
L)" note — never a silent failure.
- **Per-slice enforcement:** every slice touching Launcher.Core, Headless,
Runtime, Bake, or Platform runs its test projects on Linux (native
Ubuntu or WSL, matching the K-slice practice) before the slice is DONE;
LA4/LA9/LA10 additionally prove a real `linux-x64` self-contained
publish. LA11's connected-gate script gets a Linux section: launcher on
Ubuntu doing CRUD, probe, headless launch with plugin + login commands,
first-run install with a manual DAT path, and an update swap.
- When Slice L later ships, the launcher's Linux GUI modes light up with
NO launcher changes (the session-config contract is host-agnostic) —
that expectation is part of LA's design acceptance.
## LA0 — `AcDream.Platform` extraction
New BCL-only project `src/AcDream.Platform/` holding `ApplicationPathSet` +
`IApplicationPathEnvironment` (today
`src/AcDream.Runtime/Platform/ApplicationPathSet.cs` — self-contained, no
intra-Runtime dependencies; clean cut). Runtime/App/Headless reference it.
Recon facts (2026-08-14): blast radius is the definition, six source files
(`GraphicalHostPlatformServices.cs`, `GraphicalLegacyConfigurationMigrator.cs`,
`App/Program.cs`, `GameWindow.cs:533`, `HeadlessPathSet.cs`,
`HeadlessPlatformEnvironment.cs`; two more files are doc-comment-only), two
test files (`ApplicationPathSetTests.cs` moves to a new
`tests/AcDream.Platform.Tests/`;
`GraphicalLegacyConfigurationMigratorTests.cs` fixtures), and the dependency
guards — CORRECTED post-review (the original recon here asserted the wrong
guard, the C4-closeout failure mode): the K0 Headless guard
(`HeadlessAssemblyReferencesOnlyTheRuntimeProject`) asserts HEADLESS's own
csproj reference list, which this move does not touch — it stays UNCHANGED;
the guard that actually needs amending is Runtime's own
`RuntimeDependencyBoundaryTests.RuntimeProjectDeclaresOnlyApprovedProjectDependencies`
(Runtime gains the `AcDream.Platform` reference), amended with a cited
comment in the same commit. Namespace stays `AcDream.Runtime.Platform`?
NO — rename to `AcDream.Platform` and fix the eight usings (clean naming beats
avoiding a mechanical edit). Register new projects in `AcDream.slnx`.
**Acceptance:** build + full test suite green; guard test asserts the new
exact reference set; launcher-side consumability proven by the LA3 project
referencing only `AcDream.Platform`.
## LA1 — launch contract (client side)
Three pieces, one slice, because they share the session-config/status seam:
1. **App `--session-config <path>`:** parsed once in `Program.cs` into
`RuntimeOptions` (code-structure rule 4); carries endpoint, account,
optional character selector, `Plugins`, `LoginCommands`, `Content`
(DatDirectory/PreparedAssetPath), status-file path, credential reference.
Recon: `Program.cs` has NO subcommand dispatch today — args handling is
one positional DAT-dir (`Program.cs:35`), so the flag is purely additive
(preserve the positional arg). The live-credential seam is a single call
site (`SessionPlayerComposition.cs:1128-1135`
`LiveSessionConnectOptions`); the config path populates the same
`RuntimeOptions` fields from a different source. Env-var dev flow
untouched. App gains the `StandardInput` credential read (mirroring
`HeadlessCredentialResolver.ResolveStandardInput` — one line, immediately
wrapped in an erasable secret, redacted `ToString`; today
`RuntimeOptions.LivePass` is a bare string — the config path must not
widen that exposure).
2. **Status stream both hosts:** per-session `status.jsonl` (path given in
config; absent → no writer constructed, zero cost). Versioned event
vocabulary (`"v":1`): `started`, `connected`, `characterList`,
`enteredWorld`, `pluginLoaded`/`pluginFailed`, `disconnected`, `exited`.
Recon: today's `HeadlessDiagnosticWriter` is a single shared-stdout JSONL
sink with four kinds (lifecycle/failure/event/resources) and NO per-session
file — the status writer is a second, separate sink, not a rework of the
diagnostics writer. App has no structured writer today; it gets the same
shared implementation (lands in Runtime so both hosts borrow it).
3. **Roster plumbing:** `CharacterList.Parsed` is consumed inside
`LiveSessionController.StartCore` (`LiveSessionController.cs:612`) and
never escapes — add a typed roster report on the lifecycle-host seam
(`ILiveSessionLifecycleHost`) so hosts can emit the `characterList` status
event and (later) the char-select screen can populate. No behavior change
to selection itself in this slice.
**Acceptance:** round-trip tests (config → `RuntimeOptions`; stdin credential;
status events in order with exact shapes; roster surfaced); App/Headless/
Runtime suites green; redaction test proves the password never appears in
status/diagnostics output.
## LA2 — headless probe mode + `idle` policy
Recon facts: the probe's shape already exists as the `NoCharacters` early-exit
(`LiveSessionController.cs:613-622``StopCore()` → 4-stage
`SessionScope.DrainTeardown`, graceful, `_inWorld == false` so no pre-logoff
flush) — but it fires only on selection FAILURE and maps to exit code 5
(`HeadlessProcessHost.RunOnUpdateThread:203-212` treats any non-`Connected`
start as `ConnectionError`).
1. **Probe:** a `Probe` flag on the connect options short-circuits `StartCore`
right after `GetCharacters` (before `TrySelectCharacter`): report roster,
`StopCore()`, return a NEW `LiveSessionStartStatus.ProbeComplete`.
`HeadlessProcessHost` maps it to exit code 0 with a final `characterList` +
`exited(reason: "probe")` status pair. Config: `mode: "probe"` on the
session descriptor relaxes the `JsonRequired` character selector + policy
for probe sessions ONLY (loader keeps strict validation otherwise —
recon: violations currently surface as raw `JsonException` → exit 3; probe
relaxation must be shape-level in the loader, not attribute removal).
2. **`idle` policy:** new consumer `HeadlessBotPolicy` id — enter world, run
plugins/login-commands (arrive in LA5/LA6), stay until stopped, clean
SIGINT teardown (K4's graceful-logout path already proves the mechanism).
**Acceptance:** probe test (fixture session → roster event → graceful teardown
receipt → exit 0, no `EnterWorld` on the wire); loader tests for probe-shape
relaxation + strict normal validation; idle-policy lifecycle test; suites
green. Connected verification (user gate, LA11 batch): live probe against ACE
twice in a row with no lingering session (spec §11.9).
## LA3 — `AcDream.Launcher.Core`
New BCL-only project + `tests/AcDream.Launcher.Core.Tests/`. References
`AcDream.Platform` ONLY.
- Profile store: `launcher-profiles.json` (spec §5 schema) — load/save/
validate, full CRUD operations, roster merge (fold `characterList` events
in, preserving per-character user settings), 0600 on Linux.
- Session-config composition: profile + install records → the LA1 config
shape (typed writer; probe shape included). Passwords excluded — stdin only.
- Process orchestration: spawn App/Headless per launch mode, feed password to
child stdin then close, supervise lifetime, tail `status.jsonl`
(share-tolerant reads), surface typed session state.
- SHA-256 utility (pak record + download verify — consumed by LA9/LA10).
**Acceptance:** CRUD/round-trip/merge tests; composition tests (all three
modes + probe); supervision tests against a fake child process (echo script);
status-tail tests including partial-line handling; suites green.
## LA4 — `AcDream.Launcher` (Avalonia)
New Avalonia project (Windows + Linux). MVVM over Launcher.Core; no game
solution references beyond `AcDream.Platform` transitively.
- Views: server list → accounts → characters tree; add/edit/remove dialogs
for servers (name/host/port) and accounts (account + password entry);
per-character settings editor (launch mode, plugin set, login commands);
per-account "refresh characters" (probe); running-sessions status column.
- Launch actions per mode (`gui` / `guiSelect` / `headless`); probe disabled
while the launcher runs a session for that account.
- First-run wizard shell + update prompt shell (bodies land in LA9/LA10).
**Acceptance:** ViewModel tests in Launcher.Core.Tests patterns (VMs live in
the Avalonia project but stay logic-thin; anything testable pushes down);
build green on Windows; `linux-x64` publish compiles. Visual polish is gated
at LA11 (user).
## LA5 — plugin hosting on both hosts
Recon facts (2026-08-14): `PluginLoader`/`PluginDiscovery`/`PluginManifest`
already live in `AcDream.Core` (Headless-reachable). App's single load loop
(`App/Program.cs:110-121`) loads ALL discovered plugins from two roots
(`AppContext.BaseDirectory/plugins` + `ApplicationPathSet.PluginsDirectory`,
dup-id skip) — no allow-list exists on either host. `AppPluginHost` is a
26-line pass-through; three of four `IPluginHost` surfaces (`State`
`WorldGameState`, `Events``WorldEvents`, `Selection``SelectionState`)
are backed by Core-owned types already; only `Ui` (`BufferedUiRegistry`) is
genuinely App-only. Headless has zero plugin hosting today (confirmed).
1. Session-config `Plugins` allow-list filters the discovery result on BOTH
hosts (absent list = load all, preserving today's dev behavior).
2. `HeadlessPluginHost : IPluginHost` in Headless over the same Core-owned
`State`/`Events`/`Selection`; `Ui` is an explicit no-op behind a new
capability flag on `IPluginHost` (e.g. `HasUi`) so plugins can detect
headless. Contract documented in `Plugin.Abstractions`.
3. `pluginLoaded`/`pluginFailed` status events from both hosts' load loops.
**Acceptance:** fixture plugin in Headless suite (load, capability flag,
markup no-op, teardown via collectible ALC); allow-list filter tests both
hosts; status events asserted; suites green.
## LA6 — login commands on both hosts
Recon facts (2026-08-14): the command core is dependency-CLEAN —
`ChatInputParser` (zero usings), `ChatCommandRouter` (BCL +
`AcDream.Core.Chat`), `RetailClientCommandCatalog` (FrozenDictionary),
`ChatVM` (Core.Chat/Combat + `System.Numerics` only), `ICommandBus` + the
four command records (BCL-only). The block is assembly identity, not
coupling. `ChatCommandRouter.Submit`'s two entanglements: a hard `ChatVM`
parameter (uses only `ShowInterfaceText`/`ShowSystemMessage`/
`LastIncomingTellSender`/`LastOutgoingTellTarget`) and the `ICommandBus`,
whose production implementation (`LiveSessionCommandRouter`,
`App/Net/LiveSessionCommandRouter.cs`) is App-only and wraps wire-send
delegates from the live session. GUI already has a login-command analog:
`RetailUiAutomationScriptRunner` feeds `ChatCommandRouter.Submit` at
`RetailUiRuntime.cs:523-527`.
1. **Extraction:** move parser/router/catalog + `ICommandBus` + the four
command records (+ sibling tables they require) into Runtime
(`AcDream.Runtime/Chat/...`); the router's `ChatVM` parameter becomes a
narrow feedback interface defined beside it (exactly the four members
used); `ChatVM` (stays in UI.Abstractions) implements it. GUI path stays
bit-identical — same call sites (`ChatWindowController.cs:326`,
`FloatingChatWindowController.cs:157`), same routing, CH-accepted
behavior regression-checked by the existing chat suites.
2. **Headless dispatch:** a Runtime/Headless `ICommandBus` binding the same
session send delegates (`SendTalk`/`SendTell`/`SendChannel`/
`SendTurbineChat`) + Runtime state that App's router binds — paralleling
`LiveSessionCommandRouter`'s registrations, feedback lands in
`RuntimeCommunicationState.AddText`.
3. **Execution:** both hosts run `LoginCommands` sequentially as-if-typed
(default 500 ms inter-command delay, config-overridable) once
entered-world; per-command failures → status stream, never abort.
4. K0 guard: if the code folds into Runtime, the single-reference assertion
stands untouched; the forbidden-prefix closure tests keep passing. Any
guard text change is deliberate and documented.
**Acceptance:** extraction lands with zero GUI chat test regressions
(UI.Abstractions + App chat suites bit-green); headless executes a
login-command script against a fixture session with ordered wire sends;
delay + failure-tolerance tests; suites green.
## LA7 — character-select: state + wire
Recon facts (2026-08-14): retail's screen is `gmCharacterManagementUI`
(`acclient.h:56545`) — flat listbox + Create/Enter/Delete/Restore buttons +
dialog contexts. **No 3D preview exists on retail's select screen** (the
`gmCG3DView`/`CreatureMode` viewport is chargen-only; the old
"rotating pedestal" line in `retail-ui/05-panels.md` §13 is uncited and
wrong). Our `CharacterList` parse already matches ACE's serializer exactly
(two-array shape, status/deleted always zero from ACE) and the two-phase
enter-world (0xF7C8 → 0xF7DF → 0xF657) is implemented. Missing wire:
delete/restore/error.
1. **Wire messages** (`AcDream.Core.Net/Messages/`, retail citations in
file docs per house style): `CharacterDelete` 0xF655 — outbound
account String16L + **slot index** (`Proto_UI::SendDeleteCharacter
@0x00546b30`; NOT guid), inbound opcode-only ack followed by a fresh
CharacterList; `CharacterRestore` 0xF7D9 guid-only (ACE + holtburger
consensus; the decomp's apparent extra strings are a decompiler
artifact — spec §11.4), response 0xF643 (flag + guid + name +
secondsDisabled); `CharacterError` 0xF659 parser (new — today NO
character-stage server error can be surfaced).
2. **Runtime selection state** (J-owner pattern): roster with per-entry
greyed/pending-delete state (`SecondsGreyedOut != 0` ⇒ pending; ACE
sends a constant 1 during the grace window — treat as boolean, never a
countdown), highlight, pending-delete dialog state, typed commands
(highlight / enter / delete-request / delete-confirm / restore).
Retail behavior oracles: `RebuildCharacterList@0x004ec3a0`,
`SelectCharacter@0x004ec160`, `UpdateButtons@0x004ec240`
(Delete↔Restore swap on greyed state), `EnterGame@0x004ed440`.
3. **No-selector flow:** a graphical session config without a character
selector stops at selection state instead of auto-enter; the
first-available fallback (`CharacterList.TrySelectFirstAvailable`,
used at `LiveSessionController.cs:848-851`) remains ONLY for
selector-carrying/headless sessions. Selection feeds the existing
`EnterWorld` path unchanged.
**Acceptance:** message round-trip tests against ACE's serializer shapes;
selection-state tests (greyed transitions, delete→list-refresh, restore,
error surfacing); no-selector stop + enter flow tests; suites green.
## LA8 — character-select: authored retail screen
Scope: project LA7's state through the REAL retail screen. No 3D preview
(recon-corrected; a preview would be an unapproved divergence).
1. **Layout resolution:** retail resolves the root via
`UIMainFramework::CreateAndAddRootElement(0x10000005, 0x1000039a)` +
`DBObj::GetDIDByEnum(..., 5)` — reuse OP8's ported GetDIDByEnum
machinery (category 4 precedent) for enum-table 5; slice starts by
dumping that table from installed DATs to pin the concrete DataID.
Child ids: listbox `0x1000039d`, create `0x100003a0` (present,
disabled — Create is a future campaign), enter `0x100003a2`, delete
`0x1000039f`, restore `0x1000039e`.
2. **Dialogs:** delete-confirm, please-wait, entering-world, error — the
retail dialog machinery from the OP8 WaitDialog work (`2a81e813`
mapped WaitDialog class type 0x19) is the base.
3. **Open item resolved here:** whether retail draws a render-loop
background scene behind the UI (pseudo-C proves only that the UI class
owns no viewport) — settle via user recollection + the visual gate
before polishing.
**Acceptance:** authored screen builds from DAT assets; button-state
matrix matches `UpdateButtons` oracle (incl. Delete↔Restore swap);
enter/delete/restore/error flows drive LA7 state end-to-end; suites
green. User visual gate at LA11 (screen look, dialog flows, delete +
restore against local ACE).
## LA9 — installer (first-run)
- DAT locate: auto-detect `%USERPROFILE%\Documents\Asheron's Call` and
`C:\Turbine\Asheron's Call` + manual picker; validate the four DATs.
- Bake: spawn `acdream-bake --dat-dir <dats> --out <DataDirectory>/pak/acdream.pak
--threads N`. Recon: default `--out` is INSIDE the DAT dir — the launcher
always passes `--out` explicitly. Progress: add `--progress-json` to
`AcDream.Bake` (JSONL progress lines alongside the existing 5-second human
text, which stays default) — scraping human text is fragile and we own the
tool. Recon: the bake has NO whole-file SHA — after a successful bake the
LAUNCHER computes and records SHA-256 + size + `BakeToolVersion` in its
install record, and re-verifies on subsequent startups (fast corruption
check trades a few seconds of hashing for never launching against a
half-written pak).
- Install record feeds LA3's session-config composition
(DatDirectory/PreparedAssetPath).
**Acceptance:** wizard flow tests over Launcher.Core (fake bake child emitting
`--progress-json` lines); bake-tool progress flag tests in
`tests/AcDream.Bake.Tests`; SHA record/verify tests; suites green. Connected
gate (user): clean-profile first-run against real DATs.
## LA10 — updater
- Manifest: GitHub Releases; `manifest.json` release asset — version, per-RID
client zip URL + SHA-256 + size, minimum-launcher version. Launcher pins
owner/repo in its config.
- Client update: poll on launch (+ manual check), download to staging, SHA
verify, unpack to `DataDirectory/app/<version>/`, atomic `current.json`
pointer swap, refuse while any session runs, keep previous version for
one-step rollback.
- Launcher self-update: staged download + rename-dance on next start.
- Session-config composition targets `app/current`'s binaries.
**Acceptance:** manifest/download/verify/swap tests against a local HTTP
fixture; rollback test; refusal-while-running test; self-update staging test;
suites green. Connected gate (user): staged-manifest update swap end-to-end.
## LA11 — closeout
- One connected-gate script `docs/research/2026-XX-XX-campaign-la-test-script.md`
covering: all three launch modes vs local ACE, probe round-trip ×2 (no
lingering session), char-select visual matrix + delete flow, login-commands
+ plugin behavior on both hosts, add-server/add-account purely in UI,
clean-profile first-run wizard, staged update swap.
- Roadmap shipped-table entry, CLAUDE.md Current-state flip, memory distill,
ledger below completed, program closeout section.
## Review protocol
Per slice: implementer commit(s) → Opus dual-lens review (architectural +
retail-where-applicable) → fix round → narrow re-review of fixes → slice DONE
in ledger. Reviews name blast radius explicitly
(`claude-memory/feedback_blast_radius_single_lens.md`). Slices LA7/LA8 add the
retail-fidelity lens against named-retail symbols cited in the slice body;
LA6 adds CH-regression scrutiny; LA0 adds guard-integrity scrutiny.
## Ledger
| Slice | Status | Commits | Review | Notes |
|---|---|---|---|---|
| LA0 | review PASS; fix round applied | `cb6502c8` + fixes | Opus PASS w/ 6 findings 2026-08-14; narrow re-review pending | Byte-identity proven; CI Linux lanes + arch doc + Platform self-guard + App explicit ref fixed; plan recon corrected |
| LA1 | in flight (Sonnet) | | | pinned contract v1 + 5 optional fields |
| LA2 | — | | | |
| LA3 | in flight (Sonnet, isolated worktree) | | | pinned contract shared with LA1 |
| LA4 | — | | | |
| LA5 | — | | | |
| LA6 | — | | | |
| LA7 | — | | | |
| LA8 | — | | | |
| LA9 | — | | | |
| LA10 | — | | | |
| LA11 | — | | | |