Skip to content

Commit b711f2d

Browse files
committed
feat: guard wide fallback ground pickups
1 parent d79f25f commit b711f2d

4 files changed

Lines changed: 16 additions & 2 deletions

File tree

docs/qa/manual-client-checklist.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ Expected result:
297297
- if a corrupt/disposable fixture reaches the shared-world ground-handle seam with stale equipment-slot metadata on an otherwise unequipped ground snapshot, registration fails closed and no temporary ground actor becomes available
298298
- if a corrupt/disposable fixture has duplicate live items in the same carried cell, `ITEM_DROP` / `ITEM_DROP2` fails closed with no ground actor, no carried-slot deletion/update, no quickslot mutation, and no persisted-state mutation
299299
- missing, malformed, mismatched, or ground-count-over-template-`max_count` authored pickup template metadata fails closed: no item pickup notice, no inventory mutation, and the ground handle remains available for a later valid retry
300+
- fallback/no-template pickup fixtures whose ground stack count exceeds the current one-byte item refresh range (`255`) fail closed before item pickup notice, inventory mutation, or ground-handle removal
300301
- loaded pickup template metadata marked `anti_give` or restricted by the selected character's job/sex/min-level metadata also fails closed with the bootstrap inventory-full info message and leaves the ground handle available for a later valid retry
301302

302303
### 4.5.7 Drag inventory stack onto inventory stack (`ITEM_MOVE`)

internal/player/runtime.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ func (r *Runtime) PickupGroundItemWithTemplate(item inventory.ItemInstance, pref
441441
}
442442

443443
func (r *Runtime) PickupGroundItem(item inventory.ItemInstance, preferred inventory.SlotIndex, maxCount uint16) (GroundItemPickupResult, bool) {
444-
if r == nil || item.ID == 0 || item.Vnum == 0 || item.Count == 0 || preferred >= inventory.CarriedInventorySlotCount {
444+
if r == nil || item.ID == 0 || item.Vnum == 0 || item.Count == 0 || item.Count > 255 || preferred >= inventory.CarriedInventorySlotCount {
445445
return GroundItemPickupResult{}, false
446446
}
447447
if err := item.Validate(); err != nil {

internal/player/runtime_inventory_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,19 @@ func TestPickupGroundItemWithTemplateRejectsAuthoredRestrictionsWithoutMutation(
397397
}
398398
}
399399

400+
func TestPickupGroundItemRejectsFallbackWideGroundStackBeforeMutation(t *testing.T) {
401+
runtime := NewRuntime(loginticket.Character{}, SessionLink{})
402+
before := runtime.LiveInventory()
403+
ground := inventory.ItemInstance{ID: 31, Vnum: 27001, Count: 256, Slot: 6}
404+
405+
if result, ok := runtime.PickupGroundItem(ground, 6, 0); ok {
406+
t.Fatalf("expected fallback wide-stack pickup to fail closed before uint8 refresh, got %+v", result)
407+
}
408+
if got := runtime.LiveInventory(); !reflect.DeepEqual(got, before) {
409+
t.Fatalf("fallback wide-stack pickup mutated inventory: got %#v want %#v", got, before)
410+
}
411+
}
412+
400413
func TestPickupGroundItemWithAntiStackTemplateUsesFreshCarriedSlot(t *testing.T) {
401414
runtime := NewRuntime(loginticket.Character{
402415
Inventory: []inventory.ItemInstance{{ID: 41, Vnum: 27001, Count: 198, Slot: 5}},

spec/protocol/item-drop-pickup-bootstrap.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ For the first visible-peer pickup runtime slice, accepted pickup is visible-worl
142142
1. Accepted drops are registered as temporary bootstrap ground handles at the dropper's current effective map/position after the selected character mutation is persisted; zero-HP/dead owners, locked item snapshots, snapshots still marked equipped, and unequipped snapshots that still carry authored equipment-slot metadata are rejected at the shared-world ground-handle seam so death-state races or stale lock/equipment-state snapshots cannot create new visible handles. The recipient-side pickup placement boundary likewise rejects locked ground snapshots before merge or fresh-slot placement, so a stale locked ground handle cannot be reclaimed into carried inventory.
143143
2. The dropper receives the same direct `GC::ITEM_GROUND_ADD` + `GC::ITEM_OWNERSHIP` already owned by the first drop slice, currently visible living peers receive one queued add/ownership pair for the same handle, and later visible sessions that enter `GAME` while the handle remains pending receive that same add/ownership pair after self bootstrap, peer-player bootstrap, and visible static-actor bootstrap frames. Zero-HP/dead peers stay registered for their own restart/teardown flow, but they are skipped as recipients for new ground-handle visibility fanout.
144144
3. `ITEM_PICKUP` is accepted when its `vid` matches a still-pending bootstrap ground handle in the collector's visible world and the collector is within the first owned pickup reach (`300` coordinate units) of the ground handle. Visible-but-out-of-reach attempts fail closed with no frames, no inventory mutation, and the temporary handle left pending.
145-
4. The picked ground item snapshot itself must validate as a well-formed, unlocked, unequipped carried item before any compatible-stack merge or fresh-slot placement is attempted. Malformed ground snapshots, including stale snapshots that still carry an authored equipment slot while not marked equipped, snapshots still marked `equipped`, or snapshots still marked `locked`, fail closed before recipient inventory mutation, pickup notice, persistence, or temporary ground-handle removal.
145+
4. The picked ground item snapshot itself must validate as a well-formed, unlocked, unequipped carried item before any compatible-stack merge or fresh-slot placement is attempted. Malformed ground snapshots, including stale snapshots that still carry an authored equipment slot while not marked equipped, snapshots still marked `equipped`, snapshots still marked `locked`, or fallback/no-template ground stacks whose count exceeds the current legacy item-refresh byte range (`255`), fail closed before recipient inventory mutation, pickup notice, persistence, or temporary ground-handle removal.
146146
5. When the picked item has authored loaded template metadata, that template must exist, must be valid, and its `vnum` must match the ground item `vnum` before template-authored stack metadata is applied. Missing, malformed, or mismatched authored template metadata fails closed before any inventory mutation, pickup notice, persistence, or temporary ground-handle removal. The deterministic bootstrap fallback snapshot used when no item-template file exists remains valid for local smoke/runtime startup; this missing-template guard applies after an authored item-template snapshot has been loaded.
147147
6. Template-authored selected-character and transfer restrictions fail closed before the recipient inventory mutates. The template-backed player pickup placement helper rejects `anti_drop`, `anti_give`, `anti_sell`, authored job/sex restrictions (`anti_warrior`, `anti_assassin`, `anti_sura`, `anti_shaman`, or `anti_male`/`anti_female`), `min_level` restrictions for the recipient, and ground stacks whose live count is already above the resolved template-authored `max_count`. The minimal runtime now freezes `anti_drop` / `anti_give` / `anti_sell` or restricted self pickup through the normal packet path as one inventory-full `CHAT_TYPE_INFO` rejection while leaving the temporary ground handle pending for a later valid retry; over-`max_count` ground stacks fail closed with no frames and also leave the pending handle available for a later valid retry.
148148
7. When the picked item is stackable, not authored with `anti_stack`, and a carried compatible, unlocked, non-equipped stack can absorb the full ground count, pickup merges into the lowest such carried slot and refreshes it with `GC::ITEM_UPDATE`.

0 commit comments

Comments
 (0)