Basically, you have (99.9% likely) a dangling pointer. And you write at some point to memory you already freed, and that may have been allocated by something else. So you overwrite that "something else" with unexpected data.
"You write" => can include any write access by the RTL, triggered by any action of your code. E.g. a "Free" on a dangling object, will cause writes to memory.
As for does only happen with/without heaptrc. When using heaptrc, then extra memory is used (to keep info, for detecting mem leaks). So when you allocate mem, you get mem at different addresses. The address of your dangling pointer changes, and that can (by random chance) change if the overwritten memory is of any importance or not (e.g. it may still be unused, then it may be that nothing happens)...
Mind, that any change in your app (even if not related to the problem) can also have the effect of changing in which order memory is allocated. So if you make a change, the crash may go away. That does not mean the bug has gone.
If you can run your app on Linux (setup a virtual machine if you need) then you can use valgrind. And it will (almost guaranteed) tell you exactly which line(s) access dangling pointers/objects. And also where those were freed (which really helps).
Compile with "debug info for valgrind -gv" => This is important, as it changes the mem manager too / heaptrc must be off for this (don't worry valgrind finds the error, even if it does not crash).
valgrind --tool=memcheck ./yourapp
The output can be pasted into menu "view" > "leaks and traces", which allows to navigate to the correct source.