issue-04-attribstate-crashdumps.zip
Summary
SkillBar.LoadSkillTemplate() deterministically crashes the Guild Wars client. The client dies on ArenaNet's own assertion, not an access violation:
Assertion: attribState
P:\Code\Gw\Char\Cli\ChCliAttrib.cpp(212)
exception_code: 0x80000003 (breakpoint / assert)
Build: 38734
This is not timing-dependent, not framework-dependent, and not related to the template's validity. The bundled Nightfall Leveler hits it at the "Equip Skill Bar" step inside Craft_First_Weapon (right after the first attribute-point quest reward grants new skills), but the leveler is incidental -- a bare call reproduces it on its own.
Minimal reproduction (100%, five lines)
from Py4GWCoreLib import *
def main():
GLOBAL_CACHE.SkillBar.LoadSkillTemplate('OQBDAhITAoohAAAAAAAA')
ActionQueueManager().ProcessQueue("ACTION")
Run on a level-2 Mesmer standing idle in an outpost (Chahbek Village). No bot, no FSM, no other scripts. Client aborts immediately.
The Python stack contains nothing but this script:
#00 ActionQueue.py:45 in ActionQueue.execute_next
#01 ActionQueue.py:133 in ActionQueueNode.execute_next
#02 ActionQueue.py:171 in ActionQueueNode.ProcessQueue
#03 ActionQueue.py:255 in ActionQueueManager.ProcessQueue
#04 <string>:36 in main
Root cause (source-traced)
All paths below are in the Py4GW_Reforged_Native repo (not this one) -- src/GW/skillbar/skillbar_methods.cpp:
LoadSkillTemplate(temp) // :308
if (map::GetInstanceType() != Outpost) return false; // guard - ok
DecodeSkillTemplate(...) // succeeds
profession_state = GetAgentProfessionState(agent_id) // guard - ok, :322
if (!profession_state) return false;
LoadSkillbar(skill_template.skills, ...) // ok, :346
SetAttributes(skill_template.attributes, ...) // crash site, :347
SetAttributes(...) // :407
if (!g_load_attributes_func) return false; // only guard: pointer non-null
g_load_attributes_func(agent_id, count, ids, values); // raw call - no target-state check
g_load_attributes_func is a pattern-resolved raw function pointer into ArenaNet's client code. It is checked for existence only. Nothing validates that the attribute state is actually ready to accept a write.
Naming confirmation -- Py4GW_Reforged_Native/include/GW/context/world.h:230:
GW::GWArray<ProfessionState> party_profession_states; // ... used in skill window. aka attribStates
The struct is literally annotated attribStates, matching the assertion text. And note that GetAgentProfessionState() -- the guard the function does have at :322 -- only confirms an entry exists in that array. That is a different precondition from "the client's attribute-loading internals are ready for a write," and only the former is checked.
Relevant context: PySkillbar.Skillbar was migrated from a legacy ctypes exposure to a full native pybind class on 2026-07-04 (Py4GW_Reforged_Native/docs/python-migration-progress.md:329), so this is recently-wired code rather than a long-exercised path.
The design observation
LoadSkillTemplate returns bool. It guards the instance type and returns false. It guards the profession state and returns false. SetAttributes also returns bool and returns false on a null pointer.
Every precondition in this function has a guard and a clean failure path -- except the attribute write, which aborts the process instead of returning false. The failure channel already exists in the signature; it simply isn't wired to this case.
Eliminated, each by a direct test
| Suspect |
How it was ruled out |
| Timing / race |
Crash is byte-identical across a 2000 ms delay; the bare repro has no delay at all. 5+ runs, zero variance. |
| Botting framework / FSM |
Not present in the repro. |
| Nightfall Leveler |
Not present in the repro. |
| Attribute affordability |
Character has 5 unspent points; template needs 4. |
| Uninitialised attribute state |
Manually spent a point via the in-game panel first -- crashes identically. |
| Malformed template |
Decodes cleanly (Fast Casting 1 / Domination 2 / Inspiration 1; Ether Feast, Empathy, Resurrection Signet). Valid data. |
Same structural shape as issue #3
| Issue |
Raw function pointer |
Guard |
Failure |
| #3 |
g_get_graphics_renderer_value_func |
null-check only |
native AV (0xC0000005) |
| this issue |
g_load_attributes_func |
null-check only |
ArenaNet assert (0x80000003) |
Both are pattern-resolved pointers into client code, checked for existence but not for target-state readiness. #3 faulted silently; here the game caught it itself.
What we could not determine
We have not identified the specific internal precondition g_load_attributes_func requires. That lives in ArenaNet's client code and is outside what we can read. This report establishes where the guard is missing and that the failure is deterministic -- not the exact runtime condition that would satisfy the game's assert. That likely needs your knowledge of the client internals.
Environment
- Guild Wars Reforged, build 38734
Py4GW_Reforged, current main; Python 3.13.0 32-bit
- Crash dumps attached:
py4gw-20260713-1651-51840-23068 (bare-call repro) and py4gw-20260713-1659-5140-48728 (same repro, after manually spending an attribute point first -- identical crash)
issue-04-attribstate-crashdumps.zip
Summary
SkillBar.LoadSkillTemplate()deterministically crashes the Guild Wars client. The client dies on ArenaNet's own assertion, not an access violation:This is not timing-dependent, not framework-dependent, and not related to the template's validity. The bundled Nightfall Leveler hits it at the "Equip Skill Bar" step inside
Craft_First_Weapon(right after the first attribute-point quest reward grants new skills), but the leveler is incidental -- a bare call reproduces it on its own.Minimal reproduction (100%, five lines)
Run on a level-2 Mesmer standing idle in an outpost (Chahbek Village). No bot, no FSM, no other scripts. Client aborts immediately.
The Python stack contains nothing but this script:
Root cause (source-traced)
All paths below are in the
Py4GW_Reforged_Nativerepo (not this one) --src/GW/skillbar/skillbar_methods.cpp:g_load_attributes_funcis a pattern-resolved raw function pointer into ArenaNet's client code. It is checked for existence only. Nothing validates that the attribute state is actually ready to accept a write.Naming confirmation --
Py4GW_Reforged_Native/include/GW/context/world.h:230:The struct is literally annotated
attribStates, matching the assertion text. And note thatGetAgentProfessionState()-- the guard the function does have at:322-- only confirms an entry exists in that array. That is a different precondition from "the client's attribute-loading internals are ready for a write," and only the former is checked.Relevant context:
PySkillbar.Skillbarwas migrated from a legacy ctypes exposure to a full native pybind class on 2026-07-04 (Py4GW_Reforged_Native/docs/python-migration-progress.md:329), so this is recently-wired code rather than a long-exercised path.The design observation
LoadSkillTemplatereturnsbool. It guards the instance type and returnsfalse. It guards the profession state and returnsfalse.SetAttributesalso returnsbooland returnsfalseon a null pointer.Every precondition in this function has a guard and a clean failure path -- except the attribute write, which aborts the process instead of returning
false. The failure channel already exists in the signature; it simply isn't wired to this case.Eliminated, each by a direct test
Same structural shape as issue #3
g_get_graphics_renderer_value_func0xC0000005)g_load_attributes_func0x80000003)Both are pattern-resolved pointers into client code, checked for existence but not for target-state readiness. #3 faulted silently; here the game caught it itself.
What we could not determine
We have not identified the specific internal precondition
g_load_attributes_funcrequires. That lives in ArenaNet's client code and is outside what we can read. This report establishes where the guard is missing and that the failure is deterministic -- not the exact runtime condition that would satisfy the game's assert. That likely needs your knowledge of the client internals.Environment
Py4GW_Reforged, currentmain; Python 3.13.0 32-bitpy4gw-20260713-1651-51840-23068(bare-call repro) andpy4gw-20260713-1659-5140-48728(same repro, after manually spending an attribute point first -- identical crash)