12 KiB
Crusader USECODE Equipment System
Purpose
This note records the current evidence-backed read of Crusader's surviving equip / unequip system.
The short answer is: yes, a real inherited equipment-style event system survived into Crusader, but by this point it has been generalized far beyond RPG inventory handling. In Crusader, equip and unequip are standard item usecode events that many classes reuse for actor setup, turret arming, trap activation, alarm-state changes, and environmental control.
Short version
The funny interpretation is half true and half misleading.
What is true:
- Crusader still has first-class
equipandunequipitem events. - The live binary has dedicated intrinsics for them.
- The extracted USECODE corpus has a large number of slot
0x0A equipand slot0x0B unequipbodies. - Actor-like classes such as
NPCandMONSTERreally do implementequipbodies.
What is misleading:
- this is not yet proof of a fully intact Ultima-style paper-doll equipment subsystem
- many
equipbodies are plainly being used as generalized state-change hooks rather than literal "put item on actor" logic - alarms, sentries, guns, floors, conveyors, cameras, and hazard objects all reuse the same event vocabulary
Current best read:
Crusader inherited the Ultima 8 event names and event slots, then repurposed them into a broad object-activation vocabulary. The old RPG-flavored name equip survived, but its semantics widened into apply mode / arm / initialize / attach behavior / activate state.
Compiled-side proof
The live CRUSADER.EXE session already has named intrinsic handlers for these events:
10a0:2a35=Item_Equip10a0:2a68=Item_Unequip- nearby sibling
10a0:2b30=Item_EnterFastArea
The decompiler output for Item_Equip is the key proof:
word __cdecl16far Item_Equip(int *pitemno,uint dest)
{
res = Usecode_ItemCallEvent(pitemno,0x400,UC_Equip,...);
if (res != 0) {
return in_stack_0000fffc;
}
return 0;
}
Item_Unequip is parallel:
word __cdecl16far Item_Unequip(int *pitemno,int val)
{
res = Usecode_ItemCallEvent(pitemno,0x800,UC_Unequip,...);
if (res != 0) {
return in_stack_0000fffc;
}
return 0;
}
That proves several things immediately:
equipandunequipare real compiled-side usecode events, not parser inventions.- They are gated by dedicated owner-row capability masks:
0x400for equip and0x800for unequip. - The intrinsic does not directly manipulate an inventory table itself. It forwards into a generic item-event dispatcher.
The central dispatcher decompiles as Usecode_ItemCallEvent. Its current Ghidra decompile explicitly comments it as a generic owner-row mask gate:
- compute item usecode class id
- consult the owner-row table behind the runtime at
0x6611 - test the supplied capability mask
- if the row supports the event, create a usecode process for the requested event
That is the architectural core of the surviving system: equip and unequip are class capabilities, not hardwired game-engine special cases.
Cross-check against the engine source lineage
ScummVM's Ultima8/Crusader code preserves the same event interpretation.
In engines/ultima/ultima8/world/item.cpp:
callUsecodeEvent_equipWithParam(param)is explicitly documented as eventAcallUsecodeEvent_unequipWithParam(param)is explicitly documented as eventB
That source also shows the event neighborhood:
- event
9= release - event
A= equip - event
B= unequip - event
C= combine - event
E= called from anim - event
F= enter fast area - event
10= leave fast area - event
11= cast
This strongly supports the idea that Crusader inherited an Ultima-style item event vocabulary wholesale and kept using it even after the gameplay moved away from a classic RPG.
How widespread it is in the extracted corpus
The current exported pseudocode corpus contains:
77slot0x0A equipentries50slot0x0B unequipentries
That is far too widespread to be accidental or restricted to a tiny one-off subsystem.
The distribution is also telling:
- actor/NPC classes:
NPC,MONSTER - turret/weapon classes:
BASEGUN,SENTRY,GATGUN*,WALGUN*,GOVGUN* - alarm/hazard/environment classes:
ALARMBOX,ALARMHAT,FFFLOOR,FLAMEBOX,STEAMBOX - cameras and movers:
CAM_*,EYECAM*,CONVEY_*,ROLL_*,HOVER*
So the names survived as a general event interface across many object families.
What equip means in practice
1. General activation or state application
Many non-actor classes use equip exactly the way a modern engine might use activate, arm, enable, or set mode.
Representative example: BASEGUN::equip
set_info(0x0211, *(arg_06));
process_exclude();
spawn class_0A1A_slot_24(pid, arg_0A, ..., arg_06);
suspend;
Parallel example: SENTRY::equip
Its exported pseudocode is the same structural pattern as BASEGUN::equip, and SENTRY::unequip mirrors the same shutdown logic as BASEGUN::unequip.
That is not wearable-item behavior. It is a clean arm/disarm lifecycle.
2. Alarm-state or environmental mode switching
ALARMBOX::equip and ALARMHAT::equip are good examples of the generalized meaning.
They both begin with:
set_info(0x0211, *(arg_06));
process_exclude();
Then they branch by frame/state and spawn or equip helper objects in specific response modes.
ALARMHAT is especially illustrative because it calls Item::I_equip(pid, 0x17, item) and Item::I_equip(pid, 0x15, item) on nearby shape 0x04D0 helper objects. That is exactly where the older RPG-flavored naming sounds absurdly literal, but the behavior is really push nearby helper into alarm response mode.
3. NPC and monster setup
Actor-facing classes really do implement equip, but the current evidence still points to scripted setup and state application rather than visible inventory slot management.
Representative example: NPC::equip
The exported body does not look like a paper-doll or inventory routine. It looks like a setup dispatcher keyed by arg_0A:
arg_0A == 10: reset, teleport to egg, gather several values, create or legalize the NPC/item context, then spawnclass_0A11_slot_29(...)arg_0A == 30: read several values withIntrinsic00DF(...)arg_0A == 31: suspendarg_0A == 1/2/3: explicit but empty branches
That reads much more like apply initialization mode or run NPC setup variant than equip helmet.
Representative example: MONSTER::equip
MONSTER::equip checks frame/state, stamps 0x0211, then spawns class_0A1E_slot_2D(pid, var, monster1, arg_06) for a range of mode values. Again, that looks like a dispatcher for monster behavior setup or activation, not a visible inventory pane.
What unequip means in practice
unequip is the paired shutdown or detachment event.
Representative example: BASEGUN::unequip
set_info(0x0212, *(arg_06));
process_exclude();
if (Item.getStatus(arg_06) & 4) {
spawn class_0A1A_slot_27(arg_06);
}
SENTRY::unequip is the same pattern.
That strongly suggests unequip has become the general reverse transition for classes that use equip as arm or enable.
Environmental example: FFFLOOR::unequip
This body does not resemble inventory logic at all. It scans nearby family-6 objects, filters by overlap, and then spawns an actor-facing helper class_0A11_slot_2D(retval, *(arg_06), item) before suspending.
So by the time we reach Crusader, unequip has clearly broadened into deactivate / detach / reverse state / cleanup side effects.
Concrete map-side example: map 13 fixed:4767
The recent map-13 wall-jump follow-up produced a useful grounded example of what FFFLOOR looks like in the exported scene cache.
- Retail decoded scene entry
fixed:4767isitem:12469:fixed:309:0:47966:53598:97. - Reference data identifies shape
0x0135/shape:309asterrainwith dimensions4 x 4 x 0and traitssolid,fixed, andland. - The extracted EUSECODE corpus identifies class
0x0135asFFFLOOR, with livegotHit,equip, andunequiphandlers.
The important part is the behavior, not the raw shape flags:
FFFLOOR::gotHitloops while an actor remains on the tile and repeatedly callsFREE.slot_20(pid, 8)followed byNPC.slot_2d(...).NPC.slot_2dis not a teleport or pure logic stub in this lane; it resolves into the normal actor hit/damage path and can end inItem.receiveHit(...).FFFLOOR::equipcases29and30also toggle nearby0x0135tiles between frame1and frame0, and case30explicitlytouches those tiles after restoring frame0.
This makes the safest current interpretation:
FFFLOORis a gameplay-side environmental hazard / sensor floor family.- The suspicious map-13 tile near the wall-jump setup is therefore better read as
trap or trigger floor on the same upper platformthan ashidden collision override for the wall itself.
That same map-side follow-up also found a nearby family-4 egg on the same upper platform, fixed:4770 (shape 17, egg id 37, subtype selector QLo 4), which resolves to CHANGER in retail Remorse.
That companion egg is now behaviorally useful too:
- the extracted
CHANGER::hatchbody reads the egg id frommapNum, scans nearbyroofitems, compares each roof's low quality byte against that egg id, and destroys matching roofs - the local decoded map-13 scene contains nearby roof placements (
shape:538, kindroof) whosequality & 0xff = 37, matching the egg id fromfixed:4770
So the current best local read is hazard/sensor floor plus keyed roof-destruction trigger cluster, not single suspicious floor tile beside a special-cased wall.
What survived from the RPG ancestor
The surviving part is not just the word choice. The system architecture survived too:
- item classes advertise event capabilities through owner-row mask bits
- the engine exposes generic intrinsics that dispatch into class-specific usecode handlers
- the event numbers remain organized in an Ultima-style item-event vocabulary
- actor and item classes both participate in the same event model
That is exactly the kind of subsystem that makes sense as an inherited RPG engine layer which later got repurposed for an action game.
What we do not have yet
We do not yet have proof of a fully intact classic RPG equipment layout such as:
- fixed visible paper-doll slots
- a clean actor inventory table indexed by helmet/armor/weapon slots
- universal actor gear attachment semantics across all NPCs
The current evidence is stronger for:
- an inherited event vocabulary with
equip/unequip - class-specific scripted setup and mode application
- actor and environmental reuse of the same event names
So the safe statement is:
Crusader definitely has a hidden equipment event system.
Crusader may also retain deeper actor-equipment semantics under that layer, but that is not yet proven from the current pass.
Current best model
The most defensible current model is:
- Ultima-style item events
A = equipandB = unequipsurvived into Crusader. - The compiled engine still dispatches them through generic item-event intrinsics.
- Owner-row capability bits decide which classes support those events.
- USECODE then interprets
equipandunequipper class. - In Crusader, many classes reinterpret those events as
activate/deactivate,arm/disarm,initialize/teardown, orswitch mode, while actor-facing classes also use them for setup-like behavior.
Good follow-up targets
The best next places to deepen this are:
- compare more actor-facing equip bodies (
NPC,MONSTER,ANDROID,AVATAR) against each other - find compiled-side consumers of the value passed as the
equipparameter to see whether it maps to actor slots, modes, or stance/state enums - recover more names around the helper slots repeatedly spawned from equip bodies, especially the
0A11,0A1A, and0A1Efamilies - check whether any actor-only classes expose both equip and unequip in ways that look like real gear attach/detach rather than generic initialization
- decode more loop-selector idioms so actor/environment equip bodies become less VM-shaped
- cross-check ScummVM Crusader actor/inventory code for surviving slot semantics that may still map to the retail binary