Repository: CleverRaven/Cataclysm-DDA (2026-03-26T04:47:30Z)

Issue:
DEBUG : Tried to find item parent using an object that doesn't contain it
### Describe the bug

When trying to pick up any item with charges, the debug msg in the title shows up.

```
20:29:02.170 ERROR : (error message will follow backtrace)
  #0
    (dbghelp: debug_write_backtrace+0xbe@00007FF70CD8584E[cataclysm-tiles.exe+0x55584e]), 
  #1
    (dbghelp: DebugLog+0x21e@00007FF70CD83DEE[cataclysm-tiles.exe+0x553dee]), 
  #2
    (dbghelp: realDebugmsg+0x135@00007FF70CD88515[cataclysm-tiles.exe+0x558515]), 
  #3
    (dbghelp: read_only_visitable::find_parent+0xd7@00007FF70D629A47[cataclysm-tiles.exe+0xdf9a47]), 
  #4
    (dbghelp: form_loc_recursive<Character>+0x27@00007FF70DDAE807[cataclysm-tiles.exe+0x157e807]), 
  #5
    (dbghelp: Character::add_to_inv_search_caches+0x172@00007FF70D8985D2[cataclysm-tiles.exe+0x10685d2]), 
  #6
    (dbghelp: std::_Func_impl_no_alloc<`Character::on_item_acquire'::`2'::<lambda_1>,enum VisitResponse,item *,item *>::_Do_call+0x1c@00007FF70D896C1C[cataclysm-tiles.exe+0x1066c1c]), 
  #7
    (dbghelp: visit_internal+0x43@00007FF70D62CA83[cataclysm-tiles.exe+0xdfca83]), 
  #8
    (dbghelp: Character::on_item_acquire+0x5b@00007FF70D8A690B[cataclysm-tiles.exe+0x107690b]), 
  #9
    (dbghelp: item::on_pickup+0x64@00007FF70D663DB4[cataclysm-tiles.exe+0xe33db4]), 
  #10
    (dbghelp: pick_one_up+0xb21@00007FF70DA7BC51[cataclysm-tiles.exe+0x124bc51]), 
  #11
    (dbghelp: Pickup::do_pickup+0x286@00007FF70DA79B26[cataclysm-tiles.exe+0x1249b26]), 
  #12
    (dbghelp: pickup_activity_actor::do_turn+0x250@00007FF70D7B9AA0[cataclysm-tiles.exe+0xf89aa0]), 
  #13
    (dbghelp: player_activity::do_turn+0x59c@00007FF70D26DDDC[cataclysm-tiles.exe+0xa3dddc]), 
  #14
    (dbghelp: do_turn+0xc69@00007FF70CD8D639[cataclysm-tiles.exe+0x55d639]), 
  #15
    (dbghelp: WinMain+0xc4c@00007FF70CC2822C[cataclysm-tiles.exe+0x3f822c]), 
  #16
    (dbghelp: __scrt_common_main_seh+0x106@00007FF70E561CD2[cataclysm-tiles.exe+0x1d31cd2]), 
  #17
    (dbghelp: BaseThreadInitThunk+0x14@00007FFCC6347374[KERNEL32.DLL+0x17374]), 
  #18
    (dbghelp: RtlUserThreadStart+0x21@00007FFCC78DCC91[ntdll.dll+0x4cc91]), 
