From 1aa7709988e31d00e76862d899d697e64d7deb3b Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 10 Aug 2026 13:09:32 +0200 Subject: [PATCH] =?UTF-8?q?fix(chat):=20CH6a/b=20rework=20=E2=80=94=20grip?= =?UTF-8?q?=20media,=20retail=20window-id=20model,=20floaty=20fixture?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Applies docs/research/2026-08-10-ch6ab-review-findings.md in full: - BLOCKER 1: UiResizeGrip now carries its ElementInfo/resolve pair and draws its own authored DirectState media (a synthetic parameterless grip still draws nothing, preserving existing resize-drag tests). DatWidgetFactory.BuildResizeGrip threads resolve through. All seven live grips on the main chat window now resolve a non-zero sprite, restoring the visible borders/corners CH6a silently dropped. - SHOULD-FIX 2: ChatWindowState gains BroadcastTargetWindow, a sentinel distinct from every real window id (0-4), fixing the bug where the main window's explicit-addressing branch coincided with the broadcast check (both were literal 0). SetFilter's main-window no-op is dropped — the main window's filter is now genuinely settable. ChatWindowController .Bind takes a ChatWindowState (the same canonical instance the floating windows already share) and GetTranscriptLines builds a real accept predicate instead of accept:null. Verified safe: ClientLocal (0x1A) never reaches ChatLog (AddText routes it to the SpewBox and returns), so nothing observable regresses. - SHOULD-FIX 3: UiButton.SuppressSelfToggle stops the four chat-window indicator buttons (DAT property 0x0B=true, no retail click handler) from flipping their own Selected mirror on a stray click. - SHOULD-FIX 4: generated and committed chat_floaty_2100005b.json from the real installed dats; added the permanent RetailLayoutFixtureGenerator entry. All three flagged FloatingChatWindowController assumptions (input field, title bar, close button) are confirmed correct against real data — no controller code changes needed. New finding: unlike the main window, ALL EIGHT floaty border/corner elements are live Type-9 grips (the floaty's own title bar is its move handle), so a floaty window resizes from every edge and corner. - SHOULD-FIX 5: register row AP-189 documents the shared-500-entry/ 200-line-tail vs retail's per-window 10,000-line scrollback depth gap. - NITs 1-5: documented the filter-persistence-only-on-/saveautoui asymmetry and the reconnect-preserves-filters intent; corrected the research doc's modifier-mask mislabel and the "ONLY function" false superlative; moved WrapText off ChatWindowController onto ChatTranscriptRenderer, closing the circular dependency. Full Release suite: 12,420 passed / 4 skipped / 0 failed (baseline 12,392/4/0 at 22020ef2; net +28 tests, zero regressions). Co-Authored-By: Claude Opus 5 --- .../retail-divergence-register.md | 3 +- docs/plans/2026-08-09-chat-parity-campaign.md | 99 +- .../2026-08-09-chat-retail-window-shell.md | 40 +- .../UI/Layout/ChatTranscriptRenderer.cs | 104 +- .../UI/Layout/ChatWindowController.cs | 144 +- src/AcDream.App/UI/Layout/DatWidgetFactory.cs | 10 +- .../UI/Layout/FloatingChatWindowController.cs | 3 +- .../UI/Layout/IndicatorDetailText.cs | 2 +- .../UI/Layout/ItemAppraisalReport.cs | 2 +- src/AcDream.App/UI/RetailUiRuntime.cs | 12 + src/AcDream.App/UI/UiButton.cs | 19 +- src/AcDream.App/UI/UiResizeGrip.cs | 72 +- src/AcDream.Core/Chat/ChatWindowState.cs | 78 +- .../Gameplay/RuntimeCommunicationState.cs | 7 + .../UI/Layout/ChatLayoutConformanceTests.cs | 42 +- .../UI/Layout/ChatTranscriptRendererTests.cs | 89 + .../UI/Layout/ChatWindowControllerTests.cs | 112 +- .../UI/Layout/FixtureLoader.cs | 19 + .../FloatingChatLayoutConformanceTests.cs | 120 + .../UI/Layout/RetailLayoutFixtureGenerator.cs | 1 + .../Layout/fixtures/chat_floaty_2100005b.json | 5474 +++++++++++++++++ tests/AcDream.App.Tests/UI/UiButtonTests.cs | 28 + .../Chat/ChatWindowStateTests.cs | 52 +- 23 files changed, 6284 insertions(+), 248 deletions(-) create mode 100644 tests/AcDream.App.Tests/UI/Layout/ChatTranscriptRendererTests.cs create mode 100644 tests/AcDream.App.Tests/UI/Layout/FloatingChatLayoutConformanceTests.cs create mode 100644 tests/AcDream.App.Tests/UI/Layout/fixtures/chat_floaty_2100005b.json diff --git a/docs/architecture/retail-divergence-register.md b/docs/architecture/retail-divergence-register.md index 7543512b..b2a0581f 100644 --- a/docs/architecture/retail-divergence-register.md +++ b/docs/architecture/retail-divergence-register.md @@ -170,7 +170,7 @@ readiness/requeue adaptation. See --- -## 3. Documented approximation (AP) — 134 active rows (AP-188 filed 2026-08-10 at Campaign CH slice CH6b — a floating chat window's chat entry always sends on the Say channel because the floaty LayoutDesc authors no talk-focus menu and acdream does not (yet) share the main window's currently-selected channel across all five chat-window instances; AP-187 filed 2026-08-10 at Campaign CH slice CH6b — the four floating chat windows' text-type filters persist in local `settings.json` only (`ChatSettings.ChatWindow1..4Filter`), with no analog to retail's server-side `0x1000008C` GameplayOptions blob, so a character's floaty filter customization does not travel between acdream installs or round-trip to/from a retail client sharing the same character; AP-186 filed 2026-08-10 at Campaign CH user-gate round 3, while tracing DoHelp's print sequence — ChatCommandRouter's type-0x1A local-presentation fallbacks (now including the byte-exact "Unknown command" text) still render via the chat scroll instead of the SpewBox, an architectural layering gap (UI.Abstractions cannot see Runtime's `RuntimeCommunicationState.AddText`), tracked as ISSUES.md #367; AP-185 filed 2026-08-10 at Campaign CH slice CH6a — the chat window's UiLocked border-art cosmetic swap is unported, see the row for detail; AP-184 filed 2026-08-09 at Campaign CH user-gate round 2, item 3 — three of the seven retail `/help` group-topic listings (channels/chatting/commands) remain PARTIAL, not fully verbatim: their detail text is built in full or in part by `ClientCommunicationSystem::HelpStupidChannelHack @0x0056f290`, which constructs its output from three BN-mislabeled data fragments (`&ClientCommunicationSystem::\`vftable'.RecvNotice_StartBarberNotice` etc. are NOT real vtable dispatch) concatenated around a live `ChannelSystem::GetChannelName` lookup, not decodable with confidence from a static string sweep; each partial group keeps its own verbatim summary line and an explicit UNVERIFIED note instead of the fully-fabricated meta-message the user caught on `/help death` (that group, plus status/text/allegiances, are now COMPLETE verbatim listings); tracked as ISSUES.md #364. Round 2 item 2 also deletes `PortalWaitNoticeController` (the dedicated centered-overlay presentation the user reported was the wrong retail surface) and reroutes the portal-space wait-cue notice through the same `AddText`/SpewBox chokepoint every other on-screen interface-text site uses — AP-178's open SpewBox position/extent/font/colour questions now cover this notice too, since its separate controller and consts are gone; no new row was needed for the surface mismatch itself, since it was never separately registered (`PortalWaitNoticeController`'s own doc comment asserted "not a chat message" as an accepted design, not a flagged divergence). AP-150 RETIRED 2026-08-09 at Campaign CH user-gate round 1, item D (#329) — `PortalTunnelPresentation.TickRotation` now emits `"In Portal Space - Please Wait..."` unconditionally on every rotation-segment expiry, exactly matching `gmSmartBoxUI::UseTime`'s `else`-arm at 0x004D6FCD, instead of gating on `_waitCueVisible`, which only ever went true after the invented 5-second `RuntimeWorldTransitState.RetailWaitCueDelay` hold; `RetailWaitCueDelay`/`ObserveWait`/`SetWaitCue` remain as `LocalPlayerTeleportController`'s own hold-delay telemetry (`RuntimePortalSnapshot.WaitCueShown`) but no longer gate the on-screen cue, so they are not a residual of this row — closes issue #329; AP-183 filed 2026-08-09 at the CH4 REJECT-review, item 9 — roughly 10 chat refusal/usage call sites this campaign added route through `ChatVM.ShowSystemMessage`'s single `ClientLocal 0x00` sink where retail types several of them `0x1A`: `DoStupidChannelHack`, `DoChannelList`/`On`/`Off`, `DoAllegiance`, `DoHouseAvailableList`, `DoReply`; three sites (`DoSpeaker`/`DoEndurance`/`DoTitle`) are already correct at `0x00`, matching retail. Retail's own bad-args fallback (`DoCommand @0x0057E46D`) also answers with `HandleFailureEvent(0x26)`, not a local "Usage:" line, which acdream's `ChatCommandRouter.Submit` synthesizes instead. Deliberately NOT re-plumbed this session — filed as issue #363, marked for CH5-or-later; AP-182 filed 2026-08-09 at Campaign CH slice CH4, corrected at the CH4 REJECT-review (nit 11) — `@title` is wired to a pure no-op (the value is neither stored nor consumed anywhere) and also omits `DoTitle`'s three local failure messages; recount at the CH3 Opus review corrected a pre-existing off-by-one; AP-181 filed 2026-08-09, Campaign CH slice CH3 — the local chat spam throttle (`IsMessageSpam`) has no acdream port. AP-178 NARROWED 2026-08-09 at the CH2 REJECT-review rework NIT 3, wording corrected at the CH2 re-review nits pass (`docs/plans/2026-08-09-chat-parity-campaign.md`, nits 1/2/6) — the original `dats.Portal` pass used an id source that was not Portal's own (`dats.Portal.GetAllIdsOfType()` is empty for this type), so it established nothing about Portal either way; extending a correctly-paired sweep to `dats.Local` FOUND the SpewBox element there; extent (`450×72`) and `MaxConcurrentItems` (`4`, not the code-default `1`) are now AUTHORED, leaving absolute screen position, colour, AND vertical content flow (now TOP-aligned, acdream's own invention pending measurement) open. AP-180 filed 2026-08-09 at the CH2 REJECT-review rework — `RuntimeCommunicationState.AddText`'s `windowId` parameter is accepted but not consumed, so retail's dual-destination echo (a `0x1A` message with a non-zero `windowId` lands in both the SpewBox and its originating chat window) is unimplemented; latent today since every production caller passes `windowId = 0`. AP-177/AP-178/AP-179 filed 2026-08-09, Campaign CH slice CH2 (interface text / SpewBox) — AP-177 records the invented 5-second SpewBox line lifetime (retail's real timeout is keystone-owned and unmeasured); AP-178's original filing recorded the invented SpewBox screen position/extent/font/colour/MaxConcurrentItems after `SpewBoxLayoutDumpDiagnostic`'s Portal-only sweep found zero elements of class 0x10000016 — see the NARROWED note above for the corrected finding; AP-179 is the OnCombatLine half of the RETIRED AP-176 split out to its own row. AP-176 RETIRED the same day — the WeenieErrorMessages full 344-row `HandleFailureEvent` port (`WeenieErrorMessages.Resolve`) replaces the single-stand-in-`LogTextType` approximation that row recorded for `ChatLog.OnWeenieError`. AP-175 filed 2026-08-09, Campaign CH slice CH1 — PopUpString renders as a chat-log line instead of retail's modal dialog; AP-39 updated the same day — chat coloring is now retail's exact 34-value `LogTextType` table, not a synthetic per-`ChatKind` approximation of it. AP-173 and AP-174 filed 2026-08-08, Campaign A slice A2 — AP-173 expresses retail's ±15 dB DirectSound pan as an OpenAL azimuth by inverting the constant-power pan law, since AL exposes no per-channel gain for a mono source; AP-174 records acdream's extra master volume knob on top of retail's three, folded into retail's single master multiply so the −50 dB cutoff and dB quantisation move with it. AP-172 and AP-171 filed 2026-08-08, #354 spell-bar drag-reorder fix — the favorite-bar reorder gesture defers its own list rebuild for the drag's duration so `UiRoot`'s drag-cancel safety net cannot destroy the in-flight cell, compensating the drop-time target index for the resulting stale sibling numbering; final positions and the wire pair are retail-exact, only the mid-drag visual reflow timing differs. AP-170 filed 2026-08-08, grand-gate finding G3 — an out-of-range vendor Use now arms on arrival instead of sending immediately, because the user's local ACE server polls for the player to actually reach use range before opening the shop panel and a too-early Use is silently lost; AP-169 filed 2026-08-08, grand-gate finding G2 — the vendor toolbar split-slider resolver falls back to the packed shop-supply-count field when the item's own `PublicWeenieDesc._stackSize` is absent, because the user's local ACE server never populates the latter for a browse-list item; AP-167/AP-168 filed 2026-08-09 at the Opus review of `92ea3977` (findings F1/F6) — Buy All's container-vs-item slot classification approximates retail's bitfield/capacity test with `ItemType.Container` [AP-168], and SellSingleItem's non-empty-container refusal branch is not ported [AP-167]; AP-164 RETIRED the same review (finding F4) — BF_RETAINED is now checked end to end; AP-162 NARROWED the same review (finding F1) — Buy All's four client-side pre-send guards are now ported, leaving only the single-item TryBuy path without one; AP-161 gains a REVIEW CORRECTIONS paragraph the same review (findings F1-F13) summarizing the rest as bug fixes to already-claimed behavior, not new divergences. AP-164/AP-165/AP-166 filed 2026-08-09 at Slice 6b/6c (staging+sell arc) — InqAcceptability's non-sellable bitfield is unmodeled [AP-164], the Buy-side stackable-removal-amount test substitutes DescStackSize for retail's _maxStackSize [AP-165], and the Buying/Selling tabs' own purse/count text plus the cross-panel pending-sell inventory highlight are unwired [AP-166]; AP-161 NARROWED the same day — the row's last vendor-specific residual (Buying/Selling tabs render but carry no data binding) CLOSES now that both tabs are fully wired (staging, drag-to-sell, InqAcceptability gating, Sell 0x0060, the X-close confirmation), leaving only the two long-standing PRE-EXISTING residuals (dropdown arrow-cap glyph, alt-currency m_last_sale simplification) plus the three new AP-164/165/166 residuals just filed; AP-162 EXTENDED the same day — the same no-client-pre-check omission now also covers the batched "Buy All" path (TryBuyAll), not just the single-item TryBuy. AP-162/AP-163 filed 2026-08-09 at Slice 6.3 (buy arc) — no client-side Buy affordability/capacity pre-check [AP-162] and the shop-item guid-collision skip-not-clobber policy [AP-163]; AP-161 NARROWED the same day — the private-selection and unwired-examine residuals CLOSE at Slice 6.1/6.2, leaving only the dropdown arrow-cap glyph and the alt-currency `m_last_sale` simplification, plus a confirmed-absent-from-retail note on double-click-to-buy. AP-161 REWRITTEN 2026-08-09 at the Slice 5.4 review (findings F1-F8) — the popup-never-rendered, wrong-quantity-price, no-auto-select, dropped-icon-layer, stale-category-on-vendor-switch, and unguarded-Apply-fanout bugs the review found are fixed (`VendorUiController.cs`, `VendorState.cs`, `GameEventWiring.cs`, `RetailUiRuntime.cs`); the row now records only the four consciously-deferred residuals it still owns (private per-panel selection vs. retail's global `ACCWeenieObject::selectedID`, the unwired shop-item examine route, the dropdown button-face arrow-cap glyph, and the alt-currency held-amount's `m_last_sale`-free simplification). AP-110's "retail-correct per-unit prices" phrasing is corrected the same day to "quantity-correct pricing" — the OLD phrase mischaracterized what retail even shows (a `GetObjectSplitSize`-quantity price, not literally one unit) independent of whether the code was buggy. AP-161 filed 2026-08-09 at Slice 5.4 (vendor browse panel) — the authored "Buying"/"Selling" tabs render and switch pages but carry no data binding, per contract decision 8's required successor to AP-110's narrowing; AP-110 NARROWED the same day — "vendor" is retired from its absent-panels list now that the "Items" browse tab is user-reachable. AP-160 filed 2026-08-07 at Slice 5.3 — the client-local vendor-panel distance watcher closes on plain 3D center distance instead of retail/ACE's cylinder-gap distance, because Runtime has no per-entity collision radius/height source outside the App-layer's Setup-cylinder resolver. AP-158 RETIRED 2026-08-06 by the #333 fix, closing #337 — the `maxReach` distance pre-filter is DELETED rather than re-centred, because retail has none: `CObjCell::find_obj_collisions` @0x0052b750 walks the cell's shadow list and calls `CPhysicsObj::FindObjCollisions` unconditionally. The row's predicted symptom was observed live at Neftet before it was fixed — a tall prop AP-156 had just placed correctly still not blocking, plus jumps sinking into the mesh and corpses falling through. Perf measured, not assumed: at the live-maximum 38 in-cell candidates 10.61 µs → 16.68 µs per resolve. AP-159 filed 2026-08-06 at the #334 fix — the INDOOR half of AP-156’s traversal residual is all that remains of it; the outdoor half is CLOSED by the `find_bbox_cell_list` port, and AP-156’s RISK COLUMN IS CORRECTED at the same commit: it recorded the residual as “extra broadphase candidates, never a missed one”, which generalised the indoor direction to the whole row and is exactly why #334 — a MISSED one, and a user-observed loss of collision on landblock-spanning formations — sat inside it unnoticed. AP-158 filed 2026-08-06 at the AP-156 fix review — the shadow broadphase's `maxReach` distance pre-filter is acdream's own invention with NO retail counterpart, and it measures from the part origin, so it can discard a genuine contact for exactly the off-centre parts AP-156 just placed correctly; issue #333. AP-156 CORRECTED at the same review: its population was understated — 172 is AP-152's DISPATCH population, not AP-156's CONTAINMENT population. AP-155 NARROWED and AP-156/AP-157 filed 2026-08-06 at the AP-152 retail-conformance review. AP-155 bundled two divergences with different code paths, populations and gates under one id; its flood half is now AP-156, **with its direction corrected**. AP-155(b) recorded the BSP flood approximation as OVER-inclusive and used that direction as the reason the residual was safe to defer; measured over the installed DAT it was UNDER-inclusive for 428 of the 530 BSP-bearing Setups (the AP-156 fix review corrected the originally-recorded '170 of 172'), because `BuildFloodSpheres` carried each physics-BSP part's root bounding-sphere RADIUS while discarding that sphere's own ORIGIN and centring it on the part origin. That is the #98/#168 class, and for 43 Setups the post-AP-152 flood was strictly smaller than the pre-AP-152 one. AP-156 records the correction and the fix — `ShadowShape.BoundsCenter`, filled from the same resolver that supplies the radius, plus the retirement of the 10-sphere clamp on a branch where retail has none — and keeps open only the sphere-vs-portal TRAVERSAL approximation. AP-157 is the previously unregistered third-branch substitution: retail floods from one `CPartArray::GetSortingSphere` where acdream floods from every Sphere shape, and acdream's cylinder flood ignores `CylHeight`. AP-152 RETIRED 2026-08-06, one day after it was filed: `ShadowShapeBuilder.FromSetup` now dispatches BSP-first instead of unioning, and `ShadowObjectRegistry.BuildFloodSpheres` now applies `calc_cross_cells`' own BSP → cylsphere → sorting-sphere order. Four statements in the row were false and are corrected in its retirement text — most importantly its predicted symptom, "catching on a doorway sill", which could not have been occurring: `Transition.BspOnlyDispatch` had already made the extra primitive inert at collision-query time since 2026-05-25. The live half was CELL MEMBERSHIP, the #98/#168 symptom class, which had no such guard. AP-153/AP-154/AP-155 filed at that retirement — retail's dispatch flag is cached once at part-array construction where acdream's gate is live [AP-153]; acdream's query-time guard takes a CLIENT-DERIVED flag off the WIRE and never derives it, an undeclared dependency on ACE reading the same DAT bit [AP-154]; and the static publication paths emit a Setup Sphere as a height-capped Cylinder while `BuildFloodSpheres` approximates retail's bounding BOX with bounding SPHERES [AP-155, whose flood-priority half is closed by the same commit]. AP-152 filed 2026-08-06 at the AP-22 retirement — the LIVE collision path emits Setup primitives and per-part physics-BSP shapes additively where retail's `CPhysicsObj::FindObjCollisions` dispatches exclusively; 172 of 5,935 installed Setups are affected, including BSP doors, so it needs its own visual gate and was deliberately not folded into the AP-22 commit; the count is unchanged because AP-22 retired in the same commit. AP-22 RETIRED 2026-08-06 — retail synthesizes no shape for a shapeless object (`CPhysicsObj::FindObjCollisions` 0x0050f050 exits at `0x0050f22f je 0x50f31b` returning the seeded OK_TS, and `CPartArray::GetRadius`/`GetHeight` are absent from its whole call set), so the invented `setup.Radius` cylinder was deleted rather than re-derived; the row's site list named one file that never contained the fallback and omitted the two that did, one of them the headless-only copy, and its "rare decorative props" risk described an unreachable branch — 0 of 5,935 installed Setups can satisfy the guard. AP-150/AP-151 filed 2026-08-06 at the #280 dual review — the wait cue's five-second arming is acdream's own and not retail's trigger [AP-150], and the reveal gate is materially stricter than retail's DAT-residency prefetch predicate on the mesh-build/GPU-upload axis [AP-151], the opposite asymmetry from AP-149; AP-149 filed 2026-08-05 at the #280 portal-prefetch fix — the reveal gate's outer ring accepts terrain-only publication where retail requires LandBlockInfo and every building EnvCell; the fix closes the reveal-window/visible-window ratio, not this residual; AP-148 filed 2026-08-05 at the C5b closeout — acdream's local-player Gate A requires the wire TELEPORT_TS to be EQUAL where retail requires only that it not be OLDER, verified by disassembly against the PDB-paired binary after two review rounds read the Binary Ninja tautology and missed it; AP-147 filed 2026-08-05 at the C5b architecture review, finding D3 — the accepted-Position delta stream's cardinality change and its torn intermediate; AP-138 amended at the same review — C5b staled its route-2 first-submit `CurrentCellId` measurement; AP-131 RETIRED 2026-08-05, C5b, closing #275 — the steady-state merge's `installPlacementFrame: true, clearParent: true` literals no longer exist; `InboundPhysicsStateController.TryApplyPosition` now computes both flags PRE-MERGE from `(disposition, hasAnimations(old))`, which is exactly `RuntimeAuthoritativePositionRouteClassifier.ClassifyAcceptedPosition`'s own `ApplyPlacementFrameBeforeRouting`/`UnparentBeforeRouting` rows (false/false on the Gate A force row, `!HasAnimations`/true on every accepted non-force route). Retail decides both writes BEFORE `MoveOrTeleport` is consulted — Gate A @0x0045400C returns @0x0045409D ahead of `unset_parent` @0x00454129 and the `HasAnims` `SetPlacementFrame` gate @0x00454137 — so the flags need no route, no player distance and no signature change. The row's predicted symptoms are gone: an animated entity's ordinary Position no longer installs a placement frame retail skips, and a ForcePosition no longer unparents. Evidence: `InboundPhysicsStateControllerTests` — `ApplyOnAnimatedEntity_NeverInstallsTheWirePlacementFrame`, `ApplyOnNonAnimatedEntity_InstallsTheWirePlacementFrame`, `ForcePositionOnParentedLocalPlayer_RetainsTheParentAttachment`, and the 12-row `MergedPrePlacementFieldsMatchTheClassifiedRouteFlags` matrix which uses the production classifier as its oracle rather than re-encoding the table; all four sabotage-verified in both directions. The row's "the legacy caller is deleted at the production cutover" framing was overtaken: the caller was CORRECTED, not deleted, and remains the only production Position wire caller; AP-145 RETIRED 2026-08-05, C5a commit 1, closing #318 — `TryPublishPlace` now publishes the local player's Place through `LocalPlayerShadowSynchronizer.SyncPose`, the same publisher ordinary per-tick movement uses, instead of a direct `LocalPlayerShadowState.Set` that never touched `PhysicsEngine.ShadowObjects`; AP-1 RETIRED 2026-08-05, C5a deletion sweep — `PhysicsEngine.Resolve`/`ResolvePlacement`/`HasCellSurface` deleted outright, zero production callers, so "production zero-delta routes remain on the legacy resolver" is now structurally false; AP-146 filed 2026-08-05, #319 fix — the local player's canonical cell is written only at login/inbound-Position/teleport, not per ordinary-movement tick as retail's SetPositionInternal does; #319's fix makes a player-parented child inherit exactly this coarseness, stale-but-equal to the parent, not a new staleness class; follow-up filed as issue #320; AP-144 filed 2026-08-05, C4 route 3 round 3 (R7) — the portal-arrival movement-event send reuses `UsePositionFromServer` (`autonomy_level != 2`) where retail's actual gate, `SendMovementEvent`, is `autonomy_level != 0`; the two agree everywhere except level 1, which no production caller can reach today; AP-142/AP-143 filed 2026-08-04, C4 route 7 — the parented-child single-field cell model (id/pointer collapse, zero-not-stale removal propagation, same-cell tick-loop subsumption) and the headless parent-realize drive's skipped holding-location validation; AP-141 filed 2026-08-04, C4 route 5, NARROWED 2026-08-04 at the round-2 delta review — the far-branch StopInterpolating clause was wrong for the adopted-body case (it is now ported there) and the row's language now distinguishes "never armed" from "never re-anchored"; CORRECTED 2026-08-04 at the round-3 delta review — the risk column's "would drag the body toward a stale anchor" claim was itself wrong (the leash anchor is write-only; `ConstraintManager::adjust_offset` only brakes, never pulls) and is retracted; every half remains test-gated only, since ACE never sends a missile UpdatePosition; AP-140 filed AND RETIRED 2026-08-04 — filed at the Bug B Opus review because the two accepted-Position routing gates read the client `Airborne` flag, i.e. walkability, where retail's free-flight predicate is CONTACT, and Bug B had just turned "in contact, not on walkable ground" from unreachable into ordinary; retired the same day by pointing both gates at `PhysicsBody.InContact`, retail's literal `transient_state & 1` test at `InterpolationManager::adjust_offset` @0x00555D52 (bit 0 = `CONTACT_TS`, acclient.h:3690), while leaving `Airborne` and all five of its `!Body.OnWalkable` writers untouched — the narrow shape the row itself pinned. A remote sliding on a steep face now interpolates as retail does instead of snapping at UpdatePosition cadence; AP-139 filed 2026-08-04, Bug B remote steep-contact slide — the interpolation-queue clear on the landing edge, carried over from the deleted hand-rolled remote landing block; AP-81 narrowed the same day by that fix, which retired its whole GRAVITY half; AP-87 annotated the same day — its predicted symptom was observed live and then fixed at the source, with the row's own thresholds and conditions deliberately unchanged; AP-138 filed 2026-08-04, C4 route 4b-2 dual Opus review, parts (1) and (2) rewritten the same day at the DELTA review — the far snap's refusable-placement residual: store_position only on the outcomes that never reached the engine, the two quiescence parks made restorable at the source, with the rollback gated on the cell it actually restores into, rather than refused by a pre-flight that structurally cannot see them, and the leash not armed through a superseded incarnation; AP-137 filed 2026-08-04, C4 route 4b-2 and rewritten the same day at that review, `teleport_hook`'s call list completed at the delta review — the acdream-only null/rejected/cell-less leftover arm, what the deleted duplicated 96 m/4 m constant pairs actually computed, and the vacuous headless satisfaction; AP-136 filed 2026-08-04, C4 route 4b-1 review, NARROWED 2026-08-04 at the C4 route 4b-2 delta review and AMENDED 2026-08-04 by the cancelled-park presentation rollback (the row's "restored visible" claim covered only the CANONICAL half; the presentation half was never rolled back, which left a parked-then-cancelled remote that stops moving invisible in the world AND absent from the radar for the rest of the session — a defect, now fixed by the `WithdrawalRestored` receipt, with the selection residual filed as AD-63) — a cancelled lost-cell park re-shows the entity where retail keeps it hidden until cell load, and the rollback's scope now covers the two placement-side quiescence parks whenever the cell it restores into is not itself quiescing — round 4 (2026-08-04) applies that same test a second time at RESTORE time, because a retained park's rollback lands a packet later; AP-135 filed 2026-08-03, C4 route 4a — the airborne no-op's retained acdream bookkeeping; the stated total was 2 rows stale before that filing and is now a literal count of this section; AP-130/AP-131/AP-132 filed 2026-08-02, continuation-executor slice; AP-5 retired 2026-07-31 at Campaign P Slice 2A — every successful `step_down` now performs retail's final `PLACEMENT_INSERT`; AP-3/AP-4 retired 2026-07-31 at Campaign P Slice 1B — `transitional_insert` and `edge_slide` now preserve retail's valid-contact early return and Branch-1-first order; AP-127 retired 2026-07-31 by #268 — the complete augmentation chain is shared by character UI and Runtime movement; AP-30 retired 2026-07-30 by the movement parity audit — retail Frame::is_equal genuinely uses the 0.0002 epsilon [byte-confirmed], so the row recorded a NON-divergence; acdream already matches; AP-129 narrowed 2026-07-30 at the P4 Opus review fix — `CanMoveInto`/`RestrictionDB::IsAllowedIn` are now ported and fed end-to-end (CreateObject HouseOwner/HouseRestrictions/Monarch tail fields + live `House_UpdateRestrictions 0x0248`, resolved through `PhysicsEngine.Objects`), retiring the original "CanMoveInto entirely unmodeled, unconditional fail-closed" gap the row described — the review was triggered by `RestrictionObjPrevalenceInspectionTests` showing 103,766 of 729,888 installed EnvCells (the whole housing estate) carry a baked `RestrictionObj`, so the unconditional fail-closed default would have locked every house for every player including its own owner; AP-10 retired 2026-07-30 at Campaign P Slice P4 — restored retail's 0.1 m dry-corner water sink-in, full suite green proving the sticky-bit no-regression argument; AP-71 retired same slice — `check_entry_restrictions` ported at the head of the indoor `FindEnvCollisions` branch, `CellPhysics.RestrictionObj` wired from the DAT-baked `EnvCell` field in both the dev and production caching paths; AP-128 filed 2026-07-30 at the P3 Opus review — PK-timer clock basis; AP-25 retired 2026-07-30 at Campaign P Slice P1 — the vitae/enchantment-aware run/jump skill chain; AP-7 retired 2026-07-30 at Campaign P Slice P2 — `calc_friction`'s threshold ported to retail's confirmed 0.25f; its still-open cos(10°)-vs-0.99999536f Sledding constant question moved to AD-55) +## 3. Documented approximation (AP) — 135 active rows (AP-189 filed 2026-08-10 at the CH6a/b REJECT-review rework, SHOULD-FIX 5 — acdream's ONE shared 500-entry/200-line-display-tail chat log gives every window a shallower EFFECTIVE per-window scrollback depth than retail's own per-window 10,000-line log, though the accumulate-while-closed and independent-per-window-scroll BEHAVIORS are both correctly reproduced; AP-188 filed 2026-08-10 at Campaign CH slice CH6b — a floating chat window's chat entry always sends on the Say channel because the floaty LayoutDesc authors no talk-focus menu and acdream does not (yet) share the main window's currently-selected channel across all five chat-window instances; AP-187 filed 2026-08-10 at Campaign CH slice CH6b — the four floating chat windows' text-type filters persist in local `settings.json` only (`ChatSettings.ChatWindow1..4Filter`), with no analog to retail's server-side `0x1000008C` GameplayOptions blob, so a character's floaty filter customization does not travel between acdream installs or round-trip to/from a retail client sharing the same character; AP-186 filed 2026-08-10 at Campaign CH user-gate round 3, while tracing DoHelp's print sequence — ChatCommandRouter's type-0x1A local-presentation fallbacks (now including the byte-exact "Unknown command" text) still render via the chat scroll instead of the SpewBox, an architectural layering gap (UI.Abstractions cannot see Runtime's `RuntimeCommunicationState.AddText`), tracked as ISSUES.md #367; AP-185 filed 2026-08-10 at Campaign CH slice CH6a — the chat window's UiLocked border-art cosmetic swap is unported, see the row for detail; AP-184 filed 2026-08-09 at Campaign CH user-gate round 2, item 3 — three of the seven retail `/help` group-topic listings (channels/chatting/commands) remain PARTIAL, not fully verbatim: their detail text is built in full or in part by `ClientCommunicationSystem::HelpStupidChannelHack @0x0056f290`, which constructs its output from three BN-mislabeled data fragments (`&ClientCommunicationSystem::\`vftable'.RecvNotice_StartBarberNotice` etc. are NOT real vtable dispatch) concatenated around a live `ChannelSystem::GetChannelName` lookup, not decodable with confidence from a static string sweep; each partial group keeps its own verbatim summary line and an explicit UNVERIFIED note instead of the fully-fabricated meta-message the user caught on `/help death` (that group, plus status/text/allegiances, are now COMPLETE verbatim listings); tracked as ISSUES.md #364. Round 2 item 2 also deletes `PortalWaitNoticeController` (the dedicated centered-overlay presentation the user reported was the wrong retail surface) and reroutes the portal-space wait-cue notice through the same `AddText`/SpewBox chokepoint every other on-screen interface-text site uses — AP-178's open SpewBox position/extent/font/colour questions now cover this notice too, since its separate controller and consts are gone; no new row was needed for the surface mismatch itself, since it was never separately registered (`PortalWaitNoticeController`'s own doc comment asserted "not a chat message" as an accepted design, not a flagged divergence). AP-150 RETIRED 2026-08-09 at Campaign CH user-gate round 1, item D (#329) — `PortalTunnelPresentation.TickRotation` now emits `"In Portal Space - Please Wait..."` unconditionally on every rotation-segment expiry, exactly matching `gmSmartBoxUI::UseTime`'s `else`-arm at 0x004D6FCD, instead of gating on `_waitCueVisible`, which only ever went true after the invented 5-second `RuntimeWorldTransitState.RetailWaitCueDelay` hold; `RetailWaitCueDelay`/`ObserveWait`/`SetWaitCue` remain as `LocalPlayerTeleportController`'s own hold-delay telemetry (`RuntimePortalSnapshot.WaitCueShown`) but no longer gate the on-screen cue, so they are not a residual of this row — closes issue #329; AP-183 filed 2026-08-09 at the CH4 REJECT-review, item 9 — roughly 10 chat refusal/usage call sites this campaign added route through `ChatVM.ShowSystemMessage`'s single `ClientLocal 0x00` sink where retail types several of them `0x1A`: `DoStupidChannelHack`, `DoChannelList`/`On`/`Off`, `DoAllegiance`, `DoHouseAvailableList`, `DoReply`; three sites (`DoSpeaker`/`DoEndurance`/`DoTitle`) are already correct at `0x00`, matching retail. Retail's own bad-args fallback (`DoCommand @0x0057E46D`) also answers with `HandleFailureEvent(0x26)`, not a local "Usage:" line, which acdream's `ChatCommandRouter.Submit` synthesizes instead. Deliberately NOT re-plumbed this session — filed as issue #363, marked for CH5-or-later; AP-182 filed 2026-08-09 at Campaign CH slice CH4, corrected at the CH4 REJECT-review (nit 11) — `@title` is wired to a pure no-op (the value is neither stored nor consumed anywhere) and also omits `DoTitle`'s three local failure messages; recount at the CH3 Opus review corrected a pre-existing off-by-one; AP-181 filed 2026-08-09, Campaign CH slice CH3 — the local chat spam throttle (`IsMessageSpam`) has no acdream port. AP-178 NARROWED 2026-08-09 at the CH2 REJECT-review rework NIT 3, wording corrected at the CH2 re-review nits pass (`docs/plans/2026-08-09-chat-parity-campaign.md`, nits 1/2/6) — the original `dats.Portal` pass used an id source that was not Portal's own (`dats.Portal.GetAllIdsOfType()` is empty for this type), so it established nothing about Portal either way; extending a correctly-paired sweep to `dats.Local` FOUND the SpewBox element there; extent (`450×72`) and `MaxConcurrentItems` (`4`, not the code-default `1`) are now AUTHORED, leaving absolute screen position, colour, AND vertical content flow (now TOP-aligned, acdream's own invention pending measurement) open. AP-180 filed 2026-08-09 at the CH2 REJECT-review rework — `RuntimeCommunicationState.AddText`'s `windowId` parameter is accepted but not consumed, so retail's dual-destination echo (a `0x1A` message with a non-zero `windowId` lands in both the SpewBox and its originating chat window) is unimplemented; latent today since every production caller passes `windowId = 0`. AP-177/AP-178/AP-179 filed 2026-08-09, Campaign CH slice CH2 (interface text / SpewBox) — AP-177 records the invented 5-second SpewBox line lifetime (retail's real timeout is keystone-owned and unmeasured); AP-178's original filing recorded the invented SpewBox screen position/extent/font/colour/MaxConcurrentItems after `SpewBoxLayoutDumpDiagnostic`'s Portal-only sweep found zero elements of class 0x10000016 — see the NARROWED note above for the corrected finding; AP-179 is the OnCombatLine half of the RETIRED AP-176 split out to its own row. AP-176 RETIRED the same day — the WeenieErrorMessages full 344-row `HandleFailureEvent` port (`WeenieErrorMessages.Resolve`) replaces the single-stand-in-`LogTextType` approximation that row recorded for `ChatLog.OnWeenieError`. AP-175 filed 2026-08-09, Campaign CH slice CH1 — PopUpString renders as a chat-log line instead of retail's modal dialog; AP-39 updated the same day — chat coloring is now retail's exact 34-value `LogTextType` table, not a synthetic per-`ChatKind` approximation of it. AP-173 and AP-174 filed 2026-08-08, Campaign A slice A2 — AP-173 expresses retail's ±15 dB DirectSound pan as an OpenAL azimuth by inverting the constant-power pan law, since AL exposes no per-channel gain for a mono source; AP-174 records acdream's extra master volume knob on top of retail's three, folded into retail's single master multiply so the −50 dB cutoff and dB quantisation move with it. AP-172 and AP-171 filed 2026-08-08, #354 spell-bar drag-reorder fix — the favorite-bar reorder gesture defers its own list rebuild for the drag's duration so `UiRoot`'s drag-cancel safety net cannot destroy the in-flight cell, compensating the drop-time target index for the resulting stale sibling numbering; final positions and the wire pair are retail-exact, only the mid-drag visual reflow timing differs. AP-170 filed 2026-08-08, grand-gate finding G3 — an out-of-range vendor Use now arms on arrival instead of sending immediately, because the user's local ACE server polls for the player to actually reach use range before opening the shop panel and a too-early Use is silently lost; AP-169 filed 2026-08-08, grand-gate finding G2 — the vendor toolbar split-slider resolver falls back to the packed shop-supply-count field when the item's own `PublicWeenieDesc._stackSize` is absent, because the user's local ACE server never populates the latter for a browse-list item; AP-167/AP-168 filed 2026-08-09 at the Opus review of `92ea3977` (findings F1/F6) — Buy All's container-vs-item slot classification approximates retail's bitfield/capacity test with `ItemType.Container` [AP-168], and SellSingleItem's non-empty-container refusal branch is not ported [AP-167]; AP-164 RETIRED the same review (finding F4) — BF_RETAINED is now checked end to end; AP-162 NARROWED the same review (finding F1) — Buy All's four client-side pre-send guards are now ported, leaving only the single-item TryBuy path without one; AP-161 gains a REVIEW CORRECTIONS paragraph the same review (findings F1-F13) summarizing the rest as bug fixes to already-claimed behavior, not new divergences. AP-164/AP-165/AP-166 filed 2026-08-09 at Slice 6b/6c (staging+sell arc) — InqAcceptability's non-sellable bitfield is unmodeled [AP-164], the Buy-side stackable-removal-amount test substitutes DescStackSize for retail's _maxStackSize [AP-165], and the Buying/Selling tabs' own purse/count text plus the cross-panel pending-sell inventory highlight are unwired [AP-166]; AP-161 NARROWED the same day — the row's last vendor-specific residual (Buying/Selling tabs render but carry no data binding) CLOSES now that both tabs are fully wired (staging, drag-to-sell, InqAcceptability gating, Sell 0x0060, the X-close confirmation), leaving only the two long-standing PRE-EXISTING residuals (dropdown arrow-cap glyph, alt-currency m_last_sale simplification) plus the three new AP-164/165/166 residuals just filed; AP-162 EXTENDED the same day — the same no-client-pre-check omission now also covers the batched "Buy All" path (TryBuyAll), not just the single-item TryBuy. AP-162/AP-163 filed 2026-08-09 at Slice 6.3 (buy arc) — no client-side Buy affordability/capacity pre-check [AP-162] and the shop-item guid-collision skip-not-clobber policy [AP-163]; AP-161 NARROWED the same day — the private-selection and unwired-examine residuals CLOSE at Slice 6.1/6.2, leaving only the dropdown arrow-cap glyph and the alt-currency `m_last_sale` simplification, plus a confirmed-absent-from-retail note on double-click-to-buy. AP-161 REWRITTEN 2026-08-09 at the Slice 5.4 review (findings F1-F8) — the popup-never-rendered, wrong-quantity-price, no-auto-select, dropped-icon-layer, stale-category-on-vendor-switch, and unguarded-Apply-fanout bugs the review found are fixed (`VendorUiController.cs`, `VendorState.cs`, `GameEventWiring.cs`, `RetailUiRuntime.cs`); the row now records only the four consciously-deferred residuals it still owns (private per-panel selection vs. retail's global `ACCWeenieObject::selectedID`, the unwired shop-item examine route, the dropdown button-face arrow-cap glyph, and the alt-currency held-amount's `m_last_sale`-free simplification). AP-110's "retail-correct per-unit prices" phrasing is corrected the same day to "quantity-correct pricing" — the OLD phrase mischaracterized what retail even shows (a `GetObjectSplitSize`-quantity price, not literally one unit) independent of whether the code was buggy. AP-161 filed 2026-08-09 at Slice 5.4 (vendor browse panel) — the authored "Buying"/"Selling" tabs render and switch pages but carry no data binding, per contract decision 8's required successor to AP-110's narrowing; AP-110 NARROWED the same day — "vendor" is retired from its absent-panels list now that the "Items" browse tab is user-reachable. AP-160 filed 2026-08-07 at Slice 5.3 — the client-local vendor-panel distance watcher closes on plain 3D center distance instead of retail/ACE's cylinder-gap distance, because Runtime has no per-entity collision radius/height source outside the App-layer's Setup-cylinder resolver. AP-158 RETIRED 2026-08-06 by the #333 fix, closing #337 — the `maxReach` distance pre-filter is DELETED rather than re-centred, because retail has none: `CObjCell::find_obj_collisions` @0x0052b750 walks the cell's shadow list and calls `CPhysicsObj::FindObjCollisions` unconditionally. The row's predicted symptom was observed live at Neftet before it was fixed — a tall prop AP-156 had just placed correctly still not blocking, plus jumps sinking into the mesh and corpses falling through. Perf measured, not assumed: at the live-maximum 38 in-cell candidates 10.61 µs → 16.68 µs per resolve. AP-159 filed 2026-08-06 at the #334 fix — the INDOOR half of AP-156’s traversal residual is all that remains of it; the outdoor half is CLOSED by the `find_bbox_cell_list` port, and AP-156’s RISK COLUMN IS CORRECTED at the same commit: it recorded the residual as “extra broadphase candidates, never a missed one”, which generalised the indoor direction to the whole row and is exactly why #334 — a MISSED one, and a user-observed loss of collision on landblock-spanning formations — sat inside it unnoticed. AP-158 filed 2026-08-06 at the AP-156 fix review — the shadow broadphase's `maxReach` distance pre-filter is acdream's own invention with NO retail counterpart, and it measures from the part origin, so it can discard a genuine contact for exactly the off-centre parts AP-156 just placed correctly; issue #333. AP-156 CORRECTED at the same review: its population was understated — 172 is AP-152's DISPATCH population, not AP-156's CONTAINMENT population. AP-155 NARROWED and AP-156/AP-157 filed 2026-08-06 at the AP-152 retail-conformance review. AP-155 bundled two divergences with different code paths, populations and gates under one id; its flood half is now AP-156, **with its direction corrected**. AP-155(b) recorded the BSP flood approximation as OVER-inclusive and used that direction as the reason the residual was safe to defer; measured over the installed DAT it was UNDER-inclusive for 428 of the 530 BSP-bearing Setups (the AP-156 fix review corrected the originally-recorded '170 of 172'), because `BuildFloodSpheres` carried each physics-BSP part's root bounding-sphere RADIUS while discarding that sphere's own ORIGIN and centring it on the part origin. That is the #98/#168 class, and for 43 Setups the post-AP-152 flood was strictly smaller than the pre-AP-152 one. AP-156 records the correction and the fix — `ShadowShape.BoundsCenter`, filled from the same resolver that supplies the radius, plus the retirement of the 10-sphere clamp on a branch where retail has none — and keeps open only the sphere-vs-portal TRAVERSAL approximation. AP-157 is the previously unregistered third-branch substitution: retail floods from one `CPartArray::GetSortingSphere` where acdream floods from every Sphere shape, and acdream's cylinder flood ignores `CylHeight`. AP-152 RETIRED 2026-08-06, one day after it was filed: `ShadowShapeBuilder.FromSetup` now dispatches BSP-first instead of unioning, and `ShadowObjectRegistry.BuildFloodSpheres` now applies `calc_cross_cells`' own BSP → cylsphere → sorting-sphere order. Four statements in the row were false and are corrected in its retirement text — most importantly its predicted symptom, "catching on a doorway sill", which could not have been occurring: `Transition.BspOnlyDispatch` had already made the extra primitive inert at collision-query time since 2026-05-25. The live half was CELL MEMBERSHIP, the #98/#168 symptom class, which had no such guard. AP-153/AP-154/AP-155 filed at that retirement — retail's dispatch flag is cached once at part-array construction where acdream's gate is live [AP-153]; acdream's query-time guard takes a CLIENT-DERIVED flag off the WIRE and never derives it, an undeclared dependency on ACE reading the same DAT bit [AP-154]; and the static publication paths emit a Setup Sphere as a height-capped Cylinder while `BuildFloodSpheres` approximates retail's bounding BOX with bounding SPHERES [AP-155, whose flood-priority half is closed by the same commit]. AP-152 filed 2026-08-06 at the AP-22 retirement — the LIVE collision path emits Setup primitives and per-part physics-BSP shapes additively where retail's `CPhysicsObj::FindObjCollisions` dispatches exclusively; 172 of 5,935 installed Setups are affected, including BSP doors, so it needs its own visual gate and was deliberately not folded into the AP-22 commit; the count is unchanged because AP-22 retired in the same commit. AP-22 RETIRED 2026-08-06 — retail synthesizes no shape for a shapeless object (`CPhysicsObj::FindObjCollisions` 0x0050f050 exits at `0x0050f22f je 0x50f31b` returning the seeded OK_TS, and `CPartArray::GetRadius`/`GetHeight` are absent from its whole call set), so the invented `setup.Radius` cylinder was deleted rather than re-derived; the row's site list named one file that never contained the fallback and omitted the two that did, one of them the headless-only copy, and its "rare decorative props" risk described an unreachable branch — 0 of 5,935 installed Setups can satisfy the guard. AP-150/AP-151 filed 2026-08-06 at the #280 dual review — the wait cue's five-second arming is acdream's own and not retail's trigger [AP-150], and the reveal gate is materially stricter than retail's DAT-residency prefetch predicate on the mesh-build/GPU-upload axis [AP-151], the opposite asymmetry from AP-149; AP-149 filed 2026-08-05 at the #280 portal-prefetch fix — the reveal gate's outer ring accepts terrain-only publication where retail requires LandBlockInfo and every building EnvCell; the fix closes the reveal-window/visible-window ratio, not this residual; AP-148 filed 2026-08-05 at the C5b closeout — acdream's local-player Gate A requires the wire TELEPORT_TS to be EQUAL where retail requires only that it not be OLDER, verified by disassembly against the PDB-paired binary after two review rounds read the Binary Ninja tautology and missed it; AP-147 filed 2026-08-05 at the C5b architecture review, finding D3 — the accepted-Position delta stream's cardinality change and its torn intermediate; AP-138 amended at the same review — C5b staled its route-2 first-submit `CurrentCellId` measurement; AP-131 RETIRED 2026-08-05, C5b, closing #275 — the steady-state merge's `installPlacementFrame: true, clearParent: true` literals no longer exist; `InboundPhysicsStateController.TryApplyPosition` now computes both flags PRE-MERGE from `(disposition, hasAnimations(old))`, which is exactly `RuntimeAuthoritativePositionRouteClassifier.ClassifyAcceptedPosition`'s own `ApplyPlacementFrameBeforeRouting`/`UnparentBeforeRouting` rows (false/false on the Gate A force row, `!HasAnimations`/true on every accepted non-force route). Retail decides both writes BEFORE `MoveOrTeleport` is consulted — Gate A @0x0045400C returns @0x0045409D ahead of `unset_parent` @0x00454129 and the `HasAnims` `SetPlacementFrame` gate @0x00454137 — so the flags need no route, no player distance and no signature change. The row's predicted symptoms are gone: an animated entity's ordinary Position no longer installs a placement frame retail skips, and a ForcePosition no longer unparents. Evidence: `InboundPhysicsStateControllerTests` — `ApplyOnAnimatedEntity_NeverInstallsTheWirePlacementFrame`, `ApplyOnNonAnimatedEntity_InstallsTheWirePlacementFrame`, `ForcePositionOnParentedLocalPlayer_RetainsTheParentAttachment`, and the 12-row `MergedPrePlacementFieldsMatchTheClassifiedRouteFlags` matrix which uses the production classifier as its oracle rather than re-encoding the table; all four sabotage-verified in both directions. The row's "the legacy caller is deleted at the production cutover" framing was overtaken: the caller was CORRECTED, not deleted, and remains the only production Position wire caller; AP-145 RETIRED 2026-08-05, C5a commit 1, closing #318 — `TryPublishPlace` now publishes the local player's Place through `LocalPlayerShadowSynchronizer.SyncPose`, the same publisher ordinary per-tick movement uses, instead of a direct `LocalPlayerShadowState.Set` that never touched `PhysicsEngine.ShadowObjects`; AP-1 RETIRED 2026-08-05, C5a deletion sweep — `PhysicsEngine.Resolve`/`ResolvePlacement`/`HasCellSurface` deleted outright, zero production callers, so "production zero-delta routes remain on the legacy resolver" is now structurally false; AP-146 filed 2026-08-05, #319 fix — the local player's canonical cell is written only at login/inbound-Position/teleport, not per ordinary-movement tick as retail's SetPositionInternal does; #319's fix makes a player-parented child inherit exactly this coarseness, stale-but-equal to the parent, not a new staleness class; follow-up filed as issue #320; AP-144 filed 2026-08-05, C4 route 3 round 3 (R7) — the portal-arrival movement-event send reuses `UsePositionFromServer` (`autonomy_level != 2`) where retail's actual gate, `SendMovementEvent`, is `autonomy_level != 0`; the two agree everywhere except level 1, which no production caller can reach today; AP-142/AP-143 filed 2026-08-04, C4 route 7 — the parented-child single-field cell model (id/pointer collapse, zero-not-stale removal propagation, same-cell tick-loop subsumption) and the headless parent-realize drive's skipped holding-location validation; AP-141 filed 2026-08-04, C4 route 5, NARROWED 2026-08-04 at the round-2 delta review — the far-branch StopInterpolating clause was wrong for the adopted-body case (it is now ported there) and the row's language now distinguishes "never armed" from "never re-anchored"; CORRECTED 2026-08-04 at the round-3 delta review — the risk column's "would drag the body toward a stale anchor" claim was itself wrong (the leash anchor is write-only; `ConstraintManager::adjust_offset` only brakes, never pulls) and is retracted; every half remains test-gated only, since ACE never sends a missile UpdatePosition; AP-140 filed AND RETIRED 2026-08-04 — filed at the Bug B Opus review because the two accepted-Position routing gates read the client `Airborne` flag, i.e. walkability, where retail's free-flight predicate is CONTACT, and Bug B had just turned "in contact, not on walkable ground" from unreachable into ordinary; retired the same day by pointing both gates at `PhysicsBody.InContact`, retail's literal `transient_state & 1` test at `InterpolationManager::adjust_offset` @0x00555D52 (bit 0 = `CONTACT_TS`, acclient.h:3690), while leaving `Airborne` and all five of its `!Body.OnWalkable` writers untouched — the narrow shape the row itself pinned. A remote sliding on a steep face now interpolates as retail does instead of snapping at UpdatePosition cadence; AP-139 filed 2026-08-04, Bug B remote steep-contact slide — the interpolation-queue clear on the landing edge, carried over from the deleted hand-rolled remote landing block; AP-81 narrowed the same day by that fix, which retired its whole GRAVITY half; AP-87 annotated the same day — its predicted symptom was observed live and then fixed at the source, with the row's own thresholds and conditions deliberately unchanged; AP-138 filed 2026-08-04, C4 route 4b-2 dual Opus review, parts (1) and (2) rewritten the same day at the DELTA review — the far snap's refusable-placement residual: store_position only on the outcomes that never reached the engine, the two quiescence parks made restorable at the source, with the rollback gated on the cell it actually restores into, rather than refused by a pre-flight that structurally cannot see them, and the leash not armed through a superseded incarnation; AP-137 filed 2026-08-04, C4 route 4b-2 and rewritten the same day at that review, `teleport_hook`'s call list completed at the delta review — the acdream-only null/rejected/cell-less leftover arm, what the deleted duplicated 96 m/4 m constant pairs actually computed, and the vacuous headless satisfaction; AP-136 filed 2026-08-04, C4 route 4b-1 review, NARROWED 2026-08-04 at the C4 route 4b-2 delta review and AMENDED 2026-08-04 by the cancelled-park presentation rollback (the row's "restored visible" claim covered only the CANONICAL half; the presentation half was never rolled back, which left a parked-then-cancelled remote that stops moving invisible in the world AND absent from the radar for the rest of the session — a defect, now fixed by the `WithdrawalRestored` receipt, with the selection residual filed as AD-63) — a cancelled lost-cell park re-shows the entity where retail keeps it hidden until cell load, and the rollback's scope now covers the two placement-side quiescence parks whenever the cell it restores into is not itself quiescing — round 4 (2026-08-04) applies that same test a second time at RESTORE time, because a retained park's rollback lands a packet later; AP-135 filed 2026-08-03, C4 route 4a — the airborne no-op's retained acdream bookkeeping; the stated total was 2 rows stale before that filing and is now a literal count of this section; AP-130/AP-131/AP-132 filed 2026-08-02, continuation-executor slice; AP-5 retired 2026-07-31 at Campaign P Slice 2A — every successful `step_down` now performs retail's final `PLACEMENT_INSERT`; AP-3/AP-4 retired 2026-07-31 at Campaign P Slice 1B — `transitional_insert` and `edge_slide` now preserve retail's valid-contact early return and Branch-1-first order; AP-127 retired 2026-07-31 by #268 — the complete augmentation chain is shared by character UI and Runtime movement; AP-30 retired 2026-07-30 by the movement parity audit — retail Frame::is_equal genuinely uses the 0.0002 epsilon [byte-confirmed], so the row recorded a NON-divergence; acdream already matches; AP-129 narrowed 2026-07-30 at the P4 Opus review fix — `CanMoveInto`/`RestrictionDB::IsAllowedIn` are now ported and fed end-to-end (CreateObject HouseOwner/HouseRestrictions/Monarch tail fields + live `House_UpdateRestrictions 0x0248`, resolved through `PhysicsEngine.Objects`), retiring the original "CanMoveInto entirely unmodeled, unconditional fail-closed" gap the row described — the review was triggered by `RestrictionObjPrevalenceInspectionTests` showing 103,766 of 729,888 installed EnvCells (the whole housing estate) carry a baked `RestrictionObj`, so the unconditional fail-closed default would have locked every house for every player including its own owner; AP-10 retired 2026-07-30 at Campaign P Slice P4 — restored retail's 0.1 m dry-corner water sink-in, full suite green proving the sticky-bit no-regression argument; AP-71 retired same slice — `check_entry_restrictions` ported at the head of the indoor `FindEnvCollisions` branch, `CellPhysics.RestrictionObj` wired from the DAT-baked `EnvCell` field in both the dev and production caching paths; AP-128 filed 2026-07-30 at the P3 Opus review — PK-timer clock basis; AP-25 retired 2026-07-30 at Campaign P Slice P1 — the vitae/enchantment-aware run/jump skill chain; AP-7 retired 2026-07-30 at Campaign P Slice P2 — `calc_friction`'s threshold ported to retail's confirmed 0.25f; its still-open cos(10°)-vs-0.99999536f Sledding constant question moved to AD-55) Wave-0 UI ledger repair (2026-07-10) retired stale AP-38, resolved the AP-84 collision, restored overwritten paperdoll rows as AP-92/AP-93, and registered @@ -342,6 +342,7 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps. | AP-186 | **Filed 2026-08-10 at Campaign CH user-gate round 3, while tracing `ClientCommunicationSystem::DoHelp @0x0057f9e0`'s complete print sequence for findings (b)/(c).** Retail's DoHelp fallback for an unresolved `/help ` is `AddTextToScroll(u"Unknown command", 0x1A, 1, 0)` — type `0x1A` (`ClientLocal`) is HARDCODED to the SpewBox, never the chat window (same routing rule round 2 item 2 already ported for the portal-space notice; `docs/research/2026-08-09-chat-retail-interface-text.md` §2.1/§2.2). `RuntimeCommunicationState.AddText` (Runtime layer) implements this rule correctly. But `ChatCommandRouter`/`ChatVM` live in `AcDream.UI.Abstractions`, a layer beneath Runtime that must stay presentation/Runtime-independent (Code Structure Rules) — they have no path to the SpewBox, so every local-presentation fallback typed `ClientLocal` (now `RetailCommandHelpTable.UnknownCommand`; also the pre-existing "Unknown command: {verb}." command-shaped-input refusal in `ChatCommandRouter.Submit`'s main body) still writes through `ChatVM.ShowSystemMessage`, which only ever reaches `ChatLog`. Not a regression this round — the fallback already showed in the chat window before this round's fix (with fabricated text); this round corrected the TEXT and traced the routing divergence clearly enough to register it. Filed as ISSUES.md #367. `src/AcDream.UI.Abstractions/Panels/Chat/ChatCommandRouter.cs`; `src/AcDream.UI.Abstractions/Panels/Chat/ChatVM.cs` | This is an architectural layering gap (UI.Abstractions cannot see Runtime), not a data-availability gap — `RuntimeCommunicationState.AddText` already implements the correct routing rule one layer up, so fixing it is a plumbing exercise (an optional delegate hook), not new research | A user typing an unresolvable `/help ` sees "Unknown command" in the chat scroll instead of the top-of-screen SpewBox flash retail shows — text content is now byte-exact, only the SURFACE differs | `ClientSystem::AddTextToScroll @0x00563C50`; `docs/research/2026-08-09-chat-retail-interface-text.md` §2.1/§2.2; `src/AcDream.Runtime/Gameplay/RuntimeCommunicationState.cs` (`AddText`, the correctly-implemented oracle) | | AP-187 | **Filed 2026-08-10 (Campaign CH slice CH6b — floating chat windows).** The four floating chat windows' text-type filters (`AcDream.Core.Chat.ChatWindowState`, retail's `0x1000007F` per-window option) persist only in local `settings.json` (`ChatSettings.ChatWindow1Filter`..`ChatWindow4Filter`, `SettingsStore.LoadChat`/`SaveChat`). Retail's authoritative store for this same data is the per-window option array (`0x1000008C`) packed inside the character-scoped `GameplayOptions` blob, which ACE stores and echoes as opaque bytes without parsing (window-shell research doc §4.1/§4.4); acdream has no reader or writer for that blob (CH3 already deleted one malformed attempt at the outbound `SetCharacterOptions 0x01A1` builder — `SocialActions.cs`). Geometry and open/visible state for these same four windows do NOT need a row of their own: they persist through the pre-existing generic `RetailWindowLayoutPersistence` path (X/Y/W/H/visible/collapsed/maximized per window name), which is retail's OWN local-file mechanism too (`gmGamePlayUI::SaveScreenLayout`/`LoadScreenLayout`, window-shell research doc §4.3) — only the filter mask lacks any such local-file precedent in retail and is acdream's own addition to make the feature usable before CH6f lands. `src/AcDream.UI.Abstractions/Panels/Settings/ChatSettings.cs`; `src/AcDream.UI.Abstractions/Panels/Settings/SettingsStore.cs` (`LoadChat`/`SaveChat`/`BuildChatObject`); `src/AcDream.App/UI/RetailUiRuntime.cs` (`MountFloatingChatWindows`, `SaveChatWindowFilters`) | CH6a/CH6b's own port-shape recommendation (window-shell research doc §6.1) explicitly chose local persistence first and deferred the `0x1000008B`/`0x1000008C` wire to a dedicated CH6f slice, citing CH3's deleted malformed builder as the reason not to rush it | A character's floating-window filter customization does not travel to a different acdream install, and would not round-trip through a retail client sharing the same character (retail would see acdream's local-only values as unset, falling back to its own `PostInit` defaults) — cosmetic/preference-only, no gameplay effect | `PlayerModule::GetChatOptionStructure @0x005D5300`; `PlayerModule::InqChatWindowOption/SetChatWindowOption @0x005D5540/:70`; `docs/research/2026-08-09-chat-retail-window-shell.md` §4.1/§4.4/§6.1; `docs/plans/2026-08-09-chat-parity-campaign.md` (CH6f row) | | AP-188 | **Filed 2026-08-10 (Campaign CH slice CH6b — floating chat windows).** A floating chat window's chat entry always sends on the `Say` channel (`FloatingChatWindowController.Bind`'s `OnSubmit` hardcodes `ChatChannelKind.Say`). The floaty LayoutDesc (`0x2100005B`) authors no talk-focus menu (window-shell research doc §2.2 — only the main window's layout has one, element `0x10000014`), so there is no visible channel picker on a floaty window either way, matching retail's authored UI exactly. What is UNVERIFIED is whether retail's actual SEND path for a floaty window's typed message reads a per-window channel or the single globally-current talk-focus channel/target the main window's menu (or the last-selected/last-speakable-target state `gmMainChatUI::UseTime @0x004CDB20` tracks) last set — if the latter, a real retail floaty window would send on whatever channel the player most recently picked from the MAIN window, not always `Say`. Confirming this requires tracing `gmCCommunicationSystem`'s send-command path from a floaty `ChatInterface` instance, not yet done. Filed as ISSUES.md #369. `src/AcDream.App/UI/Layout/FloatingChatWindowController.cs` (`Bind`, the `OnSubmit` wiring) | Building genuine cross-window shared-channel state (reading `ChatWindowController`'s private `_activeChannel` from four independent sibling controllers, or promoting it to a shared owner) is a real design decision outside this slice's explicit scope (task items 1-6 do not ask for cross-window channel sharing); `Say` is retail's own default channel and the safest fixed value absent confirmation | If retail's actual mechanism is "send on the currently-selected global channel," a user who selects e.g. Fellowship from the main window's talk-focus menu and then types into a floaty window would see it sent as Fellowship in retail but as Say in acdream — no data loss (the message still sends), only channel-selection mismatch | `gmMainChatUI::InitTalkFocusMenu @0x004CDC50`; `gmMainChatUI::UseTime @0x004CDB20`; `docs/research/2026-08-09-chat-retail-window-shell.md` §2.2 | +| AP-189 | **Filed 2026-08-10 at the CH6a/b REJECT-review rework (SHOULD-FIX 5, `docs/research/2026-08-10-ch6ab-review-findings.md`).** Retail keeps a PER-`ChatInterface` `m_chatLog`, truncated at 10,000 lines (`ChatInterface::RecvNotice_DisplayFinalStringInfo @0x004F4711` → `TruncateChatLog`) — each of the five windows (main + 4 floaty) owns its OWN 10,000-line backlog, and a closed window keeps accumulating into its own log because `gmFloatyMainChatUI::SetVisible @0x004CE9B0` never unregisters the handler. acdream instead shares ONE canonical `ChatLog` capped at 500 entries (`RuntimeCommunicationState`'s ctor, `maximumChatEntries: 500`) with a 200-line display tail every window filters from (`InteractionRetainedUiComposition.cs:564`'s `displayLimit: 200` feeding `ChatVM.RecentLinesDetailed`; `ChatWindowState.ShouldDisplay` does the per-window filtering). The accumulate-while-closed and independent-per-window-scroll BEHAVIORS both fall out correctly from this shared-log shape, but the EFFECTIVE per-window scrollback DEPTH differs from retail's: a window whose filter accepts only a rare message type (e.g. a Fellowship-only floaty) sees only the fellowship lines that happen to still be inside the shared log's last 200-of-500 lines, not up to 10,000 like retail's own per-window log. `src/AcDream.Core/Chat/ChatLog.cs` (`_maxEntries`); `src/AcDream.App/Composition/InteractionRetainedUiComposition.cs:564` (`displayLimit: 200`); `src/AcDream.App/UI/Layout/ChatWindowController.cs`/`FloatingChatWindowController.cs` (`GetTranscriptLines`) | A single shared canonical log matches acdream's Slice-J "one canonical transcript, many filtered presentations" pattern and keeps memory bounded regardless of how many windows are open; 500 shared entries covers many minutes of typical mixed-channel play, and both retail-observable BEHAVIORS this row could have broken (closed-window accumulation, independent per-window scroll position) are reproduced correctly — only the numeric DEPTH ceiling differs | In a busy mixed-channel session (heavy General/Trade traffic), a rarely-used channel (Fellowship, a Turbine room) can scroll out of the shared 500-entry window long before a floaty window filtered to just that channel would have neared retail's 10,000-line depth — a user who opens that floaty window after a long session sees a much shorter backlog than retail would show for the same play session | `ChatInterface::RecvNotice_DisplayFinalStringInfo @0x004F4640`/`TruncateChatLog @0x004F4711`; `gmFloatyMainChatUI::SetVisible @0x004CE9B0`; `docs/research/2026-08-09-chat-retail-window-shell.md` §1.2 | ## 4. Temporary stopgap (TS) — 39 active rows (TS-70 RETIRED 2026-08-09 at Campaign CH user-gate round 1, item E (#362) — `ClientCommandResponses.cs` now parses and renders all four named inbound GameEvents (`ChannelIndex 0x0149`, `ChannelList 0x0148`, `AvailableHouses 0x0271`, `AllegianceInfoResponse 0x027C`), each wired into `GameEventWiring.cs` and rendering retail-shaped `LogTextType 0x00` lines ported from the named-retail decomp (`Handle_Communication__ChannelIndex`/`ChannelList` @0x0057d0c0/@0x0057d230, `Handle_House__Recv_AvailableHouses` + `DisplayListOfCoords` @0x00585d50/@0x00585c20, `Handle_Allegiance__AllegianceInfoResponseEvent` @0x0056a1d0); the row's `@on`/`@off` mention was never itself missing a handler (both already resolve through the pre-existing `WeenieErrorWithString` registration) so nothing there needed a fix; TS-68/TS-69 filed 2026-08-09, Campaign CH slice CH4 — the deferred allegiance/house subcommand dispatchers, the three unported pure-local commands (day/log/render); TS-66/TS-67 filed and TS-29 retired 2026-08-08, Campaign A slice A5 — the region ambient system landed, so TS-29's ambient half is ported and its music half turned out to have nothing to port; TS-66 is the omitted `seen_outside` interior case and TS-67 the in-plane contribution weight. TS-64/TS-65 filed 2026-08-08, Campaign A slice A2 — TS-64 the two unimplemented retail sound preferences (unfocused-app silence, pan disable) plus the three enable bools; TS-65 the volume-squared quirk, applied on the ambient path where two lanes byte-confirmed it and deliberately NOT on the hook path where the pre-multiplying overload is unpinned. TS-62/TS-63 filed 2026-08-02, continuation-executor slice; TS-4 and TS-8 retired 2026-07-31; Campaign P's goal-enumerated physics stopgaps are now zero. TS-4's graph/flat Path-6 branches match retail's foot SetCollide/Adjusted and head CollisionNormal/Collided split with no BSP-layer sliding-normal write; TS-8's live 0x02C2 carries its complete StatMod through the canonical enchantment record and updates effective stats immediately. Campaign P P7 2026-07-30: TS-25 retired — outbound stance has shipped via RawState.CurrentStyle since #219; TS-24 re-argued to AD-57; TS-40 re-argued to AD-58; TS-35 retired at P5; earlier same campaign: TS-1/TS-5/TS-23/TS-46 retired by ports; TS-23 retired 2026-07-30 at Campaign P Slice P3 — every mover-flags call site (local player world-entry ×2, remote DR sweep ×2, remote teleport, ordinary movers) now ORs in the mover's real PK/PKLite/Impenetrable `ObjectInfoState` bits via the new `ClientObjectTable`-backed `EntityCollisionFlagsExt.ResolveMoverPvpState` — **narrative corrected 2026-08-03 (#297): "real" only became true at #297. Until then the bits existed but the source `PublicWeenieBitfield` was frozen at CreateObject, so every one of those sites read a stale value for the whole session. The site enumeration is also incomplete: `RuntimeSetPositionMoverPreparation.cs:183-188` is a SEVENTH mover-flags site that decodes `record.Snapshot.ObjectDescriptionFlags` directly rather than calling `ResolveMoverPvpState`, and it also derives `ObjectInfoState.IsPlayer` from the PWD bit, contradicting `EntityCollisionFlags.cs:119-123`'s claim that every site uses a GUID-prefix heuristic. See AP-134.** — and `PlayerWeenie.JumpStaminaCost`'s `pk` parameter reads the real `PlayerKillerStatus`/`LastPkAttackTimestamp` pair against a 20-second window instead of a hardcoded `false`; the non-PK invariant (every ACE default-created character) is bit-identical to the pre-P3 value since `ResolveMoverPvpState` and the PK-timer predicate both resolve to a no-op for `PublicWeenieBitfield` absent/0; TS-46 retired 2026-07-30 at Campaign P Slice P3 — the Setup's verbatim ≤2-sphere list (`CPhysicsObj::transition` 0x00512dc0 → `SPHEREPATH::init_sphere` 0x0050c670) now seeds the sweep for the local player, remote dead-reckoning, and ordinary movers alike, replacing the two-scalar (radius, height) capsule reconstruction; remote/ordinary step-up/step-down are now Setup-derived (`CPartArray::GetStepUpHeight`/`GetStepDownHeight`, 0x005180d0/0x005180f0, ×ObjScale) instead of a hardcoded 0.4 m, closing both residuals the row named; TS-5 retired 2026-07-30 at Campaign P Slice P1 — real burden-gated CanJump + real JumpStaminaCost, both decomp-verbatim; TS-1 retired 2026-07-30 at Campaign P Slice P2 — the row was stale; the EdgeSlide → PrecipiceSlide/CliffSlide chain is already a real, tested port; TS-57..TS-61 filed 2026-07-29 during Campaign N — no outbound RejectRetransmit; TS-27 narrowed same slice to the inbound direction) + TS-37 historical note (TS-20 retired 2026-07-16 — the later named-retail audit disproved the proposed DrawingBSP polygon filter; TS-37 is a retired-row historical note, not an active count; TS-39 retired R5-V3 — sticky seams bound to the ported PositionManager/StickyManager, radii threaded; TS-45 retired 2026-07-07 — hand-rolled `SphereCollision` replaced by the faithful CSphere family port, fixing the player-vs-monster crowd wedge; TS-3 retired 2026-07-07 — `frames_stationary_fall` accounting ported in the #182 verbatim UpdateObjectInternal rebuild, fixing the airborne falling-animation wedge; TS-41 retired 2026-07-07 — SERVERVEL synth-velocity remote body-drive replaced by the retail interp catch-up + unconditional MovementManager::UseTime, the remote-creature de-overlap #184; TS-42 retired 2026-07-19 — semantic animation completion now precedes the ordered Target/Movement/PartArray/Position tail; TS-44 narrowed again 2026-07-19 — complete orientation joined interpolation, only during-stick enqueue suppression remains) diff --git a/docs/plans/2026-08-09-chat-parity-campaign.md b/docs/plans/2026-08-09-chat-parity-campaign.md index 1d79020f..7e42fa92 100644 --- a/docs/plans/2026-08-09-chat-parity-campaign.md +++ b/docs/plans/2026-08-09-chat-parity-campaign.md @@ -245,10 +245,10 @@ implementer per slice against a pinned contract (per | CH6a main-window layout + 8-grip resize | (this commit) | 12,317 passed / 4 skipped / 0 failed | pending (no subagent review pass this session — implementer-only) | pending — needs the next in-client round (items H/I round 1, item 6 round 2) | | CH6b/CH6c floating windows + opacity | superseded — split below | — | — | superseded | | User gate round 2 | (this commit) | 12,267 passed / 4 skipped / 0 failed | — | items 2/4/5 fixed this commit, item 3 confirmed-fixed, item 6 folded into CH6a's spec, item 1 NOT reproduced (see "User gate — round 2" below) | -| CH6a main-window layout + 8-grip resize | `1fd51543` | 12,317 passed / 4 skipped / 0 failed | pending | pending — landed same day as round 2 | +| CH6a main-window layout + 8-grip resize | `1fd51543`, reworked (this commit) | 12,420 passed / 4 skipped / 0 failed | REJECT (docs/research/2026-08-10-ch6ab-review-findings.md, BLOCKER 1) → reworked (this commit) | pending — needs the next in-client round to confirm the border/corner art now renders | | Jump-in-air root cause (round-2 item 1, resolved) | `a5a7eb4f` | Runtime tests 1,323/0 | — | round-3 probe evidence pinpointed a missing `OnInterfaceText` wire on the production controller-commit path (`RuntimeLocalPlayerMovementState.CommitRuntimeOwnedController`); FIXED, regression test added | | User gate round 3 | (this commit) | Debug (all projects): 12,329 passed / 4 skipped / 1 failed (pre-existing #351 Debug-only flake — reproduces identically on the pristine pre-round-3 commit, not a regression); Release (every project reachable while a live `AcDream.App.exe` client — PID 15064, must not be killed per project policy — holds its own Release binaries locked, blocking `AcDream.App`/`AcDream.App.Tests`/`AcDream.Core.Tests` specifically): `AcDream.UI.Abstractions.Tests` (the layer this round's `/help` fix lives in) 867/867, plus `Core.Net.Tests` 823/823, `Runtime.Tests` 1,323/1,323, `Content.Tests` 130/130, `Headless.Tests` 89/89, `Bake.Tests` 15/15, `Cli.Tests` 4/4 — all 0 failed | — | findings (a)-(c) fixed this commit — SpewBox flush-top + retail dat font, `/help`/`/help death` exact retail print sequence (see "User gate — round 3" below) | -| CH6b floating windows 1–4 | (this commit) | 12,392 passed / 4 skipped / 0 failed | REJECT (docs/research/2026-08-10-ch6ab-review-findings.md); rework in progress | pending — no client launches this session (hard constraint); needs the next connected round for keybind/mirror/filter visual confirmation | +| CH6b floating windows 1–4 | `22020ef2`, reworked (this commit) | 12,420 passed / 4 skipped / 0 failed | REJECT (docs/research/2026-08-10-ch6ab-review-findings.md) → reworked (this commit) — SHOULD-FIXES 2/3/4/5 + NITs 1-5 applied | pending — no client launches this session (hard constraint); needs the next connected round for keybind/mirror/filter visual confirmation, plus the new 0x2100005B fixture's resolved-type assumptions | | CH6c opacity | not started | — | — | not started | ### CH4 closeout (2026-08-09) @@ -722,3 +722,98 @@ retail LayoutDesc dumps) to find the smallest font id actually CONFIRMED in use by any retail UI import acdream has ported, rather than merely present in the DAT. `0x40000025` (11px) was both the global minimum across those fixtures AND the chat window's own minimum — no tie-break needed. + +## CH6a/b REJECT-review rework (2026-08-10) + +Applied everything in `docs/research/2026-08-10-ch6ab-review-findings.md` +under the session's hard constraints (no subagents, no client launches). +The retail research itself held up under independent re-derivation +(masks, Alt+1..4, pure-mirror indicators all CONFIRMED); the rework was +entirely against the FIX list, not the research. + +- **BLOCKER 1 (CH6a — invisible borders).** `UiResizeGrip` (`src/AcDream.App/UI/UiResizeGrip.cs`) + gained a second constructor carrying its `ElementInfo`/sprite-resolve + pair and an `OnDraw` override that stamps the active DirectState sprite + exactly like `UiDatElement` — a synthetic (parameterless) grip still + draws nothing, matching every existing resize-drag unit test. + `DatWidgetFactory.BuildResizeGrip` now threads `resolve` through. + `SpriteFile` exposes the raw dat sprite id for conformance tests without + needing a live GL context; `ChatLayoutConformanceTests + .MountedChatWindow_LiveGrip_ResolvesNonZeroSprite` pins all seven live + grips against the committed `chat_2100006f.json` fixture. +- **SHOULD-FIX 2 (CH6b — window-id model).** `ChatWindowState` gained + `BroadcastTargetWindow` (a sentinel distinct from every real window id + `0`-`4`) so `ShouldDisplay`'s explicit-addressing branch can no longer + coincide with the broadcast check for the main window (id `0`). + `SetFilter`'s main-window no-op is dropped — the main window's filter is + now genuinely settable, matching retail's options-page-driven main + filter. `ChatWindowController.Bind` gained a `ChatWindowState + windowFilters` parameter (the same canonical + `RuntimeCommunicationState.ChatWindows` instance the floating windows + already read) and `GetTranscriptLines` now builds a real accept + predicate from it instead of `accept: null` — verified safe: `0x1A` + (`ClientLocal`) never reaches `ChatLog` in the first place + (`RuntimeCommunicationState.AddText` routes it to the SpewBox and + returns before `Chat.OnSystemMessage`), so excluding it from the main + filter has zero effect on WeenieError/refusal text. Every production + gameplay chat type (Speech/Tell/Social/Fellowship/Allegiance/Turbine + rooms) stays within the default filter's covered bits. Register row + AP-189 documents the resulting shallower shared-log scrollback depth + (SHOULD-FIX 5, below) as the one behavioral residual. +- **SHOULD-FIX 3 (CH6b — indicator self-toggle).** `UiButton` gained + `SuppressSelfToggle`, checked alongside `ToggleBehavior` at the + press-release toggle site; `ChatWindowController.Bind` sets it on all + four chat-window indicator buttons (which carry DAT property `0x0B` = + true but have no retail click handler). New `UiButtonTests + .SuppressSelfToggle_PressReleaseDoesNotFlipSelected` and an extended + `MountedChatWindow_IndicatorButtons_ImportVisibleAndInert` (now presses + and releases the real fixture's indicator, asserting `Selected` + unchanged) replace the "wrong level" `OnClick == null` check. +- **SHOULD-FIX 4 (CH6b — 0x2100005B never dumped).** The floaty layout + DAT dir at `%USERPROFILE%\Documents\Asheron's Call\` (the default + `ConformanceDats.ResolveDatDir()`/`RetailLayoutFixtureGenerator` path) + IS reachable in this environment — `chat_floaty_2100005b.json` (171 KB, + 5,473 lines) was generated for real and committed; + `RetailLayoutFixtureGenerator.Layouts` gained the permanent entry for + future regenerations. All three flagged untested assumptions in + `FloatingChatWindowController` turned out CORRECT against the real + fixture — no controller code changed: `0x10000016` is Type 12 with + property `0x16` (Editable) = true, resolving to `UiField`; `0x100004D9` + is Type 12 with NO `0x16`, resolving to `UiText`; `0x1000052A` is Type 1, + resolving to `UiButton`. One extra finding beyond the three named + assumptions: unlike the main window (whose plain top strip is a Type-2 + Dragbar move handle because the main window has no title bar), the + floaty window's move handle is its own title bar group (`0x10000529`, + Type 2) — so ALL EIGHT of the floaty's border/corner elements are live + Type-9 grips, not seven; a floaty window resizes from every edge and + corner. New `FloatingChatLayoutConformanceTests.cs` pins the resolved + widget types and extends BLOCKER 1's sprite-non-zero guard to all eight + floaty grips. +- **SHOULD-FIX 5 (register row).** AP-189 files the shared-500-entry/ + 200-line-tail vs. retail's per-window 10,000-line log scrollback-depth + divergence — the accumulate-while-closed and independent-per-window- + scroll BEHAVIORS both reproduce correctly; only the numeric DEPTH ceiling + differs. +- **NITs 1-5.** N1: documented the filter-persistence-only-on-`/saveautoui` + asymmetry on `RetailUiRuntime.SaveChatWindowFilters`, deferred to CH6e. + N2: documented why `ChatWindows.ResetToDefaults()` only runs in + `RuntimeCommunicationState.Dispose` (full teardown), not on reconnect — + deliberate, matches window-geometry persistence. N3: replaced the §1.3 + UNVERIFIED modifier-mapping hedge and the "`0x00000002` = shift" mislabel + with `retail-default.keymap.txt`'s own `MetaKeys` legend (Shift=`0x1`, + Ctrl=`0x2`, Alt=`0x4`, Win=`0x8`) — no code change, `KeyBindings` already + had `ModifierMask.Alt` right. N4: fixed the false "ONLY function that + branches on `idMessage == 1`" superlative in §1.4 and + `ChatWindowController.SetIndicatorOpen`'s doc + (`gmFloatyChatUI::ListenToElementMessage @0x004CE330` also does); the + substantive "no case for the indicator ids" claim stands. N5: moved + `WrapText`/`WrapSingleLine` off `ChatWindowController` onto + `ChatTranscriptRenderer` (closing the circular dependency where + `BuildLines` called back into one of its own two consumers); updated the + two unrelated external callers (`IndicatorDetailText.cs`, + `ItemAppraisalReport.cs`) and moved the WrapText tests into the new + `ChatTranscriptRendererTests.cs`. + +Full Release suite: 12,420 passed / 4 skipped / 0 failed (baseline +12,392/4/0 at `22020ef2`; net +28 tests, all new coverage from this +rework, zero regressions). diff --git a/docs/research/2026-08-09-chat-retail-window-shell.md b/docs/research/2026-08-09-chat-retail-window-shell.md index 2c9de333..0c679d15 100644 --- a/docs/research/2026-08-09-chat-retail-window-shell.md +++ b/docs/research/2026-08-09-chat-retail-window-shell.md @@ -87,11 +87,30 @@ ToggleFloatingChatWindow4 [ "" [ 0 DIK_4 ] 0x00000004 ] ``` `0x00000004` is the modifier mask (the same mask the `UseQuickSlot_14..18` rows -use for `Alt`+digit, versus `0x00000002` for the shift-digit quickslot rows). +use for `Alt`+digit, versus `0x00000002` for the CTRL-digit quickslot rows — +corrected below; the original filing mislabeled `0x00000002` as "shift"). So retail's default is **Alt+1 … Alt+4**, not bare 1–4. -UNVERIFIED: the exact modifier bit → key mapping (4 = Alt vs 4 = Ctrl). Cheapest -resolution: read `keymap-default.txt`'s modifier legend, or one cdb breakpoint on -`UIElementManager::KeyPressEvent`. The *mechanism* below is independent of this. +RESOLVED at Campaign CH slice CH6b: `retail-default.keymap.txt`'s own +`MetaKeys` legend (not a guess — the keymap file's literal index table) reads +``` +MetaKeys +[ + 1 [ 0 DIK_LSHIFT ] + 2 [ 0 DIK_LCONTROL ] + 2 [ 0 DIK_RCONTROL ] + 3 [ 0 DIK_LMENU ] + 3 [ 0 DIK_RALT ] + 4 [ 0 DIK_LWIN ] + 4 [ 0 DIK_RWIN ] +] +``` +so the modifier-mask bit for MetaKeys index *N* is `1 << (N-1)`: **index 1 = +Shift → `0x1`, index 2 = Ctrl → `0x2`, index 3 = Alt → `0x4`, index 4 = Win → +`0x8`**. `0x00000004` on the `ToggleFloatingChatWindow1..4` rows is therefore +unambiguously **Alt**, cross-checked against the same file's own Alt+A/D +strafe and Alt+Enter/Tab/F4 rows (all `0x00000004`). `KeyBindings` already +carried `ModifierMask.Alt` for these four actions since Phase K.1c — no code +change was needed, only removing this hedge. The dispatch chain is fully generic — there is no chat-specific code in it: @@ -151,10 +170,15 @@ State 6 = "on/depressed", state 1 = "normal". No handler anywhere in the binary switches on `0x10000522..0x10000525` as a *source* of a click — `grep` over the whole pseudo-C returns only `gmMainChatUI::RecvNotice_SetPanelVisibility`. -**RESOLVED 2026-08-10 at Campaign CH slice CH6b.** The prior UNVERIFIED -paragraph's hedge ("safe to wire both") is superseded by a direct read of -`gmMainChatUI::ListenToElementMessage @0x004CDA80` — the ONLY function in the -whole 2013 binary that branches on `idMessage == 1` ("clicked"). It handles +**RESOLVED 2026-08-10 at Campaign CH slice CH6b; wording corrected at the +CH6a/b REJECT-review (NIT 4) — the original "ONLY function in the whole 2013 +binary that branches on `idMessage == 1`" superlative was false** +(`gmFloatyChatUI::ListenToElementMessage @0x004CE330`, the floaty windows' +close-button handler, also branches on `idMessage == 1`; §1.5). **The +substantive claim stands: within `gmMainChatUI::ListenToElementMessage` +specifically, there is no case for `0x10000522`-`0x10000525`.** The prior +UNVERIFIED paragraph's hedge ("safe to wire both") is superseded by a direct +read of `gmMainChatUI::ListenToElementMessage @0x004CDA80`. It handles exactly two element ids: `0x1000046f` (max/min, dispatching `HandleMaximizeButton`) and the talk-focus menu's selection message (`idMessage == 7`, checked against `this->m_pCCS` / a `0x1000000b` attribute diff --git a/src/AcDream.App/UI/Layout/ChatTranscriptRenderer.cs b/src/AcDream.App/UI/Layout/ChatTranscriptRenderer.cs index 93ef941d..37694a68 100644 --- a/src/AcDream.App/UI/Layout/ChatTranscriptRenderer.cs +++ b/src/AcDream.App/UI/Layout/ChatTranscriptRenderer.cs @@ -19,6 +19,13 @@ namespace AcDream.App.UI.Layout; /// (revision/wrap-width/font keyed), matching the caching each controller /// already had before this extraction. /// +/// +/// +/// also lives here (CH6a/b REJECT-review NIT 5 — it +/// used to live on , which made +/// call BACK into its own caller's class, a circular +/// dependency between this "shared" module and one of its two consumers). +/// /// internal static class ChatTranscriptRenderer { @@ -28,12 +35,15 @@ internal static class ChatTranscriptRenderer /// /// Optional per-line filter — retail's ChatInterface::TypeIsActive /// (or the full ShouldDisplay predicate) for THIS window. Null - /// accepts every line (the main window has no user filter — color-table - /// research doc §4). A line that fails the filter is dropped from this - /// window's view WITHOUT advancing the carried-forward color, matching - /// retail's m_curFontColor only advancing for lines actually - /// appended to THIS window's own scroll (AppendStringInfoWithFont - /// only runs for displayed lines). + /// accepts every line. CH6a/b REJECT-review SHOULD-FIX 2: the main + /// window now ALWAYS passes a real predicate too (its own retail default + /// 0xFBFFFFFF filter via ChatWindowState) — null remains supported + /// for callers with no filter concept at all (there are none in + /// production today, but the shape stays general). A line that fails the + /// filter is dropped from this window's view WITHOUT advancing the + /// carried-forward color, matching retail's m_curFontColor only + /// advancing for lines actually appended to THIS window's own scroll + /// (AppendStringInfoWithFont only runs for displayed lines). /// public static List BuildLines( IReadOnlyList detailed, @@ -57,9 +67,89 @@ internal static class ChatTranscriptRenderer continue; if (RetailChatColorTable.TryGetColor(d.LogTextType, out Vector4 resolved)) currentColor = resolved; - foreach (string frag in ChatWindowController.WrapText(d.Text, maxW, measure)) + foreach (string frag in WrapText(d.Text, maxW, measure)) result.Add(new UiText.Line(frag, currentColor)); } return result; } + + /// + /// Greedy word-wrap: split into fragments that each fit in + /// pixels (per ), breaking at spaces. + /// A word that is itself wider than the line is broken at CHARACTER boundaries (no + /// hyphen), packed onto the current line first — so a long unbroken token (e.g. a URL + /// or "wwwww…") wraps instead of overflowing, and a "You say," prefix stays on the same + /// row as the start of the message. Mirrors retail GlyphList::Recalculate's per-GlyphLine + /// emission (which breaks mid-glyph-run when a run exceeds the wrap width). + /// + public static IEnumerable WrapText(string text, float maxW, Func measure) + { + if (string.IsNullOrEmpty(text)) + { + yield return string.Empty; + yield break; + } + + // Campaign CH user-gate round 1 (item F): server text (e.g. /help's + // reply) carries embedded '\n's. This function used to hand the + // WHOLE blob — newlines and all — to the single early-out below, + // rendering multi-line text as one UiText.Line with literal newline + // characters in it instead of one rendered line per segment. Split + // on '\n' FIRST (normalizing "\r\n"/bare "\r" the same way), then + // word-wrap each segment independently; the early-out is now scoped + // to one already-newline-free segment, so it only ever collapses a + // single-segment text to one line, never a multi-line one. + string normalized = text.Replace("\r\n", "\n").Replace('\r', '\n'); + foreach (string segment in normalized.Split('\n')) + { + foreach (string frag in WrapSingleLine(segment, maxW, measure)) + yield return frag; + } + } + + /// + /// Greedy word-wrap for a single, already newline-free line. Split out of + /// (Campaign CH user-gate round 1, item F) so the + /// multi-segment split there can call this once per '\n'-delimited + /// segment without re-deriving the per-line wrap algorithm. + /// + private static IEnumerable WrapSingleLine(string text, float maxW, Func measure) + { + if (text.Length == 0 || maxW <= 0f || measure(text) <= maxW) + { + yield return text; + yield break; + } + + var line = new System.Text.StringBuilder(); + foreach (var word in text.Split(' ')) + { + string sep = line.Length > 0 ? " " : string.Empty; + if (measure(line.ToString() + sep + word) <= maxW) + { + line.Append(sep).Append(word); // fits on the current line + continue; + } + if (line.Length > 0 && measure(word) <= maxW) + { + yield return line.ToString(); // word fits alone → push to a new line + line.Clear(); + line.Append(word); + continue; + } + // Word too long for any single line: char-wrap it, packing onto the current + // line's remaining space first (keeps the prefix with the message start). + if (line.Length > 0) line.Append(' '); + foreach (char ch in word) + { + if (line.Length > 0 && measure(line.ToString() + ch) > maxW) + { + yield return line.ToString(); + line.Clear(); + } + line.Append(ch); + } + } + if (line.Length > 0) yield return line.ToString(); + } } diff --git a/src/AcDream.App/UI/Layout/ChatWindowController.cs b/src/AcDream.App/UI/Layout/ChatWindowController.cs index 6dfef4a8..58816641 100644 --- a/src/AcDream.App/UI/Layout/ChatWindowController.cs +++ b/src/AcDream.App/UI/Layout/ChatWindowController.cs @@ -119,12 +119,18 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta private ChatChannelKind _activeChannel = ChatChannelKind.Say; + // The main window's own per-window filter/open state (CH6a/b REJECT-review + // SHOULD-FIX 2) — the SAME canonical instance the floating windows read + // (RuntimeCommunicationState.ChatWindows), never a presentation-owned copy. + private ChatWindowState _windowFilters = null!; + // UiText polls LinesProvider while drawing and hit-testing. Keep the fully // formatted + wrapped transcript until either its source revision or the // metrics that determine wrapping change. This makes an idle chat window // allocation-free instead of snapshotting/formatting/wrapping every frame. private IReadOnlyList _cachedTranscriptLines = Array.Empty(); private long _cachedTranscriptRevision = -1; + private ulong _cachedFilter; private float _cachedTranscriptWrapWidth = float.NaN; private UiDatFont? _cachedTranscriptDatFont; private BitmapFont? _cachedTranscriptDebugFont; @@ -202,6 +208,11 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta /// Called on every chat submit so it resolves /// even when the live session is established AFTER runs /// (mirrors the ImGui ChatPanel which re-reads the bus each frame). + /// Runtime's canonical per-window filter/open state + /// () — the + /// SAME instance the floating windows read. Read live on every transcript rebuild + /// (CH6a/b REJECT-review SHOULD-FIX 2: the main window's own filter is a real, + /// user-settable predicate now, not an inert no-op). /// Retail dat font for transcript + input rendering. /// Fallback debug bitmap font (used when /// is null). @@ -212,10 +223,13 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta ImportedLayout layout, ChatVM vm, Func busProvider, + ChatWindowState windowFilters, UiDatFont? datFont, BitmapFont? debugFont, Func resolve) { + ArgumentNullException.ThrowIfNull(windowFilters); + // Their parent panels must exist as real widgets in the layout tree. var transcriptPanel = layout.FindElement(TranscriptPanelId); var inputBar = layout.FindElement(InputBarId); @@ -240,6 +254,7 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta { Root = window, DatWindowInfo = FindInfo(rootInfo, RootId) ?? rootInfo, + _windowFilters = windowFilters, }; // The 8 cosmetic "_Locked" border-art twins default HIDDEN — CH6a does not @@ -253,10 +268,22 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta // ── Chat-window 1-4 indicator buttons — resolve now, wired later by // RetailUiRuntime via SetIndicatorOpen as each floating window's own // visibility changes (gmMainChatUI::RecvNotice_SetPanelVisibility - // @0x004CCD80 — see this class's doc + Indicator1Id..Indicator4Id). ── + // @0x004CCD80 — see this class's doc + Indicator1Id..Indicator4Id). + // CH6a/b REJECT-review SHOULD-FIX 3: these carry DAT property 0x0B + // (ToggleBehavior) = true, but retail's own click dispatch + // (gmMainChatUI::ListenToElementMessage @0x004CDA80) has no case for + // any of their element ids — clicking one does NOTHING in retail. + // SuppressSelfToggle keeps SetIndicatorOpen the ONLY writer of their + // Selected mirror; without it, a click would flip the Highlight/Normal + // art with no underlying visibility change. ── uint[] indicatorIds = { Indicator1Id, Indicator2Id, Indicator3Id, Indicator4Id }; for (int i = 0; i < indicatorIds.Length; i++) - c._indicatorButtons[i] = layout.FindElement(indicatorIds[i]) as UiButton; + { + var indicator = layout.FindElement(indicatorIds[i]) as UiButton; + if (indicator is not null) + indicator.SuppressSelfToggle = true; + c._indicatorButtons[i] = indicator; + } // ── Transcript ─────────────────────────────────────────────────── // The factory now builds the Type-12 transcript element (0x10000011) as a UiText. @@ -483,11 +510,14 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta /// /// /// One-directional by design — a decomp read of - /// gmMainChatUI::ListenToElementMessage @0x004CDA80 (the ONLY - /// function in the whole 2013 binary that branches on - /// idMessage == 1, i.e. "clicked") shows it handles exactly two - /// element ids: 0x1000046f (max/min) and the talk-focus menu's - /// selection message. There is no case for + /// gmMainChatUI::ListenToElementMessage @0x004CDA80 (one of + /// several functions in the 2013 binary that branch on + /// idMessage == 1, i.e. "clicked" — CH6a/b REJECT-review NIT 4 + /// corrected the earlier "the ONLY function" superlative, e.g. + /// gmFloatyChatUI::ListenToElementMessage @0x004CE330 also + /// branches on it for the floaty close button) shows THIS function + /// handles exactly two element ids: 0x1000046f (max/min) and the + /// talk-focus menu's selection message. There is no case for /// 0x10000522-0x10000525 — clicking a chat-window /// indicator button does NOTHING in retail. acdream ports this exactly: /// these buttons have no OnClick (research doc §1.4 corrected — @@ -543,8 +573,10 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta UiDatFont? datFont = Transcript.DatFont; BitmapFont? debugFont = Transcript.Font; long revision = vm.Revision; + ulong filter = _windowFilters.GetFilter(ChatWindowState.MainWindowId); if (_cachedTranscriptRevision == revision + && _cachedFilter == filter && _cachedTranscriptWrapWidth.Equals(maxW) && ReferenceEquals(_cachedTranscriptDatFont, datFont) && ReferenceEquals(_cachedTranscriptDebugFont, debugFont)) @@ -556,7 +588,7 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta if (detailed.Count == 0) { return StoreTranscriptLayout( - Array.Empty(), revision, maxW, datFont, debugFont); + Array.Empty(), revision, filter, maxW, datFont, debugFont); } // Word-wrap each message to the transcript's current pixel width (ports retail @@ -570,21 +602,27 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta // Campaign CH slice CH6b: the wrap + retail color-carry-forward // algorithm is now shared with FloatingChatWindowController via // ChatTranscriptRenderer (approximation note on the color carry - // moved there). The main window passes accept:null — it has no - // user filter (color-table research doc §4); this is behaviorally - // identical to the inline loop this replaced. - var result = ChatTranscriptRenderer.BuildLines(detailed, maxW, measure, accept: null); - return StoreTranscriptLayout(result, revision, maxW, datFont, debugFont); + // moved there). CH6a/b REJECT-review SHOULD-FIX 2: the main window + // now has a REAL accept predicate (retail's default 0xFBFFFFFF — + // everything except 0x1A, high dword zeroed so Society is opt-in), + // driven by the SAME ChatWindowState the floating windows read — + // no more accept:null "no user filter" placeholder. + bool Accept(uint logTextType) => _windowFilters.ShouldDisplay( + ChatWindowState.MainWindowId, ChatWindowState.BroadcastTargetWindow, logTextType); + var result = ChatTranscriptRenderer.BuildLines(detailed, maxW, measure, Accept); + return StoreTranscriptLayout(result, revision, filter, maxW, datFont, debugFont); } private IReadOnlyList StoreTranscriptLayout( IReadOnlyList lines, long revision, + ulong filter, float wrapWidth, UiDatFont? datFont, BitmapFont? debugFont) { _cachedTranscriptRevision = revision; + _cachedFilter = filter; _cachedTranscriptWrapWidth = wrapWidth; _cachedTranscriptDatFont = datFont; _cachedTranscriptDebugFont = debugFont; @@ -593,86 +631,6 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta return lines; } - /// - /// Greedy word-wrap: split into fragments that each fit in - /// pixels (per ), breaking at spaces. - /// A word that is itself wider than the line is broken at CHARACTER boundaries (no - /// hyphen), packed onto the current line first — so a long unbroken token (e.g. a URL - /// or "wwwww…") wraps instead of overflowing, and a "You say," prefix stays on the same - /// row as the start of the message. Mirrors retail GlyphList::Recalculate's per-GlyphLine - /// emission (which breaks mid-glyph-run when a run exceeds the wrap width). - /// - public static IEnumerable WrapText(string text, float maxW, Func measure) - { - if (string.IsNullOrEmpty(text)) - { - yield return string.Empty; - yield break; - } - - // Campaign CH user-gate round 1 (item F): server text (e.g. /help's - // reply) carries embedded '\n's. This function used to hand the - // WHOLE blob — newlines and all — to the single early-out below, - // rendering multi-line text as one UiText.Line with literal newline - // characters in it instead of one rendered line per segment. Split - // on '\n' FIRST (normalizing "\r\n"/bare "\r" the same way), then - // word-wrap each segment independently; the early-out is now scoped - // to one already-newline-free segment, so it only ever collapses a - // single-segment text to one line, never a multi-line one. - string normalized = text.Replace("\r\n", "\n").Replace('\r', '\n'); - foreach (string segment in normalized.Split('\n')) - { - foreach (string frag in WrapSingleLine(segment, maxW, measure)) - yield return frag; - } - } - - /// - /// Greedy word-wrap for a single, already newline-free line. Split out of - /// (Campaign CH user-gate round 1, item F) so the - /// multi-segment split there can call this once per '\n'-delimited - /// segment without re-deriving the per-line wrap algorithm. - /// - private static IEnumerable WrapSingleLine(string text, float maxW, Func measure) - { - if (text.Length == 0 || maxW <= 0f || measure(text) <= maxW) - { - yield return text; - yield break; - } - - var line = new System.Text.StringBuilder(); - foreach (var word in text.Split(' ')) - { - string sep = line.Length > 0 ? " " : string.Empty; - if (measure(line.ToString() + sep + word) <= maxW) - { - line.Append(sep).Append(word); // fits on the current line - continue; - } - if (line.Length > 0 && measure(word) <= maxW) - { - yield return line.ToString(); // word fits alone → push to a new line - line.Clear(); - line.Append(word); - continue; - } - // Word too long for any single line: char-wrap it, packing onto the current - // line's remaining space first (keeps the prefix with the message start). - if (line.Length > 0) line.Append(' '); - foreach (char ch in word) - { - if (line.Length > 0 && measure(line.ToString() + ch) > maxW) - { - yield return line.ToString(); - line.Clear(); - } - line.Append(ch); - } - } - if (line.Length > 0) yield return line.ToString(); - } - public void Dispose() { if (_disposed) return; diff --git a/src/AcDream.App/UI/Layout/DatWidgetFactory.cs b/src/AcDream.App/UI/Layout/DatWidgetFactory.cs index 34f069db..99f9bd73 100644 --- a/src/AcDream.App/UI/Layout/DatWidgetFactory.cs +++ b/src/AcDream.App/UI/Layout/DatWidgetFactory.cs @@ -95,7 +95,7 @@ public static class DatWidgetFactory // gmUIElement_*Indicator custom button classes 6 => new UiMenu(), // UIElement_Menu (reg :120163) 7 => BuildMeter(info, resolve, elementFont), // UIElement_Meter - 9 => BuildResizeGrip(info), // UIElement_Resizebar (reg 0x0046B920) + 9 => BuildResizeGrip(info, resolve), // UIElement_Resizebar (reg 0x0046B920) 0xD => new UiViewport(), // UIElement_Viewport — 3-D mini-scene blit leaf 11 => BuildScrollbar(info, resolve), // UIElement_Scrollbar (reg :124137) 12 => BuildText(info, resolve, elementFont, stringResolve), // UIElement_Text @@ -266,14 +266,18 @@ public static class DatWidgetFactory /// all-false Type-9 element is handled defensively the same way retail's own /// BORDER_NONE fallback does) decodes to /// — then treats it as contributing no resize edges. + /// is carried through (CH6a/b REJECT-review + /// BLOCKER 1) so the grip draws its own authored border/corner media + /// instead of nothing. /// - private static UiResizeGrip BuildResizeGrip(ElementInfo info) + private static UiResizeGrip BuildResizeGrip( + ElementInfo info, Func resolve) { bool bottom = info.TryGetEffectiveBool(0x2Au, out bool bottomValue) && bottomValue; bool left = info.TryGetEffectiveBool(0x2Bu, out bool leftValue) && leftValue; bool right = info.TryGetEffectiveBool(0x2Cu, out bool rightValue) && rightValue; bool top = info.TryGetEffectiveBool(0x2Du, out bool topValue) && topValue; - return new UiResizeGrip + return new UiResizeGrip(info, resolve) { BorderLocation = UiResizeGrip.DecodeBorderLocation(bottom, left, right, top), }; diff --git a/src/AcDream.App/UI/Layout/FloatingChatWindowController.cs b/src/AcDream.App/UI/Layout/FloatingChatWindowController.cs index 858bdae0..3f3c7255 100644 --- a/src/AcDream.App/UI/Layout/FloatingChatWindowController.cs +++ b/src/AcDream.App/UI/Layout/FloatingChatWindowController.cs @@ -275,7 +275,8 @@ public sealed class FloatingChatWindowController : IRetainedPanelController : debugFont is { } bf ? s => bf.MeasureWidth(s) : static s => s.Length * 7f; - bool Accept(uint logTextType) => windowFilters.ShouldDisplay(WindowId, targetWindowId: 0u, logTextType); + bool Accept(uint logTextType) => windowFilters.ShouldDisplay( + WindowId, ChatWindowState.BroadcastTargetWindow, logTextType); var result = ChatTranscriptRenderer.BuildLines(detailed, maxW, measure, Accept); _cachedTranscriptRevision = revision; diff --git a/src/AcDream.App/UI/Layout/IndicatorDetailText.cs b/src/AcDream.App/UI/Layout/IndicatorDetailText.cs index fc613209..baf4b5b2 100644 --- a/src/AcDream.App/UI/Layout/IndicatorDetailText.cs +++ b/src/AcDream.App/UI/Layout/IndicatorDetailText.cs @@ -32,7 +32,7 @@ internal static class IndicatorDetailText continue; } - foreach (string line in ChatWindowController.WrapText(paragraph, maxWidth, Measure)) + foreach (string line in ChatTranscriptRenderer.WrapText(paragraph, maxWidth, Measure)) lines.Add(new UiText.Line(line, target.DefaultColor)); } return lines; diff --git a/src/AcDream.App/UI/Layout/ItemAppraisalReport.cs b/src/AcDream.App/UI/Layout/ItemAppraisalReport.cs index 3997e6ee..5f062813 100644 --- a/src/AcDream.App/UI/Layout/ItemAppraisalReport.cs +++ b/src/AcDream.App/UI/Layout/ItemAppraisalReport.cs @@ -169,7 +169,7 @@ internal static class ItemAppraisalTextLayout continue; } - foreach (string wrapped in ChatWindowController.WrapText( + foreach (string wrapped in ChatTranscriptRenderer.WrapText( logicalLine, maxWidth, Measure)) diff --git a/src/AcDream.App/UI/RetailUiRuntime.cs b/src/AcDream.App/UI/RetailUiRuntime.cs index 7feeeea9..edec8c2b 100644 --- a/src/AcDream.App/UI/RetailUiRuntime.cs +++ b/src/AcDream.App/UI/RetailUiRuntime.cs @@ -575,6 +575,17 @@ public sealed class RetailUiRuntime : IDisposable /// Capture the four floating windows' current text-type filters into /// the local settings store (research doc §4.4/§6.1 — local-only until /// CH6f's wire format). No-op when no store was wired. + /// + /// + /// CH6a/b REJECT-review NIT 1: unlike window geometry/visibility (which + /// auto-saves on every change), a filter + /// change is only captured here — i.e. only when + /// runs, which today is exclusively the explicit /saveautoui + /// command. Harmless in practice (nothing currently mutates a window's + /// filter live — no options UI exists yet), but worth tightening to + /// auto-save-on-change once CH6e/CH6f gives filters a live settings + /// surface a user can actually edit mid-session. + /// /// private void SaveChatWindowFilters() { @@ -807,6 +818,7 @@ public sealed class RetailUiRuntime : IDisposable layout, _bindings.Chat.ViewModel, _bindings.Chat.CommandBus, + _bindings.Chat.Windows, _bindings.Assets.DefaultFont, _bindings.Assets.DebugFont, _bindings.Assets.ResolveSprite); diff --git a/src/AcDream.App/UI/UiButton.cs b/src/AcDream.App/UI/UiButton.cs index e642ccc4..235c4ac7 100644 --- a/src/AcDream.App/UI/UiButton.cs +++ b/src/AcDream.App/UI/UiButton.cs @@ -111,6 +111,23 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful public float HotClickInitialDelay { get; } public float HotClickRepeatInterval { get; } + /// + /// Opt-out for a button whose + /// state is a PURE MIRROR of external state (a producer other than the click + /// itself is the only legitimate writer — e.g. + /// mirroring a floating chat window's own visibility). CH6a/b REJECT-review + /// SHOULD-FIX 3: the chat-window 1-4 indicators (0x10000522-0x10000525) + /// carry DAT property 0x0B () = true, so + /// without this flag a click flips their Highlight/Normal art with no + /// underlying visibility change — the mirror lies until the next real + /// toggle. Retail confirms clicking these buttons does nothing + /// (gmMainChatUI::ListenToElementMessage @0x004CDA80 has no case for + /// their element ids — see 's + /// doc). Default — every OTHER toggle button (max/min, + /// checkboxes) keeps retail's normal click-toggles-itself behavior. + /// + public bool SuppressSelfToggle { get; set; } + public bool Selected { get => _selected; @@ -435,7 +452,7 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful _suppressNextClick = _hotClicking && _pointerOver; _hotClicking = false; _nextHotClickTime = double.NaN; - if (_pressed && _pointerOver && Enabled && ToggleBehavior) + if (_pressed && _pointerOver && Enabled && ToggleBehavior && !SuppressSelfToggle) _selected = !_selected; if (_pressed && Enabled) OnReleased?.Invoke(); diff --git a/src/AcDream.App/UI/UiResizeGrip.cs b/src/AcDream.App/UI/UiResizeGrip.cs index c4930ec0..425bacd8 100644 --- a/src/AcDream.App/UI/UiResizeGrip.cs +++ b/src/AcDream.App/UI/UiResizeGrip.cs @@ -1,3 +1,7 @@ +using System; +using System.Numerics; +using AcDream.App.UI.Layout; + namespace AcDream.App.UI; /// @@ -23,10 +27,19 @@ namespace AcDream.App.UI; /// /// /// -/// A grip is a small (5px) leaf with no children and no authored background -/// media beyond its own cursor-feedback sprite; it exists purely as a precise -/// hit region. gives a directly-hit grip's -/// own priority over the generic proximity-based +/// A grip is a small (5px) leaf with no children; it exists primarily as a +/// precise hit region, but it IS authored with real border/corner media (the +/// 0x06006129-family sprites — CH6a/b REJECT-review BLOCKER 1, +/// docs/research/2026-08-10-ch6ab-review-findings.md: every one of the +/// seven live grips on the main chat window carries a non-zero DirectState +/// sprite, and drawing nothing left the window with only its 400×5 top strip +/// visible). A factory-built grip (, +/// Type 9) therefore draws its own DirectState media exactly like +/// would; a synthetic grip +/// built with the parameterless constructor (unit tests exercising only the +/// resize-drag behavior) carries no media and draws nothing, matching its +/// prior behavior. gives a directly-hit +/// grip's own priority over the generic proximity-based /// heuristic, so e.g. the chat /// window's top-left/top-right CORNER grips resize (including the Y/top axis) /// while the plain top EDGE strip in between remains a pure move handle. @@ -34,6 +47,57 @@ namespace AcDream.App.UI; /// public sealed class UiResizeGrip : UiElement { + private readonly ElementInfo? _info; + private readonly Func? _resolve; + + /// Synthetic grip with no authored media — used by unit tests that + /// exercise only the resize-drag hit-testing/edge behavior. + public UiResizeGrip() + { + } + + /// + /// Factory-built grip (CH6a/b REJECT-review BLOCKER 1): carries its resolved + /// and sprite resolver so it draws its own + /// authored DirectState media, the same way every other dat-imported leaf + /// does. is left at the base + /// default — unlike 's + /// decoration default of — because a grip must stay + /// hit-testable to capture the resize drag. + /// + public UiResizeGrip(ElementInfo info, Func resolve) + { + _info = info; + _resolve = resolve; + } + + /// + /// This grip's authored DirectState sprite file id (0 for a synthetic grip, + /// or an authored grip with no media). Exposed for conformance tests — + /// BLOCKER 1's regression guard is that every one of the seven live Type-9 + /// grips resolves a non-zero value here. + /// + public uint SpriteFile => _info is not null && _info.StateMedia.TryGetValue("", out var media) + ? media.File + : 0u; + + /// + /// Draws this grip's own DirectState media exactly like + /// — tiled at native size (Normal), same + /// blend for Overlay/Alphablend since the sprite shader already + /// alpha-blends. A synthetic (parameterless-constructed) grip has no + /// resolver and draws nothing, matching its pre-BLOCKER-1 behavior. + /// + protected override void OnDraw(UiRenderContext ctx) + { + if (_resolve is null) return; + uint file = SpriteFile; + if (file == 0u) return; + var (tex, tw, th) = _resolve(file); + if (tex == 0 || tw == 0 || th == 0) return; + ctx.DrawSprite(tex, 0, 0, Width, Height, 0, 0, Width / tw, Height / th, Vector4.One); + } + /// Retail BorderLocation (acclient.h:4327): /// BORDER_NONE=0, BORDER_UL=1, BORDER_TOP=2, BORDER_UR=3, BORDER_RIGHT=4, /// BORDER_LR=5, BORDER_BOTTOM=6, BORDER_LL=7, BORDER_LEFT=8. diff --git a/src/AcDream.Core/Chat/ChatWindowState.cs b/src/AcDream.Core/Chat/ChatWindowState.cs index 39079535..9706eae1 100644 --- a/src/AcDream.Core/Chat/ChatWindowState.cs +++ b/src/AcDream.Core/Chat/ChatWindowState.cs @@ -8,7 +8,13 @@ namespace AcDream.Core.Chat; /// id 0 is the main chat window, ids 1-4 are the four /// floating chat windows (Campaign CH slice CH6b, /// docs/research/2026-08-09-chat-retail-window-shell.md §1.2 and -/// docs/research/2026-08-09-chat-retail-color-table.md §4). +/// docs/research/2026-08-09-chat-retail-color-table.md §4). These are +/// acdream's OWN compact indices, not retail's raw m_eWindowID values: +/// retail's actual main window is m_eWindowID == 8, its floaties are +/// 2-5, and m_eWindowID == 0 is retail's ctor-default +/// "unauthored" sentinel — none of that is 0-4 (CH6a/b +/// REJECT-review SHOULD-FIX 2, +/// docs/research/2026-08-10-ch6ab-review-findings.md). /// /// /// Ports two retail mechanisms exactly: @@ -16,38 +22,45 @@ namespace AcDream.Core.Chat; /// ChatInterface::PostInit @0x004F3DD0's m_oldState /// switch seeds each window's default 64-bit /// m_llTextTypeFilter (color-table doc §4's table — the constants -/// below are byte-identical to that table). +/// below are byte-identical to that table; retail's own main-window default +/// 0xFBFFFFFF is shared by m_eWindowID 1 AND 8, and the floaty +/// defaults come from retail m_eWindowID 2-5, one higher than +/// acdream's compact 1-4). /// ChatInterface::RecvNotice_DisplayFinalStringInfo /// @0x004F4640's display predicate: a line shows in window /// W when the message's target window id equals W -/// (explicit addressing) OR the message is broadcast (target id -/// 0) AND W's filter accepts the line's -/// (ChatInterface::TypeIsActive +/// (explicit addressing) OR the message is broadcast +/// () AND W's filter accepts the +/// line's (ChatInterface::TypeIsActive /// @0x004F2F10). /// /// /// /// -/// UpdateFromPlayerModule @0x004F3920 early-returns for window id -/// 0 — the main window never has a user-settable filter. That -/// invariant needs no special case here: for windowId == 0, -/// 's first branch (targetWindowId == -/// windowId) is already true for every broadcast line (target id -/// 0), so the main window's filter is never actually consulted — -/// exactly matching retail's "no user filter" behavior without a guard. -/// and are still no-ops for -/// window 0 (it is always open and its seeded filter is inert), for -/// the same reason retail's setters gate on m_eWindowID != 0 -/// (persistence doc §4.2). +/// is a sentinel distinct from every real +/// window id (0-4) — the acdream-internal analogue of retail's +/// wire arg5 == 0 broadcast marker, deliberately kept separate from +/// window id 0 (main). Earlier CH6b code conflated the two (both were +/// literal 0), so 's explicit-addressing +/// branch (targetWindowId == windowId) short-circuited true for EVERY +/// broadcast line evaluated against the main window, regardless of its own +/// filter — the CH6a/b REJECT-review BLOCKER. With the sentinel separated +/// out, the main window's filter is genuinely consulted for broadcast lines, +/// is a real (not inert) write for window 0 +/// too, and a future explicit targetWindowId == MainWindowId (AP-180's +/// m_idCurrentCommandSource per-window echo) stays distinguishable +/// from an ordinary broadcast line. / +/// keep their own, UNRELATED no-op for window 0 — retail's main window +/// is simply never closable, independent of the broadcast-sentinel fix. /// /// /// /// No production carries an explicit target window /// id yet (register row AP-180 — the windowId dual-destination echo /// is deferred); every current line is effectively broadcast -/// (targetWindowId == 0). still accepts -/// the full retail shape so the routing predicate does not need to change -/// shape when AP-180 lands. +/// (targetWindowId == ). +/// still accepts the full retail shape so the +/// routing predicate does not need to change shape when AP-180 lands. /// /// public sealed class ChatWindowState @@ -56,6 +69,15 @@ public sealed class ChatWindowState public const int MinFloatingWindowId = 1; public const int MaxFloatingWindowId = 4; + /// + /// Sentinel targetWindowId meaning "broadcast to every window, + /// subject to each window's own filter" — the acdream-internal analogue + /// of retail's wire arg5 == 0. Deliberately outside the + /// 0-4 real-window-id range (see the class doc) so it can + /// never collide with . + /// + public const uint BroadcastTargetWindow = uint.MaxValue; + private const int WindowCount = MaxFloatingWindowId + 1; private readonly object _gate = new(); @@ -83,8 +105,9 @@ public sealed class ChatWindowState { lock (_gate) { - // "everything 0x00-0x1F except 0x1A" (m_oldState 1/8) — inert for - // routing (see class doc) but seeded for fidelity/inspection. + // "everything 0x00-0x1F except 0x1A" (m_oldState 1/8) — genuinely + // consulted for broadcast lines now that BroadcastTargetWindow is + // distinct from MainWindowId (see class doc). _filters[0] = 0xFBFFFFFFu; // Speech, Tell, Speech_Direct_Send, Emote (m_oldState 2). _filters[1] = 0x0000101Cu; @@ -110,13 +133,15 @@ public sealed class ChatWindowState } /// - /// Set window 's 64-bit type filter. No-op for - /// the main window (id 0) — see the class doc. + /// Set window 's 64-bit type filter — including + /// the main window (id 0): CH6a/b REJECT-review SHOULD-FIX 2 drops + /// the old no-op (see the class doc's + /// paragraph). Retail's main window IS user-settable through the options + /// page; only the settings UI to drive this is future work. /// public void SetFilter(int windowId, ulong filter) { ValidateWindowId(windowId); - if (windowId == MainWindowId) return; lock (_gate) { if (_filters[windowId] == filter) return; @@ -182,12 +207,15 @@ public sealed class ChatWindowState /// /// ChatInterface::RecvNotice_DisplayFinalStringInfo @0x004F4640's /// exact display predicate — see the class doc for the two branches. + /// is either a real window id + /// (0-4, explicit addressing) or + /// (broadcast, filtered per window). /// public bool ShouldDisplay(int windowId, uint targetWindowId, uint logTextType) { ValidateWindowId(windowId); if (targetWindowId == (uint)windowId) return true; - return targetWindowId == 0u && TypeIsActive(windowId, logTextType); + return targetWindowId == BroadcastTargetWindow && TypeIsActive(windowId, logTextType); } private static void ValidateWindowId(int windowId) diff --git a/src/AcDream.Runtime/Gameplay/RuntimeCommunicationState.cs b/src/AcDream.Runtime/Gameplay/RuntimeCommunicationState.cs index 460d300a..c6cdcccd 100644 --- a/src/AcDream.Runtime/Gameplay/RuntimeCommunicationState.cs +++ b/src/AcDream.Runtime/Gameplay/RuntimeCommunicationState.cs @@ -216,6 +216,13 @@ public sealed class RuntimeCommunicationState : IDisposable Squelch.Clear(); Chat.ResetSessionIdentity(); SpewBox.Reset(); + // CH6a/b REJECT-review NIT 2: this only runs here, at full teardown + // (process exit / GameRuntime disposal) — NOT on an ordinary + // reconnect, which never calls Dispose. Deliberate: a user's chat- + // window filter customization and open/closed state are client-side + // presentation preferences, the same class as window geometry + // (RetailWindowLayoutPersistence, which also survives reconnect) — + // reconnecting should not silently discard them. ChatWindows.ResetToDefaults(); } diff --git a/tests/AcDream.App.Tests/UI/Layout/ChatLayoutConformanceTests.cs b/tests/AcDream.App.Tests/UI/Layout/ChatLayoutConformanceTests.cs index f40c87d9..d1d50134 100644 --- a/tests/AcDream.App.Tests/UI/Layout/ChatLayoutConformanceTests.cs +++ b/tests/AcDream.App.Tests/UI/Layout/ChatLayoutConformanceTests.cs @@ -77,7 +77,7 @@ public class ChatLayoutConformanceTests var infos = FixtureLoader.LoadChatInfos(); var layout = LayoutImporter.Build(infos, NoTex, null); var controller = ChatWindowController.Bind( - infos, layout, new ChatVM(new ChatLog()), () => NullCommandBus.Instance, null, null, NoTex); + infos, layout, new ChatVM(new ChatLog()), () => NullCommandBus.Instance, new ChatWindowState(), null, null, NoTex); Assert.NotNull(controller); UiElement? indicator = layout.FindElement(indicatorId); @@ -91,6 +91,18 @@ public class ChatLayoutConformanceTests var button = Assert.IsType(indicator); Assert.Null(button.OnClick); Assert.Null(button.OnClickAt); + + // CH6a/b REJECT-review SHOULD-FIX 3: OnClick==null is the wrong level + // to prove "inert" — the fixture's own 0x0B (ToggleBehavior) = true + // means a plain press/release would still flip UiButton's internal + // Selected mirror even with no OnClick bound. SuppressSelfToggle + // (set by ChatWindowController.Bind) is the actual guard; a press + // and release must leave Selected unchanged. + Assert.True(button.SuppressSelfToggle); + bool before = button.Selected; + button.OnEvent(new UiEvent(0, button, UiEventType.MouseDown, Data1: 2, Data2: 2)); + button.OnEvent(new UiEvent(0, button, UiEventType.MouseUp, Data1: 2, Data2: 2)); + Assert.Equal(before, button.Selected); } [Theory] @@ -110,7 +122,7 @@ public class ChatLayoutConformanceTests var infos = FixtureLoader.LoadChatInfos(); var layout = LayoutImporter.Build(infos, NoTex, null); var controller = ChatWindowController.Bind( - infos, layout, new ChatVM(new ChatLog()), () => NullCommandBus.Instance, null, null, NoTex); + infos, layout, new ChatVM(new ChatLog()), () => NullCommandBus.Instance, new ChatWindowState(), null, null, NoTex); Assert.NotNull(controller); UiElement? twin = layout.FindElement(lockedTwinId); @@ -191,6 +203,7 @@ public class ChatLayoutConformanceTests layout, new ChatVM(new ChatLog()), () => NullCommandBus.Instance, + new ChatWindowState(), null, null, NoTex); @@ -226,6 +239,7 @@ public class ChatLayoutConformanceTests layout, new ChatVM(new ChatLog()), () => NullCommandBus.Instance, + new ChatWindowState(), null, null, NoTex); @@ -250,6 +264,7 @@ public class ChatLayoutConformanceTests layout, new ChatVM(new ChatLog()), () => NullCommandBus.Instance, + new ChatWindowState(), null, null, NoTex)!; @@ -324,6 +339,27 @@ public class ChatLayoutConformanceTests Assert.True(topStrip!.WindowMoveHandle); } + [Theory] + [InlineData(0x1000069Bu)] // TL corner + [InlineData(0x1000069Du)] // TR corner + [InlineData(0x1000069Eu)] // left edge + [InlineData(0x1000069Fu)] // BL corner + [InlineData(0x100006A0u)] // bottom edge + [InlineData(0x100006A1u)] // BR corner + [InlineData(0x100006A2u)] // right edge + public void MountedChatWindow_LiveGrip_ResolvesNonZeroSprite(uint elementId) + { + // CH6a/b REJECT-review BLOCKER 1: pre-fix, UiResizeGrip drew nothing — + // it was constructed without its ElementInfo/resolve pair, so all seven + // live Type-9 grips rendered NOTHING even though the fixture proves + // every one of them carries real authored art (the 0x06006129 family). + // This is the regression guard: every live grip must resolve a non-zero + // DirectState sprite id, not just decode the right BorderLocation. + var layout = FixtureLoader.LoadChat(); + var grip = Assert.IsType(layout.FindElement(elementId)); + Assert.NotEqual(0u, grip.SpriteFile); + } + [Fact] public void MountedChatWindow_BottomRightGrip_GrowsBothAxes_NotOnlyShrinks() { @@ -340,6 +376,7 @@ public class ChatLayoutConformanceTests layout, new ChatVM(new ChatLog()), () => NullCommandBus.Instance, + new ChatWindowState(), null, null, NoTex)!; @@ -396,6 +433,7 @@ public class ChatLayoutConformanceTests layout, new ChatVM(new ChatLog()), () => NullCommandBus.Instance, + new ChatWindowState(), null, null, NoTex)!; diff --git a/tests/AcDream.App.Tests/UI/Layout/ChatTranscriptRendererTests.cs b/tests/AcDream.App.Tests/UI/Layout/ChatTranscriptRendererTests.cs new file mode 100644 index 00000000..4a131c1c --- /dev/null +++ b/tests/AcDream.App.Tests/UI/Layout/ChatTranscriptRendererTests.cs @@ -0,0 +1,89 @@ +using System.Collections.Generic; +using System.Linq; +using AcDream.App.UI.Layout; + +namespace AcDream.App.Tests.UI.Layout; + +/// +/// tests — moved here from +/// ChatWindowControllerTests (CH6a/b REJECT-review NIT 5: WrapText +/// itself moved off ChatWindowController onto +/// to close the circular dependency +/// where called back into +/// one of its own two consumers). +/// +/// +/// Campaign CH user-gate round 1, item F: /help (and "probably many +/// places") never split on embedded '\n' — the whole multi-line blob +/// rode the single early-out as ONE line. Split on '\n' first, then +/// word-wrap each segment; a single-segment text keeps the pre-existing +/// early-out behavior exactly. +/// +/// +public class ChatTranscriptRendererTests +{ + private static float MeasureByCharCount(string s) => s.Length; + + [Fact] + public void WrapText_EmbeddedNewlines_ProduceOneRenderedLinePerSegment() + { + string text = "line one\nline two\nline three"; + + // maxW is generous — every segment fits without word-wrapping, so + // this isolates the newline-split behavior specifically. + var lines = new List(ChatTranscriptRenderer.WrapText(text, 1000f, MeasureByCharCount)); + + Assert.Equal(new[] { "line one", "line two", "line three" }, lines); + } + + [Fact] + public void WrapText_CarriageReturnNewline_NormalizesTheSameAsBareNewline() + { + string text = "line one\r\nline two"; + + var lines = new List(ChatTranscriptRenderer.WrapText(text, 1000f, MeasureByCharCount)); + + Assert.Equal(new[] { "line one", "line two" }, lines); + } + + [Fact] + public void WrapText_SegmentLongerThanMaxWidth_StillWordWraps() + { + // Each segment is independently word-wrapped by the SAME algorithm + // the single-line path always used — a multi-line server message + // whose second line overflows the window still wraps that line. + string text = "short\nthis segment is much too long to fit on one line"; + + var lines = new List(ChatTranscriptRenderer.WrapText(text, 10f, MeasureByCharCount)); + + Assert.Equal("short", lines[0]); + Assert.True(lines.Count > 2, "the long second segment should have wrapped into multiple lines"); + Assert.All(lines, line => Assert.True(MeasureByCharCount(line) <= 10f)); + Assert.Equal( + "this segment is much too long to fit on one line", + string.Join(" ", lines.Skip(1))); + } + + [Fact] + public void WrapText_SingleSegmentText_KeepsTheEarlyOutBehavior() + { + // No '\n' at all — the pre-existing single-line early-out path + // (whole text fits => returned verbatim as one fragment) is + // unchanged. + string text = "no newlines here"; + + var lines = new List(ChatTranscriptRenderer.WrapText(text, 1000f, MeasureByCharCount)); + + Assert.Equal(new[] { text }, lines); + } + + [Fact] + public void WrapText_ConsecutiveNewlines_ProduceABlankLine() + { + string text = "first\n\nthird"; + + var lines = new List(ChatTranscriptRenderer.WrapText(text, 1000f, MeasureByCharCount)); + + Assert.Equal(new[] { "first", "", "third" }, lines); + } +} diff --git a/tests/AcDream.App.Tests/UI/Layout/ChatWindowControllerTests.cs b/tests/AcDream.App.Tests/UI/Layout/ChatWindowControllerTests.cs index 12696592..071a1fd7 100644 --- a/tests/AcDream.App.Tests/UI/Layout/ChatWindowControllerTests.cs +++ b/tests/AcDream.App.Tests/UI/Layout/ChatWindowControllerTests.cs @@ -152,7 +152,7 @@ public class ChatWindowControllerTests var (rootInfo, layout, vm) = BuildTestTree(); var bus = new CaptureBus(); - var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, null, null, NoTex); + var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, new ChatWindowState(), null, null, NoTex); Assert.NotNull(ctrl); } @@ -165,7 +165,7 @@ public class ChatWindowControllerTests var (rootInfo, layout, vm) = BuildTestTree(); var bus = new CaptureBus(); - var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, null, null, NoTex); + var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, new ChatWindowState(), null, null, NoTex); Assert.NotNull(ctrl); var panel = layout.FindElement(0x10000010u); @@ -182,7 +182,7 @@ public class ChatWindowControllerTests var (rootInfo, layout, vm) = BuildTestTree(); var bus = new CaptureBus(); - var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, null, null, NoTex); + var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, new ChatWindowState(), null, null, NoTex); Assert.NotNull(ctrl); Assert.False(ctrl!.Transcript.Centered); @@ -195,7 +195,7 @@ public class ChatWindowControllerTests var (rootInfo, layout, vm) = BuildTestTree(); var bus = new CaptureBus(); - var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, null, null, NoTex); + var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, new ChatWindowState(), null, null, NoTex); Assert.NotNull(ctrl); var bar = layout.FindElement(0x10000013u); @@ -209,7 +209,7 @@ public class ChatWindowControllerTests var (rootInfo, layout, vm) = BuildTestTree(); var bus = new CaptureBus(); var ctrl = ChatWindowController.Bind( - rootInfo, layout, vm, () => bus, null, null, NoTex)!; + rootInfo, layout, vm, () => bus, new ChatWindowState(), null, null, NoTex)!; vm.ShowSystemMessage("one wrapped transcript line"); IReadOnlyList first = ctrl.Transcript.LinesProvider(); @@ -247,8 +247,14 @@ public class ChatWindowControllerTests var log = new ChatLog(); var (rootInfo, layout, vm) = BuildTestTree(log); var bus = new CaptureBus(); + // 0x22 is above the default main filter's low dword (retail's + // 0xFBFFFFFF only covers types 0-31) — accept everything so this + // test isolates the color-carry-forward behavior it's actually + // about from the (correctly) separate window-filter concern. + var filters = new ChatWindowState(); + filters.SetFilter(ChatWindowState.MainWindowId, ulong.MaxValue); var ctrl = ChatWindowController.Bind( - rootInfo, layout, vm, () => bus, null, null, NoTex)!; + rootInfo, layout, vm, () => bus, filters, null, null, NoTex)!; log.OnSystemMessage("first", chatType: 0x05u); // System, colorBrightPurple log.OnSystemMessage("middle", chatType: 0x22u); // out of range — carries 0x05's color @@ -269,7 +275,7 @@ public class ChatWindowControllerTests var (rootInfo, layout, vm) = BuildTestTree(); var bus = new CaptureBus(); - var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, null, null, NoTex); + var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, new ChatWindowState(), null, null, NoTex); Assert.NotNull(ctrl); ctrl!.Input.OnSubmit!.Invoke("hello world"); @@ -286,7 +292,7 @@ public class ChatWindowControllerTests var (rootInfo, layout, vm) = BuildTestTree(); var bus = new CaptureBus(); - var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, null, null, NoTex); + var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, new ChatWindowState(), null, null, NoTex); Assert.NotNull(ctrl); ctrl!.Input.OnSubmit!.Invoke("/ls"); @@ -303,7 +309,7 @@ public class ChatWindowControllerTests var (rootInfo, layout, vm) = BuildTestTree(); var bus = new CaptureBus(); - var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, null, null, NoTex); + var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, new ChatWindowState(), null, null, NoTex); Assert.NotNull(ctrl); // Switch channel to General via the generic OnSelect (payload is ChatChannelKind). @@ -328,7 +334,7 @@ public class ChatWindowControllerTests var vm = new ChatVM(new ChatLog()); var bus = new CaptureBus(); - var ctrl = ChatWindowController.Bind(root, layout, vm, () => bus, null, null, NoTex); + var ctrl = ChatWindowController.Bind(root, layout, vm, () => bus, new ChatWindowState(), null, null, NoTex); Assert.Null(ctrl); } @@ -351,7 +357,7 @@ public class ChatWindowControllerTests // AnchorEdges fallback branch of the fix. var (rootInfo, layout, vm) = BuildTestTree(); var bus = new CaptureBus(); - var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, null, null, NoTex); + var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, new ChatWindowState(), null, null, NoTex); Assert.NotNull(ctrl); Assert.Null(ctrl!.Input.LayoutPolicy); Assert.Equal(AnchorEdges.Left | AnchorEdges.Right, ctrl.Input.Anchors & (AnchorEdges.Left | AnchorEdges.Right)); @@ -377,7 +383,7 @@ public class ChatWindowControllerTests { var (rootInfo, layout, vm) = BuildTestTree(); var bus = new CaptureBus(); - var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, null, null, NoTex); + var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, new ChatWindowState(), null, null, NoTex); Assert.NotNull(ctrl); const float authoredParentWidth = 490f; @@ -415,7 +421,7 @@ public class ChatWindowControllerTests // inputs (BuildTestTree already built one without them). layout = LayoutImporter.Build(rootInfo, NoTex, null); var bus = new CaptureBus(); - var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, null, null, NoTex); + var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, new ChatWindowState(), null, null, NoTex); Assert.NotNull(ctrl); Assert.NotNull(ctrl!.Input.LayoutPolicy); @@ -439,76 +445,8 @@ public class ChatWindowControllerTests return null; } - // ── WrapText: Campaign CH user-gate round 1, item F ────────────────────── - // /help (and "probably many places") never split on embedded '\n' — the - // whole multi-line blob rode the single early-out as ONE line. Split on - // '\n' first, then word-wrap each segment; a single-segment text keeps - // the pre-existing early-out behavior exactly. - - private static float MeasureByCharCount(string s) => s.Length; - - [Fact] - public void WrapText_EmbeddedNewlines_ProduceOneRenderedLinePerSegment() - { - string text = "line one\nline two\nline three"; - - // maxW is generous — every segment fits without word-wrapping, so - // this isolates the newline-split behavior specifically. - var lines = new List(ChatWindowController.WrapText(text, 1000f, MeasureByCharCount)); - - Assert.Equal(new[] { "line one", "line two", "line three" }, lines); - } - - [Fact] - public void WrapText_CarriageReturnNewline_NormalizesTheSameAsBareNewline() - { - string text = "line one\r\nline two"; - - var lines = new List(ChatWindowController.WrapText(text, 1000f, MeasureByCharCount)); - - Assert.Equal(new[] { "line one", "line two" }, lines); - } - - [Fact] - public void WrapText_SegmentLongerThanMaxWidth_StillWordWraps() - { - // Each segment is independently word-wrapped by the SAME algorithm - // the single-line path always used — a multi-line server message - // whose second line overflows the window still wraps that line. - string text = "short\nthis segment is much too long to fit on one line"; - - var lines = new List(ChatWindowController.WrapText(text, 10f, MeasureByCharCount)); - - Assert.Equal("short", lines[0]); - Assert.True(lines.Count > 2, "the long second segment should have wrapped into multiple lines"); - Assert.All(lines, line => Assert.True(MeasureByCharCount(line) <= 10f)); - Assert.Equal( - "this segment is much too long to fit on one line", - string.Join(" ", lines.Skip(1))); - } - - [Fact] - public void WrapText_SingleSegmentText_KeepsTheEarlyOutBehavior() - { - // No '\n' at all — the pre-existing single-line early-out path - // (whole text fits => returned verbatim as one fragment) is - // unchanged. - string text = "no newlines here"; - - var lines = new List(ChatWindowController.WrapText(text, 1000f, MeasureByCharCount)); - - Assert.Equal(new[] { text }, lines); - } - - [Fact] - public void WrapText_ConsecutiveNewlines_ProduceABlankLine() - { - string text = "first\n\nthird"; - - var lines = new List(ChatWindowController.WrapText(text, 1000f, MeasureByCharCount)); - - Assert.Equal(new[] { "first", "", "third" }, lines); - } + // WrapText moved to ChatTranscriptRendererTests.cs (CH6a/b REJECT-review + // NIT 5 — WrapText itself moved from this class to ChatTranscriptRenderer). // ── SetIndicatorOpen: Campaign CH slice CH6b — the button mirror ───────── // gmMainChatUI::RecvNotice_SetPanelVisibility @0x004CCD80: State 6 @@ -524,7 +462,7 @@ public class ChatWindowControllerTests { var (rootInfo, layout, vm) = BuildTestTree(); var bus = new CaptureBus(); - var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, null, null, NoTex)!; + var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, new ChatWindowState(), null, null, NoTex)!; var indicator = Assert.IsType(layout.FindElement(indicatorId)); ctrl.SetIndicatorOpen(windowId, open: true); @@ -537,7 +475,7 @@ public class ChatWindowControllerTests { var (rootInfo, layout, vm) = BuildTestTree(); var bus = new CaptureBus(); - var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, null, null, NoTex)!; + var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, new ChatWindowState(), null, null, NoTex)!; var indicator = Assert.IsType(layout.FindElement(0x10000522u)); ctrl.SetIndicatorOpen(1, open: true); @@ -551,7 +489,7 @@ public class ChatWindowControllerTests { var (rootInfo, layout, vm) = BuildTestTree(); var bus = new CaptureBus(); - var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, null, null, NoTex)!; + var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, new ChatWindowState(), null, null, NoTex)!; var indicator2 = Assert.IsType(layout.FindElement(0x10000523u)); ctrl.SetIndicatorOpen(1, open: true); @@ -566,7 +504,7 @@ public class ChatWindowControllerTests { var (rootInfo, layout, vm) = BuildTestTree(); var bus = new CaptureBus(); - var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, null, null, NoTex)!; + var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, new ChatWindowState(), null, null, NoTex)!; Assert.Throws(() => ctrl.SetIndicatorOpen(windowId, open: true)); } diff --git a/tests/AcDream.App.Tests/UI/Layout/FixtureLoader.cs b/tests/AcDream.App.Tests/UI/Layout/FixtureLoader.cs index c5433491..5bae7def 100644 --- a/tests/AcDream.App.Tests/UI/Layout/FixtureLoader.cs +++ b/tests/AcDream.App.Tests/UI/Layout/FixtureLoader.cs @@ -60,6 +60,25 @@ public static class FixtureLoader public static AcDream.App.UI.Layout.ElementInfo LoadChatInfos() => LoadInfos("chat_2100006f.json"); + /// + /// Deserializes the committed chat_floaty_2100005b.json fixture + /// (retail's floating chat windows 1-4, LayoutDesc 0x2100005B — CH6a/b + /// REJECT-review SHOULD-FIX 4) into a raw tree and + /// builds the using a null-returning sprite + /// resolver and no dat font — sufficient for conformance checks on resolved + /// widget types. + /// + public static ImportedLayout LoadFloatyChat() + => LayoutImporter.Build(LoadFloatyChatInfos(), _ => (0u, 0, 0), null); + + /// + /// Deserializes the committed chat_floaty_2100005b.json fixture into + /// a raw tree WITHOUT calling + /// . + /// + public static AcDream.App.UI.Layout.ElementInfo LoadFloatyChatInfos() + => LoadInfos("chat_floaty_2100005b.json"); + /// Builds the committed retail radar LayoutDesc 0x21000074 fixture. public static ImportedLayout LoadRadar() => LayoutImporter.Build(LoadRadarInfos(), _ => (0u, 0, 0), null); diff --git a/tests/AcDream.App.Tests/UI/Layout/FloatingChatLayoutConformanceTests.cs b/tests/AcDream.App.Tests/UI/Layout/FloatingChatLayoutConformanceTests.cs new file mode 100644 index 00000000..d5ec9f3d --- /dev/null +++ b/tests/AcDream.App.Tests/UI/Layout/FloatingChatLayoutConformanceTests.cs @@ -0,0 +1,120 @@ +using AcDream.App.UI; +using AcDream.App.UI.Layout; + +namespace AcDream.App.Tests.UI.Layout; + +/// +/// Dat-free conformance tests for the committed chat_floaty_2100005b.json +/// golden fixture — retail's ACTUAL floating chat window (LayoutDesc +/// 0x2100005B, window root 0x100004F7, authored 250x108; CH6a/b +/// REJECT-review SHOULD-FIX 4, +/// docs/research/2026-08-10-ch6ab-review-findings.md). Pins the resolved +/// widget types assumes at +/// Bind time — before this fixture existed those assumptions were +/// untested against real dat data. +/// +public class FloatingChatLayoutConformanceTests +{ + private static ElementInfo? Find(ElementInfo n, uint id) + { + if (n.Id == id) return n; + foreach (var c in n.Children) + { + var f = Find(c, id); + if (f is not null) return f; + } + return null; + } + + [Fact] + public void FloatyFixture_ResolvesKnownElements() + { + var root = FixtureLoader.LoadFloatyChatInfos(); + Assert.NotNull(Find(root, 0x10000011u)); // transcript + Assert.NotNull(Find(root, 0x10000016u)); // input + Assert.NotNull(Find(root, 0x10000012u)); // scrollbar track + Assert.NotNull(Find(root, 0x10000019u)); // send button + Assert.NotNull(Find(root, 0x100004D9u)); // title bar + Assert.NotNull(Find(root, 0x1000052Au)); // close button + Assert.NotNull(Find(root, 0x10000529u)); // title-bar drag handle + // No talk-focus menu, no max/min button, no 1-4 indicators — a floaty + // window has none of these (research doc §2.2). + Assert.Null(Find(root, 0x10000014u)); + Assert.Null(Find(root, 0x1000046Fu)); + Assert.Null(Find(root, 0x10000522u)); + } + + [Fact] + public void FloatyFixture_ResolvedTypes_MatchRetailRegistry() + { + // CH6a/b REJECT-review SHOULD-FIX 4's three flagged untested + // assumptions: the chat entry field, the title bar, and the close + // button. All three are confirmed correct against the real fixture. + var root = FixtureLoader.LoadFloatyChatInfos(); + Assert.Equal(12u, Find(root, 0x10000016u)!.Type); // Text/style-prototype (input) + Editable 0x16 + Assert.True(Find(root, 0x10000016u)!.TryGetEffectiveBool(0x16u, out bool editable) && editable); + Assert.Equal(12u, Find(root, 0x100004D9u)!.Type); // Text (title bar) — no Editable + Assert.False(Find(root, 0x100004D9u)!.TryGetEffectiveBool(0x16u, out bool titleEditable) && titleEditable); + Assert.Equal(1u, Find(root, 0x10000019u)!.Type); // Button (Send) + Assert.Equal(1u, Find(root, 0x1000052Au)!.Type); // Button (Close) + Assert.Equal(11u, Find(root, 0x10000012u)!.Type); // Scrollbar + Assert.Equal(2u, Find(root, 0x10000529u)!.Type); // Dragbar (title-bar move handle) + } + + [Fact] + public void MountedFloatyWindow_ResolvesToTheExpectedWidgetTypes() + { + // The resolved WIDGET types (post-DatWidgetFactory), not just the raw + // dat Type numbers above — this is what FloatingChatWindowController.Bind + // actually casts against. + var layout = FixtureLoader.LoadFloatyChat(); + Assert.IsType(layout.FindElement(0x10000016u)); // Editable 0x16 -> UiField + Assert.IsType(layout.FindElement(0x100004D9u)); // no Editable -> UiText + Assert.IsType(layout.FindElement(0x10000019u)); // Send + Assert.IsType(layout.FindElement(0x1000052Au)); // Close + Assert.IsType(layout.FindElement(0x10000012u)); + } + + // ── Border/corner grips — CH6a/b SHOULD-FIX 4's "also determines whether + // floaties resize from real grips" ──────────────────────────────────────── + + [Theory] + [InlineData(0x100004FCu)] // TL corner + [InlineData(0x1000000Fu)] // top edge + [InlineData(0x100004FEu)] // TR corner + [InlineData(0x100004D2u)] // left edge + [InlineData(0x10000501u)] // BL corner + [InlineData(0x100004D4u)] // bottom edge + [InlineData(0x10000503u)] // BR corner + [InlineData(0x100004D3u)] // right edge + public void FloatyFixture_EveryBorderElement_IsALiveResizeGrip(uint elementId) + { + // Unlike the main window (whose plain top EDGE strip is a Type-2 + // Dragbar move handle, not a grip — the main window has no title bar + // so the top strip doubles as the move handle), the floaty window's + // move handle is its OWN title-bar group (0x10000529, Type 2) — so + // ALL EIGHT border/corner elements here are genuine Type-9 Resizebar + // grips. A floaty window resizes from every edge and every corner. + var root = FixtureLoader.LoadFloatyChatInfos(); + Assert.Equal(9u, Find(root, elementId)!.Type); + } + + [Theory] + [InlineData(0x100004FCu)] + [InlineData(0x1000000Fu)] + [InlineData(0x100004FEu)] + [InlineData(0x100004D2u)] + [InlineData(0x10000501u)] + [InlineData(0x100004D4u)] + [InlineData(0x10000503u)] + [InlineData(0x100004D3u)] + public void MountedFloatyWindow_EveryLiveGrip_ResolvesNonZeroSprite(uint elementId) + { + // BLOCKER 1's regression guard extended to the floaty layout: every + // one of the eight live grips must carry real authored border/corner + // media, not draw nothing. + var layout = FixtureLoader.LoadFloatyChat(); + var grip = Assert.IsType(layout.FindElement(elementId)); + Assert.NotEqual(0u, grip.SpriteFile); + } +} diff --git a/tests/AcDream.App.Tests/UI/Layout/RetailLayoutFixtureGenerator.cs b/tests/AcDream.App.Tests/UI/Layout/RetailLayoutFixtureGenerator.cs index 14a4b02e..7807f6da 100644 --- a/tests/AcDream.App.Tests/UI/Layout/RetailLayoutFixtureGenerator.cs +++ b/tests/AcDream.App.Tests/UI/Layout/RetailLayoutFixtureGenerator.cs @@ -17,6 +17,7 @@ public sealed class RetailLayoutFixtureGenerator private static readonly (uint Id, string FileName)[] Layouts = { (ChatWindowController.LayoutId, "chat_2100006f.json"), + (FloatingChatWindowController.LayoutId, "chat_floaty_2100005b.json"), (0x21000016u, "toolbar_21000016.json"), (0x2100001Du, "link_status_2100001D.json"), (0x21000020u, "vitae_21000020.json"), diff --git a/tests/AcDream.App.Tests/UI/Layout/fixtures/chat_floaty_2100005b.json b/tests/AcDream.App.Tests/UI/Layout/fixtures/chat_floaty_2100005b.json new file mode 100644 index 00000000..eb4da451 --- /dev/null +++ b/tests/AcDream.App.Tests/UI/Layout/fixtures/chat_floaty_2100005b.json @@ -0,0 +1,5474 @@ +{ + "Id": 268436727, + "Type": 268435520, + "X": 0, + "Y": 80, + "Width": 250, + "Height": 108, + "OriginalParentWidth": 0, + "OriginalParentHeight": 0, + "HasOriginalParentSize": false, + "Left": 0, + "Top": 0, + "Right": 0, + "Bottom": 0, + "ReadOrder": 0, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "60": { + "Kind": 4, + "MasterPropertyId": 60, + "UnsignedValue": 0, + "IntegerValue": 928, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "61": { + "Kind": 4, + "MasterPropertyId": 61, + "UnsignedValue": 0, + "IntegerValue": 1024, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "62": { + "Kind": 4, + "MasterPropertyId": 62, + "UnsignedValue": 0, + "IntegerValue": 76, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "63": { + "Kind": 4, + "MasterPropertyId": 63, + "UnsignedValue": 0, + "IntegerValue": 150, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": {}, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [ + { + "Id": 268436776, + "Type": 3, + "X": 5, + "Y": 20, + "Width": 240, + "Height": 5, + "OriginalParentWidth": 250, + "OriginalParentHeight": 108, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 2, + "ReadOrder": 10, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100688170, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100688170, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268436777, + "Type": 2, + "X": 5, + "Y": 5, + "Width": 240, + "Height": 20, + "OriginalParentWidth": 250, + "OriginalParentHeight": 108, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 2, + "ReadOrder": 11, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": null, + "Cursor": { + "File": 100688153, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + }, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": {}, + "StateCursors": { + "": { + "File": 100688153, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + } + }, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268436778, + "Type": 1, + "X": 230, + "Y": 6, + "Width": 14, + "Height": 14, + "OriginalParentWidth": 250, + "OriginalParentHeight": 108, + "HasOriginalParentSize": true, + "Left": 2, + "Top": 1, + "Right": 1, + "Bottom": 2, + "ReadOrder": 12, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": null, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "3": { + "Id": 3, + "Name": "Normal_pressed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100688406, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "1": { + "Id": 1, + "Name": "Normal", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100688405, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 1, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "Normal": { + "Item1": 100688405, + "Item2": 1 + }, + "Normal_pressed": { + "Item1": 100688406, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "Normal", + "Children": [] + }, + { + "Id": 268436732, + "Type": 9, + "X": 0, + "Y": 0, + "Width": 5, + "Height": 5, + "OriginalParentWidth": 250, + "OriginalParentHeight": 108, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 2, + "Bottom": 2, + "ReadOrder": 1, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100688169, + "DrawMode": 1 + }, + "Cursor": { + "File": 100688166, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + }, + "Properties": { + "Values": { + "45": { + "Kind": 1, + "MasterPropertyId": 45, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "43": { + "Kind": 1, + "MasterPropertyId": 43, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100688169, + "Item2": 1 + } + }, + "StateCursors": { + "": { + "File": 100688166, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + } + }, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268436734, + "Type": 9, + "X": 245, + "Y": 0, + "Width": 5, + "Height": 5, + "OriginalParentWidth": 250, + "OriginalParentHeight": 108, + "HasOriginalParentSize": true, + "Left": 2, + "Top": 1, + "Right": 1, + "Bottom": 2, + "ReadOrder": 3, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100688169, + "DrawMode": 1 + }, + "Cursor": { + "File": 100688167, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + }, + "Properties": { + "Values": { + "44": { + "Kind": 1, + "MasterPropertyId": 44, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "45": { + "Kind": 1, + "MasterPropertyId": 45, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100688169, + "Item2": 1 + } + }, + "StateCursors": { + "": { + "File": 100688167, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + } + }, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268436690, + "Type": 9, + "X": 0, + "Y": 5, + "Width": 5, + "Height": 98, + "OriginalParentWidth": 250, + "OriginalParentHeight": 108, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 2, + "Bottom": 1, + "ReadOrder": 4, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100688171, + "DrawMode": 1 + }, + "Cursor": { + "File": 100688168, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + }, + "Properties": { + "Values": { + "43": { + "Kind": 1, + "MasterPropertyId": 43, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100688171, + "Item2": 1 + } + }, + "StateCursors": { + "": { + "File": 100688168, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + } + }, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268435471, + "Type": 9, + "X": 5, + "Y": 0, + "Width": 240, + "Height": 5, + "OriginalParentWidth": 250, + "OriginalParentHeight": 108, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 2, + "ReadOrder": 2, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100688170, + "DrawMode": 1 + }, + "Cursor": { + "File": 100687462, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + }, + "Properties": { + "Values": { + "45": { + "Kind": 1, + "MasterPropertyId": 45, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100688170, + "Item2": 1 + } + }, + "StateCursors": { + "": { + "File": 100687462, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + } + }, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268435472, + "Type": 3, + "X": 5, + "Y": 25, + "Width": 224, + "Height": 60, + "OriginalParentWidth": 250, + "OriginalParentHeight": 108, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 13, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100682946, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100682946, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [ + { + "Id": 268435473, + "Type": 12, + "X": 0, + "Y": 0, + "Width": 224, + "Height": 60, + "OriginalParentWidth": 224, + "OriginalParentHeight": 60, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 1, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "35": { + "Kind": 4, + "MasterPropertyId": 35, + "UnsignedValue": 0, + "IntegerValue": 2, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "26": { + "Kind": 7, + "MasterPropertyId": 26, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 2, + "MasterPropertyId": 24, + "UnsignedValue": 1073741824, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + }, + "37": { + "Kind": 4, + "MasterPropertyId": 37, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "27": { + "Kind": 7, + "MasterPropertyId": 27, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 6, + "MasterPropertyId": 25, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 204, + "Green": 204, + "Red": 204, + "Alpha": 255 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + }, + "28": { + "Kind": 7, + "MasterPropertyId": 28, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 2, + "MasterPropertyId": 24, + "UnsignedValue": 1073741824, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + }, + "29": { + "Kind": 7, + "MasterPropertyId": 29, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 6, + "MasterPropertyId": 25, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 178, + "Red": 0, + "Alpha": 255 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + }, + "20": { + "Kind": 0, + "MasterPropertyId": 20, + "UnsignedValue": 2, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "21": { + "Kind": 0, + "MasterPropertyId": 21, + "UnsignedValue": 4, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "36": { + "Kind": 4, + "MasterPropertyId": 36, + "UnsignedValue": 0, + "IntegerValue": 2, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "81": { + "Kind": 1, + "MasterPropertyId": 81, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "114": { + "Kind": 0, + "MasterPropertyId": 114, + "UnsignedValue": 268435474, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "39": { + "Kind": 1, + "MasterPropertyId": 39, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "205": { + "Kind": 0, + "MasterPropertyId": 205, + "UnsignedValue": 3, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + }, + "2": { + "Id": 2, + "Name": "Normal_rollover", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "27": { + "Kind": 7, + "MasterPropertyId": 27, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 6, + "MasterPropertyId": 25, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 255, + "Green": 255, + "Red": 255, + "Alpha": 255 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + } + } + } + }, + "3": { + "Id": 3, + "Name": "Normal_pressed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "27": { + "Kind": 7, + "MasterPropertyId": 27, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 6, + "MasterPropertyId": 25, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 51, + "Green": 51, + "Red": 51, + "Alpha": 255 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + } + } + } + }, + "6": { + "Id": 6, + "Name": "Highlight", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "27": { + "Kind": 7, + "MasterPropertyId": 27, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 6, + "MasterPropertyId": 25, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 255, + "Green": 255, + "Red": 255, + "Alpha": 255 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + } + } + } + }, + "7": { + "Id": 7, + "Name": "Highlight_rollover", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "27": { + "Kind": 7, + "MasterPropertyId": 27, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 6, + "MasterPropertyId": 25, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 255, + "Green": 255, + "Red": 255, + "Alpha": 255 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + } + } + } + }, + "8": { + "Id": 8, + "Name": "Highlight_pressed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "27": { + "Kind": 7, + "MasterPropertyId": 27, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 6, + "MasterPropertyId": 25, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 51, + "Green": 51, + "Red": 51, + "Alpha": 255 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + } + } + } + }, + "13": { + "Id": 13, + "Name": "Ghosted", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "27": { + "Kind": 7, + "MasterPropertyId": 27, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 6, + "MasterPropertyId": 25, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 51, + "Green": 51, + "Red": 51, + "Alpha": 255 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 1073741824, + "HJustify": 0, + "VJustify": 2, + "FontColor": { + "X": 0.8, + "Y": 0.8, + "Z": 0.8, + "W": 1 + }, + "StateMedia": {}, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [ + { + "Id": 268436620, + "Type": 1, + "X": 0, + "Y": 64, + "Width": 16, + "Height": 16, + "OriginalParentWidth": 224, + "OriginalParentHeight": 60, + "HasOriginalParentSize": true, + "Left": 3, + "Top": 2, + "Right": 3, + "Bottom": 1, + "ReadOrder": 1, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": null, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "3": { + "Id": 3, + "Name": "Normal_pressed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100687630, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": { + "59": { + "Kind": 1, + "MasterPropertyId": 59, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + }, + "1": { + "Id": 1, + "Name": "Normal", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100687630, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": { + "59": { + "Kind": 1, + "MasterPropertyId": 59, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + }, + "13": { + "Id": 13, + "Name": "Ghosted", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "59": { + "Kind": 1, + "MasterPropertyId": 59, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 13, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "Normal": { + "Item1": 100687630, + "Item2": 1 + }, + "Normal_pressed": { + "Item1": 100687630, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "Ghosted", + "Children": [] + } + ] + } + ] + }, + { + "Id": 268436691, + "Type": 9, + "X": 245, + "Y": 5, + "Width": 5, + "Height": 98, + "OriginalParentWidth": 250, + "OriginalParentHeight": 108, + "HasOriginalParentSize": true, + "Left": 2, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 8, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100688173, + "DrawMode": 1 + }, + "Cursor": { + "File": 100688168, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + }, + "Properties": { + "Values": { + "44": { + "Kind": 1, + "MasterPropertyId": 44, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100688173, + "Item2": 1 + } + }, + "StateCursors": { + "": { + "File": 100688168, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + } + }, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268436737, + "Type": 9, + "X": 0, + "Y": 103, + "Width": 5, + "Height": 5, + "OriginalParentWidth": 250, + "OriginalParentHeight": 108, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 2, + "Right": 2, + "Bottom": 1, + "ReadOrder": 5, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100688169, + "DrawMode": 1 + }, + "Cursor": { + "File": 100688167, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + }, + "Properties": { + "Values": { + "42": { + "Kind": 1, + "MasterPropertyId": 42, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "43": { + "Kind": 1, + "MasterPropertyId": 43, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100688169, + "Item2": 1 + } + }, + "StateCursors": { + "": { + "File": 100688167, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + } + }, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268436692, + "Type": 9, + "X": 5, + "Y": 103, + "Width": 240, + "Height": 5, + "OriginalParentWidth": 250, + "OriginalParentHeight": 108, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 2, + "Right": 1, + "Bottom": 1, + "ReadOrder": 6, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100688172, + "DrawMode": 1 + }, + "Cursor": { + "File": 100687462, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + }, + "Properties": { + "Values": { + "42": { + "Kind": 1, + "MasterPropertyId": 42, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100688172, + "Item2": 1 + } + }, + "StateCursors": { + "": { + "File": 100687462, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + } + }, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268435474, + "Type": 11, + "X": 229, + "Y": 25, + "Width": 16, + "Height": 60, + "OriginalParentWidth": 250, + "OriginalParentHeight": 108, + "HasOriginalParentSize": true, + "Left": 2, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 14, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100682946, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": { + "121": { + "Kind": 1, + "MasterPropertyId": 121, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "137": { + "Kind": 4, + "MasterPropertyId": 137, + "UnsignedValue": 0, + "IntegerValue": 16, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "130": { + "Kind": 1, + "MasterPropertyId": 130, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "119": { + "Kind": 0, + "MasterPropertyId": 119, + "UnsignedValue": 268435570, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "131": { + "Kind": 1, + "MasterPropertyId": 131, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "120": { + "Kind": 0, + "MasterPropertyId": 120, + "UnsignedValue": 268435569, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "81": { + "Kind": 1, + "MasterPropertyId": 81, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100682946, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [ + { + "Id": 1, + "Type": 1, + "X": 0, + "Y": 0, + "Width": 16, + "Height": 16, + "OriginalParentWidth": 16, + "OriginalParentHeight": 48, + "HasOriginalParentSize": true, + "Left": 0, + "Top": 0, + "Right": 0, + "Bottom": 0, + "ReadOrder": 1, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 56, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "19": { + "Kind": 1, + "MasterPropertyId": 19, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + }, + "1": { + "Id": 1, + "Name": "Normal", + "PassToChildren": true, + "IncorporationFlags": 1, + "Image": null, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "2": { + "Id": 2, + "Name": "Normal_rollover", + "PassToChildren": true, + "IncorporationFlags": 1, + "Image": null, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "3": { + "Id": 3, + "Name": "Normal_pressed", + "PassToChildren": true, + "IncorporationFlags": 1, + "Image": null, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 1, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": {}, + "StateCursors": {}, + "DefaultStateName": "Normal", + "Children": [ + { + "Id": 268436324, + "Type": 3, + "X": 0, + "Y": 0, + "Width": 16, + "Height": 3, + "OriginalParentWidth": 16, + "OriginalParentHeight": 16, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 2, + "ReadOrder": 1, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 62, + "Image": null, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "1": { + "Id": 1, + "Name": "Normal", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100682848, + "DrawMode": 3 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "2": { + "Id": 2, + "Name": "Normal_rollover", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100682849, + "DrawMode": 3 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "3": { + "Id": 3, + "Name": "Normal_pressed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100682850, + "DrawMode": 3 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "Normal": { + "Item1": 100682848, + "Item2": 3 + }, + "Normal_rollover": { + "Item1": 100682849, + "Item2": 3 + }, + "Normal_pressed": { + "Item1": 100682850, + "Item2": 3 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268436325, + "Type": 3, + "X": 0, + "Y": 3, + "Width": 16, + "Height": 10, + "OriginalParentWidth": 16, + "OriginalParentHeight": 16, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 2, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 62, + "Image": null, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "1": { + "Id": 1, + "Name": "Normal", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100682851, + "DrawMode": 3 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "2": { + "Id": 2, + "Name": "Normal_rollover", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100682852, + "DrawMode": 3 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "3": { + "Id": 3, + "Name": "Normal_pressed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100682853, + "DrawMode": 3 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "Normal": { + "Item1": 100682851, + "Item2": 3 + }, + "Normal_rollover": { + "Item1": 100682852, + "Item2": 3 + }, + "Normal_pressed": { + "Item1": 100682853, + "Item2": 3 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268436326, + "Type": 3, + "X": 0, + "Y": 13, + "Width": 16, + "Height": 3, + "OriginalParentWidth": 16, + "OriginalParentHeight": 16, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 2, + "Right": 1, + "Bottom": 1, + "ReadOrder": 3, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 62, + "Image": null, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "1": { + "Id": 1, + "Name": "Normal", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100682854, + "DrawMode": 3 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "2": { + "Id": 2, + "Name": "Normal_rollover", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100682855, + "DrawMode": 3 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "3": { + "Id": 3, + "Name": "Normal_pressed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100682856, + "DrawMode": 3 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "Normal": { + "Item1": 100682854, + "Item2": 3 + }, + "Normal_rollover": { + "Item1": 100682855, + "Item2": 3 + }, + "Normal_pressed": { + "Item1": 100682856, + "Item2": 3 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + } + ] + }, + { + "Id": 268435569, + "Type": 1, + "X": 0, + "Y": 0, + "Width": 16, + "Height": 16, + "OriginalParentWidth": 16, + "OriginalParentHeight": 48, + "HasOriginalParentSize": true, + "Left": 2, + "Top": 1, + "Right": 1, + "Bottom": 2, + "ReadOrder": 2, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 62, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "15": { + "Kind": 1, + "MasterPropertyId": 15, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "16": { + "Kind": 3, + "MasterPropertyId": 16, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0.5, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "17": { + "Kind": 3, + "MasterPropertyId": 17, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0.1, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "19": { + "Kind": 1, + "MasterPropertyId": 19, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + }, + "3": { + "Id": 3, + "Name": "Normal_pressed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100682859, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "1": { + "Id": 1, + "Name": "Normal", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100682857, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "2": { + "Id": 2, + "Name": "Normal_rollover", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100682858, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 1, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "Normal": { + "Item1": 100682857, + "Item2": 1 + }, + "Normal_rollover": { + "Item1": 100682858, + "Item2": 1 + }, + "Normal_pressed": { + "Item1": 100682859, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "Normal", + "Children": [] + }, + { + "Id": 268435570, + "Type": 1, + "X": 0, + "Y": 32, + "Width": 16, + "Height": 16, + "OriginalParentWidth": 16, + "OriginalParentHeight": 48, + "HasOriginalParentSize": true, + "Left": 2, + "Top": 2, + "Right": 1, + "Bottom": 1, + "ReadOrder": 3, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 62, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "15": { + "Kind": 1, + "MasterPropertyId": 15, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "16": { + "Kind": 3, + "MasterPropertyId": 16, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0.5, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "17": { + "Kind": 3, + "MasterPropertyId": 17, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0.1, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "19": { + "Kind": 1, + "MasterPropertyId": 19, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + }, + "3": { + "Id": 3, + "Name": "Normal_pressed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100682862, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "1": { + "Id": 1, + "Name": "Normal", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100682860, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "2": { + "Id": 2, + "Name": "Normal_rollover", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100682861, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 1, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "Normal": { + "Item1": 100682860, + "Item2": 1 + }, + "Normal_rollover": { + "Item1": 100682861, + "Item2": 1 + }, + "Normal_pressed": { + "Item1": 100682862, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "Normal", + "Children": [] + } + ] + }, + { + "Id": 268436739, + "Type": 9, + "X": 245, + "Y": 103, + "Width": 5, + "Height": 5, + "OriginalParentWidth": 250, + "OriginalParentHeight": 108, + "HasOriginalParentSize": true, + "Left": 2, + "Top": 2, + "Right": 1, + "Bottom": 1, + "ReadOrder": 7, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100688169, + "DrawMode": 1 + }, + "Cursor": { + "File": 100688166, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + }, + "Properties": { + "Values": { + "44": { + "Kind": 1, + "MasterPropertyId": 44, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "42": { + "Kind": 1, + "MasterPropertyId": 42, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100688169, + "Item2": 1 + } + }, + "StateCursors": { + "": { + "File": 100688166, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + } + }, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268436697, + "Type": 12, + "X": 5, + "Y": 5, + "Width": 240, + "Height": 16, + "OriginalParentWidth": 250, + "OriginalParentHeight": 108, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 2, + "ReadOrder": 9, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100682946, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": { + "35": { + "Kind": 4, + "MasterPropertyId": 35, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "26": { + "Kind": 7, + "MasterPropertyId": 26, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 2, + "MasterPropertyId": 24, + "UnsignedValue": 1073741826, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + }, + "37": { + "Kind": 4, + "MasterPropertyId": 37, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "27": { + "Kind": 7, + "MasterPropertyId": 27, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 6, + "MasterPropertyId": 25, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 255, + "Green": 255, + "Red": 255, + "Alpha": 255 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + }, + "20": { + "Kind": 0, + "MasterPropertyId": 20, + "UnsignedValue": 1, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "21": { + "Kind": 0, + "MasterPropertyId": 21, + "UnsignedValue": 1, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + }, + "13": { + "Id": 13, + "Name": "Ghosted", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "27": { + "Kind": 7, + "MasterPropertyId": 27, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 6, + "MasterPropertyId": 25, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 76, + "Green": 76, + "Red": 76, + "Alpha": 255 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 1073741826, + "HJustify": 1, + "VJustify": 1, + "FontColor": { + "X": 1, + "Y": 1, + "Z": 1, + "W": 1 + }, + "StateMedia": { + "": { + "Item1": 100682946, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268436745, + "Type": 3, + "X": 5, + "Y": 85, + "Width": 240, + "Height": 18, + "OriginalParentWidth": 250, + "OriginalParentHeight": 108, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 2, + "Right": 1, + "Bottom": 1, + "ReadOrder": 15, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": true, + "IncorporationFlags": 31, + "Image": { + "File": 100682946, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100682946, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [ + { + "Id": 268435478, + "Type": 12, + "X": 0, + "Y": 0, + "Width": 202, + "Height": 18, + "OriginalParentWidth": 240, + "OriginalParentHeight": 18, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 2, + "Right": 1, + "Bottom": 1, + "ReadOrder": 1, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": true, + "IncorporationFlags": 31, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "35": { + "Kind": 4, + "MasterPropertyId": 35, + "UnsignedValue": 0, + "IntegerValue": 2, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "26": { + "Kind": 7, + "MasterPropertyId": 26, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 2, + "MasterPropertyId": 24, + "UnsignedValue": 1073741824, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + }, + "37": { + "Kind": 4, + "MasterPropertyId": 37, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "27": { + "Kind": 7, + "MasterPropertyId": 27, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 6, + "MasterPropertyId": 25, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 255, + "Green": 255, + "Red": 255, + "Alpha": 255 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + }, + "28": { + "Kind": 7, + "MasterPropertyId": 28, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 2, + "MasterPropertyId": 24, + "UnsignedValue": 1073741824, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + }, + "29": { + "Kind": 7, + "MasterPropertyId": 29, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 6, + "MasterPropertyId": 25, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 178, + "Red": 0, + "Alpha": 255 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + }, + "20": { + "Kind": 0, + "MasterPropertyId": 20, + "UnsignedValue": 2, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "21": { + "Kind": 0, + "MasterPropertyId": 21, + "UnsignedValue": 1, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "22": { + "Kind": 1, + "MasterPropertyId": 22, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "36": { + "Kind": 4, + "MasterPropertyId": 36, + "UnsignedValue": 0, + "IntegerValue": 2, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "81": { + "Kind": 1, + "MasterPropertyId": 81, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "39": { + "Kind": 1, + "MasterPropertyId": 39, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "205": { + "Kind": 0, + "MasterPropertyId": 205, + "UnsignedValue": 3, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "30": { + "Kind": 4, + "MasterPropertyId": 30, + "UnsignedValue": 0, + "IntegerValue": 255, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "32": { + "Kind": 1, + "MasterPropertyId": 32, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + }, + "2": { + "Id": 2, + "Name": "Normal_rollover", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "27": { + "Kind": 7, + "MasterPropertyId": 27, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 6, + "MasterPropertyId": 25, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 255, + "Green": 255, + "Red": 255, + "Alpha": 255 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + } + } + } + }, + "3": { + "Id": 3, + "Name": "Normal_pressed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "27": { + "Kind": 7, + "MasterPropertyId": 27, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 6, + "MasterPropertyId": 25, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 51, + "Green": 51, + "Red": 51, + "Alpha": 255 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + } + } + } + }, + "6": { + "Id": 6, + "Name": "Highlight", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "27": { + "Kind": 7, + "MasterPropertyId": 27, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 6, + "MasterPropertyId": 25, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 255, + "Green": 255, + "Red": 255, + "Alpha": 255 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + } + } + } + }, + "7": { + "Id": 7, + "Name": "Highlight_rollover", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "27": { + "Kind": 7, + "MasterPropertyId": 27, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 6, + "MasterPropertyId": 25, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 255, + "Green": 255, + "Red": 255, + "Alpha": 255 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + } + } + } + }, + "8": { + "Id": 8, + "Name": "Highlight_pressed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "27": { + "Kind": 7, + "MasterPropertyId": 27, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 6, + "MasterPropertyId": 25, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 51, + "Green": 51, + "Red": 51, + "Alpha": 255 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + } + } + } + }, + "13": { + "Id": 13, + "Name": "Ghosted", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "27": { + "Kind": 7, + "MasterPropertyId": 27, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 6, + "MasterPropertyId": 25, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 51, + "Green": 51, + "Red": 51, + "Alpha": 255 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + } + } + } + }, + "4": { + "Id": 4, + "Name": "Normal_focussed", + "PassToChildren": true, + "IncorporationFlags": 1, + "Image": null, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 1073741824, + "HJustify": 0, + "VJustify": 1, + "FontColor": { + "X": 1, + "Y": 1, + "Z": 1, + "W": 1 + }, + "StateMedia": {}, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [ + { + "Id": 268436779, + "Type": 3, + "X": 0, + "Y": 0, + "Width": 202, + "Height": 18, + "OriginalParentWidth": 202, + "OriginalParentHeight": 18, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 1, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "59": { + "Kind": 1, + "MasterPropertyId": 59, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + }, + "4": { + "Id": 4, + "Name": "Normal_focussed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "59": { + "Kind": 1, + "MasterPropertyId": 59, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": {}, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [ + { + "Id": 268435977, + "Type": 3, + "X": 0, + "Y": 0, + "Width": 1, + "Height": 100, + "OriginalParentWidth": 100, + "OriginalParentHeight": 100, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 2, + "Bottom": 1, + "ReadOrder": 2, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100682953, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100682953, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268435978, + "Type": 3, + "X": 99, + "Y": 0, + "Width": 1, + "Height": 100, + "OriginalParentWidth": 100, + "OriginalParentHeight": 100, + "HasOriginalParentSize": true, + "Left": 2, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 3, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100682953, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100682953, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268435979, + "Type": 3, + "X": 0, + "Y": 99, + "Width": 100, + "Height": 1, + "OriginalParentWidth": 100, + "OriginalParentHeight": 100, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 2, + "Right": 1, + "Bottom": 1, + "ReadOrder": 4, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100682953, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100682953, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268436149, + "Type": 3, + "X": 0, + "Y": 0, + "Width": 100, + "Height": 1, + "OriginalParentWidth": 100, + "OriginalParentHeight": 100, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 2, + "ReadOrder": 1, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100682953, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100682953, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + } + ] + } + ] + }, + { + "Id": 268435481, + "Type": 1, + "X": 202, + "Y": 0, + "Width": 38, + "Height": 18, + "OriginalParentWidth": 240, + "OriginalParentHeight": 18, + "HasOriginalParentSize": true, + "Left": 2, + "Top": 2, + "Right": 1, + "Bottom": 1, + "ReadOrder": 2, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "35": { + "Kind": 4, + "MasterPropertyId": 35, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "26": { + "Kind": 7, + "MasterPropertyId": 26, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 2, + "MasterPropertyId": 24, + "UnsignedValue": 1073741826, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + }, + "37": { + "Kind": 4, + "MasterPropertyId": 37, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "27": { + "Kind": 7, + "MasterPropertyId": 27, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 6, + "MasterPropertyId": 25, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 255, + "Green": 255, + "Red": 255, + "Alpha": 255 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + }, + "20": { + "Kind": 0, + "MasterPropertyId": 20, + "UnsignedValue": 1, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "21": { + "Kind": 0, + "MasterPropertyId": 21, + "UnsignedValue": 1, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "23": { + "Kind": 5, + "MasterPropertyId": 23, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 79829758, + "TableId": 587202561, + "Override": 0, + "English": 1, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + }, + "3": { + "Id": 3, + "Name": "Normal_pressed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 1, + "FontDid": 1073741826, + "HJustify": 1, + "VJustify": 1, + "FontColor": { + "X": 1, + "Y": 1, + "Z": 1, + "W": 1 + }, + "StateMedia": {}, + "StateCursors": {}, + "DefaultStateName": "Normal", + "Children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/AcDream.App.Tests/UI/UiButtonTests.cs b/tests/AcDream.App.Tests/UI/UiButtonTests.cs index b653e39e..764df993 100644 --- a/tests/AcDream.App.Tests/UI/UiButtonTests.cs +++ b/tests/AcDream.App.Tests/UI/UiButtonTests.cs @@ -90,6 +90,34 @@ public class UiButtonTests Assert.Equal("Highlight", b.ActiveState); } + [Fact] + public void SuppressSelfToggle_PressReleaseDoesNotFlipSelected() + { + // CH6a/b REJECT-review SHOULD-FIX 3: the chat-window 1-4 indicators + // (0x10000522-0x10000525) carry DAT property 0x0B (ToggleBehavior) = + // true — same shape as ToggleRelease_SelectsHighlightState above — + // but retail's own click dispatch has no case for their element ids + // (gmMainChatUI::ListenToElementMessage @0x004CDA80), so a click must + // NOT flip their Selected mirror. Only SetIndicatorOpen (an external + // writer) may change it. + var info = ButtonInfo("Normal", "Highlight"); + AddBoolProperty(info, 0x0Bu, true); + var b = CreateButton(info); + b.SuppressSelfToggle = true; + + b.OnEvent(new UiEvent(0, b, UiEventType.MouseDown, Data1: 5, Data2: 5)); + b.OnEvent(new UiEvent(0, b, UiEventType.MouseUp, Data1: 5, Data2: 5)); + + Assert.False(b.Selected); + Assert.Equal("Normal", b.ActiveState); + + // The external mirror path still works — SuppressSelfToggle only + // blocks the self-click, not a producer's own write. + b.Selected = true; + Assert.True(b.Selected); + Assert.Equal("Highlight", b.ActiveState); + } + [Fact] public void DisabledProperty_SelectsGhostedAndSuppressesClick() { diff --git a/tests/AcDream.Core.Tests/Chat/ChatWindowStateTests.cs b/tests/AcDream.Core.Tests/Chat/ChatWindowStateTests.cs index fd8f8108..f84d738f 100644 --- a/tests/AcDream.Core.Tests/Chat/ChatWindowStateTests.cs +++ b/tests/AcDream.Core.Tests/Chat/ChatWindowStateTests.cs @@ -116,7 +116,8 @@ public sealed class ChatWindowStateTests { var state = new ChatWindowState(); // Addressed to window 2, evaluated from window 1's perspective: not a - // broadcast (targetWindowId != 0) and not addressed to window 1. + // broadcast (targetWindowId is a real window id, not + // BroadcastTargetWindow) and not addressed to window 1. Assert.False(state.ShouldDisplay(windowId: 1, targetWindowId: 2u, logTextType: 0x02u)); } @@ -124,38 +125,65 @@ public sealed class ChatWindowStateTests public void ShouldDisplay_Broadcast_FilterHit_Shows() { var state = new ChatWindowState(); - Assert.True(state.ShouldDisplay(windowId: 1, targetWindowId: 0u, logTextType: 0x02u)); // Speech + Assert.True(state.ShouldDisplay( + windowId: 1, targetWindowId: ChatWindowState.BroadcastTargetWindow, logTextType: 0x02u)); // Speech } [Fact] public void ShouldDisplay_Broadcast_FilterMiss_DoesNotShow() { var state = new ChatWindowState(); - Assert.False(state.ShouldDisplay(windowId: 1, targetWindowId: 0u, logTextType: 0x0Au)); // Social + Assert.False(state.ShouldDisplay( + windowId: 1, targetWindowId: ChatWindowState.BroadcastTargetWindow, logTextType: 0x0Au)); // Social } [Fact] - public void ShouldDisplay_MainWindow_ShowsEveryBroadcastRegardlessOfSeededFilter() + public void ShouldDisplay_MainWindow_BroadcastRespectsItsOwnFilter() { + // CH6a/b REJECT-review BLOCKER (SHOULD-FIX 2): before the fix, window + // 0's explicit-addressing branch (targetWindowId == windowId) used the + // SAME literal 0 as the broadcast sentinel, so it short-circuited true + // for EVERY broadcast line regardless of window 0's own filter. Now + // that BroadcastTargetWindow is a distinct sentinel, the main + // window's filter (0xFBFFFFFF — excludes 0x1A, Society opt-in) is + // genuinely consulted for a broadcast line, exactly like every other + // window. + var state = new ChatWindowState(); + Assert.False(state.ShouldDisplay( + windowId: 0, targetWindowId: ChatWindowState.BroadcastTargetWindow, logTextType: 0x1Au)); // excluded + Assert.False(state.ShouldDisplay( + windowId: 0, targetWindowId: ChatWindowState.BroadcastTargetWindow, logTextType: 0x20u)); // Society, opt-in + Assert.True(state.ShouldDisplay( + windowId: 0, targetWindowId: ChatWindowState.BroadcastTargetWindow, logTextType: 0x02u)); // Speech + } + + [Fact] + public void ShouldDisplay_MainWindow_ExplicitlyAddressed_AlwaysShows() + { + // Window id 0 (main) is still a valid EXPLICIT address, distinct from + // BroadcastTargetWindow — this is what keeps a future + // targetWindowId == MainWindowId (AP-180's per-window echo) + // expressible: explicit addressing to main always wins, even for a + // type main's own filter would otherwise reject. var state = new ChatWindowState(); - // Window 0's own filter is 0xFBFFFFFF (excludes 0x1A) but that filter - // is never actually consulted for a broadcast message: targetWindowId - // (0) == windowId (0) is already true via the first branch. Assert.True(state.ShouldDisplay(windowId: 0, targetWindowId: 0u, logTextType: 0x1Au)); - Assert.True(state.ShouldDisplay(windowId: 0, targetWindowId: 0u, logTextType: 0x21u)); } // ── SetFilter / SetOpen / Toggle ───────────────────────────────────────── [Fact] - public void SetFilter_MainWindow_IsANoOp() + public void SetFilter_MainWindow_Persists() { + // CH6a/b REJECT-review SHOULD-FIX 2: dropped the old no-op — the main + // window's filter is now genuinely settable, matching retail's own + // options-page-driven main-window filter. var state = new ChatWindowState(); - ulong before = state.GetFilter(0); - state.SetFilter(0, 0u); + state.SetFilter(0, 0x1u); - Assert.Equal(before, state.GetFilter(0)); + Assert.Equal(0x1u, state.GetFilter(0)); + Assert.True(state.TypeIsActive(0, 0x00u)); + Assert.False(state.TypeIsActive(0, 0x02u)); } [Fact]