feat(ui): authored state media animates, so the unseen-text indicator blinks
The blink is not code. It is data, and we were throwing it away. A retail UI state's media is a small program: images interleaved with timed pauses, branches, and a terminal hand-off to another state. Our importer kept the FIRST image per state and dropped the rest, so nothing authored could ever animate — the indicator was correct in every other respect and simply sat still. Measured from the installed dats (LayoutDump --media 0x1000048C), the chat unseen-text indicator's Normal state authors thirteen steps: two frames alternating every half second, three times, then `State 13` — Ghosted, whose authored 0x3B is Invisible. So retail's indicator is a three-second attention FLASH that hides itself, not a badge that stays lit until you scroll to the bottom. Nobody would guess that from the code, because there is no blink code anywhere; the behaviour lives entirely in the authored sequence. Our shipped version stayed lit, which is the one thing the data says it must not do. Sampling is a pure function of (steps, elapsed) rather than a playback object holding a cursor, so an element only has to remember WHEN its state began and the whole thing is testable without a clock, a GPU or a frame loop. One shared UiMediaClock is advanced once per frame by RetailUiRuntime; a UI element has no tick of its own. The controller change is the other half: it starts the flash on the rising edge ONLY. Re-setting Normal every frame would pin the sequence on frame zero and it would never blink at all — which is the failure mode the second new test exists to catch, and which no "is it visible?" assertion would notice. When the sequence reaches its terminal step the controller follows it down instead of re-lighting it. Two guesses are refused rather than made, and both are registered: a Pause's max duration (every sequence measured sets min == max, and what the range MEANS is not in the decomp) and a sub-1 branch probability (falls through, the direction where a malformed sequence stops rather than animates forever). A jump-cycle with no elapsed time is bounded so a bad sequence cannot spin inside a frame. Kept `Other` steps in the list rather than filtering them, so a jump's authored index still lands on the entry it names. Register: CT-3, CT-4. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
f44f7641b1
commit
89db9a794c
10 changed files with 557 additions and 56 deletions
|
|
@ -1,4 +1,4 @@
|
|||
# Retail Divergence Register — current through 2026-07-31
|
||||
# Retail Divergence Register — current through 2026-07-31
|
||||
|
||||
**What this is.** The single auditable register of every known place acdream's
|
||||
runtime behavior can deviate from the retail client (Sept 2013 EoR build,
|
||||
|
|
@ -492,6 +492,8 @@ equivalence argument (promote to AD/AP) or a fix.
|
|||
| UN-7 | Outdoor OBJECT point lighting uses `calc_point_light` (wrap/norm + per-channel cap, `~1/d²`) for ALL meshes including static buildings, but retail's object path is unconfirmed — `config_hardware_light` (0x0059ad30) sets D3D-FF point lights (`Diffuse=color×intensity`, `Attenuation=(0,1,0)`⇒`1/d`, `Range=falloff×1.5`, `material.diffuse=white`) yet that math would blow walls WHITE while retail stays DIM, so static buildings may instead use the `SetStaticLightingVertexColors` bake. Model + the brightness-scaling factor both UNRESOLVED (issue #140 / Fix D) | `src/AcDream.App/Rendering/Shaders/mesh_modern.vert` (`pointContribution`); `src/AcDream.Core/Lighting/LightManager.cs` (`SelectForObject`) | Fix A/B ported calc_point_light + per-object selection for objects without confirming retail uses that model for static buildings; cdb captured the D3D-FF path but it contradicts the observed dim result | Outdoor buildings blow out warm near torches (the #140 meeting-hall symptom); whichever model is wrong, the object torch contribution is too strong | `config_hardware_light` 0x0059ad30; `SetStaticLightingVertexColors` 0x0059cfe0; `rangeAdjust=1.5` 0x00820cc4 — see docs/research/2026-06-18-lighting-a7-fixABC-shipped-fixD-handoff.md |
|
||||
| CT-1 | Transcript truncation uses ONE character threshold (10,000) where retail uses two — it beheads to ~7,500 (`0x1D4C`) on passing 10,000 (`0x2710`), so its buffer oscillates between the two. acdream also cuts at whole LINES rather than searching for a newline near a byte offset | `src/AcDream.App/UI/Layout/ChatTranscriptRenderer.cs` (`MaxTranscriptCharacters`, `FirstLineWithinBudget`) | Retail's hysteresis exists to avoid re-trimming an ACCUMULATING buffer on every append; we rebuild the visible list from the log each time, so there is nothing to damp and a second threshold would only make the oldest visible line jump around as messages arrive. Whole-line cutting is what retail's newline preference is trying to achieve — our unit already is the line | acdream shows up to ~2,500 characters more scrollback than retail at the moment retail has just trimmed. Visible only as a slightly longer history; no state, wire or memory effect (ChatLog's own entry cap still bounds the model) | `ChatInterface::TruncateChatLog @0x004F4290`; threshold read at `RecvNotice_DisplayFinalStringInfo @0x004F4640` |
|
||||
| CT-2 | No client-side chat word filtering. Retail runs every transcript line through a taboo table when the `FilterLanguage` option is on and SUBSTITUTES matches; acdream performs no substitution at all. The option itself is kept and still stores/ships its bit to the server exactly as retail does | `src/AcDream.Core.Net/GameEventWiring.cs` (no filter in the AddText path); option at `src/AcDream.Runtime/Gameplay/CharacterOptionTable.cs` | DELIBERATE PRODUCT DECISION by the user, 2026-08-21: "I do not want any censoring." Not an oversight and not a porting gap | A player who enables FilterLanguage expecting retail's behaviour sees unfiltered text. No state, wire or server-visible effect — the option bit is still sent, so anything the SERVER gates on it behaves normally | `PlayerModule::FilterLanguage` + `TabooTableAdaptor::CheckCensorsW @0x00682A30` inside `ClientSystem::AddTextToScroll @0x00563C50`; matching at `TabooTable::CreateCheckString @0x00681570` / `StringMatchesFilter @0x00681600` |
|
||||
| CT-3 | A media `Pause` step holds for its `MinDuration`; retail authors a min AND a max and acdream ignores the max. Every sequence measured so far sets them equal, so nothing shipped is affected | `src/AcDream.App/UI/Layout/UiMediaSequence.cs` (`Sample`, the `Pause` case) | Whether the range means a random hold, a ramp, or a min-with-a-frame-budget ceiling is NOT determinable from the decomp, and picking one would be a guess dressed as a port. Using the min is the one reading that is right in every interpretation for the equal-valued case we can actually observe | A sequence authoring min != max would animate faster than retail. None does in the elements dumped so far; if one is found, the reading has to be measured before it is implemented | `MediaDescPause` in the LayoutDesc dat; playback at `UIElement::AnimateMedia` |
|
||||
| CT-4 | A media `Jump`/`State` step with a probability below 1 FALLS THROUGH rather than branching; retail rolls for it | `src/AcDream.App/UI/Layout/UiMediaSequence.cs` (`Sample`) | The roll's distribution and its re-roll cadence (per visit? per state entry?) are not in the decomp. Falling through is the conservative direction: a sequence that ends early stops animating, where treating it as certain would animate forever and could pin a state that never hands off | A probabilistic sequence plays its deterministic tail instead of its branch. The chat indicator authors p=1 throughout, so it is exact there | `MediaDescJump{Probability}` / `MediaDescState{Probability}` in the LayoutDesc dat |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue