Campaign CT slice B2, and the autocomplete the user asked about directly.
Typing "/r " now rewrites the chat entry to "@tell {LastTeller}, " the moment
the space lands, matching ChatInterface::HandleTextReplacements @0x004F50D0 ->
SetReplyTextInChatBox @0x004F4760.
This is display sugar rather than routing: "/r hello" already SENT correctly
through ChatInputParser's reply aliases. What was missing is that the player
could not SEE who they were about to reply to before pressing enter.
The trigger strings came out of the constant pool, not the decompiled listing —
Binary Ninja renders them as bare data_* references with no preview:
data_7C4C70 = "r " data_7C4C68 = "rp " data_7C4C58 = "reply "
Retail stores them WITHOUT the leading prefix and tests the first character
separately against '/' (0x2F) or '@' (0x40), which is why both prefixes work.
The research summary for this area listed the triggers as "/t ", "/tell " and
"reply " — reading the pool corrected that.
Three boundaries, each pinned by test because each is a way to get this subtly
wrong:
- The trailing space is PART of the trigger. "/r" alone must be left alone —
the player may still be typing "/roleplay", and expanding early would
hijack a different command mid-word.
- Only on space. Running the replacer per keystroke would rewrite text out
from under someone mid-word; retail keys on 0x20 specifically.
- Only with the caret at the end. Otherwise the player is editing existing
text, and expanding would corrupt a sentence they are part way through
fixing.
With nobody to reply to, nothing is rewritten — retail leaves the text alone
rather than producing a tell addressed to nobody, and the ordinary submit path
still reports "Someone must @tell you first!".
Solution builds clean; full hermetic gate green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fixes the crash the user hit twice today (captured in
artifacts/coldeve-acceptance-20260729/crash-hunt.log): clicking into a
multiline UiField - the examination window's inscription field - after
the text had changed since the last draw threw an unhandled
ArgumentOutOfRangeException from String.Substring and took the whole
client down (UiField.MeasureRange <- HitChar <- OnEvent MouseDown).
Root cause: _wrappedLines is a DRAW-side cache (rebuilt only in
DrawMultiLine) consumed by the INPUT side (HitChar on MouseDown and
drag-select MouseMove). Input events are pumped before the frame's
draw, so a mutation (backspace, SetText, paste) followed by a click in
the same pumped frame handed HitChar wrap lines describing the OLD,
longer text; measuring those stale ranges ran past the end of the live
string.
Fix: text mutations now bump a version (the _text field became a
private property so every existing mutation site participates without
churn), the draw records which version its wrap lines describe, and
HitChar proves coherence via EnsureWrappedLinesCurrent() - rebuilding
with the last draw width when stale. Rebuilding rather than clamping
keeps caret placement CORRECT against the live text, not merely
non-throwing. Two inversion-sensitive regression tests reproduce the
exact crash sequence (wrap long text, shrink without a draw, click);
they throw without the HitChar coherence call.
App tests 3,962 passed / 3 skipped (3,960 + 2 new).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bind the authored stack count entry and horizontal slider to one Core split-quantity owner, preserve retail count-first naming and exact 1000-step rounding, refresh on stack changes, and consume the selected amount during merges. Conformance covers the production DAT fixture and retained pointer/focus paths.
Co-Authored-By: Codex <codex@openai.com>
- Rename UiChatInput → UiField (UIElement_Field, RegisterElementClass(3) @ :126190);
update doc to cite retail's CatchDroppedItem/MouseOverTop drag-drop hooks for
future item windows. BackgroundColor default → transparent (controller sets
the translucent 0.35α value explicitly, matching UiText pattern).
- Register Type 3 in DatWidgetFactory.Create: `3 => new UiField()`.
- ChatWindowController.Bind (Variant B): factory now builds 0x10000016 as an
invisible UiText placeholder (Type 12); Bind removes that placeholder via
FindElement(InputId).Parent.RemoveChild and places a UiField at the same rect.
Result: exactly ONE input widget in the input bar, no stray UiText duplicate.
- Input property type changed from UiChatInput to UiField; GameWindow.cs:1861
UiField.Keyboard assignment compiles unchanged (field exists).
- Tests: UiChatInputTests → UiFieldTests (class + all ctor refs renamed);
DatWidgetFactoryTests: new Type3_Field_MakesUiField test; ChatWindowControllerTests:
updated stale "skipped by factory" comments; LayoutConformanceTests: updated
VitalsTree_ChromeCornerHasExpectedSprite — Type-3 chrome-corner elements are
now UiField (sprite rendering for Type-3 dat image elements is a known
limitation, tracked for post-Task-8 UiField.BackgroundSprite follow-up).
- Full suite: 404 passed, 2 skipped, 0 failed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 17:48:51 +02:00
Renamed from tests/AcDream.App.Tests/UI/UiChatInputTests.cs (Browse further)