824 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d233f81dce |
feat(session): the in-world logoff — LogOut animation, reverse wormhole, live return to character select
Retires AD-74 (Exit to Character Selection 'behaves as Exit Game') and files AD-110 (the composed handoff edge) — register rows in this commit. Retail derivation (named decomp): - gmGamePlayUI::UseTime @0x004EA3A0: confirmed Yes drains into CPlayerSystem::LogOffCharacter(0) when grounded (transient_state & CONTACT); the grounded three-way branch now also covers the indicator-bar end-session control (it was Options-only). - CPlayerSystem::LogOffCharacter @0x00563520: SaveToServer FIRST (the existing pre-logoff flush hook), then RequestLogOff @0x00562DD0: 'Logging off...' chat (type 0), 0xF653 via Proto_UI::LogOffCharacter @0x00546A20, logOffRequestTime = now + 3.0 (+20.0 when IsPlayerKiller @0x0058C910 — PWD bits 0x20|0x2000000), and CommandInterpreter::HandleLogOff @0x006B3330 -> Disable. - The log-off ANIMATION is server-driven: ACE broadcasts MotionCommand.LogOut (0x1000011E, Player.cs:596 SendMotionAsCommands) and it plays on the local player through the existing inbound unpack_movement funnel during the 3 s hold — retail plays nothing locally; Disable() is the whole client-side effect. - gmSmartBoxUI::UseTime @0x004D6E64: hold elapsed -> BeginTeleportAnimation(TAS_WORLD_FADE_OUT) @0x004D6E83 (enter cue @0x004D638E, unconditional) -> TunnelFadeIn -> Tunnel. The tunnel plays the SAME forward 40 fps animation; nothing renders backwards, and NO exit cue ever fires on logout (the char-select swap preempts the TunnelContinue/FadeOut tail). - Inbound 0xF653 echo (dispatch case 3 @0x0055C963) -> ExecuteLogOff @0x0055D780: world teardown with the LOGON CONNECTION KEPT (ExitWorldDisconnect @0x00541E00 removes every connection except logonRecID_ — one connection against ACE) and Proto_UI::SetEventCounter(0) @0x00541E79; the fresh CharacterList in the same batch re-shows character management (gmGamePlayUI::Update @0x004E9CD0 -> QueueUIMode(0x1000000a)). ACE mirrors it: SendFinalLogOffMessages (Session.cs:249) sends 0xF653 + CharacterList + ServerName >=6 s after the request and leaves the session AuthConnected — a second EnterWorld needs no re-handshake. Implementation: - RuntimeWorldTransitState: the canonical logout lifecycle (Requested/PresentationActive/Confirmed, retail 3 s/+20 s holds, cancel/reset/ownership convergence). - WorldSession: RequestCharacterLogOff (non-blocking 0xF653), IsCharacterLogOffConfirmed, ReturnToCharacterSelect (InWorld -> InCharacterSelect + game-action sequence reset; transport untouched). - LiveSessionController: BeginCharacterLogOff (flush-first request) and CompleteCharacterLogOff — the return-to-selection transaction (ReconnectCore minus the transport swap: retire the world generation's routes, host reset, state flip, fresh generation re-bind, roster re-applied from the pushed CharacterList; failures degrade to the full StopCore teardown). - RuntimeLocalPlayerMovementState.DisableCommandInterpreter + DispatcherMovementInputSource gate: retail's Disable() — held keys produce no movement while the server LogOut motion plays; cleared by the generation reset. - LocalPlayerTeleportController: the logout pump as the third arm of the one wormhole machine (request/hold/wormhole/confirmed handoff; teleport starts refused during logout; the handoff runs the session transaction whose world reset retires the tunnel as the fresh selection state re-shows the character screen). - UI: both end-session surfaces share the retail three-way grounded gate and now run the REAL flow; Options' Exit Game keeps the app exit (window close -> the existing graceful-shutdown logoff). Tests: +5 transit lifecycle, +4 session transaction, +7 logout pump. Runtime 1756/0 (baseline 1747), App live-DAT 5523/3 (baseline 5512/3 + 11 this round), Core.Net 1004/0, full solution green (0 failures). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6b0fa4ff0d |
feat(ui): Map/House panel — slices 2+3, panel shell + Map tab
Mounts host 0x2100006E slot 0x1000018C (RetailPanelCatalog.MapHouse = 16) as a two-tab UiTabPanel (Map default, House second) through the OP3/FA3 recipe (LayoutImporter.Build -> Bind -> ActivateTabBehavior). Toolbar button 0x1000019A un-ghosts (added to both RetailPanelCatalog.Mounted and .Toolbar). Combined into one commit because MapHousePanelController.Bind depends on both MapPageController and HousePageController existing — splitting them would mean landing dead code first. Map tab (gmMapUI, MapPageController): - Calendar formatter matching gmMapUI::Update's "Date: %s\nTime: %s" shape, reusing WorldTimeService.CurrentCalendar (new Func<DerethDateTime.Calendar> dependency threaded through InteractionRetainedUiDependencies/GameWindow — a stable long-lived service, not routed through the deferred-binding machinery Radar's per-session state needs). MonthName enum values already match retail display text; HourName's "AndHalf" suffix is rewritten to "-and-Half". - Coordinate math + marker placement reuse RadarCoordinates/ LandDefs.GidToLcoord verbatim (both already byte-exact ports of CPlayerSystem::InqPlayerCoords/LandDefs::gid_to_lcoord) — no re-port. PlaceMarkerOnMap's centering math (m_x0 + x - w/2) ported from gmMapUI::PlaceMarkerOnMap @0x004a18b0. Indoor gating clears the coordinate text and hides the player marker, matching gmMapUI::Update's else branch. - 53-town s_rgLocations table ported verbatim into MapLocations.cs. Markers built once at bind time via the panel's own RowTemplateResolver against m_pMap's authored hotspot-template attrs (0x47/0x48), with literal-string tooltips through AuthoredTooltipText/Enabled (RetailTooltipPresenter) — closes divergence-register row TS-85's last item, gmMapUI::AddMapNote @0x004A1C51. - Structural finding: m_pMap (0x100001EC) is itself authored as a Type-1 BUTTON (the GM click-to-teleport hook at gmMapUI::ListenToElementMessage), and the player/house icons (0x100001ED/EE) are its own NESTED children, not siblings — UiButton.ConsumesDatChildren swallows them from the normally-built tree. Both are re-resolved standalone through the same template resolver the town hotspots use and reattached under m_pMap. House tab (gmHouseUI, HousePageController): mounts the ListBox (0x100001E6) with its authored row template, wired to an empty Lines() source by default — genuinely empty until Slice 4's wire lands, matching retail's own PostInit (no Update call, no static content). 21 new tests (7 MapHousePanelControllerTests, 14 MapPageControllerTests): tab table pairing, close button, town-hotspot count/tooltips, calendar formatter golden values (Frostfell 27/119 P.Y., every HourName incl. AndHalf), player/house marker placement and indoor-gating reproduced against the real fixture via already-tested RadarCoordinates (no re-derivation). Fixture map_house_2100006E_1000018C.json captured via the shared RetailLayoutFixtureGenerator (other 34 fixtures deliberately NOT regenerated — out of scope for this batch, would touch unrelated panels' schema drift). Full solution builds clean; App suite 5391/0 failed/71 skipped (non-live; one earlier flaky streaming failure unrelated to this change, confirmed pre-existing on the branch before these commits). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
691b925952 |
fix #406: launcher session exit observation carries the real code + captures client stderr
GameWindow.Dispose() (via Program.cs's `using var window = ...`) runs
unconditionally even when invoked mid-unwind of an exception that escaped
Run()'s Silk.NET frame loop. Resource teardown itself can converge
cleanly regardless, so CompleteShutdown had no way to tell "normal Run()
return" from "a crash is propagating through me right now" and always
wrote the hardcoded exited{code:0,reason:"graceful"} — exactly the
symptom #406 observed against a real 0xE0434352 crash. Fixed by latching
_runFailure in Run()'s existing catch block (before the pre-existing
throw) and consulting it from a new ReportExited method, the one call
site for the terminal status write: crashed(1)/graceful(0)/
shutdown-incomplete(1) as appropriate. No wire-contract amendment needed
— §LA1 pins the exited event NAME, and reason is already free text that
StatusEventParser round-trips unchanged.
Sibling gap fixed in the same commit: the launcher discarded the child's
stdout/stderr entirely, which is why diagnosing this exact crash required
a manual console re-run. Added BoundedProcessOutputCapture, a 2 MiB-capped
sink mirroring SessionStatusWriter's open-append-flush-close-per-write
posture (a long-lived write handle is not actually concurrently readable
on Windows even with FileShare.Read — confirmed by isolated repro), wired
into both SystemChildProcess (ProcessStartInfo.RedirectStandardError;
Linux + Windows graphical children, i.e. this bug's own scenario) and
WindowsSystemChildProcess (a real native pipe via CreateChildOutputPipe,
mirroring the existing stdin pipe; Windows console-capable/Headless
children). Opt-in via LauncherProcessSpec.StderrLogPath (null = unchanged
behavior), threaded through SessionConfigComposer -> client.err.log
beside status.jsonl -> LauncherExecutableSet -> LauncherOrchestrator.
Tests: GameWindowCrashStatusTests (source-shape, matching the existing
GameWindow test pattern — the class cannot be constructed without a live
GPU/window), BoundedProcessOutputCaptureTests (10 unit tests), and three
new LauncherProcessSupervisorTests spawning real child processes through
both capture code paths.
Launcher.Core.Tests: 337/0 (was 324/0). Launcher.Tests: 67/0 (unchanged).
Full solution build green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
34e3a534be |
feat(chargen): Campaign CC slice CC5 — Summary page, Finish flow, RandomizeCharacter port
Fills TS-82's Summary placeholder with a faithful port of gmCGSummaryPage (name field with NameInputFilter + the retail commit-on-focus-lost/submit dispatch + the >32-char ID_CharGen_NameTooLong reject-and-revert path, the REAL three-row-template listbox confirmed against the installed EoR dat before writing any page code, and Summary's own independent gmCG3DView preview instance wired through a second ChargenPreviewController pair mirroring the Appearance page's exact composition shape). Ports CharGenState::RandomizeCharacter and its six sub-primitives into RuntimeCharacterCreationState — not approximated: the RandInt/RollDice semantics are independently confirmed from both the decompiled RNG bodies and the CharGenStateVtbl union struct in acclient.h. Three consumers: the chargen screen's open-roll (retiring AP-214's honest-blank deviation and reproducing the Appearance page's gender-flip-on-init quirk), the Summary page's Random button (behind the retail randomize-warning confirm), and the Appearance page's Random button (narrowing AP-212 to just Heritage/Profession/Town's still-approximated rolls and Skills' still-unported RandomizeSkills). Wires the Finish button (previously ghosted) with retail's NoName/ CreditWarning dialog pair, adds the F12 amendment's HeritageOrGenderUnset local refusal to TryBeginFinish (register AP-223) as a defensive backstop now that the screen-open roll normally makes it unreachable, and wires the four ID_Character_Err_* rejection dialogs for the 0xF643 response codes CC3 already parsed but nothing displayed. Register: TS-82 retired, AP-214 retired, AP-212 narrowed, AP-223/224/225 filed (heritage/gender Finish refusal, Summary's two-bucket skill-list narrowing, the 32-vs-33 name-length threshold reconciliation). Runtime 1722/0 (was 1713), App 5240/3 skips (was 5223/3), Headless 166/0 unchanged, full solution Release build green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
34c6fceab0 |
feat(chargen): Campaign CC slice CC6b-MOUNT — Appearance page + preview mount
The page-mount half CC6b-PRE deferred: CharacterCreationAppearancePage (gender buttons, Face/Clothes sub-tabs, nine spin controls with retail's decrement/increment/select-as-current-part OnClickAt zones, nine color swatches, shade scrollbar, zoom/rotate wiring) plus ChargenPreviewController, which bridges the ChargenPreviewRenderer/ChargenPreviewZoomController camera-injection gap CC6a/CC6b-PRE left open and mounts as the third private creature viewport beside paperdoll/creature-appraisal. Color-wheel scouting (campaign risk item 4): live-DAT probe found every color-wheel-family id resolves through existing DatWidgetFactory mappings (Button/Scrollbar/generic fallback) — no new widget type needed. The @140355 gender-flip-on-init oddity (risk item 5): resolved via decomp alone — gmCharGenMainUI's own ctor calls CharGenState::RandomizeCharacter before any page constructs, so retail's chargen screen is never actually blank on open; the Appearance page's gender-flip code always fires against a real, randomly-rolled gender. Filed AP-214 (acdream doesn't port RandomizeCharacter this round, so it opens honestly blank instead) and AP-215 (two narrow visual substitutions: swatch .Selected highlight vs retail's separate overlay, ordinal labels vs retail's icon-only spins). AD-101 retired: the Heritage page's auto-gender-select interim default is deleted now that the Appearance page's real gender buttons exist. TS-82 narrowed to Summary-only. Scope addendum: ChargenPreviewRotationController's parameterless-constructor default changes from 0f to a new RetailDefaultHeadingDegrees=180f constant (retail's InitializePage override, not the ctor's raw 0) — every real gmCG3DView owner converges on 180 before its first frame, so a controller defaulting to 0 was a trap for future consumers. Runtime 1713/0, Core 4786/1 skip, Content 147/0, App 5220/3 skips (Release, ACDREAM_PROBE_LIVE_MOUNT=1) — zero failures across two clean full-solution runs; the one Core.Net.Tests NakEmissionTests flake observed on a third run is the same pre-existing, previously-documented timing flake (zero files under src/AcDream.Core.Net/ touched, passes 100% in isolation). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
fbe9c8a288 | fix(plugins): close LA5 host lifecycle review | ||
|
|
95f4be94db | feat(plugins): complete Campaign LA5 cross-host hosting | ||
|
|
d511e4c348 | fix(launcher): close Campaign LA LA1 review findings | ||
|
|
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> |
||
|
|
cb6502c8a5 |
feat(platform): Campaign LA LA0 — extract ApplicationPathSet to AcDream.Platform
The launcher (LA3/LA4) needs the XDG/Windows path contract (ApplicationPathSet/IApplicationPathEnvironment) without pulling in any gameplay assembly. Move it out of AcDream.Runtime into a new BCL-only AcDream.Platform project so the launcher-side Launcher.Core project can reference it directly per the campaign plan (docs/plans/2026-08-14-launcher-campaign.md, LA0). Namespace renamed AcDream.Runtime.Platform -> AcDream.Platform; code is otherwise byte-identical (no logic changes). AcDream.Runtime now carries a ProjectReference to AcDream.Platform and re-exports it transitively, so App and Headless keep resolving the type without a direct reference and K0's Headless single-ProjectReference guard (HeadlessAssemblyReferencesOnlyTheRuntimeProject) stands unchanged. The sibling Runtime dependency-boundary guard (RuntimeProjectDeclaresOnlyApprovedProjectDependencies) does assert Runtime's own project-reference set, so it needed a deliberate, documented addition of AcDream.Platform to its expected list. Moved tests/AcDream.Runtime.Tests/Platform/ApplicationPathSetTests.cs to a new tests/AcDream.Platform.Tests/ project (namespace AcDream.Platform.Tests) referencing only AcDream.Platform. Registered both new projects in AcDream.slnx. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
13d388e5a9 |
fix #391: curated modern-only resolution list from the monitor's modes
User-directed (2026-08-13): "we should only support modern resolutions. Not any old format." New DisplayModeCatalog enumerates the window's monitor (Silk IMonitor.GetAllVideoModes) once at GameWindow load and curates via a pure, tested rule: modern widescreen families only (16:9/16:10/21:9/32:9 within 2.5%), at least 1280 wide, must fit the desktop (an impossible windowed pick is not offered - the measured 3840x2160-on-2560x1440 silent clamp class), desktop mode always included, refresh-rate duplicates collapsed, ascending order. The Config Resolution row consumes the catalog through two new optional Bind parameters; its Defaults value becomes the desktop's own mode. Fixture/headless callers keep the static preset ladder, which now drops 800x600 and is pinned by test to pass the same curation rule (the OP6 S4 "default must be re-selectable" invariant holds on both paths). Deliberate retail deviation, register row IA-22: retail listed the adapter's complete enumeration including 4:3 legacy modes and authored 800x600 as the Config default (gmConfigUI::InitOptions SetDefaultValue(0x03200258); gmClient::Init @0x004047af). The catalog is also the designated fullscreen mode-switch validation source for #376/#388 - an offered mode is supported by construction. Tests: DisplayModeCatalogTests (8 - filter/clamp/dedupe/sort/ultrawide/ desktop-inclusion/fallback-consistency); ConfigOptionsPageControllerTests row-12 default updated. App suite 4,961/3 skips; UI.Abstractions 916. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a57287f8ad |
fix: move the #387 resize evidence line into the typed owner
GameWindowSlice8BoundaryTests.FramebufferResize_IsOneTypedOwnerHandoff correctly rejected the log line added to GameWindow.OnFramebufferResize — the window callback is contractually a one-line handoff. The line now lives in FramebufferResizeController.Resize after its zero-size gate, which is also the better home (one owner, all callers covered). Full Debug App suite 4,941/3 skips. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
11e26c909d |
diag #387: permanent evidence lines on the resize chain (event, swapchain recreate, resolution apply)
Three rare-event log lines so any future resize report is diagnosable from the launch log alone: 'window: framebuffer resize event WxH', 'vulkan: swapchain recreated WxH ok=', and 'display: resolution pick WxH (window was WxH)'. An instrumented live run on the Windows AMD box shows the full chain firing for both the programmatic resolution apply and external window resizes, and screen captures at 784x561 vs 1584x861 confirm fixed-pixel UI with a true pixel-count re-render. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
07f2b3f72e |
fix(settings): OP9 review residuals — restore SaveAudio live-apply pin, delete dead residues
Closes the OP9 combined review's findings (docs/research/2026-08-11-op9-review.md, APPROVE-WITH-FIXES): - MUST-FIX 1: SaveAudio -> ApplyAudio (OP6's Config-tab live-apply) lost its ONLY assertion when the retired SettingsVM save-order test was deleted. Restored directly on the now-public seam: SaveAudioPersistsThenPushesLiveApplyAudioWithTheSavedSnapshot pins persist-then-push order + the pushed snapshot; SaveAudioSkipsTheLivePushWhenPersistenceFails pins the failure ordering (a failed persist pushes nothing and commits nothing). Also closes SF-5: the OP6 effective-volume comment's 'target-audio assertion above' reference is real again and now names the restored test. - SF-3: dead residues deleted — RuntimeSettingsController's private SaveCharacter (zero callers post-371197a3), ISettingsStorage.SaveCharacter + its JsonRuntimeSettingsStorage/FakeStorage implementations (the deleted private method was the only caller), and IngressShutdownRoots.Settings (zero readers since the view-model shutdown stage died). SettingsStore's PUBLIC SaveCharacter stays: it is the tested storage-API seam, and per-toon entries in existing settings.json files still load through the live LoadCharacter path. - SF-2: code-structure.md's presentation-seam list no longer routes the settings preview through 'optional SettingsVM'. - NIT 6: AP-196's retirement note now attributes LockUI (/lockui + PlayerDescription SetUiLocked convergence) and UseMouseTurning (Gameplay-tab macro + Config-tab row) to their real channels instead of folding all 13 members into the Character tab. Full Release suite: 13,077 passed / 4 skipped / 0 failed (13,075 + the two restored tests). One unnamed App-assembly failure appeared on the first post-fix full run and did not reproduce on the isolated assembly rerun nor a second full run — consistent with the known #250-class parallel-load flake, recorded here for honesty rather than silently rerun. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b4edee970f |
feat(ui): Campaign OP slice OP8 — Configure Keyboard
Ports retail's Configure Keyboard screen (gmKeyboardUI, LayoutDesc 0x21000009) — its own separate full-screen window, not a fifth Options- panel tab. Retires OP3's INERT contract for the Gameplay tab's Configure Keyboard button (0x10000204). DAT reader (src/AcDream.Core/Input/RetailActionMap.cs): reads the ActionMap singleton (DID 0x26000000, empirically the only one — not 0x27000000 as GetDBOType's Turbine-internal tag would suggest) and both MasterInputMap defaults (0x14000000 "gmDefaultMap"/0x14000002 "DefaultMap"), union-merged per (InputMapId, ActionId) — proven order- independent since the two maps' one shared context (0x5) has disjoint action-id sets. Empirically resolved three lane-D unknowns against the live DAT: the six ActionClass values (1=Movement, 2=Camera, 3=UI, 4=Combat, 5=Emote, 7=CharacterSettings — 6 is genuinely absent), that the six unnamed InputMaps are 100% non-bindable (render nothing, not an unlabeled group), and that the enum-to-DID pairing for the two master maps is inconsequential to the merge result. Identity table (src/AcDream.UI.Abstractions/Input/RetailActionIdentityTable.cs): maps DAT (InputMapId, ActionId) pairs to acdream's InputAction where a live consumer exists (~140 of 306 user-bindable rows — Movement/Camera/ Combat map almost completely; UI/Quickslot/Chat partially; only 5 of 87 Emotes and none of 48 CharacterSettings hotkeys, since acdream has no general emote player or hotkey-to-option-toggle dispatcher yet). Every entry cross-verified by label match AND a DAT-default-vs- KeyBindings.RetailDefaults() byte comparison (RetailActionIdentityRoundTripTests), which caught a real off-by-one in the Quickslot 13-18 block before it shipped and found three genuine pre-existing RetailDefaults() gaps (walk-mode's Shift-echoed chord, ten CameraAlternateControls arrow-key alternates, and the Quickslot Ctrl+N use-vs-select ambiguity) — none introduced by this slice, all documented rather than silently patched. KeyboardConfigController: six ActionClass list boxes built from the DAT, merged with live KeyBindings for mapped rows (rebind applies immediately through the same InputDispatcher every other input path uses) and a new sibling RetailUnmappedKeyBindings store for rows with no InputAction yet. Left-click a key button opens real InputDispatcher modal capture; right-click erases that slot. N-way conflict detection scans every other row plus the live KeyBindings table for acdream-only actions (Ctrl+M mute, debug F-keys) as the non-user-bindable refusal analogue, using retail's own byte-verified "Could not overwrite " string (table 0x23000004). OK/Cancel/Defaults/Revert reuse the OptionPage/IOptionRow verb model via a new ActionKeyMapOptionRow. Persistence is keybinds.json only (D4 — no .keymap file interchange). Five register rows: AP-202 (.keymap interchange narrowing), AP-203 (store-only rows with no live consumer), AP-204 (silent auto-reassign instead of retail's confirm dialog; OK/Cancel ported as left-click not right-click-release). Small supporting additions: UiButton.OnRightClick (additive, no existing behavior changed), InputDispatcher.Bindings getter (the screen's single live-truth read seam), RetailScanCodeMap (DIK scan code <-> Silk.NET Key, keyboard + the one mouse-device row). 19 new tests (6 ActionMap reader conformance incl. live-DAT row-count/ label pins, 1 DAT-vs-RetailDefaults round-trip, 12 controller behavior tests against the committed keyboard_config_21000009.json fixture) — full solution suite 13,147 passed / 4 skipped / 0 failed (baseline 13,128/4/0, zero regressions). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
bc43fb1d1d |
fix(ui,runtime): OP4 review fixes — live re-seed, enable-gating, Combat panel re-point, universal timestamps
Both OP4 reviews converged on one headline bug (Character-tab rows never re-read live server truth after their pre-login constructor-word seed) plus overlapping MUST-FIXes. All ten converged/consolidated findings land here: MUST-FIX: - BoolOptionRow.SaveCurrentValue now re-reads its live binding (retail's GetValue()-into-SaveCurrentValue) on every OnShown — panel open, tab switch in, initial activation — instead of trusting the pre-login constructor word it was built with. Reset/tab-switch can now only restore values that were actually live at the last show. LockUI's host.Root.UiLocked one-shot mount seed now also converges on every PlayerDescription via the existing OnCharacterOptionsChanged hook. - Apply/Reset are wired to OptionPage.OnOptionChanged in production (Ghosted when nothing changed, Normal when dirty, run once at bind so both start disabled per retail's PostInit); Defaults stays ungated. - The Combat panel's three LEDs (Repeat Attacks/Auto Target/Keep in View) now read/write the same RuntimeCharacterOptionsState seam the Character tab uses instead of a disconnected client-local GameplaySettings copy — closes the "two writable copies" divergence. The three now-orphaned GameplaySettings fields and RuntimeSettingsController's mirror properties/SetCombatGameplay are deleted outright; the headless host's hardcoded AutoRepeatAttack/AutoTarget now read the live option bit. - RuntimeSettingsController.SetUiLocked's convergence guard now compares against the last value actually applied to the runtime target instead of the persisted GameplaySettings.LockUI snapshot, which could already match a server-derived request without ever having been pushed. SHOULD-FIX: - DisplayTimeStamps now prefixes every chat producer (ChatLog.Append is the one seam all of them funnel through), not just AddText's own callers — heard speech, emotes, Turbine channels, and combat text were previously missed. The prefix format escapes its colons and forces InvariantCulture instead of the culture-dependent TimeSeparator placeholder. - sky.frag now honors uFogParams.w (fog mode) like the mesh/terrain shaders, so Disable Distance Fog stops the sky dome's horizon band from blending toward fog color too. - Corrected the "byte-verified" overclaim on the timestamp format string doc comment (BN-sourced, wire doc U6) and the AP-194 anchor-column class-name typo; the RunAsDefaultMovement doc comments now cite retail's actual acclient.h enumerator name. - Added: DispatcherMovementInputSource's option x modifier truth table (incl. || AutoRunActive with the option off), the per-page Apply/Reset enable-gate tests, a real checkbox.OnClick/ToggleBehavior-driven click test, and hash-pins for the six header string keys. - Gate script step 8 corrected for the logout-flush false-failure (closing the panel before relogging is load-bearing); a new step documents the enable-gate sequence and the Combat-panel/Character-tab cross-check. Register: AP-196 (the Group-C default-source change + GameplaySettings retirement) and AP-197 (the ignored per-character timestamp format override) filed in this commit. Full Release suite: 13,044 passed / 4 skipped / 0 failed (was 13,008/4/0; net +36 tests from new coverage and legitimate assertion updates from the GameplaySettings retirement). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
22b86b9ff4 |
feat(ui): Campaign OP slice OP4 — the Character tab
Binds LayoutDesc 0x21000028 (gmCharacterSettingsUI) through OP2's template-list mechanism and OP3's OptionPage model: 6 authored group headers + 50 toggle rows (49 from the 2013 build + D3's "Listen to PK death messages", AP-193) in research doc §2's authored order, each row resolved by PlayerOption id through CharacterOptionTable, seeded from live RuntimeCharacterOptionsState, defaulted from CharacterOptionTable. ClientDefault (byte-verified against UIOption_Checkbox::SetPlayerOption @0x00486e80's own GetDefaultOptionValue call — AP-194 updated to confirm the directive was followed), labels/tooltips resolved by name from string table 0x23000003 (never hard-coded English), and registered with OptionsPanelController.CharacterPage. Apply/Reset/Defaults (0x100001FC/FD/FE) are now wired per-page via a scoped subtree search (UiElement.FindDescendant, promoted from UiTabPanel) since Character/ Chat/Config each author their own physical instance under the SAME element ids. Consumers: Group A (29 ids) wire+store only via the existing SetSingleCharacterOptionRuntimeCmd/TrySetOption seam. Group B: Display Timestamps prefixes new transcript lines (RuntimeCommunicationState. DisplayTimestampsSource); Disable Distance Fog forces FogMode.Off (WeatherSystem.DisableDistanceFogSource, retiring half of TS-73); Run as Default Movement inverts the walk-mode modifier's default (RuntimeLocalPlayerMovementState.RunAsDefaultMovementSource). Group C re-points AutoTarget/AutoRepeatAttack/ViewCombatTarget (CharacterOptionCombatSettingsSource), VividTargetingIndicator/ CoordinatesOnRadar/LockUI/AcceptLootPermits from the client-local GameplaySettings record to the canonical server bit — closing two previously-unfiled divergences where AutoRepeatAttack and AcceptCorpseLootingPermissions never reached the wire despite being retail auto-save ids. TS-73 narrowed to its two still-open cases; TS-75..TS-80 file the genuine gaps (no day/night force, no weather- particle/profanity-filter/salvage/housing/pickup-preference subsystem, fellowship-create's unaudited client-sourced field) rather than inventing stand-ins. Conformance: CharacterOptionsPageControllerTests pins all 50 rows against CharacterOptionTable in both directions (an invented or dropped row fails the build), the authored group/order row-by-row, and the build/seed/Apply/Reset/Defaults/wire-publish behavior end-to-end against the committed fixture. 52 new tests; full solution suite 13,008 passed / 4 skipped / 0 failed (was 12,956/4/0). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9d3df5f627 |
fix(app): #348 — cursor switches ride a process-lifetime native cache; the per-flip Win32 handle leak is closed
Silk's per-mouse ICursor recreates the native Win32 cursor on every Image assignment; a per-frame cursor alternation (the pick cursor flickering between kinds while hovering an ANIMATED NPC — exactly the stand-at-a-vendor posture) allocated a fresh USER handle each flip until CreateCursor died with "Not enough memory" and took the render loop with it (vendor-gate.log, exit 82 — surfaced as one clean stack by #343's fix, as designed). GlfwCursorCache restores retail's own shape: each distinct MediaDescCursor is created ONCE for the process lifetime (glfwCreateCursor, rejected media cached as permanent misses) and switching is an O(1) zero-allocation glfwSetCursor. The AP-72 missing-art standard-cursor fallback rides the same cache (Arrow/Hand/Crosshair/IBeam; anything else keeps the Silk path). Graphical hosts attach after the native window exists; tests and windowless hosts keep the Silk path untouched. RetailCursorManager's dedup and PlanApplication logic are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6c6664a685 |
fix(app): #343 — a wounded render loop defers the native release instead of throwing over the real failure
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
Root cause pinned by IL-decompiling Silk.NET.Windowing.Common: ViewImplementationBase._inRenderLoop is set at DoRender/DoUpdate entry and cleared ONLY on normal return, so a throwing frame callback leaves it armed forever and any later Dispose -> Reset throws "You cannot call Reset inside of the render loop", exit 82, replacing the original wounding exception in the report. The fix mirrors Silk's own bracket exactly: GameWindow._renderLoopArmed set at OnUpdate/OnRender entry, cleared only on their normal return — deliberately NOT in a finally, so it tracks the wound the same way Silk's private field does. ReleaseNativeWindow checks it before disposing: armed -> best-effort Close() (swallowed so it can never become the reported failure), no Dispose, and a new terminal status CompleteWithDeferredNativeRelease with Error kept null — the original exception stays the primary report. Healthy paths (OnClosing's in-loop completion, Run()'s tail release) are byte-unchanged, and the new PublishNativeWindow parameters default to null so every existing caller and test behaves identically. Sabotage: disabling the armed-check flipped the deferral test to Expected CompleteWithDeferredNativeRelease / Actual Complete — the guard is what the test exercises. Clean-room suite 11,262 / 6 / 1, the 1 being #340's documented load flake (passed standalone; second recorded firing noted in its entry). Queue: #344 done, #343 done; next #345's instrumented mechanism session, then #341's boundary hunt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
55b07f6a62 |
refactor(physics): hoist the live-entity collision builder to Runtime (#330 groundwork)
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
LiveEntityCollisionBuilder and LiveEntityDefaultPoseResolver move from AcDream.App.Physics to AcDream.Runtime.Physics with no behaviour change — diff-verified byte-identical shape math by both review lenses. The Build signature's App-record parameter is replaced by presentation-free primitives with identical guard semantics, INCLUDING the FinalPhysicsState read the contract had missed and the implementer surfaced rather than dropped. Visibility stays internal: Runtime's existing InternalsVisibleTo grants already cover every consumer, so the implementation's public widening is reverted per the architecture review's finding 11. The registration WIRING is deliberately WITHHELD. Both Opus lenses failed it, converging: a shadow registered at spawn freezes there (RuntimeRemotePhysicsUpdater is Runtime-homed but App-driven — nothing headless ticks it), so a walking NPC becomes a phantom obstacle at its spawn point while the real NPC still passes through the bot; three of five shadow-lifetime edges leaked (pickup leaves a permanent invisible collider, supersession orphans a duplicate, generation reset never unregisters and the K-ledger convergence oracle only checks retained shadows AFTER disposal clears them); and headless cannot resolve BSP collision assets at all, so doors and chests would still be walk-through. The frozen-shadow root was the SESSION LEAD's contract error (fact 3), not the implementer's. #330 stays OPEN, rewritten as the seven-point scope map the reviews produced — the honest overnight deliverable is that map, not a half-mechanism carrying new divergences. Suite 11,235 passed / 4 skipped / 0 failed (the withheld seam's two tests account for the delta from the implementation run's 11,237). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6dc7ba51ee |
feat(physics): C4 route 4b-3 — remote teleport + cell-less through the canonical placement
Flips the last remote classification (SetPosition: teleport-advanced and
cell-less) onto 4b-1's RuntimeRemotePlacementDriveController, runs retail's
teleport_hook before the placement, and deletes the legacy remote-teleport
machinery. Contract: docs/research/2026-08-04-c4-route-4b-3-contract.md.
Retail: MoveOrTeleport @0x00516330's branch @0x00516386 -> teleport_hook
@0x005163EF -> SetFlags(0x1012) @0x00516414 -> SetPosition @0x00516420 ->
return 1 @0x00516438. The hook @0x00514ED0 runs BEFORE the placement and
regardless of its outcome. Retail places this branch unconditionally, at any
distance and any contact state (arg4 is read only @0x0051638E, after the
branch) — which is what retires AP-137's cell-less enqueue-vs-place delta.
D1 — the classifier's cell-less input is now the PRE-merge committed cell.
Retail's predicate is `this_1->cell == 0`, the BODY's own cell at
MoveOrTeleport entry (this_1 is assigned from this @0x00516334). acdream fed
the POST-merge canonical.FullCellId, which RefreshSnapshot ->
RefreshDerivedState -> SetFullCell has already stamped with the accepted wire
cell; a zero wire cell fails validation into RejectedData first. The shipped
remote cell-less predicate was therefore dead code, not merely different from
remotePlacementRequired. Threaded via a builder overload; route 1's overload
is untouched. The graphical !IsSpatiallyVisible arm of
projectionRequiresTeleportHook is deleted — a presentation predicate with no
retail analogue that fired the teleport machinery on a routine hot path.
Deleted: RemoteTeleportController (605), RemoteTeleportPlacement (85),
RemoteShadowPlacementSynchronizer (49), their 1,709 lines of tests, the
remotePlacementRequired predicate, the TeleportHookRequired plumbing, the
legacy pre-operation ConstrainTo fallback, and the player arm's legacy
!IsGrounded fallback. Net -2,030 lines.
Structural fix (two independent Opus reviews, round 1 FAIL/FAIL): three of the
four MAJORs were one defect — OnPosition carried two parallel inline copies of
the routing tail (player-guid, NPC-guid) that had drifted. Extracted
RunRemoteArmTail (3 call sites) and ApplyWireAirborneLeftoverBookkeeping (2),
both branches now share one implementation.
A1 ToConstraintArm mapped AirborneSnap -> AirborneNoOperation, so the NPC
arm armed ConstrainTo ZERO times for an out-of-contact wire-grounded
creature — a regression this slice introduced while closing a
structurally identical hole. Now maps to NearInterpolate; switch made
total with a throwing default proven unreachable.
R1 D2's write-nothing shape existed on the player arm only; NPC packets
fell through and wrote the body. Retail makes no player/NPC distinction.
R2 report_collision_end(this,1) @0x00514F31 was bound to
ShadowObjects.Suspend, a port of a DIFFERENT retail function
(remove_shadows_from_cells) that teleport_hook never calls. Now routes
to RuntimeCollisionReportingState.LeaveWorld, which wraps the private
ForceEnd in an admission-blocking transaction so a DoCollisionEnd
callback cannot recreate the contact table.
R3/A2 A teleported NPC synthesized ServerVelocity from the teleport distance
(~1,000+ m/s) and planned a run cycle from it. Both the install and
RemoteServerControlledVelocityCycle.Apply now gate on !isTeleportRoute.
BISECT HAZARD — A1's fix is correct only BECAUSE R1 landed. AirborneSnap is
reachable wire-airborne on the NPC arm only while D2's shape is missing there.
Reverting R1 alone silently inverts A1 into the opposite divergence: arming
where retail returns 0. Revert both or neither.
Also in the velocity hunk: the NPC block's two !IsPlayerGuid(update.Guid)
guards were dropped when it was wrapped in `if (!isTeleportRoute)`. Safe — all
five exit paths of the enclosing IsPlayerGuid block return, so the predicate is
unconditionally false below it — but it was unremarked by both reviews.
Register: AP-137 REWRITTEN (not deleted) to the surviving acdream-only
divergences — null classification during the login window and Rejected*
through UnroutedCatchUp keep a row. AD-42's RemoteTeleportController citation
retired; AP-136/AP-138 writer lists corrected to the two surviving non-Position
rebucket writers; AP-138 gains the teleport arm as a second producer of the
visible-without-collision residual (retirement path remains #309). AP-135 is
untouched and its two airborne bookkeeping writes are preserved on both arms.
AP-131 does not retire; #276 does not close.
Proof obligation 1: ParkCollisionResidents' overlap throw stays unreachable —
the teleport arm adds packets to the same TryBeginExclusiveAuthoredPlacement
one-operation-per-key machinery the far arm uses, opens no new operation shape,
and every DeferredCell outcome cancels synchronously with
restoreCancelledPark: true. The guarded property remains
HasOldPrefixPlacementDebt's stall, not a throw (4b-1's B2 caveat stands).
Correction to an earlier claim: LiveEntityPresentationController's
_activePlacementOwners was NOT write-never at HEAD —
remotePlacementRequired -> BeginPlacement -> Begin -> BeginAuthoritativePlacement
was a live writer chain. It becomes write-never BECAUSE this slice deletes that
chain, which is why deleting the dead half is behaviour-preserving.
Probe: ACDREAM_PROBE_REMOTE_TELEPORT=1 emits one [remote-teleport] line per
routed arm (guid, cause, hook-ran, placement status). TEMPORARY, strip with the
probe family.
Carried, disclosed not fixed: no dedicated bidirectional collision-partner test
for R2 (the wiring, not LeaveWorld itself, is what lacks coverage); the
stress test's teleport step drives hand-written field assignments rather than
the canonical arm; the per-packet runTeleportHook closure allocation (network
path, not the resolve path Slice I's 0 B discipline governs — file before
route 5 adds a fourth call site). B2: IRuntimeCollisionReportObserver has zero
production implementations, so retail's bidirectional DoCollisionEnd half still
reaches no gameplay consumer — this fix closes the wrong-function binding, not
that nobody listens.
Complete Release suite MEASURED at 11,013 passed / 4 skipped / 0 failed
(baseline 11,027/4/0; net -14 = ~33 deleted test cases against ~19 added).
Neither known flake fired (#302 PortalProjectionTests GC-allocation, #308
NakEmissionTests wall-clock).
STILL OWED: the two-client connected gate, which MUST use an NPC/creature
teleport target. Both round-1 MAJORs lived on the NPC arm and the velocity
cycle early-returns for 0x50xxxxxx guids, so a player target structurally
cannot observe A1, A2, or R3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
bb7b899bfe |
fix(physics): TS-23 - plumb real PK/PKLite/Impenetrable mover flags
Campaign P Slice P3 item 3. The wire parse (CreateObject's PublicWeenieDesc._bitfield), the decode (EntityCollisionFlagsExt. FromPwdBitfield), the per-GUID storage (ClientObjectTable. PublicWeenieBitfield), and the exemption logic (CollisionExemption. ShouldSkip) all already existed and were already correct -- every mover-flags call site just fed a GUID-prefix IsPlayer heuristic instead of the real per-entity PK/PKLite/Impenetrable state (retail OBJECTINFO::init 0x0050cf30 state |= 0x80/0x800/0x1000). Port: - EntityCollisionFlagsExt.ToMoverState translates the decoded PWD bit-space into the ObjectInfoState bit-space FindObjCollisions actually reads -- two different numberings that must not be confused. Deliberately does not translate IsPlayer (every call site already derives that correctly from its own GUID heuristic per #184 Slice 2b). - EntityCollisionFlagsExt.ResolveMoverPvpState is the one shared ClientObjectTable-backed lookup (guid -> ObjectInfoState), replacing what would otherwise have been three separate inline copies across GameWindow/LivePresentationComposition/RemoteTeleportController. - Threaded as a new optional moverPvpState parameter through RuntimeRemotePhysicsUpdater.Tick/TickHidden and RuntimeOrdinaryPhysicsUpdater.TryBegin (default None preserves every pre-P3 caller unchanged), and as PlayerMovementController.OwnPvpFlags for the local player's own two resolve call sites. - TS-23 section 12b: PlayerWeenie.JumpStaminaCost's pk parameter now reads the real PlayerKillerStatus(0x86)/LastPkAttackTimestamp(0x91) pair against retail's 20-second recency window (pkStatus in {4, 0x40} && (timestamp + 20.0) >= now), replacing the P1 hardcoded false. RuntimeMovementSkillState/Snapshot and LiveSessionEventRouter.RecomputePvpStatus push both the PWD bitfield and the PlayerKillerStatus pair reactively, riding the SAME ClientObject event triggers RecomputeBurden already uses. - A conformance test caught a genuine precision bug in the first PK-timer clock choice: DateTimeOffset.UtcNow's Unix-epoch seconds (~1.7 billion) loses ~128 seconds of precision in a 32-bit float, silently swallowing the entire 20-second window. Switched to Environment.TickCount64 (small, monotonic magnitude) -- also the more retail-plausible basis, since LastPkAttackTimestamp is itself a wire PropertyFloat and retail's Timer::cur_time is almost certainly a process/session-relative counter for the same precision reason, not an absolute epoch. Non-PK invariant (the acceptance criterion): an entity with no ClientObjectTable row, or a row whose PublicWeenieBitfield is null or 0, resolves to ObjectInfoState.None -- a no-op OR into moverFlags, bit-identical to every pre-P3 caller's hardcoded value. A dedicated test drives two real ClientObjectTable rows through CollisionExemption.ShouldSkip and confirms PK-vs-PK collides while PK-vs-non-PK and non-PK-vs-non-PK both stay exempt (walk through). Register: TS-23 retired (both the collision-flags and PK-timer halves); the stale "M2 combat must land TS-23" phase-gate note removed. dotnet build + dotnet test (Core.Tests 4008/2 skip, Runtime.Tests 425/0, App.Tests 3968/3 skip, complete solution build) all green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
dae5b1ea68 |
fix(physics): TS-46 - seed the sweep from the Setup's own sphere list
Campaign P Slice P3 item 1. Retail CPhysicsObj::transition (0x00512dc0) seeds the collision sweep from CPartArray::GetSphere (the Setup's own <=2-sphere list, each origin+radius scaled by m_scale) via SPHEREPATH::init_sphere (0x0050c670) -- not from a symmetric two-scalar (radius, height) capsule reconstruction. The human Setup 0x02000001's authored spheres are (0,0,0.475) r=.48 and (0,0,1.350) r=.48; the old reconstruction from (0.48, 1.835) produced (0,0,0.48) + (0,0,1.355), a 5 mm head-center offset the TS-46 register row documented as a residual. Port: - SpherePath.InitPath gains a sphere-list overload (ImmutableArray< FlatCollisionSphere>, scale) sharing a new InitPathCore with the existing (radius, height) overload, which is now the degenerate 2-scalar case of the same code -- byte-for-byte unchanged, so every captured-fixture replay (CellarUpTrajectoryReplayTests, DoorBugTrajectoryReplayTests, CellarLipWedgeTests) keeps passing unmodified. - PhysicsEngine.ResolveWithTransition gains optional sphereList/ sphereScale parameters; empty/default preserves the legacy scalar path for every pre-existing caller. - LiveEntityMotionRuntimeController.GetSetupMoverShape is a new sibling of GetSetupCylinder (left untouched) that resolves the Setup's own sphere list plus Setup-derived step-up/step-down (CPartArray::GetStepUpHeight/GetStepDownHeight, 0x005180d0/0x005180f0, x ObjScale, 0.4 m fallback matching the pre-existing literal). - Threaded through PlayerMovementController (both resolve call sites, new SphereList property set by PlayerModeController.ApplyStepHeights and the Headless world projection), RuntimeRemotePhysicsUpdater (Tick + TickHidden), and RuntimeOrdinaryPhysicsUpdater.TryBegin. Remote/ordinary step heights are now Setup-derived instead of a hardcoded 0.4f literal. Projectile and camera-probe sweeps are untouched (already single-sphere-exact). - PlayerModeController.ApplyStepHeights also now applies the x ObjScale multiply to the player's own step heights (previously only the remote/ordinary paths did), closing an adjacent gap the P3 research flagged. Ts46SphereListConformanceTests proves the sphere-list overload sees the exact dat spheres (not the reconstruction), that the scalar overload is unchanged, and that ResolveWithTransition's sphereList parameter actually drives the sweep (a decoy-scalar control pair using a head-height obstacle sphere). Register: TS-46 retired (both residuals it named are closed); header count corrected to 40 active TS rows. dotnet build + dotnet test (Core.Tests 3991/2 skip, Runtime.Tests 425/0, App.Tests 3968/3 skip, complete solution build) all green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8a7a0837e1 |
feat(render): Vulkan campaign V11 step 2 — delete the OpenGL backend
Vulkan is the sole, user-signed-off backend (V10 landed) and step 1 already removed ImGui/Studio/DevTools. This step deletes the GL rendering backend itself: every Gpu/Gl/** implementation, the Wb ManagedGL*/GLHelpers/GLSLShader/GLStateScope/RenderStateCache/ BindlessSupport family, Shader/ShaderProgramConstruction/SamplerCache, RenderBootstrap, and RenderFrameGlStateController. GameWindow.cs's Run()/CreateGraphics()/CreateBackbufferReader()/ OnLoad() collapse to their Vulkan-only arm; GameWindowGraphics loses its OpenGlGameWindowGraphics subclass. RuntimeOptions.RenderBackend and RenderBackendKind (incl. the Gl member of GpuBackendKind) are gone — there is nothing left to select between. The five world-draw dual-arm renderers (WbDrawDispatcher, EnvCellRenderer, TerrainModernRenderer, ParticleRenderer, SkyRenderer) and the composition roots (WorldRenderComposition, HostInputCameraComposition, LivePresentationComposition, FrameRootComposition) collapse to their RHI-only arm. GL-only diagnostic properties with a live external reader (DynamicBufferCount and friends) simplify to a documented `=> 0`/no-op rather than disappearing, since the reader is out of this commit's scope. A few GL-flavored mechanisms turned out to be backend-neutral once isolated: GlConstructionCleanupLedger is renamed ResourceConstructionCleanupLedger (exception-chain walking has nothing to do with GL), and GlfwNativePlatformProbe moved out of the otherwise GL-only GraphicalCapabilityRecord.cs into GraphicalWindowBackendSelection.cs before the rest of that file was deleted. Test files with no surviving subject are deleted outright (GraphicalCapabilityRequirementsTests, ShaderProgramConstructionTests, PortalDepthShaderParityTests, TextureCacheBindlessTests, TextRendererFailureSafetyTests, ClipFrameUploadTests, every Gpu/Gl/*Tests, GlTextureOwnershipTests, RenderFrameGlStateControllerTests); others get their dead GL-only members trimmed while their live assertions stay (ClipFrameLayoutTests' MeshClipSsboBinding check now reads GpuBindingModel.StorageClipRegions, the same binding index under its new backend-neutral name; GpuResourceRetirementTransactionTests drops its OpenGLGraphicsDevice-subclassing test double and the two GL queue tests it existed for). EnvCellRendererTests' construction helper now builds a real ObjectMeshManager via VulkanMeshPipelineDevice instead of passing null through a null-forgiving operator, since the RHI constructor never tolerated a null mesh manager and the old GL constructor (which did) is gone. Deferred to the next two steps, deliberately not touched here: the Silk.NET.OpenGL/.Extensions.ARB package references, IMeshPipelineDevice.Gl (WbMeshAdapter's GL? threading stays in place), Chorizite.Core's stale csproj comment (the package itself is still load-bearing — TextureFormat and friends are used well beyond the deleted ManagedGLUniformBuffer), and the CI/gate scripts. Build: `dotnet build AcDream.slnx -c Release` — 0 warnings, 0 errors. Tests: full-solution `dotnet test` green across every project (App.Tests 3937/3940 + 3 skips, Core.Tests 3296/3298 + 2 skips, all others 100%); the 2 App.Tests names that flake under full-suite parallel execution (#250-family, documented pre-existing) pass in isolation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
844cf092a1 |
feat(render): Campaign V slice V11 commit 1 - delete ImGui, Studio, and the DevTools frontend
The ImGui developer-tools stack (AcDream.UI.ImGui), UI Studio (src/AcDream.App/Studio), and the DevToolsFramePresenter/ SettingsDevToolsCompositionPhase ImGui composition machinery are removed. Vulkan never composed a DevTools frontend (DevToolsEnabled already forced false whenever the backend was Vulkan); this commit makes that permanent by deleting the only implementation rather than leaving a dead branch behind. What moved: Studio/SampleData.cs is a live production dependency (InteractionRetainedUiComposition's character-sheet fallback, plus three UI.Layout test files) - git mv'd to src/AcDream.App/UI/Layout/SampleData.cs, namespace AcDream.App.UI.Layout, and trimmed to the SampleCharacter API that is actually still called (BuildObjectTable/AddItem/AddEquipped/the item-guid and icon constants had zero callers left once the Studio fixture provider that used them was deleted). What survives as backend-neutral seams, per the tests that still exercise them: IDevToolsFrameLifecycle (moved into RenderFramePreparationController.cs, now always bound to null), IFramebufferDevToolsTarget/FramebufferDevToolsBinding in FramebufferResizeController.cs (its concrete DevToolsFramebufferTarget adapter is deleted), and IDevToolsGameplayCommands in GameplayInputCommandController.cs (DevToolsGameplayCommands becomes a documented no-op instead of forwarding to the deleted presenter). A follow-up re-homes Settings/Debug onto the retained UI through IPanelRenderer; until then keybind remapping falls back to editing keybinds.json. DevToolsEnabled is now `private const bool DevToolsEnabled = false`. RuntimeOptions.DevTools is unchanged and still reaches VulkanGraphicsContext for the optional debug-utils extensions; Program.cs now logs one line when ACDREAM_DEVTOOLS=1 explaining that the ImGui UI is gone and the flag is Vulkan-only now. Removed: AcDream.UI.ImGui (project + ImGui.NET/Silk.NET.OpenGL.Extensions.ImGui package refs), src/AcDream.App/Studio (minus SampleData.cs), DevToolsFramePresenter.cs and everything only it constructed (ISettingsDevToolsCompositionFactory, RetailSettingsDevToolsCompositionFactory, DevToolsCompositionOwner, IGameWindowSettingsDevToolsPublication, SettingsDevToolsOptionalDependencies, the "developer tools" shutdown-ledger stage and its DevTools-typed fields on IngressShutdownRoots/ RenderShutdownRoots), the ui-studio Program.cs verb, and the cimgui native manifest entries in GraphicalHostPlatformServices. GameWindow.cs's DevTools composition branch, its _vitalsVm/_debugVm/_devToolsComposition/ _devToolsFramePresenter/_devToolsCommandBus fields, and every settingsDevTools .DevTools?.* access across FrameRootComposition.cs/SessionPlayerComposition.cs are gone with it. Build green; complete Release solution suite 8,830 / 5 skips (App Tests 4,097/3 skips run standalone - one #250-family zero-allocation test flakes under the full parallel `dotnet test AcDream.slnx` run, a pre-existing, documented class unrelated to this change). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1f25a60999 |
fix(diag): Campaign V slice V7 commit 2 - pin the world clock, because the route never did
THE ROUTE'S TIME PIN NEVER HELD, AND EVERY V7 NUMBER SO FAR WAS TAKEN THROUGH IT.
connected-backend-differential.route.txt opened by pressing
AcdreamCycleTimeOfDay three times, on the stated theory that the cycle walks
live -> 0.00 -> 0.25 -> 0.50 and lands on noon. The mechanism underneath is
WorldTimeService.SetDebugTime, and SyncFromServer clears it -- deliberately,
because that setter is the /time slash command and the command is meant to be a
look-at-dusk-for-a-moment affordance rather than a mode. There is even a test
pinning that behaviour: WorldTimeDebugTests.SyncFromServer_ClearsDebugOverride.
ACE sends TimeSync every few seconds. The clock was therefore un-pinned again
long before the route reached its first stop, on every run this campaign has
taken, including V6m's smoke pair.
The Dereth clock does not only move the sky. It moves the SUN, so it moves the
directional term of every lit surface in the scene.
MEASURED, rather than argued. A probe route captured each stop TWICE, 45 seconds
apart, in the same run on the same backend:
GL, Holtburg, capture 1 vs capture 2: 205,772 px 22.33%
Vulkan, Holtburg, capture 1 vs capture 2: 218,732 px 23.73%
GL, Facility Hub, capture 1 vs capture 2: 108,795 px 11.81%
Vulkan, Facility Hub, capture 1 vs capture 2: 130,206 px 14.13%
One backend, one stop, nothing moving, and a fifth of the frame changes while
you watch. No cross-backend number means anything against that noise floor, and
the cross-backend numbers taken during that probe run were duly absurd -- 56% at
Holtburg, where the two launches happened to be at different times of Dereth day.
THE FIX IS A PIN THAT OUTRANKS THE SERVER CLOCK AND SURVIVES SYNC.
WorldTimeService.PinnedDayFraction is a nullable day fraction that wins over both
Calendar.DayFraction(NowTicks) and SetDebugTime, and that SyncFromServer does not
touch. ACDREAM_WORLD_TIME -> RuntimeOptions.PinnedWorldDayFraction ->
WorldEnvironmentController, which writes it once: the Runtime environment owner
and its clock are session-scoped, so one write outlives every teleport and every
reveal generation. Values outside [0, 1) are REJECTED rather than clamped -- a
day fraction of 12.5 is a typo, and silently pinning the world at it would be
worse than ignoring it.
Unset is the default and every ordinary run. The calendar DATE still advances,
which is intentional: the date drives day-group selection, and ACDREAM_DAY_GROUP
already pins that. The differential gate forces the pin at 0.5 -- noon, which is
what the three presses were aiming at -- on both launches, and the route's
presses are deleted rather than left in as decoration.
This is instrument determinism on the footing of ACDREAM_DAY_GROUP and V7's
ACDREAM_SKY_PHASE_SECONDS, not a workaround: it is off by default, nothing in the
shipping client reads it, and the alternative was to keep measuring two backends
through a fifth of a frame of sunlight.
WHAT IT MOVED. The same three-stop route, same commit otherwise, before and after:
holtburg_town 9.05% -> 2.86% (83,438 -> 26,330 px)
facility_hub_interior 12.16% -> 0.78% (112,075 -> 7,176 px)
aerlinthe_island 23.09% -> 6.82% (212,824 -> 62,892 px)
The interior stop is the headline. V6m recorded it as a route defect on the
theory that the indoor spring-arm camera settles to different distances in two
runs; that theory is now refuted. The camera was fine. The interior was lit
differently because the sun had moved, and with the sun held still the stop drops
by a factor of 15 to 0.78% -- close enough to the 0.001 threshold that its
remaining population is worth naming rather than guessing at. No route change was
needed and none was made.
WHAT REMAINS, per the difference maps, all of it now attributable by eye:
the animated portal beside the Holtburg stop; distant scenery foliage; wandering
NPCs and a chimney smoke plume, which are animation and emitter phase; the vitals
readouts, whose stamina and mana genuinely regenerate at different rates across
two logins minutes apart; and, at Aerlinthe, a dense low-magnitude speckle in a
scene whose mean luminance is 28/255 -- half of its differing pixels are exactly
delta 3, one step over a tolerance that is absolute rather than relative.
Gates. Release build green. App tests 4,134 passed / 3 skipped (one new: the
day-fraction range check); AcDream.Core.Tests WorldTimeDebugTests 6/6, including
the two new ones that assert the pin survives a sync and outranks the transient
override. GL offline pixel gate against the pre-slice tree: 2.66e-05, 15 pixels
of 563,200, inside the documented 9-31 band -- GL did not move.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
b16f820643 |
feat(render): Campaign V slice V6h — the Vulkan composition host
ACDREAM_RENDER_BACKEND=vulkan now runs the real GameWindow composition rather
than a second main(). All nine phases execute: DAT load, streaming, camera,
entity table, session, and the real retained UiHost drawing through the RHI.
No world renderers — they are raw GL until V4t and the world arm behind it.
The offline log is the client's own (acdream.pak opened, 6266 spells, Region
0x13000000, "loading world view centered on 0xA9B4FFFF", fourteen retail
LayoutDesc lines, streaming radii), and the captured frame is the retail
retained UI: vitals, combat/spell bar with DAT scarab icons, the nine-slot
toolbar, chat with tabs and Send, radar/compass with dat-font glyphs. Sampled
against the GL capture the widgets agree — chat interior RGBA (25,24,27,158)
vs (22,21,23,158), vitals bar (117,1,0) and toolbar slot (0,11,17) identical.
Three seams, as §5.5.9 specified:
1. Platform acquisition — already generic — publishes GameWindowGraphics
instead of a bare GL. Phases that still speak raw GL read Graphics.Gl and
take their Vulkan arm when it is null; each branch names the slice that
removes it.
2. VulkanHostInputCameraCompositionFactory is a new file and the whole of the
Phase-1 fork: four graphics members differ, input/camera/pointer delegate.
The default factory is chosen inside the phase from the platform result.
HostInputCameraResult gained backend-neutral Retirement and FrameSlots.
3. The frame root forks on one condition. The GL world-scene assembly is
unchanged, wrapped in `if (gl is not null)`; the Vulkan arm's graph is one
backbuffer clear pass computing the same RenderFrameFoundation from the same
clock and weather owners, then private presentation over it.
§5.5.9's three TextureCache couplings are unpicked: the constructor takes GL?
and rejects bindless without one, world entry points route through a Gl
property that throws naming V4t, and the (GlGpuTexture) VRAM-accounting cast
became a backend test. That cast's stated reason — DrawSprite's texture-unit
binding — was already stale, deleted at V6d.
VulkanBringUpHost is reduced to the capability-probe harness it is named for:
the instance/surface/device/swapchain sequence moved into VulkanGraphicsContext,
which the composition host and the harness now share. It is reached only with
ACDREAM_VULKAN_PROBE=1.
One latent Vulkan defect surfaced and is fixed here. The first composition-host
frame died with ErrorDeviceLost; validation named VUID-vkCmdDraw-None-08600 —
descriptor set 2 never bound. VulkanGpuPassEncoder bound sets 0/1/2 only as a
side effect of BindStorageBuffer/BindUniformBuffer, so a pass sampling the
texture table while binding no buffer — every retained-UI and debug-line pass —
drew with the table unbound. It survived V6c-V6g because the bring-up host
always drew VulkanRhiScene first and the UI pass inherited its binds; the
composition host has no 3-D scene. The fix is one line in the encoder's
constructor beside the viewport and scissor defaults, which exist for exactly
the same reason: a pass opens with complete binding state rather than depending
on what preceded it.
Gates: strict GL offline pixel gate against
|
||
|
|
e8a4c1af3f |
feat(render): Campaign V slice V5 - Vulkan bring-up, dark
Instance, physical-device selection, logical device, queues, swapchain, and the three-layer capability gate, behind ACDREAM_RENDER_BACKEND=vulkan. Nothing of the game renders through it. OpenGL stays the default and the only live backend until V10, and with the variable unset or set to gl the GL path executes not one new statement. The shape of the slice. Plan §4.11 asks the Vulkan gate to mirror the GL one exactly - passive record, active probes, an Evaluate producing operator-facing sentences, NotSupportedException into Program.cs's exit-code-4 contract, and an atomic JSON report. The harder question was where to put the seam, because a capability gate is precisely the code you cannot exercise on the machine that already passes it: this box has one discrete GPU, so device ranking, the split- queue path, an sRGB-only surface, a minimised window and a device missing descriptorBindingVariableDescriptorCount are all unreachable by running the client. So every decision the gate makes is a pure function over plain records, and the Silk interop layer only has to be right about which Vulkan field feeds which property. VulkanPhysicalDeviceSelection ranks candidates, VulkanExtensionSelection does the required-versus-optional set arithmetic, VulkanSwapchainConfigurationFactory chooses format, present mode, image count, extent, usage, transform and composite alpha, VulkanSwapchainRecreationPolicy classifies every acquire and present result, and VulkanCapabilityRequirements.Evaluate turns a captured record into failure sentences. All of it is unit-tested with no driver, no device and no window. This commit is the integration of that work onto the post-revert tree. The V5 branch was written on |
||
|
|
ad61f250fb |
Revert "feat(render): Campaign V slice V4d-2 - move terrain onto the RHI"
This reverts commit
|
||
|
|
b064668b63 |
feat(render): Campaign V slice V4d-2 - move terrain onto the RHI
TerrainModernRenderer records through IGpuPassEncoder instead of calling GL directly. V4d-1 already converged its two matrix uniforms; this is the plumbing. What moved. The per-frame indirect command array became an IGpuFrame.AllocateRing slice, which retires the three-deep per-frame-slot indirect buffer pool outright. That pool existed so a second terrain draw within one frame - a retail outside view can issue several - could not overwrite an earlier draw's still-pending commands; the frame ring gives that structurally, because every allocation within a frame is distinct memory that lives until the frame retires. DynamicIndirectBufferCount now reports 0, which is the truth rather than a silent change. The vertex and index arena became an IGpuBuffer pair. AddLandblock's two BufferSubData calls are Upload, and EnsureCapacity's grow-and-copy is IGpuBuffer.CopyTo, still device-side so resident landblock meshes never round-trip through system memory. The global VAO is gone: the pipeline owns one shaped by the vertex layout, and the encoder re-issues attribute pointers on every BindVertexBuffer. Locations 2-5 use GpuVertexFormat.UByte4UInt, added at |
||
|
|
096dd203fa |
feat(render): Campaign V slice V4a - port TextRenderer/BitmapFont/DebugLineRenderer/TextureCache UI path onto IGpuDevice
Second attempt at V4a after |
||
|
|
9aaf97e785 |
Revert "Campaign V slice V4a" - it lost world multisampling
This reverts
|
||
|
|
ceec3bc440 |
feat(render): Campaign V slice V4a - port TextRenderer/BitmapFont/DebugLineRenderer/TextureCache onto IGpuDevice
TextRenderer, BitmapFont, DebugLineRenderer, and TextureCache's UI-texture
upload path (GetOrUploadRenderSurface/UploadRgba8) now issue every draw and
resource creation through the pinned IGpuDevice/IGpuFrame/IGpuPassEncoder
RHI contract instead of raw GL. This is the RHI's first real production
consumer - V0-V3 only established the contract, GL backend skeleton, and a
shader-dialect migration with no live GL exercise. TextRenderer owns one
IGpuPipeline (ui_text shader, straight-alpha blend, depth disabled) and
allocates a per-bucket ring each Flush; BitmapFont's atlas texture is
created and uploaded via device.CreateTexture/.Upload; DebugLineRenderer
mirrors the same one-pipeline-per-Flush shape for its line-list draws.
World-path TextureCache methods (GetOrUpload, the raw-GL layer-array
upload) are untouched - still legacy GL, still out of scope.
Frame lifecycle: GpuDeviceFrameLifetime (RenderFrameOrchestrator.cs) wraps
IGpuDevice.BeginFrame()/IGpuFrame.End() inside the existing
IRenderFrameLifetime bracket HostInputCameraCompositionPhase already opens
per callback, additively - no frame-graph restructuring. Ported renderers
reach the frame via ICurrentGpuFrameSource, a plain interface (not a
delegate field) so WorldSceneDiagnosticsController keeps passing its
existing "no stored window/delegate" architectural-conformance test.
Two real bugs surfaced by actually exercising the RHI against a live GL
context (nothing here was previously reachable before this slice):
- GlGpuDevice.BeginFrame() now resets the render-state cache every frame.
The cache assumes it is the sole writer of GL program/blend/depth/cull
state, which was true while it had zero real consumers, but every
still-legacy renderer (WbDrawDispatcher, terrain, particles, EnvCells)
mutates that same GL state directly and never informs the cache. Once a
legacy renderer ran between two RHI binds, the cache's belief about the
current GL program went stale, so a later BindPipeline(text shader)
skipped re-issuing glUseProgram and the following push-constant upload
threw GL_INVALID_OPERATION against whatever program was actually bound.
Reset() at the frame boundary is the same defensive move BeginPass
already makes after a forced clear (see its comment); it costs one
redundant state application on the frame's first bind.
- GL_MULTISAMPLE has no representation in the pinned contract. Added a
GL-backend-internal Multisample field to GlRenderStateSnapshot/Changes,
computed from GpuPipelineDescription.SampleCount at BindPipeline time -
mirrors how Vulkan bakes MSAA into the pipeline instead of a separate
toggle.
Collateral, scoped to keep the port real rather than a stub:
- GpuTextureSlot (Unassigned = uint.MaxValue, NOT 0) now flows through
every consumer of TextureCache.GetOrUploadRenderSurface/UploadRgba8 and
TextRenderer.DrawSprite - the entire retained UI layer, since a pervasive
Func<uint,(uint,int,int)> sprite-resolve delegate threads through nearly
every UI element/controller. Every prior `== 0` / `!= 0` "no texture"
check became `.IsAssigned` / `!.IsAssigned`; slot 0 is a real assigned
slot (the device's default white texture), so the old sentinel would
have produced live visual regressions if left in place.
- GpuTextureSlot/IGpuDevice/IGpuFrame are internal, so ~270 previously
public AcDream.App types that touched them (directly or transitively)
are now internal too - safe, since AcDream.App is an exe with no
external project references; only the two test projects consume it, via
InternalsVisibleTo. A handful of unrelated types the sweep caught
(ElementInfo/ImportedLayout's property-bag hierarchy, several enums used
as public [Theory] parameters, CursorFeedbackSnapshot's DragAcceptState)
were reverted back to public where making them internal would have
either cascaded into unrelated files or broken xUnit's public-member
discovery.
- ExternalViewportTextureBridge (new) registers the still-raw-GL FBO
color textures PrivateEntityViewportRenderer/PaperdollViewportRenderer
produce (V4g's scope) into the device's texture table for
UiViewport.TextureHandle, via a temporary
GlGpuDevice.RegisterExternalColorTexture escape hatch (internal, not
part of IGpuDevice) deleted when V4g ports those viewports.
- TextRenderGlStateScope.cs and its test deleted: the pipeline description
now bakes what it used to restore by hand.
- ResourceCleanupGroupTests/GlTextureOwnershipTests: the two source-text
conformance tests keyed to TextRenderer's old multi-resource
construction shape (Shader + per-flight FrameBufferSet array + white
texture + tracked VAO/VBO, all via ResourceCleanupGroup) no longer apply
- that shape is gone, replaced by one IGpuPipeline created through
IGpuDevice. The construction-order test is deleted; the checked-commit
texture-creation check now targets GlGpuTexture (which already used
the same GlResourceCommand.CreateName primitive before this slice).
Gates:
- dotnet build -c Release: 0 warnings, 0 errors (AcDream.App has
TreatWarningsAsErrors).
- dotnet test tests/AcDream.App.Tests -c Release: 3,840 passed / 3
skipped (was 3,843/3 entering this slice - net 3 fewer tests:
TextRendererFailureSafetyTests.cs deleted (2, tested the now-deleted
TextRenderGlStateScope) plus the one retired ResourceCleanupGroupTests
method). Full solution: 8,908 passed / 5 skipped across all nine test
projects.
- Offline pixel gate (tools/run-offline-pixel-gate.ps1, parent
|
||
|
|
4f94ad7ddd |
feat(render): Campaign V slice V1 - OpenGL RHI backend (dark)
Implements GlGpuDevice and the rest of AcDream.App.Rendering.Gpu.Gl,
filling the V0-pinned IGpuDevice contract on OpenGL 4.3. This is the
first of the port slices described in
docs/plans/2026-07-27-vulkan-campaign.md: every later renderer port
(V2 onward) needs a real, driver-proven GL implementation of the RHI
to port onto, and the GL backend is deliberately built to be
behaviour-preserving rather than optimal, because that is what turns
each subsequent slice's pixel gate into a strict identity check
instead of a moving target. The Vulkan backend (V5+) is where the
actual efficiency gains land.
GlGpuDevice is a fresh root, not derived from Chorizite's
BaseGraphicsDevice/OpenGLGraphicsDevice - shedding that inheritance is
one of the things this campaign explicitly does. It owns its own
BindlessSupport instance rather than sharing the legacy WB render
path's, which is what lets it be constructed the moment a GL context
and a GpuFrameFlightController exist, with no dependency on when
WorldRenderCompositionPhase happens to detect bindless support later
in startup. The ring buffer keeps a managed staging array plus a real
GL buffer per flight slot and flushes with one BufferSubData
immediately before each Draw/DrawIndexed/MultiDrawIndexedIndirect
(never at bind time, since a renderer may still write after binding);
V1 throws on an over-capacity ring request rather than growing it,
since nothing consumes the device yet and a silent grow would hide a
future renderer's real working set. The texture table is a bump/free-
list allocator over a managed uvec2 handle array, gated through the
frame-flight retirement queue so a released slot cannot be reused
while a submitted frame might still read it. Push constants are
applied by uniform name on the currently-bound program, cached per
program, and explicitly re-applied whenever BindPipeline switches
programs - GL uniforms are per-program state, so the "survives
pipeline changes within a pass" guarantee the interface documents (a
freebie on Vulkan's shared pipeline layout) has to be emulated here.
BindlessSupport gained one additive method,
GetResidentHandle(texture, sampler), calling the same
ArbBindlessTexture.GetTextureSamplerHandle entry point
ManagedGLTextureArray already uses through a different path. The
existing GetResidentHandle(texture) cannot express
IGpuDevice.RegisterTexture's documented pair semantics ("the same
texture registered with two samplers occupies two slots"), so this
was the minimal change needed rather than a workaround.
The pure bookkeeping - ring watermark/alignment arithmetic, the
texture-slot allocator, render-state diffing, the push-constant field-
to-uniform-name table, and GL format mapping - lives in small GL-free
classes so it is unit-testable without a live context, following the
same seam pattern GpuFrameFlightController already uses for its fence
API. GlGpuTimerPool follows suit with an injectable timer-query API.
The device is constructed in HostInputCameraCompositionPhase
immediately after the frame-flight controller (the same phase that
already builds GpuFrameFlightController), rather than in
WorldRenderCompositionPhase as first considered: GlGpuDevice's self-
contained bindless detection means it has no ordering dependency on
the legacy WB path's BindlessSupport, so it can be proven against the
real driver as early as possible while keeping the composition change
to one phase. Composition, publication, and shutdown wiring follow
the existing acquire/publish/fault-injection pattern exactly, and GPU
device disposal is scheduled through the frame-flight retirement queue
before that queue itself is torn down. Nothing consumes the device
yet - that starts at V4a - so this slice's pixel gate is trivially a
tripwire.
App tests: 3834 passed / 3 skipped (V0 baseline 3785 + 49 new: ring,
texture-slot, render-state, push-constant, format-mapping, enum-
mapping, and timer-pool tests, plus one new fault-injection point in
the existing composition theory).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
11501d52ca | feat(linux): gate graphical backends and capabilities | ||
|
|
66f114b258 | feat(linux): add graphical platform services | ||
|
|
9569dadb57 | feat(headless): share immutable gameplay content | ||
|
|
ce41efb9e5 |
refactor(runtime): cut graphical host over to canonical root
Make every App composition phase borrow one GameRuntime, retire the duplicate view/event adapters, and dispose the root only after its graphical borrowers release. This preserves synchronous UI commands while giving shutdown one exact ownership ledger. Co-authored-by: OpenAI Codex <codex@openai.com> |
||
|
|
a6860d5563 | refactor(runtime): own world reveal generation | ||
|
|
902076c0a4 | refactor(runtime): own world environment state | ||
|
|
7e6033d0ad |
refactor(runtime): own per-session physics simulation
Move the sole PhysicsEngine, production cache, collision admissions, canonical bodies and hosts, remote components, ordinary/remote worksets, simulation, cell commits, and shadow synchronization under RuntimeEntityObjectLifetime. Keep App as the prepared-asset, animation-input, and render-projection adapter while preserving the named-retail update and collision order. Add exact-incarnation, object-clock, callback-reentrancy, GUID-reuse, two-runtime isolation, source ownership, collision publication, and graphical projection coverage. Release build and the complete 8,588-test solution pass. Co-authored-by: Codex <noreply@openai.com> |
||
|
|
aa3f4a60f8 |
refactor(runtime): own local movement and outbound cadence
Move the canonical local movement controller, body/motion managers, object clock, movement wire data, and MTS/jump/AP sender into AcDream.Runtime. Replace process skill defaults with typed Runtime character options, make graphical and direct commands borrow one autorun owner, retain the construction-time PartArray seam, and include movement in terminal ownership convergence. Preserve the accepted pre-inbound movement/jump and post-inbound autonomous-position order while moving the exact packet/cadence fixtures into Runtime tests. Add graphical/direct parity, two-instance isolation, teardown, allocation, architecture, and divergence-path coverage. Co-authored-by: Codex <noreply@openai.com> |
||
|
|
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> |
||
|
|
f5f7b4177f |
refactor(runtime): own interaction transactions
Move use throttling, appraisal identity, queued interactions, and exact post-arrival pickup state beneath RuntimeActionState. Keep App as the picker, movement, transport, and retained-presentation adapter while preserving retail send and UseDone ordering. Add reset, disposal, GUID-reuse, callback-reentrancy, and transport-failure coverage. Co-authored-by: Codex <noreply@openai.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
d02a12ceac |
refactor(runtime): own magic and player state
Move the coupled Spellbook and LocalPlayerState into one Runtime-owned character graph, route content, live-session, retained UI, reset, and shutdown through that exact owner, and delete the duplicate desired-component snapshot from inventory state. Preserve synchronous retail update and reset ordering while adding independent-instance and retryable-failure coverage. Co-authored-by: Codex <codex@openai.com> |
||
|
|
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> |
||
|
|
c9d25ade50 |
refactor(runtime): own communication and social state
Construct chat history, negotiated channels, friends, squelch, and reply targets in one presentation-independent Runtime owner. Make live routing, retained UI, devtools, and current-runtime projections borrow the exact instances, preserve reconnect reset semantics, and publish failure-isolated reentrant-safe chat commits. Co-authored-by: Codex <codex@openai.com> |
||
|
|
ce3ac310d9 |
refactor(runtime): publish canonical entity object deltas
Issue stable Runtime identities at canonical registration, publish entity and inventory commits through one generation-stamped synchronous stream, and make graphical adapters borrow the same direct views and events as a no-window host. Preserve exact projection teardown and retail mutation order while removing App-side event reconstruction. Make the hard-recenter ordering fixture independent of the production two-millisecond frame budget so its injected-failure gate is deterministic. Co-authored-by: Codex <codex@openai.com> |