fix(items): port retail useability and autowear rejection
Use the exact low USEABLE_NO-bit predicate so reset/zero-valued direct-use items such as Blackmoor's Favor reach the ordinary Use request. Port AutoWear's clothing-priority blocker lookup and exact named system notice through the shared activation owner. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
60387668d0
commit
f71f947475
13 changed files with 289 additions and 57 deletions
|
|
@ -61,7 +61,7 @@ accepted-divergence entries (#96, #49, #50).
|
|||
|
||||
---
|
||||
|
||||
## 2. Adaptation (AD) — 38 rows (AD-31 retired 2026-07-15 — the DAT-authored portal-space viewport replaces the black transit cover)
|
||||
## 2. Adaptation (AD) — 38 rows (AD-11 retired 2026-07-23 — exact low-bit ItemUses predicate; AD-31 retired 2026-07-15 — the DAT-authored portal-space viewport replaces the black transit cover)
|
||||
|
||||
| # | Divergence | Where (file:line) | Why it is safe / justified | Risk if assumption breaks | Retail oracle |
|
||||
|---|---|---|---|---|---|
|
||||
|
|
@ -73,7 +73,7 @@ accepted-divergence entries (#96, #49, #50).
|
|||
| AD-5 | Outdoor `point_in_cell` is an identity compare against the global XY-column cell from `LandDefs.AdjustToOutside` (no per-cell containment test) | `src/AcDream.Core/Physics/CellTransit.cs:865` | Landcells are disjoint 24 m columns — identity-compare against the column under the sphere centre is exactly equivalent to retail's per-candidate test | If block-origin/lcoord math is wrong at a landblock seam, the compare silently never matches — outdoor membership freezes at boundaries (the pre-#106 symptom) | `find_cell_list` pick pc:308788-308825; `CLandCell::point_in_cell` (get_block_offset pc:308804) |
|
||||
| AD-6 | Per-LANDBLOCK shadow re-flood on hydration vs retail per-CELL `recalc_cross_cells` | `src/AcDream.Core/Physics/ShadowObjectRegistry.cs:339` | The streaming unit IS the landblock; one hook per hydration event covers both race directions (entity-before-cells, cells-after-spawn) | Any cell-hydration path that doesn't raise the landblock hook leaves an entity's shadow set stale — walk-through / missing collisions in just-streamed cells | `CObjCell::init_objects` → `recalc_cross_cells`, 0x0052b420 / 0x00515a30 |
|
||||
| AD-10 | Remote slope projection relocated to the queue-empty/head-reached combiner boundary; retail projects inside `CTransition::adjust_offset` during the sweep | `src/AcDream.Core/Physics/PositionManager.cs:47` | Remote bodies don't run a full local transition sweep; boundary projection removes the ~5 Hz Z staircase on slopes, no-op on flat ground | The single-point terrain-normal sample can differ from the sweep's contact plane (cell boundaries, props underfoot) — remote Z drift / stair-stepping | `CTransition::adjust_offset` pc:272296-272346 |
|
||||
| AD-11 | Useability fallback: retail blocks Use entirely on null/zero useability; we allow null for the established creature/large-object fallback while preserving explicit zero/one rejection | `src/AcDream.App/Interaction/WorldSelectionQuery.cs` (`IsUseable`) | ACE's seed DB ships many weenies with `_useability` unset; without the fallback doors/lifestones/creatures are un-Useable on ACE | Objects a retail-faithful server intentionally leaves without useability can become useable in acdream — wrong interaction gating when the ACE-ships-null assumption stops holding | `ItemHolder::UseObject` pc:402923; `ItemUses::IsUseable @ 0x004FCCC0` |
|
||||
| ~~AD-11~~ | **RETIRED 2026-07-23** — the matching binary disproved the old nonzero interpretation: `ItemUses::IsUseable` executes `not bitfield; and eax,1`, so absent/reset zero is usable and only `USEABLE_NO` disables use. Toolbar, item policy, and world interaction now share that exact Core predicate. | `src/AcDream.Core/Items/ClientObject.cs` (`ItemUseability.IsUseable`); `src/AcDream.Core/Items/ItemInteractionPolicy.cs`; `src/AcDream.App/Interaction/WorldSelectionQuery.cs` | — | — | `ItemUses::IsUseable @ 0x004FCCC0`; matching v11.4186 instructions recorded in `docs/research/2026-07-23-retail-item-use-and-autowear-pseudocode.md` |
|
||||
| AD-12 | SecondaryAttributeTable coefficients hardcoded (Health=End×0.5, Stam=End×1.0, Mana=Self×1.0) instead of dat-read; unknown attributes contribute 0 | `src/AcDream.Core/Player/LocalPlayerState.cs:279` | Coefficients never vary across retail dat versions; re-confirmed by ACE AttributeFormula.cs + holtburger; dat port can replace later | A customized portal.dat with modified vital formulas silently yields wrong max-vitals; a missing attribute snapshot underestimates max | SecondaryAttributeTable portal.dat 0x0E0..0x0E2; `CreatureVital::GetMaxValue` 0x0058F2DD |
|
||||
| AD-13 | 1-second dedup window for identical system chat messages (retail has none) | `src/AcDream.Core/Chat/ChatLog.cs:29` | ACE dual-sends the same system text (0xF7E0 + 0x02EB) for back-compat; without dedup every line doubled (Phase J compromise) | Two genuinely distinct but textually identical system messages within 1 s collapse to one line where retail shows both | ACE dual-send 0xF7E0 + 0x02EB |
|
||||
| AD-15 | `IsEnv` masks low-16 of the cell id (`(Id & 0xFFFF) >= 0x100`) where retail tests the full id | `src/AcDream.Core/World/Cells/ObjCell.cs:25` | Every real prefixed EnvCell id has low-16 ≥ 0x100 and every outdoor cell ≤ 0x40 — identical answers for all real dat ids, works for both bare and prefixed forms | None for real dat data; a hypothetical convention-violating id would route to the wrong (BSP vs terrain) point-in-cell logic | `CObjCell::GetVisible` pc:308215 |
|
||||
|
|
@ -203,7 +203,7 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
|
|||
| AP-104 | Vitals detail element `0x100004A9` and root `HideDetail`/`ShowDetail` transitions are not wired | `src/AcDream.App/UI/Layout/VitalsController.cs` | Compact vitals values/bars are correct | Detail click does nothing and expanded retail state is unreachable | `gmVitalsUI::ListenToElementMessage @ 0x004BFC00`; `PostInit @ 0x004BFCE0` |
|
||||
| AP-105 | **PARTIAL 2026-07-13** — inherited scrollbar media/roles now come from DAT (decrement/top `0x06004C69`, increment/bottom `0x06004C6C`), and both chat backends share typed client-command routing plus one retained `ChatVM` for reply state. Retained chat still lacks complete tab/filter/unread, social availability, incoming squelch enforcement, and focus-opacity behavior. | `src/AcDream.App/UI/Layout/DatWidgetFactory.cs`; `ChatWindowController.cs`; `src/AcDream.App/UI/ClientCommandController.cs`; chat mount in `GameWindow.cs` | Shared log/send path, wrapping, scrollbar roles, command ownership, and outer maximize geometry work; later chat work consolidates the remaining presentation/filter state | Tabs are no-ops, squelched lines can still render, contextual social actions are absent, and focus visuals diverge | `gmMainChatUI @ 0x004CCCC0..0x004CE2A0`; `UIElement_Scrollbar::OnSetAttribute @ 0x004714D0`; `ChatInterface` methods |
|
||||
| ~~AP-107~~ | **RETIRED 2026-07-11 (Wave 3.3 / #197)** — typed `OfferPrimaryClick` returns `NotActive`, `ConsumedSuccess`, or `ConsumedRejected`; every retained item surface plus radar/world offers active target mode before local selection/open/use fallback. Rejections are consumed and cannot drift selection. | `src/AcDream.App/UI/ItemInteractionController.cs`; inventory/paperdoll/toolbar/radar/world call sites | — | — | `UIElement_ItemList::HandleTargetedUseLeftClick @ 0x004E24D0` |
|
||||
| AP-108 | Paperdoll/AutoWield still omit full `AutoWieldIsLegal`/dual-wield rules, double-click examine/drag from the doll, body-part selection lighting, and retail's synchronous `" - cannot unwield the %s"` failure suffix (the current send seam reports rejection asynchronously). **Primary replacement retired from this row 2026-07-14:** inventory activation and paperdoll drops share the confirmed blocker transaction, preserve explicit slot intent, relocate an already-worn item to an explicitly selected compatible slot, and emit retail's successful move-to-backpack status. **Aetheria retired 2026-07-13.** | `src/AcDream.App/UI/Layout/PaperdollController.cs`; `src/AcDream.App/UI/AutoWieldController.cs` | Basic equip slots, Aetheria, and live doll work; primary weapon, incompatible shield, and mismatched ammo blockers sequence through server-confirmed dequip→wield in both peace and war | Remaining illegal/off-hand cases, asynchronous dequip rejection wording, doll examine/drag, and selection lighting still differ functionally | `CPlayerSystem::AutoWield @ 0x00560A60`; `ACCWeenieObject::BlocksUseOfShield @ 0x0055D3E0`; `gmPaperDollUI @ 0x004A3590..0x004A5F90` |
|
||||
| AP-108 | Paperdoll/AutoWield still omit the remaining missile/held restrictions and corrupt-mask branch of full `AutoWieldIsLegal`, dual-wield/off-hand rules, double-click examine/drag from the doll, body-part selection lighting, and retail's synchronous `" - cannot unwield the %s"` failure suffix (the current send seam reports rejection asynchronously). **AutoWear legality retired from this row 2026-07-23:** inventory activation and paperdoll drops now apply the retail clothing-priority/location blocker lookup and exact `"You must remove your %s to wear that"` system notice. **Primary replacement retired 2026-07-14; Aetheria retired 2026-07-13.** | `src/AcDream.App/UI/Layout/PaperdollController.cs`; `src/AcDream.App/UI/AutoWieldController.cs` | Basic equip slots, Aetheria, live doll, AutoWear conflict reporting, and primary weapon/incompatible shield/mismatched ammo blocker sequencing work in peace and war | Remaining illegal/off-hand cases, asynchronous dequip rejection wording, doll examine/drag, and selection lighting still differ functionally | `CPlayerSystem::AutoWieldIsLegal @ 0x0055ED60`; `CPlayerSystem::AutoWearIsLegal @ 0x0055EF40`; `CPlayerSystem::AutoWield @ 0x00560A60`; `gmPaperDollUI @ 0x004A3590..0x004A5F90` |
|
||||
| AP-109 | Character Titles page is inert and live displayed-title/luminance state is absent | `src/AcDream.App/UI/Layout/CharacterStatController.cs`; `CharacterSheetProvider.cs` | Attributes/skills core output is user-accepted | Titles cannot be selected/displayed and level-200 luminance fields are missing | `gmCharacterTitleUI @ 0x0049A610`; `gmStatManagementUI::UpdateExperience @ 0x004F0A70` |
|
||||
| AP-110 | Remaining retained gameplay panels and world HUD are absent: advanced-combat powerbar, residual social/examine/floating chat, quests/map/options/smartbox, vendor/trade/salvage/tinkering, mini-game gameplay, Link Status NAK/retransmission packet-loss averaging, and D.6 nameplates/floaters | `src/AcDream.App/UI/RetailUiRuntime.cs`; `src/AcDream.Core.Net/LinkStatusSnapshot.cs`; D.5/D.6 roadmap | Basic combat plus M3 spellbook/component/effects/favorite-spell/Link/Vitae surfaces now cover the active melee/missile/magic loops; the Link page has exact ping RTT but reports the current transport's zero default until its missing reliability statistics land | Large portions of retail gameplay still have no production UI; real packet loss is displayed as 0.00% instead of retail's moving average | Named `gm*UI::PostInit` methods; `CLinkStatusAverages::GetAveragePacketLoss @ 0x00546610`; LayoutDesc catalog |
|
||||
| ~~AP-111~~ | **RETIRED 2026-07-11 (M2 held-object parenting)** — equipped hand items are no longer omitted from the render world. CreateObject now preserves Placement/Parent/position timestamp bootstrap; live `0xF749` ParentEvent is parsed with retail sequence freshness; a focused render controller resolves `Setup.HoldingLocations`, applies the child's placement frame, and recomposes the separate child entity after every parent animation tick. Pickup retains the weenie's visual metadata for a later wield. | `src/AcDream.Core.Net/Messages/{CreateObject,ParentEvent}.cs`; `src/AcDream.Core/Meshing/EquippedChildAttachment.cs`; `src/AcDream.App/Rendering/EquippedChildRenderController.cs` | — | — | `ClientCombatSystem::GetDefaultCombatMode @ 0x0056B310`; `SmartBox::HandleParentEvent @ 0x004535D0`; `CPhysicsObj::set_parent @ 0x00515A90`; `CPhysicsObj::UpdateChild @ 0x00512D50` |
|
||||
|
|
|
|||
|
|
@ -15,11 +15,14 @@ assessment information, equipped-child picking, vendor browsing, and
|
|||
server-authoritative vendor transactions. Slice 1's DAT-authored arrow-only
|
||||
favorite-spell scrollbar, fixed 18-cell capacity, authored arrow sides/media,
|
||||
and persistent manual offset passed the connected gate. Slice 2 is active: the
|
||||
status hand now follows canonical selection/object notices, ghosts empty and
|
||||
unusable selections, and routes enabled weapons, direct-use items, and
|
||||
use-on-target tools through the existing interaction transaction and cursor
|
||||
owners. Its connected hand-state gate is pending; the magnifying-glass Assess
|
||||
command follows in the same slice.
|
||||
status hand follows canonical selection/object notices, ghosts empty and
|
||||
explicitly unusable selections, and routes enabled weapons, direct-use items,
|
||||
and use-on-target tools through the existing interaction transaction and
|
||||
cursor owners. Its connected state gate passed. A named-retail correction now
|
||||
treats reset/absent useability zero as usable, allowing Blackmoor's Favor to
|
||||
send ordinary Use, while shared AutoWear reports the exact overlapping worn
|
||||
item that must be removed. That correction awaits its connected gate; the
|
||||
magnifying-glass Assess command follows in the same slice.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -91,8 +91,10 @@ broaden the feature surface.
|
|||
|
||||
Slice 1's 18-cell favorite-spell overflow bar is user-accepted. Slice 2 is
|
||||
active: the status hand's selected-object availability and existing
|
||||
Use/AutoWield/use-on-target command routing are code-complete, with the
|
||||
connected state/cursor gate pending before the Assess half continues.
|
||||
Use/AutoWield/use-on-target command routing passed their connected gate.
|
||||
The follow-up retail correction for zero-valued direct-use items and named
|
||||
AutoWear blockers is code-complete and awaiting its connected gate before the
|
||||
Assess half continues.
|
||||
|
||||
Carried:
|
||||
#153 far-teleport residual, #116 slide-response, the remaining R6 ownership cleanup plus
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
# World interaction completion — pre-M4 program
|
||||
|
||||
**Status:** Slice 1 user-accepted 2026-07-23. Slice 2 Use-hand state and command
|
||||
path code-complete; connected visual gate pending before Assess continues.
|
||||
**Status:** Slice 1 user-accepted 2026-07-23. Slice 2 Use-hand selection state
|
||||
passed its connected gate. The follow-up zero-useability/AutoWear correction is
|
||||
code-complete and awaits its connected gate before Assess continues.
|
||||
**Milestone:** M4 prerequisite/preamble.
|
||||
**Architecture:** retained gameplay UI over shared selection, object, and
|
||||
interaction state. `GameWindow` remains a composition/callback shell.
|
||||
|
|
@ -69,6 +70,20 @@ AP-122. The 14-test Core interaction focus, 49-test toolbar focus, Release
|
|||
solution build, 3,474 App tests / 3 skips, and 7,848 complete-solution tests /
|
||||
5 skips pass.
|
||||
|
||||
The connected selection-state gate passed. Its follow-up exposed two deeper
|
||||
shared-policy defects, now corrected from named retail plus the matching
|
||||
binary. `ItemUses::IsUseable @ 0x004FCCC0` tests only `USEABLE_NO`; reset/absent
|
||||
value zero is usable, so Blackmoor's Favor and other zero-valued direct-use
|
||||
items now emit the ordinary Use request. AutoWear now applies
|
||||
`CPlayerSystem::AutoWearIsLegal @ 0x0055EF40` through the same double-click and
|
||||
toolbar-hand path, resolves the overlapping worn object from the
|
||||
retail-ordered equipment projection, and emits the exact system line
|
||||
`You must remove your <item> to wear that`. Research:
|
||||
[`../research/2026-07-23-retail-item-use-and-autowear-pseudocode.md`](../research/2026-07-23-retail-item-use-and-autowear-pseudocode.md).
|
||||
The focused 25-test Core and 85-test App sets pass, as do the warning-free
|
||||
Release solution build, 3,476 App tests / 3 skips, and 7,857 complete-solution
|
||||
tests / 5 skips.
|
||||
|
||||
## Slice 1 — spell-bar overflow arrows
|
||||
|
||||
### Retail oracle
|
||||
|
|
@ -148,7 +163,8 @@ Named retail references and executable pseudocode are recorded in
|
|||
### Remaining Slice 2 work
|
||||
|
||||
- Port the magnifying-glass Assess command state and activation.
|
||||
- Run the connected Use-hand visual gate: no selection, spell component,
|
||||
weapon, and health kit.
|
||||
- Run the connected correction gate: activate Blackmoor's Favor, then try to
|
||||
equip armor blocked by an overlapping worn item and verify the named system
|
||||
message.
|
||||
- Continue directly into the Assess information slice after the combined
|
||||
status-control gate.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
# Retail item useability and AutoWear rejection
|
||||
|
||||
Date: 2026-07-23
|
||||
|
||||
## Retail anchors
|
||||
|
||||
- `ItemUses::ItemUses @ 0x004FCCB0`
|
||||
- `ItemUses::IsUseable @ 0x004FCCC0`
|
||||
- `PublicWeenieDesc::Reset @ 0x005ACC70`
|
||||
- `PublicWeenieDesc::UnPack @ 0x005AD470`
|
||||
- `ItemHolder::DetermineUseResult @ 0x00588460`
|
||||
- `ItemHolder::UseObject @ 0x00588A80`
|
||||
- `CPlayerSystem::AutoWearIsLegal @ 0x0055EF40`
|
||||
- `CPlayerSystem::AutoWear @ 0x005601C0`
|
||||
- `ACCWeenieObject::GetObjectAtLocation @ 0x0058CE00`
|
||||
|
||||
The named pseudo-C is the behavioral oracle. The matching v11.4186 binary was
|
||||
also inspected because an older project note interpreted
|
||||
`ItemUses::IsUseable` as a nonzero test. The actual instructions are:
|
||||
|
||||
```text
|
||||
004fccc0 mov eax,[ecx]
|
||||
004fccc2 not eax
|
||||
004fccc4 and eax,1
|
||||
004fccc7 ret
|
||||
```
|
||||
|
||||
Therefore the method tests only `USEABLE_NO` (bit zero). A zero-valued
|
||||
`ITEM_USEABLE` is usable; `USEABLE_NEVER_WALK` is a movement modifier and does
|
||||
not make the object unusable. The literal AutoWear rejection at `0x007CC868`
|
||||
is:
|
||||
|
||||
```text
|
||||
You must remove your %s to wear that
|
||||
```
|
||||
|
||||
The staged ACE, holtburger, and ACViewer reference directories are currently
|
||||
empty in this checkout. Their established roles were still checked: ACE owns
|
||||
the authoritative use/wield outcome, while the two client references do not
|
||||
replace the retail UI-side legality and notice oracle. No server behavior is
|
||||
being inferred or moved client-side here.
|
||||
|
||||
## Pseudocode
|
||||
|
||||
```text
|
||||
ItemUses.IsUseable():
|
||||
return (useableBitfield & USEABLE_NO) == 0
|
||||
```
|
||||
|
||||
`PublicWeenieDesc::Reset` zeros the useability field. `UnPack` replaces it only
|
||||
when the optional useability word is present. Consequently an absent wire word
|
||||
and an explicitly packed zero both have retail `USEABLE_UNDEF` value zero and
|
||||
both pass `IsUseable`.
|
||||
|
||||
```text
|
||||
AutoWearIsLegal(itemId, out alreadyWorn, silent):
|
||||
alreadyWorn = false
|
||||
|
||||
if player is not ready for an inventory request:
|
||||
return false
|
||||
|
||||
item = GetWeenieObject(itemId)
|
||||
if item is null:
|
||||
return false
|
||||
if (item.validLocations & 0x08007FFF) == 0:
|
||||
return false
|
||||
|
||||
if (item.priority & playerSystem.clothingPriorityMask) == 0:
|
||||
return true
|
||||
|
||||
player = GetWeenieObject(playerId)
|
||||
if player contains item at a worn location:
|
||||
alreadyWorn = true
|
||||
|
||||
if not silent:
|
||||
if alreadyWorn:
|
||||
display "The <item appropriate name> is already being worn"
|
||||
else:
|
||||
occupied = playerSystem.inventoryMask & item.validLocations
|
||||
blocker = player.GetObjectAtLocation(occupied, item.priority)
|
||||
if blocker exists:
|
||||
display "You must remove your <blocker appropriate name> to wear that"
|
||||
|
||||
return false
|
||||
```
|
||||
|
||||
```text
|
||||
AutoWear(itemId, out alreadyWorn, silent):
|
||||
if not AutoWearIsLegal(itemId, alreadyWorn, silent):
|
||||
return false
|
||||
|
||||
item = GetWeenieObject(itemId)
|
||||
if item exists:
|
||||
item.UIAttemptWield(item.validLocations)
|
||||
return true
|
||||
```
|
||||
|
||||
## Port boundary
|
||||
|
||||
- `ItemUseability.IsUseable` is the one Core implementation of the low-bit
|
||||
rule. Toolbar enablement, item activation, and world interaction consume it.
|
||||
- `ItemInteractionController` remains the shared double-click and toolbar-hand
|
||||
path. A zero-valued carried gem such as Blackmoor's Favor therefore emits the
|
||||
ordinary Use request; ACE remains authoritative for its spell and
|
||||
consumption outcome.
|
||||
- `AutoWieldController` remains the shared inventory/paperdoll equip owner. It
|
||||
resolves AutoWear blockers from `ClientObjectTable`'s retail-ordered
|
||||
equipment projection and emits the retail literal through system chat.
|
||||
- Weapon replacement is unchanged. Retail automatically moves primary weapon,
|
||||
incompatible shield, and incompatible ammunition blockers, while AutoWear
|
||||
rejects overlapping clothing/armor and tells the player what to remove.
|
||||
Loading…
Add table
Add a link
Reference in a new issue