```

### Attach save file

[La Marque-trimmed.tar.gz](https://github.com/user-attachments/files/25442405/La.Marque-trimmed.tar.gz)

### Steps to reproduce

1. Load the save, try to pick up the soap bars on the ground.
2. If no debug msg pops up, drop the soap bars and try to pick them up again.

### Expected behavior

No errors.

### Screenshots

_No response_

### Versions and configuration

- OS: Windows
    - OS Version: 10.0.19045.6937 (22H2)
- Game Version: cdda-experimental-2026-02-13-1823-dirty [64-bit]
- Graphics Version: Tiles
- Game Language: English [en]
- Mods loaded: [
    Dark Days Ahead [dda],
    Disable NPC Needs [no_npc_food],
    Portal Storms Ignore NPCs [personal_portal_storms]
]


### Additional context

_No response_


Discussion:
I have reproduced the issue using the provided save file and steps, and have also encountered this behavior in my own playthrough.

- OS: Windows
    - OS Version: 10.0.19045.6466 (22H2)
- Game Version: cdda-experimental-2026-02-23-0705-dirty [64-bit]
- Graphics Version: Tiles
- Game Language: 中文 (中国)<color_dark_gray>(98.7%)</color> [zh_CN]
- Mods loaded: [
    浩劫余生 [dda],
    NPC无基本需求 [no_npc_food],
    NPC不受空间风暴影响 [personal_portal_storms]
]

[debug.log](https://github.com/user-attachments/files/25497581/debug.log)
Yea I've been seeing it too.
I can also confirm that this happens. I just had it happen when picking up copper wires, and I've had it happen with nails in the past.

It seems to happen the first time on load, then if you hit i to skip you can continue and it doesn't seem to happen later.

--------------------------------

 DEBUG : Tried to find item parent using an object that doesn't contain it

 REPORTING FUNCTION : find_parent
 C++ SOURCE FILE    : D:\a\Cataclysm-DDA\Cataclysm-DDA\src\visitable.cpp
 LINE               : 64
 VERSION            : cdda-experimental-2026-03-12-0422-dirty

--------------------------------

- OS: Windows
    - OS Version: 10.0.19045.6466 (22H2)
- Game Version: cdda-experimental-2026-03-12-0422-dirty [64-bit]
- Graphics Version: Tiles
- Game Language: System language []
- Mods loaded: [
    Dark Days Ahead [dda],
    Disable NPC Needs [no_npc_food],
    Portal Storms Ignore NPCs [personal_portal_storms]
]

[Barclay-trimmed.tar.gz](https://github.com/user-attachments/files/25981351/Barclay-trimmed.tar.gz)

It happens every time, hitting `i` just makes it suppress the message in the future.



Patch:
diff --git a/src/pickup.cpp b/src/pickup.cpp
index 80c158e519b64..052dde7d3d3bb 100644
--- a/src/pickup.cpp
+++ b/src/pickup.cpp
@@ -303,7 +303,8 @@ static bool pick_one_up( item_location &loc, int quantity, bool &got_water, bool
             if( ret.success() ) {
                 if( added_it == item_location::nowhere ) {
                     newit.charges = last_charges - newit.charges;
-                    newit.on_pickup( player_character );
+                    // Don't call on_pickup on this local copy -- the real items
+                    // already got it via add_stash -> fill_with (#85439).
                     if( newit.charges != 0 ) {
                         auto &entry = mapPickup[newit.tname()];
                         entry.second += newit.charges;


Test patch:
diff --git a/tests/item_pickup_test.cpp b/tests/item_pickup_test.cpp
index 9bce898d8f843..6027d26a12568 100644
--- a/tests/item_pickup_test.cpp
+++ b/tests/item_pickup_test.cpp
@@ -1,3 +1,5 @@
+#include <climits>
+#include <functional>
 #include <list>
 #include <optional>
 #include <string>
@@ -6,6 +8,7 @@
 #include "avatar.h"
 #include "cata_catch.h"
 #include "coordinates.h"
+#include "debug.h"
 #include "item.h"
 #include "item_location.h"
 #include "map.h"
@@ -20,6 +23,7 @@
 
 static const itype_id itype_backpack_hiking( "backpack_hiking" );
 static const itype_id itype_debug_modular_m4_carbine( "debug_modular_m4_carbine" );
+static const itype_id itype_nail( "nail" );
 static const itype_id itype_rope_6( "rope_6" );
 
 // This test case exists by way of documenting and exhibiting some potentially unexpected behavior
@@ -200,3 +204,142 @@ TEST_CASE( "pickup_m4_with_a_rope_in_a_hiking_backpack", "[pickup][container]" )
     }
 }
 
+// Helper: count total charges of a given item type on a map tile
+static int ground_charges_of( map &here, const tripoint_bub_ms &pos,
+                              const itype_id &type )
+{
+    int total = 0;
+    for( const item &it : here.i_at( pos ) ) {
+        if( it.typeId() == type ) {
+            total += it.charges;
+        }
+    }
+    return total;
+}
+
+// Regression tests for #85439: charged-item pickup that merges into existing
+// stacks must not call on_pickup() on the detached local copy in pick_one_up().
+// The real items already get on_pickup() via item_pocket::fill_with().
+TEST_CASE( "charged_item_pickup_full_merge_no_debugmsg", "[pickup][charges]" )
+{
+    avatar &they = get_avatar();
+    map &here = get_map();
+    clear_avatar();
+    clear_map_without_vision();
+
+    const tripoint_bub_ms ground = they.pos_bub();
+
+    // Wear a backpack so we have somewhere to stash items
+    item backpack_on_ground( itype_backpack_hiking );
+    they.wear_item( backpack_on_ground );
+
+    // Pre-seed inventory with some nails
+    item seed_nails( itype_nail );
+    seed_nails.charges = 10;
+    they.i_add( seed_nails );
+
+    const int inv_before = they.charges_of( itype_nail );
+    REQUIRE( inv_before == 10 );
+
+    // Prime inv_search_cache -- the bug only fires when cache entries exist
+    they.cache_has_item_with( itype_nail );
+
+    // Place more nails on the ground (small enough to fully absorb)
+    item ground_nails( itype_nail );
+    ground_nails.charges = 5;
+    here.add_item( ground, ground_nails );
+
+    const int ground_before = ground_charges_of( here, ground, itype_nail );
+    REQUIRE( ground_before == 5 );
+    const int total_before = inv_before + ground_before;
+
+    // Build pickup list from items on the ground
+    item_location nail_loc( map_cursor( they.pos_abs() ),
+                            &here.i_at( ground ).only_item() );
+    const drop_locations to_pick_up = { std::make_pair( nail_loc, 0 ) };
+
+    std::string dmsg = capture_debugmsg_during( [&]() {
+        they.pick_up( to_pick_up );
+        process_activity( they );
+    } );
+
+    const int inv_after = they.charges_of( itype_nail );
+    const int ground_after = ground_charges_of( here, ground, itype_nail );
+
+    CHECK( dmsg.empty() );
+    CHECK( inv_after + ground_after == total_before );
+    CHECK( inv_after == 15 );
+    CHECK( ground_after == 0 );
+}
+
+TEST_CASE( "charged_item_pickup_partial_merge_no_debugmsg", "[pickup][charges]" )
+{
+    avatar &they = get_avatar();
+    map &here = get_map();
+    clear_avatar();
+    clear_map_without_vision();
+
+    const tripoint_bub_ms ground = they.pos_bub();
+
+    // Wear a backpack so we have somewhere to stash items
+    item backpack_on_ground( itype_backpack_hiking );
+    they.wear_item( backpack_on_ground );
+
+    // Determine total nail capacity of the empty backpack
+    item probe_nail( itype_nail );
+    probe_nail.charges = 1;
+    int remaining = INT_MAX;
+    they.can_stash_partial( probe_nail, remaining );
+    const int total_capacity = INT_MAX - remaining;
+    REQUIRE( total_capacity > 100 );
+
+    // Fill backpack to (capacity - margin) via i_add_or_fill
+    const int margin = 50;
+    item prefill_nails( itype_nail );
+    prefill_nails.charges = total_capacity - margin;
+    they.i_add_or_fill( prefill_nails, true, nullptr, nullptr,
+                        /*allow_drop=*/false, /*allow_wield=*/false );
+
+    const int inv_before = they.charges_of( itype_nail );
+    REQUIRE( inv_before > 0 );
+
+    // Prime inv_search_cache -- the bug only fires when cache entries exist
+    they.cache_has_item_with( itype_nail );
+
+    // Re-probe remaining capacity after prefill
+    int post_fill_remaining = INT_MAX;
+    they.can_stash_partial( probe_nail, post_fill_remaining );
+    const int can_still_fit = INT_MAX - post_fill_remaining;
+
+    // Place more nails on ground than remaining capacity
+    const int ground_count = can_still_fit + 50;
+    REQUIRE( can_still_fit > 0 );
+    REQUIRE( ground_count > can_still_fit );
+    const int expected_transferred = can_still_fit;
+    const int expected_remaining = ground_count - expected_transferred;
+
+    item ground_nails( itype_nail );
+    ground_nails.charges = ground_count;
+    here.add_item( ground, ground_nails );
+
+    const int ground_before = ground_charges_of( here, ground, itype_nail );
+    REQUIRE( ground_before == ground_count );
+
+    // Build pickup list
+    item_location nail_loc( map_cursor( they.pos_abs() ),
+                            &here.i_at( ground ).only_item() );
+    const drop_locations to_pick_up = { std::make_pair( nail_loc, 0 ) };
+
+    std::string dmsg = capture_debugmsg_during( [&]() {
+        they.pick_up( to_pick_up );
+        process_activity( they );
+    } );
+
+    const int inv_after = they.charges_of( itype_nail );
+    const int ground_after = ground_charges_of( here, ground, itype_nail );
+
+    CHECK( dmsg.empty() );
+    CHECK( inv_after == inv_before + expected_transferred );
+    CHECK( ground_after == expected_remaining );
+}
+