Game Structures¶
Reversed class layouts and offsets, cited against Ghidra addresses on the SC6 Steam build
(image base 0x140000000).
Struct index¶
Alphabetical jump table. Click through for full layout.
Battle / hit-resolution¶
| Struct | Size | Purpose |
|---|---|---|
ALuxBattleChara |
0x568 |
One fighter on stage. Three KHit list heads at +0x44478/+0x44498/+0x444B8. |
ALuxBattleManager |
(large) | Per-match actor; player chara list, axis input, config tree at +0x50. |
ALuxBattleFrameInputLog |
~0x44D0 |
Input record/replay actor; embedded per-slot input cache at +0x3C0, inbound-packet deque at +0x4480. |
FLuxReplayInputCacheEntry |
0x10 |
One slot in pReplayInputCache_at0x3C0. |
ALuxBattleKeyRecorder |
0x3BC |
Training-mode "Recorded" input playback. |
ALuxBattleReplayPlayer |
0x3D1 |
Replay playback actor. |
ALuxTraceManager |
0x408 |
Visual-only weapon-trail driver. |
FActiveAttackSlot |
0x44 |
Per-tag active-attack hash slot at chara+0x3D0. |
FKHitNodeBase / FLuxKHitNode |
Sphere 0x80; Area/FixArea 0xA0 |
KHit linked-list node — shared header plus subclass tails. |
FLuxBattleChara (Ghidra type) |
0x973F0 |
Big-struct view of a fighter's runtime state — same entity as ALuxBattleChara, with wider field coverage. (Previously documented as 0x97330; the Ghidra struct now reports 0x973F0 = 619504 bytes.) |
FLuxBattleCharaVisibilityFlags |
0x07 |
7-byte mesh visibility bitfield. |
FLuxBattleVMFreezeRecord |
0x40 |
Slow-motion / VM-freeze blend state. |
LuxBattleCharaMotionFlags |
0x40 |
64-byte motion-input flag bank at chara+0x16D0..+0x170F. Holds the guard-stance bytes (+0x02 base, +0x2C alt, +0x31 alt-locked) and other state flags. |
FLuxCapsule |
0x50 |
Trace-system capsule endpoint pair (visual). |
FLuxCapsuleContainer / FLuxMoveProvider_CapsuleSlot |
0x40 |
TArray-shaped wrapper around FLuxCapsule*. |
FLuxDamageInfo |
0x12 |
HUD/network hit-event payload. |
FTraceActiveParam |
0x30 |
Param to ALuxBattleChara::Active (only +0x00 AttackTag matters). |
FTraceInactiveParam |
0x08 |
Param to ALuxBattleChara::Inactive. |
ULuxTraceComponent |
0x4B0 |
Visual trail renderer. |
ULuxTraceDataAsset |
0x80 |
Trace-parts data asset (with three dead bDebugDrawTrace* bools). |
Move VM¶
| Struct | Size | Purpose |
|---|---|---|
FLuxMoveCommandPlayer |
0x302C |
Per-chara VM context (the "slot" indexed by g_LuxMoveVM_CommandPlayerArray). |
FLuxMoveVM_OpcodeScratch |
0x60 |
96-byte view over vmCtx+0x26AC..+0x26E4 (the per-opcode scratch). |
FLuxMoveVM_ATKPayload |
0x10 |
4-uint32 (power, range, speed, dir_mask) tuple. |
FLuxMoveBank |
0x30 |
Per-character move-bank header at chara+0x455C0. Bucket headers + sub-table offsets. |
FLuxMoveBankSlotView |
0x48 |
View into a slot record returned by ResolveBankSlot. Holds the variant→cell-bone-id table. |
FLuxMoveBankCell |
0x70 |
One row of the per-character move bank. The "attack cell" the hit pipeline consumes. |
FLuxMoveDefEntry |
0x10 |
One entry in the FMoveDef array (table indexed by current move id). +0x02 = StartCellCursor. |
FLuxMoveSchedState |
0x60 |
Dual-slot move scheduler. |
FLuxMoveStartRequest |
0x6C |
"Queue this move" request. |
FLuxMoveSubFrameRecord |
0x48 |
60→120 Hz sub-frame interpolation record. |
FLuxMoveLane |
0x468 |
Per-lane VM state; three lanes per chara at +0x444F0/+0x44958/+0x44DC0. Holds nNextMoveID_5A (immediate-transition target), flTransitionThreshold_68, aEffectOpTable_1F4[16]. |
FLuxMoveLane_EffectOp |
0x24 |
One entry of the lane's 16-slot effect-op table; (triggerFrame, argc, fired, args[16]). |
LuxMoveLaneState_Motion |
0x44 |
Small per-motion-slot playback record (Ghidra type FLuxMoveLaneState). Pointed to from the two motion slots inside FLuxMoveLane. |
FLuxAttackTouchParam |
0x1D |
Hit-registered struct. |
FLuxBattleMoveListTableRow |
0x88 |
UI move-list row. |
Camera¶
| Struct | Size | Purpose |
|---|---|---|
APlayerCameraManager POV |
(engine class) | The render-source. POV at +0x410..+0x44B. |
ALuxBattleCamera_PoseFields |
0x22 |
Packed pose snapshot. |
FCameraCacheEntry_PCM_0x400 |
0x37 |
PCM-style cache entry, customised. |
FLuxBattleCameraInternalPOV |
0x38 |
Internal POV transform. |
FLuxCameraAction |
0x2E4 |
Camera action snapshot. |
Engine / line drawing¶
| Struct | Size | Purpose |
|---|---|---|
UWorld |
(engine) | +0x40/+0x48/+0x50 line batchers; +0xF0 AuthorityGameMode; +0x140 OwningGameInstance. |
ULineBatchComponent |
0x850 |
Three append arrays at +0x808/+0x818/+0x830. |
FBatchedLine |
0x34 |
Single line entry. |
TArrayHeader |
0x10 |
UE4 {Data, Num, Max} triple. |
Stage geometry¶
| Struct | Size | Purpose |
|---|---|---|
LuxBattleRoundSnapshotPayload_Partial |
0xC0 |
Structured round-restore payload at 0x144844070; formerly misidentified as 12 barrier segments. |
FLuxBattleEventRecord |
0x18 |
Event packet used by stage barrier/wall registration event class 0x19. |
ULuxStageAssetPaths |
class | Per-stage content-side asset list. RawAssets names hit-data/camera raw assets. |
LuxStageRawAsset |
0x18 |
{ELuxStageAssetType Type; FString Path;} entry in ULuxStageAssetPaths.RawAssets. |
LuxStageSetting |
0x03 |
Three content-side cosmetic flags: anomaly VFX, wet, breath. |
LuxBattle_FrameCacheHitChkDataSetup |
0x22 |
Setup packet that seeds the A/B J_StgHitChkData* globals. |
J_StgHitChkData_* |
variable | Serialized legacy terrain/wall collision blob expanded into frame-bounds grids. |
| Frame-bounds grid | 0x44C |
Runtime terrain/wall spatial acceleration embedded in each A/B transform context. |
FLuxFrameBoundsCellBucket_Partial |
0x30 |
Three terrain Z-bucket lists for one X cell. |
FLuxTerrainTriangleEntry |
0x40 |
Triangle entry with pre-baked plane equation. |
ALuxBattleStage |
0x3a0 |
Per-stage root actor; loaded from /Game/Stage/<code>/Maps/<code>.umap. Owns one ALuxBattleStageActorManager. |
ALuxBattleStageActorManager |
0x420 |
Manages 9 stage actor/object arrays from +0x388..+0x408: StageActor, WolfCharacter, BreakableWall, Barrier, HideableMesh, VisibilitySwitcher, StageMob, StageMesh, CuttableStageMesh. Populated by LuxActor_CollectActors_By8Classes_IntoTArrays @ 0x140417a70. See Stage System. |
ALuxStageMeshActor |
0x3d0 |
Visible stage mesh actor with ordinary UE4 ULuxStageMeshComponent/UBodySetup collision; visual/camera/particle layer, not deterministic scbattle terrain by itself. |
ALuxStageHideableMeshActor |
0x3b0 |
Stage mesh actor that can be hidden/faded by camera, LOD, or stage-state conditions. |
ALuxStageVisibilitySwitcher |
0x3d0 |
Stage-state visibility controller used by streamed BG/VFX/gimmick sublevels. Reflected class string is LuxStageVisibilitySwitcher. |
ALuxStageCuttableMeshBase |
0x460 |
Optional weapon-overlap sliceable mesh actor with procedural cut mesh, particles/SE, and MinCuttableDistance; no Soul Charge gate observed in the native slice path. |
ALuxStageWolfCharacter |
0x7a0 |
Optional background creature/animal actor bucket used by WolfCharacterList; no exact stock .umap usage observed in the current dump. |
ALuxStageBreakableBarrierActor |
0x4f0 |
Breakable barrier actor registered through stage event class 0x19; its component bounds are presentation geometry, while deterministic terrain/wall/ring queries use the frame-bounds grid. |
ALuxStageBreakableWallActor |
0x480 |
Visible breakable walls (Soul Charge wall-break geometry). Standard UE4 BodySetup collision. |
FBattleStageEnumEntry |
0x20 |
One row in the master stage roster: {FString DisplayLocId; FString StageCode;}. 31 stock entries at g_LuxStage_MasterEnumStringTable @ 0x144149c50. |
LuxBattleStageInfoTableRow |
0x108 |
UDataTable row type; per-stage round-position config (Center, RingEdge, Wall, OptionalCenters). See Stage System. |
LuxBattleStageBasePositionParam |
0x28 |
Inner element of LuxBattleStageInfoTableRow arrays — {FVector Position; FRotator Rotation; float DistanceOffset; TArray<int32> RoundNumbers;}. |
| round-restore globals | 0xC4 |
Valid flag at 0x14484406C followed by the structured 0xC0 snapshot payload at 0x144844070. See Stage System. |
Misc / discovered-but-uncategorised¶
ALuxBattleManager_Partial, ALuxBattleManager_PropertyLayout, FLinearColor, FMatrix64,
FTransform64, FVector, FLuxBattleKeyRecorderSlot, FLuxRecordedFrame,
FLuxTraceManagerLayout, FLuxTraceComponentLayout, FLuxDataTablePath,
FLuxBattleOptionParam. See the
Other reversed structs section.
Net / Steam (unrelated to gameplay)¶
FNamedOnlineSession_Steam, FOnlinePresenceSteam, FOnlineSessionInfoSteam,
FFriendStateRecord. See Net / Steam structs.
Template for a new entry¶
### <ClassOrStruct name>
- **Path**: `/Script/<Module>.<Class>`
- **Size**: 0x???
- **Discovered via**: <Ghidra xref / runtime probe / native hook / ...>
| Offset | Type | Name | Notes |
|-------:|------|------|-------|
| 0x00 | FName | Id | |
Battle manager¶
ALuxBattleManager¶
- Path:
/Script/LuxorGame.LuxBattleManager - Discovered via:
ALuxBattleManager__StaticClass @ 0x140947390,ALuxBattleManager::Update_Impl @ 0x140437590,ALuxBattleManager::PlayMove_Impl @ 0x140429840
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x050 | FLuxDataTable |
ConfigTable | round / timer / per-player settings tree |
| +0x098 | UObject* |
GameState | isa-checked against ALuxBattleManager every tick |
| +0x388 | UClass* |
BattleCharaClass | TSubclassOf<ALuxBattleChara> per Z_Construct_UClass_ALuxBattleManager @ 0x140949450. Not an embedded chara. An earlier pass of these docs called it "SubChara", but a 0x568-byte embedded chara starting here would collide with the Camera/EventListener slots at +0x3A8..+0x408 in the runtime-verified subsystem map (battle-manager.md). |
| +0x390 | ALuxBattleChara** |
PlayerCharas.Data | TArray<ALuxBattleChara*> — iterate PlayerCharas[0..NumPlayerCharas-1] for all active fighters |
| +0x398 | int32 |
NumPlayerCharas | |
| +0x3A0 | uint8 |
PendingMoveCommandType | 1 = PlayMove, 2 = Stop |
| +0x3A8 | int32 |
PendingMoveCommandParam | player index |
| +0x3B0 | FLuxMoveCommandData |
PendingMoveCommandData | pending move-dispatch payload. StopMove zeroes a 0x18 local then copies it in, so the visible footprint is at least 0x18 bytes (exact size unconfirmed). |
| +0x3E0 | bool |
SavedLuxorPhotographyAllowed | PlayMove caches LuxPhotography::IsLuxorAllowed() here and clears the CVar; StopMove restores the cached value. Gameplay has nothing to do with it — this slot only exists to keep Photography Mode from capturing through scripted move dispatches. |
| +0x400 | float* |
AxisValues | dynamic float array |
| +0x408 | int32 |
AxisCount | |
| +0x410 | uint8* |
AxisInhibitFlags | dynamic byte array |
| +0x418 | int32 |
AxisInhibitCount | |
| +0x420 | float |
AxisXAccumulator | per-tick decay |
| +0x424 | float |
AxisYAccumulator | per-tick decay |
| +0x508 | UObject* |
(axis-consumer; walked by Update) |
|
| +0x12F3 | bool |
GlobalAxisInhibit | when set, zeroes every axis this tick |
See Battle Manager & DataTable Config Tree for the UFunction map and the hierarchical config-tree path convention.
Trace / hitbox system¶
ALuxBattleChara¶
- Path:
/Script/LuxorGame.LuxBattleChara - Size: 0x568 (1384)
- Class CRC:
0x5BDCD706 - Registered via: short-form
UE4_RegisterClass(no property builder for parameter metadata) - Discovered via:
ALuxBattleChara__StaticClass @ 0x14015EA40
Layout verified at runtime on the Steam build (2026-04-19)
The field table below was corrected against the actual running
binary using runtime class-name introspection on a live chara.
Earlier versions of this page described chara+0x388 as the
ULuxBattleMoveProvider pointer — that is wrong on the shipping
Steam build. +0x388 and +0x390 are actually the CharaMesh0
and WeaponMesh0 USkeletalMeshComponent*s, stored by
ALuxCharaActorBase_Constructor @ 0x140440FB0 as
param_1[0x71] / param_1[0x72]. This build has no per-chara
ULuxBattleMoveProvider.
The real opponent pointer is chara+0x973E8 (not +0x390),
identified by LuxMoveVM_CheckRangeOrDistance @ 0x140365140
dereferencing that field to read the opponent's world-space
position.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x6C | int16 |
MoveClassA | SC6 move category (5..12) — HorizontalAttack, VerticalAttack, Kick, Throw, etc. |
| +0x6E | uint16 |
MoveClassB | subclass |
| +0x70 | int32 |
MoveFlags | |
| +0x90 | float |
flVelocityZ | Per-tick movement velocity Z. Cleared by LuxBattle_PositionCharasSymmetrically @ 0x140302670 on round start (pChara->flVelocityZ_at0x90 = 0.0). |
| +0x94 | float |
flVelocityX | Per-tick movement velocity X. Set by LuxBattle_PositionCharasSymmetrically to extraout_XMM0_Da / g_LuxMoveVM_AngleSinScale (the chara's facing-yaw projected onto a per-tick contribution). |
| +0x98 | float or ALuxBattleManager* |
flVelocityY / BattleManager | Aliased slot — readers disagree, and both interpretations are attested. LuxBattle_PositionCharasSymmetrically writes 0.0f here as a 4-byte float (flVelocityY_at0x98). ALuxBattleChara_SetupWeaponBones @ 0x1403CAEB0 reads param_1[0x13] (= +0x98) as an 8-byte ALuxBattleManager*, ISA-checks the dereferenced UClass against ALuxBattleManager, then passes it to LuxAnimInstance_SetBattleManager. The Ghidra struct ALuxBattleChara_Partial types the slot as float. The two readers are mutually consistent only if the BattleManager pointer's low 4 bytes are float-0.0 (0x00000000) — possible if the pointer happens to be aligned so bytes [0x98..0x9B] are all zero, but that would be coincidence, not the canonical layout. Treat this slot as ambiguous until disassembly resolves which reader has the wrong offset. |
| +0xA0 | float |
flStartPosX | Round-spawn target X. Written by LuxBattleChara_SetStartPosition @ 0x140301E60. |
| +0xA4 | float |
flStartPosY | Round-spawn target Y (Y is plane-up in SC6's world). |
| +0xA8 | float |
flStartPosZ | Round-spawn target Z. Same float read by LuxMoveVM_CheckRangeOrDistance as SelfPos.Z for ring-distance checks (start and current Z are equal at the round-spawn instant). |
| +0xC0 | float |
flCurPosX | Game-thread current world-pose X. Updated each tick by LuxBattleChara_IntegratePhysics_PerTick. |
| +0xC4 | float |
flCurPosY | |
| +0xC8 | float |
flCurPosZ | |
| +0x168 | USceneComponent* |
CustomRoot0 | stored by ALuxCharaActorBase_Constructor param_1[0x2D] |
| +0x23C | uint8 |
CharaKindByte | row index into g_LuxCharaAttrTable_* |
| +0x250 | uint16 |
MoveSubclassAlt | alternative move-category byte (checked ==100 / 0x69 by IF predicates) |
| +0x388 | USkeletalMeshComponent* |
CharaMesh0 | NOT MoveProvider. Set by ALuxCharaActorBase_Constructor param_1[0x71]. |
| +0x390 | USkeletalMeshComponent* |
WeaponMesh0 | NOT Opponent. Set by ALuxCharaActorBase_Constructor param_1[0x72]. |
| +0x398 | float |
MaegamiL_Pos | Maegami-L (front-hair-L) position; ctor init 0 |
| +0x39C | float |
MaegamiR_Pos | Maegami-R position; ctor init 0 |
| +0x3A0 | int32 |
PlayerIndex | ctor init -1; UPROPERTY |
| +0x3A4 | int32 |
CharaID | ctor init -1; doubles as SoulChargeMode at runtime |
| +0x3A8 | int32 |
WeaponID | NOT a TraceComponent pointer (earlier docs claimed ULuxTraceComponent* here — that was wrong). Ctor init -1; doubles as WeaponTypeCode at runtime. The ctor writes *(uint32*)(param_1+0x3A8) = 0xFFFFFFFF, so this is clearly a 4-byte int, not an 8-byte pointer. |
| +0x3B0 | TArray<USkeletalMeshComponent*> |
CreationComponents | UPROPERTY Instanced; .Data/.Num/.Max at +0x3B0/+0x3B8/+0x3BC. Earlier docs described an "active-attack slot hash" here with stride 0x44; the constructor initialises this block as CreationComponents, so the slot-hash interpretation was wrong. |
| +0x3C0 | USkeletalMeshComponent* |
MaegamiL_SkeletalMeshComponent | UPROPERTY Instanced |
| +0x3C8 | USkeletalMeshComponent* |
MaegamiR_SkeletalMeshComponent | UPROPERTY Instanced |
| +0x3D0..+0x3E7 | bytes + float | Maegami / anim-side state | anim-side bytes, phase-trigger flag, phase timer |
| +0x3E8 | TArray<ALuxCharaAppxActor*> |
AppxActors | .Data/.Num/.Max at +0x3E8/+0x3F0/+0x3F4 |
| +0x3F8..+0x447 | TSet/TMap (0x50) |
AppxAnimInstanceMap | standard UE4 TSet layout — sparse array at +0x3F8..+0x42F, hash extension at +0x430..+0x447 |
| +0x448 | UBoxComponent* |
CollisionComponent | type is UBoxComponent*, not UShapeComponent* (the FName used at construction is "TestCollision" but the field is named CollisionComponent). Stored by ALuxBattleChara_Constructor param_1[0x89]. UPROPERTY Instanced. |
| +0x450 | uint32 |
BreakFlag | UPROPERTY EditAnywhere; cleared by vtable[201] ResetBreakAndAttackState |
| +0x458 | ALuxTraceManager* |
TraceManager | visual-only — drives the weapon-trail / FX. Not part of hit resolution. |
| +0x460 | ULuxBattlePlayerSetup* |
PlayerSetupOverride | UPROPERTY; weapon-data cache pointer |
| +0x468 | bool |
bDummyPlayer | UPROPERTY, ctor init false |
| +0x469 | bool |
bEvilFlag | controls MoveProvider sub-object selection |
| +0x470..+0x4BF | TSet (0x50) |
EntityTracking | internal non-UPROPERTY |
| +0x4C0..+0x50F | TSet (0x50) |
(unnamed internal) | non-UPROPERTY |
| +0x510 | TArray<ELuxPartsSE> |
SEMaterials | UPROPERTY; .Data/.Num/.Max at +0x510/+0x518/+0x51C |
| +0x520 | TArray<FWeakObjectPtr+...> |
(internal anim-inst array) | stride 16B |
| +0x530..+0x537 | 8 bytes | setup/phase flag block | see constructor plate for per-byte meaning |
| +0x538 | bool |
IsSetupCompleted | ctor init false; cleared by TearDown |
| +0x548 | TArray<FLuxPermanentEffectRuntime> |
PermanentEffects | .Data/.Num/.Max at +0x548/+0x550/+0x554 |
| +0x558 | TSharedPtr<BoneDB>.DataPtr |
(bone-DB cache) | |
| +0x560 | TSharedPtr<BoneDB>.RefCtrl |
(bone-DB cache) | |
| +0x568 | — | end of object | sizeof(ALuxBattleChara) = 0x568 |
| +0x29130 | void* |
pSubcompListHead | Sub-component linked-list head walked by LuxBattleChara_SetStartPosition @ 0x140301E60. Each node has next at +0x40, an inner array pointer at +0x48, and a count byte at +0x50; the inner walk only writes a 16-bit zero at each item's +0x7C. Useful as a "chara fully constructed" sentinel: when null, the chara is mid-rebuild (e.g. immediately after a character swap), and SetStartPosition will walk the list and crash on a null deref (raising the 0xE06D7363 unhandled C++ exception). HorseMod's ResetOverride validates this slot before invoking the engine's teleport helper. |
| +0x1438 | UObject* |
cached MoveComponent | Lazy cache filled by ALuxBattleChara_GetMoveProvider @ 0x1403F00B0. Chain: chara->GetWorld()->OwningGameInstance (world+0x140) → ISA-check ULuxGameInstance → *(ULuxGameInstance+0x140). On this build the final read returns the IEEE 754 value 0x3F800000 (float 1.0f), not a pointer, so the cache stays at sentinel (0xFFFFFFFF_FFFFFFFF) indefinitely. The misnamed Ghidra symbol ALuxBattleManager_GetMoveProviderPtr @ 0x140546600 actually operates on a ULuxGameInstance*, not an ALuxBattleManager*. Note that +0x1438 is past the 0x568 class size: either the lazy cache lives in a separately-allocated extension, or 0x568 understates the actual live size. Under active investigation. |
| +0x1463 | uint8 |
current move-state byte | set by ALuxBattleManager_SetMoveState @ 0x1403F8370; 5=playing, 6=stopping |
| +0x1982 | uint16 |
CurrentNotifToken | read by VM IF-predicate families A/B/C |
| +0x19FE | uint16 |
MoveSubclass | read by BuildMoveClassPair |
| +0x19F0..+0x1A64 | condition-flag ring | cleared by VM opcode start! (0x50001); read by IF-subject 0x60007..0x60058 |
|
| +0x2B4A4 | int32 |
MoveStateId | looked up in g_LuxMoveStateTable @ 0x1440F4750 (0xF / 7 / 0x1C / … ) |
| +0x973E8 | ALuxBattleChara* |
Opponent | direct pointer to the other player's chara — used by LuxMoveVM_CheckRangeOrDistance / LuxMoveVM_CheckAngleOrGeometry |
source: Ghidra reversing of
ALuxBattleChara_Constructor @ 0x1403AB8D0,ALuxCharaActorBase_Constructor @ 0x140440FB0,ALuxBattleChara_Active_Impl @ 0x1408CD940,ALuxBattleChara_Inactive_Impl @ 0x1408D1420,LuxMoveVM_CheckRangeOrDistance @ 0x140365140. Runtime class-name introspection on both live charas in a training match confirmed+0x388and+0x390are SkeletalMeshComponents on this Steam build.
ULuxBattleMoveProvider on this build
Searches for Z_Construct_UClass_ULuxBattleMoveProvider* and for any
literal "LuxBattleMoveProvider" / "LuxBattleMoveComponent"
string in the shipping binary return zero hits — neither name
survives as a UClass registration. The class was renamed,
dropped, or folded into another type somewhere between the earlier dev
snapshot the older docs described and the 2026-04-19 Steam build.
What's actually reachable — the four move-related slots on
ALuxBattleManager (no UE4 reflection metadata for any of these
because ALuxBattleManager is registered short-form via
UE4_RegisterClassEx with no UPROPERTY list — see
Z_Construct_UClass_ALuxBattleManager @ 0x140949450):
| Slot | Inferred class | Notes |
|---|---|---|
BM+0x140 |
UObject* (class unknown) |
Read by the misnamed ALuxBattleManager_GetMoveProviderPtr @ 0x140546600, which — despite the name — takes a ULuxGameInstance*. Lazy-cached on every chara at chara+0x1438 by ALuxBattleChara_GetMoveProvider @ 0x1403F00B0. |
BM+0x1450 |
UObject* (TSharedPtr Target) |
Read by LuxMoveProviderRef_Get @ 0x14045FC70, which ISA-checks world+0x98 against ALuxBattleManager, then fetches (*BM+0x1450, *BM+0x1458) as a TSharedPtr-style pair. On the target, vtable[0x10] is IsValid, vtable[0x100] returns the default sub-provider, and vtable[0xE0] returns an indexed sub-provider. Also referenced by ALuxBattleChara vtable slots 208 (GetWeaponData) and 210/211 (GetBoneDataSharedPtr); its role is ambiguous between a MoveProvider and a unified weapon/bone/move data asset. |
BM+0x1458 |
ref-count control block | Second half of the TSharedPtr pair at BM+0x1450. |
BM+0x4C0 |
ALuxBattleMoveCommandPlayer* |
The one slot whose class IS known — registered name "BattleMoveCommandPlayer" via Z_Construct_UClass_ALuxBattleMoveCommandPlayer @ 0x140953780. This is the command-script VM actor that runs move bytecode; see Move System. The per-move capsule data is believed to live inside this object, but its exact FLuxCapsuleContainer offset is still being located. |
Stale call paths to avoid — these functions were written for an
older layout where chara+0x388 held a MoveProvider pointer. On
this build chara+0x388 is CharaMesh0, so they produce garbage
or silently fail:
ALuxTraceManager_GetTracePosition_Impl @ 0x1408D0BB0(Ghidra-renamed; wasALuxBattleChara_GetTracePosition_Impl) readsthis+0x388 → +0x30 → +0x30 / +0x38expecting theFLuxCapsuleContainerchain, but instead walks aUSkeletalMeshComponent.LuxMoveProviderRef_Get @ 0x14045FC70andLuxMoveProviderRef_GetSubProvider @ 0x140467FE0— callers passchara+0x388as the "context" pair, and these functions invoke(*(param_1))->vtable[0]on it to "get world". On a SkeletalMeshComponent,vtable[0]is the destructor, so the ISA-check atworld+0x98 -> ALuxBattleManagerfalls through with garbage and the functions early-out. Their many callers inLuxMove_CheckConditions_Type0 @ 0x1403A66F0throughLuxMove_CheckConditions_Type1 @ 0x1403AAA60(~20 adjacent functions) are effectively dead code on this build.
The live move pipeline routes through ALuxBattleMoveCommandPlayer
and the raw BM+0x1450 TSharedPtr, not through the chara's
+0x388 slot.
ALuxTraceManager¶
- Path:
/Script/LuxorGame.LuxTraceManager - Size: 0x408
- Role: drives the visual weapon trail / particle FX for one chara. Despite the name, this
actor has nothing to do with hit resolution — live hit detection uses KHit linked-list nodes
documented in Hitbox System. Every field on this class is visual state: two
particle components, the trail-rendering
ULuxTraceComponent, and aKindIndexthat picks the visual style.
| Offset | Type | Name |
|---|---|---|
| +0x388 | ULuxTraceDataAsset* |
TraceDataAsset |
| +0x398 | UParticleSystemComponent* |
EffectSlotA |
| +0x3A0 | UParticleSystemComponent* |
EffectSlotB |
| +0x3A8 | ULuxTraceComponent* |
TraceComponent |
| +0x3B0 | … | active-trace hash |
| +0x400 | int32 |
KindIndex (ELuxTraceKindId) |
source:
Z_Construct_UClass_ALuxTraceManager @ 0x140C096B0,ALuxTraceManager_ActivateTrace_Impl @ 0x1408D5D10.
ULuxTraceDataAsset¶
- Path:
/Script/LuxorGame.LuxTraceDataAsset - Size: 128 bytes (0x80)
- Discovered via:
Z_Construct_UProperties_ULuxTraceDataAsset @ 0x140C0CF60
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00..+0x2F | — | UObject base | unread by trace code |
| +0x30 | ULuxTracePartsDataAssetList* |
TracePartsDataAssetList |
the list of capsule-shaped trace-parts metadata |
| +0x38 | ULuxTraceColorPalletDataAsset* |
TraceColorPalletDataAsset |
per-chara palette |
| +0x40 | ULuxTraceInfinityDataAsset* |
TraceInfinityDataAsset |
infinity-mode override asset |
| +0x50 | uint8 (bitfield) |
bDebugDrawBits |
bit 0 = bDebugDrawTraceFrame, bit 1 = bDebugDrawTraceKeyFrame, bit 2 = bDebugDrawTraceVelocity. All three are dead in shipping — see Dev / Debug Hooks. |
Held at ALuxTraceManager+0x388 as TraceManager.TraceDataAsset on this build.
ULuxTraceComponent¶
- Path:
/Script/LuxorGame.LuxTraceComponent - Size: 0x4B0
- Role: visual-only ticking component that renders the weapon trail. Holds the
ActiveTracesTArray of in-flight trail segments and spawns anALuxTraceMeshActorto draw them. Consumed byALuxTraceManager; not referenced by the hit resolver.
| Offset | Type | Name |
|---|---|---|
| +0x418 | FActiveTrace** |
ActiveTraces data ptr |
| +0x420 | int32 |
ActiveTraces count |
| +0x424 | int32 |
ActiveTraces capacity |
| +0x438 | uint8 |
bTraceRunning |
| +0x444 | float |
LastInputX (per-tick axis feed from ALuxTraceManager::Update) |
| +0x448 | float |
LastInputY |
| +0x470 | float |
LengthFrames |
| +0x478 | int32 |
TotalFrames |
| +0x488 | ULuxTraceKindDataAsset* |
KindDataAsset |
| +0x490 | USkeletalMeshComponent* |
MeshRef |
| +0x498 | int32 |
KindIndexCopy |
| +0x49C | uint8 |
TraceSubMode (0=normal, 1=thunder, 2=saber) |
source:
Z_Construct_UClass_ULuxTraceComponent @ 0x140C09950,ULuxTraceComponent_BeginTrace @ 0x1408D5FF0.
FLuxCapsule¶
- Size: 80 bytes (0x50) — confirmed from the Ghidra struct layout.
- Storage: the legacy container shape is
FLuxCapsuleContainer(FLuxCapsule**atcontainer+0x30, count at+0x38), but the old runtime chainchara+0x388 -> +0x30 -> containeris stale on this build becausechara+0x388isCharaMesh0, not a MoveProvider. The live capsule-container owner is still unconfirmed; see Trace System. - The first 48 bytes (
+0x00 .. +0x2F) are an internal header thatGetTracePosition_Implnever touches. The documented fields all live in the tail of the struct.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x30 | uint8 |
CapsuleType | matched against Active() tag 1..9 |
| +0x31 | uint8 |
BoneId_A | 8-bit internal index (remapped via LuxSkeletalBoneIndex_Remap) |
| +0x34 | float[3] |
LocalOffset_A | bone-local; scaled by g_LuxCmToUEScale @ 0x143E8A418. The Ghidra label calls this a cm→UE conversion, but the actual value is 10.0f (bit pattern 0x41200000), which implies the stored offset is in millimetres (or some other decimetre-like internal unit) rather than cm. Multiplying by 10 lands it in UE4's native cm units. |
| +0x40 | uint8 |
BoneId_B | second endpoint's bone index |
| +0x44 | float[3] |
LocalOffset_B | second endpoint's bone-local offset |
There is no VisualPartsAsset field on FLuxCapsule. An earlier pass guessed a
ULuxTracePartsDataAsset* at +0x50, but that offset is past the end of the 80-byte struct and
GetTracePosition_Impl never reads it. Visual-parts data-assets are referenced elsewhere in the
trace pipeline (the kind data-asset on ULuxTraceComponent, stored in the per-trace record), not
embedded in a capsule.
source:
ALuxTraceManager_GetTracePosition_Impl @ 0x1408D0BB0(formerly misnamedALuxBattleChara_GetTracePosition_Implin older Ghidra databases — the function body receives a charathis, but the UFunction is registered underALuxTraceManager),FLuxCapsuletype in the Ghidra data-type manager (80 bytes, header + 32-byte endpoint pair). Per its plate comment, this function is stale on the current Steam build — it reads the legacy MoveProvider slot, which no longer holds live capsule data.
See Hitbox System for how the equivalent KHitArea / KHitSphere
endpoints feed the per-tick hit resolver, and Trace System for
FLuxCapsule's role in the visual weapon-trail pipeline.
UScriptStructs used by the trace UFunctions¶
FTraceActiveParam (0x30 bytes)¶
Passed to ALuxBattleChara::Active. Only the first byte matters for hit logic.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | uint8 |
AttackTag | 1..9 — indexes the slot hash at chara+0x3B0 |
| +0x04 | uint32 |
Flags | visual only |
| +0x1C | FLinearColor |
InputColor | trail tint |
| +… | … | padding / cosmetic | ignored by Active_Impl |
source:
Z_Construct_UScriptStruct_FTraceActiveParam @ 0x140C3D380,execActive_ALuxBattleChara @ 0x140C3DA20.
FTraceInactiveParam (0x8 bytes)¶
| Offset | Type | Name |
|---|---|---|
| +0x00 | uint8 |
InactiveType (ETraceInactiveType: Immediatery / Standard / Stoped) |
| +0x01 | uint8 |
SubSlot |
Stage / frame spatial acceleration (used by the LuxMoveVM IF predicates)¶
The two spatial IF predicates in LuxMoveVM (CheckRangeOrDistance @ 0x140365140,
CheckAngleOrGeometry @ 0x1403652E0) query two global, non-thread-safe spatial
structures shared across the battle subsystem. Both structures come in matched
A/B pairs, selected via the byte flag g_LuxBattle_FrameContextUseB @
0x14470DEDC: when it is non-zero, the accessors
LuxBattle_GetActiveFrameBoundsGrid @ 0x1403133E0 and
LuxBattle_GetActiveFrameTransform @ 0x140313400 return the "B" variant.
Round-restore snapshot globals¶
The storage at 0x14484406C..0x14484412F is round-restore state, not stage
geometry. GetScbattleRoundSnapshotPayload @ 0x1402D7730 and
SetScbattleRoundSnapshotPayload @ 0x1402D77C0 copy the payload in twelve
16-byte XMM lanes. That copy granularity caused the former, incorrect
"12 barrier entries" interpretation. LuxBattle_SaveRoundRestoreSnapshot and
LuxBattle_RestoreRoundSnapshot independently access the same bytes as
round/VFX/RNG/motion state.
| Address | Type | Name | Notes |
|---|---|---|---|
0x14484406C |
uint32 |
g_dwLuxBattleRoundSnapshotValid |
Getter rejects zero; setter publishes one after the complete copy. |
0x144844070 |
LuxBattleRoundSnapshotPayload_Partial |
g_LuxBattleRoundSnapshotPayload |
Exact 0xC0 structured payload. |
LuxBattleRoundSnapshotPayload_Partial:
| Offset | Type | Name |
|---|---|---|
| +0x00 | int32 |
nRoundIndex |
| +0x04 | uint32[3] |
pGameplayXorshift |
| +0x10 | uint8 |
bWinnerSlotIndex |
| +0x11 | byte[3] |
pReserved11 |
| +0x14 | LuxBattleRoundSnapshotCharaRecord_Partial[2] |
pCharacters |
| +0x7C | uint16 |
wMotionEntryCount |
| +0x7E | byte[2] |
pReserved7E |
| +0x80 | LuxBattleRoundSnapshotMotionEntry_Partial[16] |
pMotionEntries |
Stage actor registration event record¶
LuxStage_RegisterBarrierActor_BattleEvent0x19 @ 0x140427490 and
LuxStage_RegisterWallActor_BattleEvent0x19 @ 0x140428EE0 build a 24-byte event
record before dispatching class 2. For barrier actors the observed values are
bEventType = 3, dwPayloadExt = 1, and bEventClassId = 0x19.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | byte |
bEventType |
barrier path writes 3 |
| +0x01 | byte[3] |
p_pad_01 |
|
| +0x04 | uint32 |
dwPayloadExt |
barrier path writes 1 |
| +0x08 | uint64 |
qwPayload |
actor / payload pointer-sized value |
| +0x10 | uint32 |
dwReserved |
|
| +0x14 | byte |
bEventClassId |
barrier/wall registration uses 0x19 |
| +0x15 | byte[3] |
p_pad_15 |
ULuxStageAssetPaths, LuxStageRawAsset, and LuxStageSetting¶
ULuxStageAssetPaths is the content-side data asset that names the raw files
used for a stage. Z_Construct_UClass_ULuxStageAssetPaths @ 0x140BACDB0
registers these reflected fields:
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x38 | FString or name-like id |
Identifier |
matched against selected stage id formatted as "%03X" by LookupLuxStageAssetPathNameByPackedStageId @ 0x14089B2C0 |
| +0x40 | TArray<LuxStageRawAsset> |
RawAssets |
entries keyed by ELuxStageAssetType |
| +0x50 | LuxStageSetting |
Setting |
three cosmetic/content behavior bools |
LuxStageRawAsset is 0x18 bytes, registered by
Z_Construct_UScriptStruct_LuxStageRawAsset @ 0x140BD8C60:
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | ELuxStageAssetType |
Type |
0=ESA_HitData, 1=ESA_HitData2, 2=ESA_IntroCameraData, 3=ESA_StartCameraData |
| +0x08 | FString |
Path |
raw asset path loaded by the Lux asset path system |
LuxStageSetting is 3 bytes, registered by
Z_Construct_UScriptStruct_LuxStageSetting @ 0x140BD9120:
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | bool |
bAnomalyStageVFxEnabled |
copied by LuxMove_RefreshProvider_CacheMeshesAndParts @ 0x1403CEDE0 into the stage/VFX refresh state |
| +0x01 | bool |
bWet |
consumed by LuxBattleManager_InitRound_TickTimers_ClearRoundData @ 0x1403FB660; true drives WetRatio to 1.0 at round init |
| +0x02 | bool |
bBreath |
copied by LuxMove_RefreshProvider_CacheMeshesAndParts @ 0x1403CEDE0 into the stage/VFX refresh state |
The VFX destination offsets observed in LuxMove_RefreshProvider_CacheMeshesAndParts
conflict with the currently documented ALuxBattleChara+0x460 UPROPERTY layout,
so treat those destination fields as owner-layout-pending until a live-memory pass
confirms the object type. The source asset offsets above are reflected and stable.
LuxObject_BuildParamSlots_FromBattleSubstrings_4Slots @ 0x1404208B0 maps
loaded RawAssets into the four setup slots. Type 0 and 1 become the A/B
J_StgHitChkData* pointers used by the frame-cache terrain/wall grid.
J_StgHitChkData frame-cache setup¶
LuxBattle_SetFrameCacheHitChkDataPtrs @ 0x1402DAE70 copies a small setup
packet into globals. The first two fields are then consumed by
LuxBattle_RefreshFrameTerrainCache @ 0x140314480, which passes them to
LuxBattle_AttachStgHitChkData @ 0x140392080.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | J_StgHitChkData* |
pStgHitChkDataA |
copied to g_pLuxBattle_StgHitChkDataA @ 0x14470D0D0 |
| +0x08 | J_StgHitChkData* |
pStgHitChkDataB |
copied to g_pLuxBattle_StgHitChkDataB @ 0x14470D0F8 |
| +0x10 | void* |
pField10 |
copied to DAT_14470D0C8; role still unknown |
| +0x18 | void* |
pField18 |
copied to DAT_14470D180; role still unknown |
| +0x20 | byte |
bField20 |
copied to DAT_14470D0DB |
| +0x21 | byte |
bField21 |
copied to DAT_14470D0D8 |
J_StgHitChkData serialized terrain/wall blob¶
J_StgHitChkData is the serialized legacy stage collision blob expanded into
the live frame-bounds grid. The attach function uses the debug tag string
"J_StgHitChkData::Attach" @ 0x143E89F20, which is the strongest fingerprint
that this path is not UE4 PhysX.
Header view (J_StgHitChkData_Header, 0x30 bytes):
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | byte[40] |
pReserved_00 |
opaque header bytes not decoded by attach |
| +0x28 | int16 |
nCellCount |
number of 16-byte cell headers |
| +0x2A | int16 |
nPoolChunkCount |
number of pool chunks / reindex entries |
| +0x2C | int16 |
nTerrainEntryCount |
terrain-entry count used for tag scans |
| +0x2E | int16 |
nReserved_2e |
Cell header (J_StgHitChkData_CellHeader, 0x10 bytes):
| Offset | Type | Name |
|---|---|---|
| +0x00 | uint16 |
wPoolBaseA |
| +0x02 | uint16 |
wMetaA |
| +0x04 | uint16 |
wPoolBaseB |
| +0x06 | uint16 |
wCountA |
| +0x08 | uint16 |
wPoolBaseC |
| +0x0A | uint16 |
wCountB |
| +0x0C | uint16 |
wMetaC |
| +0x0E | uint16 |
wCountC |
Pool chunk header (J_StgHitChkData_PoolChunk, variable-size tail):
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | void* |
pRuntimePatchedNext |
patched after attach |
| +0x08 | uint16 |
wEntryCount |
packed index count |
| +0x0A | byte |
bReady |
initialized to 1 after pointer fixup |
| +0x0B | byte |
bReserved_0b |
|
| +0x0C | uint32 |
dwRuntimePatched |
runtime-patched metadata |
| +0x10 | uint16[1] |
pPackedIndices |
variable tail; attach rewrites indices into terrain-entry pointers |
Frame-bounds grid¶
- Instances:
g_abLuxBattleFrameBoundsGridA@0x144844DD0g_abLuxBattleFrameBoundsGridB@0x144845E80- Discovered via:
LuxBattle_GetActiveFrameBoundsGrid @ 0x1403133E0,LuxBattle_TraceSegmentThroughFrameBoundsGrid @ 0x1403149E0,LuxBattle_TestFrameBoundsCell @ 0x1403916E0 - Size: exactly
0x44Cbytes, embedded atFLuxFrameTransformContext_Partial + 0xC60.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x000 | J_StgHitChkData_Header* |
pAxisSpan |
Serialized-blob header; provides cell and terrain-entry counts. |
| +0x008 | FLuxFrameBoundsCellBucket_Partial*[128] |
pCellBuckets |
X-cell spatial buckets. |
| +0x408 | FLuxTerrainTriangleEntry* |
pTerrainEntries |
Contiguous 0x40-byte records; exact count is pAxisSpan->nTerrainEntryCount. |
| +0x410 | uint8 |
bGridLive |
Non-zero after the grid has been populated. |
| +0x411 | uint8 |
bFrameFlag411 |
Partially recovered frame flag. |
| +0x412 | uint8[2] |
pAltFrameActiveFlags |
Activity flags for alternate terrain frames 1 and 2. |
| +0x418 | byte* |
pCellWorkBase |
Start of runtime cell-work allocation. |
| +0x420 | byte* |
pCellWorkEnd |
End of runtime cell-work allocation. |
| +0x428 | byte* |
pCellWorkCursor |
Current allocation cursor. |
| +0x448 | FLuxTerrainQueryFlags_Partial |
terrainQueryFlags |
Query-control flags read by terrain predicates. |
Cell layout (FLuxFrameBoundsCellBucket_Partial, 0x30 bytes) — each cell
contains three independently counted Z-bucket lists:
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | FLuxTerrainZBucket_Partial** |
pPrimaryZBuckets |
primary terrain-query list |
| +0x08 | uint16 |
wPrimaryBucketCount |
|
| +0x10 | FLuxTerrainZBucket_Partial** |
pSecondaryZBuckets |
secondary wall/segment-query list |
| +0x18 | uint16 |
wSecondaryBucketCount |
|
| +0x20 | FLuxTerrainZBucket_Partial** |
pAuxZBuckets |
auxiliary list built by attach |
| +0x28 | uint16 |
wAuxBucketCount |
Triangle entry — used by LuxBattle_IntersectSegmentWithTerrainTriangle @ 0x140390A90:
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | float[3] |
vertexA |
world XYZ |
| +0x0C | uint32 |
flagsA |
sub-flags (bits 4..11 = kind) |
| +0x10 | float[3] |
vertexB |
|
| +0x1C | uint32 |
flagsB |
bits 8..11 = sub-kind, bits 12..15 = terrain tag |
| +0x20 | float[3] |
vertexC |
|
| +0x30 | float[4] |
plane |
(nx, ny, nz, d); plane equation n·p + d = 0 |
The plane is stored with a pre-baked offset
dso the test is a single dot-product per endpoint.
Frame transform¶
- Instances:
g_abLuxBattleFrameTransformContextA@0x144844170g_abLuxBattleFrameTransformContextB@0x144845220- Discovered via:
LuxBattle_GetActiveFrameTransform @ 0x140313400 - Size: exactly
0x10AC; the matching bounds grid is embedded at+0xC60. - Alternate-frame origins: two
FLuxVec4values at+0x820/+0x830.LuxBattle_SampleTerrainAtWorldXZ @ 0x1403915A0subtracts origin 1 or 2 before sampling a triangle tagged with that frame. A renderer converts the triangle back to world space by adding the same origin. - Selector meaning: the byte at
0x14470DEDCselects a paired A/B simulation context. No player ownership or camera-stage interpretation is implied by the accessor code.
Global terrain scratch vec4s¶
- Instances:
g_LuxBattle_TerrainProbeUp@0x1440FBC38— primed to(X, +100.0f, Z, 1.0f)g_LuxBattle_TerrainProbeDown@0x1440F7688— primed to(X, -100.0f, Z, 1.0f)- Discovered via:
LuxBattle_SampleTerrainAtXZ_Impl @ 0x140391350 - Size: 16 bytes each (one
FVector4).
LuxBattle_SampleTerrainAtXZ_Impl fills both with the XZ of the probe point
and a vertical component before kicking off the terrain query. Downstream,
LuxBattle_IntersectSegmentWithTerrainTriangle reuses the same two memory
locations as edge-cross-product scratch during point-in-triangle classification.
Thread safety: these two globals are not TLS. The VM predicate call path is game-thread-serialized and depends on no other tick overlapping the terrain query. If you hook further up the chain, don't introduce parallelism here.
Orphaned constants wired to the predicate chain¶
| Address | Name | Meaning |
|---|---|---|
0x143E8A3F4 |
g_LuxMoveVM_AngleCosScale |
cos(θ) scale factor baked into the angle predicate math |
0x143E8A3F8 |
g_LuxMoveVM_AngleSinScale |
sin(θ) scale factor baked into the angle predicate math |
0x143E8A674 |
g_LuxBattle_TerrainSample_Invalid |
sentinel returned by LuxBattle_SampleTerrainAtXZ_Impl when no entry matches |
Engine structures referenced by SC6 mods¶
UWorld¶
- Path:
/Script/Engine.World - Discovered via:
Z_Construct_UClass_UWorld @ 0x1428A5B90
Offsets of interest to SC6 modding. The full UE4 4.17.2 UWorld has many more fields;
only the ones mods commonly need are listed here. The offsets below are confirmed
against the property-builder sequence inside Z_Construct_UClass_UWorld.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x030 | ULevel* |
PersistentLevel |
|
| +0x038 | UNetDriver* |
NetDriver |
|
| +0x040 | ULineBatchComponent* |
LineBatcher |
depth-tested debug lines, per-frame |
| +0x048 | ULineBatchComponent* |
PersistentLineBatcher |
depth-tested, persists until FLUSHPERSISTENTDEBUGLINES |
| +0x050 | ULineBatchComponent* |
ForegroundLineBatcher |
no depth test, always on top — recommended for debug overlays |
| +0x088 | TArray<ULevelStreaming*> |
StreamingLevels |
|
| +0x098 | FString |
StreamingLevelsPrefix |
24-byte FString — not a UObject pointer. Earlier docs claimed this was AuthorityGameMode; that is wrong. AuthorityGameMode is at +0xF0. |
| +0x0E8 | UNavigationSystem* |
NavigationSystem |
|
| +0x0F0 | AGameModeBase* |
AuthorityGameMode |
the real slot. ISA-checked against ALuxBattleManager by GetPlayerIndex, GetTracePositionForPlayer, etc. |
| +0x0F8 | AGameStateBase* |
GameState |
|
| +0x100 | UAISystemBase* |
AISystem |
|
| +0x138 | ULevel* |
CurrentLevel |
|
| +0x140 | UGameInstance* |
OwningGameInstance |
On SC6 this points to ULuxGameInstance (registered name "LuxGameInstance", size 0x220, Z_Construct_UClass_ULuxGameInstance @ 0x140A55E60; short-form static-class getter at 0x140A51870). ALuxBattleChara::GetMoveProvider @ 0x1403F00B0 reads this slot, ISA-checks it against ULuxGameInstance, then reads the GameInstance's own +0x140 to get the move-provider pointer. |
Do not confuse world+0x140 with ALuxBattleManager+0x140
They're unrelated fields on unrelated objects that happen to share an
offset. world+0x140 = OwningGameInstance (= ULuxGameInstance* on
SC6). BattleManager+0x140 is the (lazy) MoveComponent slot on the
BattleManager itself. The Ghidra symbol
ALuxBattleManager_GetMoveProviderPtr @ 0x140546600 is MISNAMED — its
actual parameter is a ULuxGameInstance*, reached through
world+0x140. See
ALuxBattleChara runtime-layout note for the
downstream consequence (the chara's cached MoveProvider slot stays
sentinel on this build).
See Drawing 3D Debug Lines for the batcher path.
ULineBatchComponent¶
- Path:
/Script/Engine.LineBatchComponent - Size: 0x850
- Discovered via:
Z_Construct_UClass_ULineBatchComponent @ 0x1425C9590
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x808 | FBatchedLine* |
BatchedLines.Data |
append target for drawing lines |
| +0x810 | int32 |
BatchedLines.Num |
|
| +0x814 | int32 |
BatchedLines.Max |
|
| +0x818 | FBatchedPoint* |
BatchedPoints.Data |
|
| +0x820 | int32 |
BatchedPoints.Num |
|
| +0x830 | FBatchedMesh* |
BatchedMeshes.Data |
|
| +0x838 | int32 |
BatchedMeshes.Num |
FBatchedLine (0x34 bytes)¶
struct FBatchedLine {
FVector Start; // +0x00
FVector End; // +0x0C
FLinearColor Color; // +0x18
float Thickness; // +0x28
float RemainingLifeTime; // +0x2C
uint8 DepthPriority; // +0x30
// +0x31..+0x33 padding to align 4
};
source:
Z_Construct_UScriptStruct_FBatchedLine @ 0x1425CFCD0(registered name"BatchedLine", size0x34).
Dead or vestigial classes — document once, stop chasing¶
ALuxBattleWeaponEventHandler¶
- Path:
/Script/LuxorGame.LuxBattleWeaponEventHandler - Status: Live event source, dead BP override slot in SC6.
The native game fires the Blueprint-implementable event
ReceiveGetWeaponTip(FLuxBattleEvent, out FVector outRoot, out FVector outTip,
out bool bReturnValue, bool bGetType) -> void on this handler during attacks, including
ranged attacks like Cervantes's gun, where the FLuxCapsule trace system is silent. On
paper, hooking it would be an attractive "universal hitbox endpoint query".
In practice, no SC6 character's Blueprint subclass overrides the event. Every post-hook
sample observed arrives with outRoot == outTip == (0,0,0) and bReturnValue == 0. The
native caller (ALuxBattleManager::GetTracePositionForPlayer) calls the event first,
ignores the result, and falls through unconditionally to
ALuxBattleChara::GetTracePosition_Impl — so any "hitbox" value a mod reads from the
hook is garbage by construction.
This is documented here so the next person who sees the class in a ProcessEvent diagnostic log
can skip the RE chase. The real hit-detection geometry lives in the
Hitbox System (KHit linked lists), not on this UFunction. And the
weapon-tip query path the event was meant to feed —
GetTracePosition_Impl — is itself
stale on the shipping build.
UFunction registration: Z_Construct_UFunction_ALuxBattleWeaponEventHandler_ReceiveGetWeaponTip
@ 0x1409CFCE0. The param block is 0x24 bytes:
| Offset | Type | Name | In/Out |
|---|---|---|---|
| +0x00 | FLuxBattleEvent (8 bytes) |
inEvent |
IN |
| +0x08 | FVector (12 bytes) |
outRoot |
OUT |
| +0x14 | FVector (12 bytes) |
outTip |
OUT |
| +0x20 | bool |
bReturnValue |
OUT |
| +0x21 | bool |
bGetType |
IN |
source:
ProcessReceiveGetWeaponTipEvent @ 0x1409A9A80(the native caller wrapper that invokesReceiveGetWeaponTipviaFindFunction+vtable[0x1F8]::ProcessEvent) called fromALuxBattleManager::GetTracePositionForPlayer @ 0x1403F4960.
Other reversed structs¶
The Ghidra database holds a number of additional struct layouts that surface in callers but don't yet have a dedicated section above. Listed here for completeness; cross-reference in the relevant subsystem doc (move system, battle manager, trace system) for context.
TArrayHeader (16 bytes)¶
The 16-byte UE4 TArray<T> header used everywhere as a {Data, Num, Max} triple. Defined
in Ghidra so other structs can reference it by name instead of pasting three raw fields.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | void* |
Data |
element pointer (heap allocation owned by GMalloc) |
| +0x08 | int32 |
Num |
element count |
| +0x0C | int32 |
Max |
allocator capacity |
Used by ULineBatchComponent_Partial for the three batch arrays at +0x808/+0x818/+0x830,
and by every TArray<T> field on a Lux class.
APlayerCameraManager POV (the live render-source)¶
The render thread reads its scene view from APlayerController->PlayerCameraManager,
not from ALuxBattleManager.BattleCamera. The latter is a "director" camera whose
output the PCM consumes. Mods that want to override the rendered pose write to
the PCM, not to the BattleCamera.
APlayerCameraManager carries an FCameraCacheEntry at +0x400, and the
FMinimalViewInfo block sits at +0x410..+0x44B:
| Offset | Type | Name | Notes |
|---|---|---|---|
+0x400 |
float |
TimeStamp |
gate — > 0.0f during active battle |
+0x410 |
float |
Location.X |
written every tick by the director chain |
+0x414 |
float |
Location.Y |
|
+0x418 |
float |
Location.Z |
|
+0x41C |
float |
Rotation.Pitch |
|
+0x420 |
float |
Rotation.Yaw |
|
+0x424 |
float |
Rotation.Roll |
|
+0x428 |
float |
FOV |
|
+0x42C |
float |
DesiredFOV |
|
+0x430..+0x44B |
… | tail | AspectRatio / OrthoWidth / PostProcess settings |
The per-tick commit chain runs from UWorld::Tick @ 0x141F02230: each frame, the
AController iteration loop calls APlayerCameraManager_CommitPOV_NoInterp(playerController[+0x84*8]),
where playerController[+0x84*8] is the PlayerCameraManager pointer.
Read-back from the renderer goes through:
APlayerController::GetPlayerViewPoint @ 0x142046410APlayerController::GetCameraViewLoc @ 0x142042730
Both read from the same +0x410..+0x424 block on the PCM, and ULocalPlayer::CalcSceneView
is the consumer.
Free-camera technique (used by HorseMod's FreeCamera): NOP the engine's per-tick
stores to +0x410..+0x428, then write your own pose into the same offsets each cockpit
tick. The 5 store sites sit at SoulcaliburVI.exe + 0x11EAB225 (primary, [rdi+...])
and in a sibling sequence ([rbx+...]); both follow the same 5-store / 4-load pattern over
the offset table:
+0x00 (8B) movsd [rdi+0x410], xmm0 ; Location.X / Y ← NOP
+0x08 (6B) movsd xmm0, [rsp+0x3C] ; load Z / pitch LEAVE
+0x0E (8B) movsd [rdi+0x41C], xmm0 ; Rotation.Pitch/Yaw ← NOP
+0x16 (5B) movups xmm0, [rsp+0x48] ; load tail FOV/etc. LEAVE
+0x1B (6B) mov [rdi+0x418], eax ; Location.Z ← NOP
+0x21 (4B) mov eax, [rsp+0x44] LEAVE
+0x25 (6B) mov [rdi+0x424], eax ; Rotation.Roll ← NOP
+0x2B (4B) mov eax, [rsp+0x5C] LEAVE
+0x2F (7B) movups [rdi+0x428], xmm0 ; FOV + tail ← NOP
Three additional pose writers may also need patching for a full lock-on rotational override (empirical):
| Function | Address | Note |
|---|---|---|
APlayerCameraManager_TickAndCommitFullPose |
0x1420520F0 |
Whole-pose committer; NOP the entire 29-byte store block. |
APlayerCameraManager_WriteTargetFollowRotation |
0x141F935B0 |
Rotation-only writer. |
APlayerCameraManager_SetPOV_LocRotCombined |
0x141D27C80 |
Combined setter; called by APlayerCameraManager_ApplyFollowCamPOV_PerTick @ 0x141D5BB90. |
Camera structs¶
ALuxBattleCamera_PoseFields (34 bytes)¶
A packed pose snapshot that the LuxBattleCamera uses to record sampled poses for
deterministic replay. The byte/float interleaving (no compiler alignment) is unusual
for UE4-derived code and looks bytecode-driven.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | uint8 |
b_prefix |
header tag |
| +0x01 | float |
flActiveTimestamp |
unaligned (sequence packing) |
| +0x05 | uint8 |
b_gap_404 |
|
| +0x06 | float |
flLocationX |
world XYZ, all unaligned |
| +0x0A | float |
flLocationY |
|
| +0x0E | float |
flLocationZ |
|
| +0x12 | float |
flRotationPitch |
euler PYR |
| +0x16 | float |
flRotationYaw |
|
| +0x1A | float |
flRotationRoll |
|
| +0x1E | float |
flFOV |
FCameraCacheEntry_PCM_0x400 (55 bytes)¶
UE4 APlayerCameraManager-style cache entry, customised — same packed-byte / unaligned-float
discipline as ALuxBattleCamera_PoseFields.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | float |
flTimeStamp |
|
| +0x04 | 2 × uint8 |
b_pad_404, b_pad_408 |
|
| +0x06 | float[3] |
flLocation_X/Y/Z |
unaligned XYZ |
| +0x12 | float[3] |
flRotation_Pitch/Yaw/Roll |
unaligned PYR |
| +0x1E | float |
flFOV |
|
| +0x22 | float |
flDesiredFOV |
|
| +0x26 | float |
flAspectRatio |
|
| +0x2A | float |
flOrthoWidth |
|
| +0x2E | float |
flOrthoNear |
|
| +0x32 | uint32 |
dwFlags |
|
| +0x36 | bool |
bConstrainAspectRatio |
FLuxBattleCameraInternalPOV (56 bytes)¶
Internal POV transform held by the battle camera's per-tick state. Mixes packed
{X,Y} qword pairs with an unaligned Z float to keep the on-disk replay
encoding compact.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | uint64 |
Location_XY |
packed pair |
| +0x08 | float |
flLocation_Z |
|
| +0x0C | uint64 |
Rotation_PitchYaw |
|
| +0x14 | float |
flRotation_Roll |
|
| +0x18 | uint64 |
FieldA_8 |
unannotated |
| +0x20 | uint64 |
FieldB_8 |
unannotated |
| +0x28 | uint32 |
dwFieldC_4 |
|
| +0x2C | uint32 |
dwFlags_Low2BitsUsed |
|
| +0x30 | bool |
bBoolFlag |
|
| +0x34 | float |
flTailFloat_FOV_Maybe |
FLuxCameraAction (740 bytes)¶
Per-action camera snapshot. Spawned and ticked by the camera state machine — holds replay timeline state plus the active camera component reference.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | void* |
vtable |
|
| +0x10 | void* |
pParentChara |
|
| +0x18 | void* |
pParentContextB |
|
| +0x24 | int32 |
nReplayCounter |
|
| +0x28 | float |
flCurrentPlaybackFrame |
|
| +0x30 | int32 |
nActiveFlag |
|
| +0x38 | void* |
pCameraComponent |
|
| +0x44 | int32 |
nSlotEnabledFlag |
|
| +0x80 | float |
flPerActionSpeed |
|
| +0x88 | int32 |
nNegSpeedMode |
|
| +0x8C | int32 |
nEndedFlag |
|
| +0x90 | int32 |
nReplayModeFlag |
|
| +0xB0 | int32 |
nLoopOnEnd |
|
| +0x2DC | int32 |
nMinFrame |
|
| +0x2E0 | int32 |
nMaxFrame |
Battle subsystem structs¶
ALuxBattleFrameInputLog (~17616 bytes)¶
Per-match input record/playback actor at BattleManager+0x478. Owns the
per-slot replay input cache (used by the online drain path), an SPSC
deque that receives inbound network packets, a master-clock counter, and a
double-tick guard that stops two ticks in a single frame from corrupting
the replay buffer.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | void* |
pVtable |
|
| +0x388 | void* |
pUE4Component_at0x388 |
UE4 actor base sub-component |
| +0x390 | int32 |
InputDelay_at0x390 |
UProperty InputDelay (registered by ALuxBattleFrameInputLog_RegisterProperties @ 0x1409188E0). Verified permanently 0 in observed builds — its only writer is the constructor's zero-init at LuxMoveProvider_BaseData_Constructor @ 0x1403DC3FB. Subtracted from dwFrameIndex at LuxBattleManager_GetCachedRoundValue_ByIndex @ 0x1403F0751 and LuxBattleChara_ReplayPlayback_PushInputsToActiveSlots @ 0x1403F66A1 when vtable[0x658]() != 0, but with the field at 0 both SUBs are no-ops. See 8WAYRUN "1 fake frame" mod. |
| +0x394 | uint32 |
dwForwardReverseBitfield_at0x394 |
|
| +0x398 | bool |
bEnable_at0x398 |
nNumPlayerSlots (typically 2) |
| +0x39C | uint32 |
dwPlaybackCursor_at0x39C |
bmActivePlayerSlots (BIT i = slot i active) |
| +0x3A0 | int32 |
nLastFrameID_at0x3A0 |
current frame id |
| +0x3A4 | int32 |
nMasterClock_at0x3A4 |
the replay master clock — see Replay System |
| +0x3A8 | void* |
pRecordedFrameBuffer_at0x3A8 |
array of FLuxRecordedFrame |
| +0x3B0 | int32 |
nTotalRecordedFrames_at0x3B0 |
|
| +0x3C0 | FLuxReplayInputCacheEntry[1024] |
pReplayInputCache_at0x3C0 |
16 KB per-slot input cache — [2 slots][512 entries][16 B]; written by the online drain, read by the catch-up loop. See Leaderboards & Online: in-match netplay. |
| +0x4400 | uint32 |
dwOnlineActive_at0x4400 |
online-active flag — 0=offline/spectator (drain early-exits), 1=PLAYER_SIDE_B, 2=PLAYER_SIDE_A, 3=hidden lobby. Set by LuxBattleChara_InitPlayerBitmask_FromOnlineSession @ 0x1403FA330. |
| +0x4404 | bool |
bDoubleTickGuard_at0x4404 |
per-tick re-entrancy guard |
| +0x4410 | int32 |
nDrainCursor_at0x4410 |
|
| +0x4414 | int32 |
nMinStoreFrameIndex_at0x4414 |
drain watermark (cache writes below this are skipped) |
| +0x4424 | byte |
bSessionShutdownState_at0x4424 |
5 = terminal; drain bails on this value |
| +0x4428 | byte[88] |
pSentInputBitmap_at0x4428 |
per-frame-per-input dedupe bitmap |
| +0x4480 | void* |
pDequeStorage_at0x4480 |
inbound-packet deque (network thread → game thread) |
| +0x4488 | void* |
pDequeEntries_at0x4488 |
|
| +0x4490 | ulonglong |
qwDequeMask_at0x4490 |
circular-buffer mask |
| +0x4498 | ulonglong |
qwDequeHeadIdx_at0x4498 |
read cursor |
| +0x44A0 | ulonglong |
qwDequeCount_at0x44A0 |
count; capped at 100 (drop-oldest) |
| +0x44A8 | CRITICAL_SECTION (40 B) |
DequeLock_at0x44A8 |
held by push (network thread) AND drain (game thread) |
FLuxReplayInputCacheEntry (16 bytes)¶
One slot in ALuxBattleFrameInputLog.pReplayInputCache_at0x3C0. Indexed by
(dwFrameIndex & 0x1FF) + playerSlot * 0x200.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | int32 |
nFrameID |
match nLastFrameID_at0x3A0 to read |
| +0x04 | uint32 |
dwFrameIndex |
absolute frame number (master-clock indexed) |
| +0x08 | uint32 |
dwInputValue |
16-bit input cast to u32 |
| +0x0C | byte |
bFilled |
1 = entry valid; written before dwInputValue (latent TOCTOU under contention) |
| +0x0D | byte[3] |
pReserved |
padding |
ALuxBattleKeyRecorder (956 bytes)¶
Training-mode key-recorder actor at BattleManager+0x4B8. Holds a slot table
of FLuxBattleKeyRecorderSlot entries that script the training dummy's input
playback (the Recorded setting in Training menu).
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | void* |
vtable |
|
| +0x388 | void* |
pParentActor |
back-ref to the BattleManager |
| +0x398 | bool |
bRecordPhase |
recording vs playback |
| +0x39C | uint32 |
dwPlayerIndex |
which side records |
| +0x3A0 | int32 |
nSlotIndex |
active slot cursor |
| +0x3A8 | void* |
pSlotTable |
FLuxBattleKeyRecorderSlot[] heap array |
| +0x3B8 | int32 |
nMoveSelectionMode |
FLuxBattleKeyRecorderSlot (12 bytes)¶
One queued input in the key recorder's slot table.
| Offset | Type | Name |
|---|---|---|
| +0x00 | int32 |
nDuration |
| +0x04 | int32 |
nWaitCounter |
| +0x08 | int32 |
nMoveID |
ALuxBattleReplayPlayer (977 bytes)¶
Playback actor at BattleManager+0x488. Reconstructs the match from a
serialised state-reset blob plus a RecordingData ref. The Ghidra struct
size is 0x3D1 (977 bytes); earlier docs cited 0x3C0 (960 bytes).
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x398 | bool |
bEnable_at0x398 |
|
| +0x39C | int32 |
nCurrentRound_at0x39C |
|
| +0x3A0 | float |
flCurrentTime_at0x3A0 |
|
| +0x3A8 | void* |
pStateResetData_at0x3A8 |
round-start serialised blob |
| +0x3B0 | int32 |
nTotalRounds_at0x3B0 |
|
| +0x3B8 | void* |
pRecordingData_at0x3B8 |
raw recording stream |
| +0x3D0 | bool |
fIsPlayingBack_at0x3D0 |
the byte that pushes the struct past 0x3C0 |
FLuxRecordedFrame (192 bytes)¶
One frame's worth of input/state recorded by ALuxBattleFrameInputLog. The
field layout has not been broken down past the byte-grid yet — enough to
size buffers, but not enough to interpret individual fields.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00..+0x9F | 20 × uint64 |
qw00..qw98 |
undecoded |
| +0xA0..+0xBF | 8 × uint32 |
dwDw_a0..dwDw_bc |
undecoded tail |
FLuxBattleCharaVisibilityFlags (7 bytes)¶
A 7-byte bitfield struct that controls per-frame visibility of the character and weapon meshes — two pad bytes plus four boolean toggles.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00..+0x01 | 2 × uint8 |
b_pad_530, b_pad_531 |
|
| +0x02 | bool |
bForceHideOverride |
master "hide" override |
| +0x03 | bool |
bCharacterMeshVisibilityFlag |
|
| +0x04 | bool |
bWeaponMeshVisibilityFlag |
|
| +0x05 | bool |
bCharaSecondaryVisibilityGate |
|
| +0x06 | bool |
bWeaponSecondaryVisibilityGate |
FLuxBattleVMFreezeRecord (64 bytes)¶
Slow-motion / VM-freeze blend state. Holds three candidate alphas + per-mode
tags and produces a blended output per tick. The flAlphaCandidate3_SlowMo
slot is the slow-mo source (e.g. dramatic finishing-blow camera).
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | bool |
bVMFreezeByte |
enable gate |
| +0x04 | float |
flBaseAlpha |
|
| +0x08 | float |
flAlphaCandidate1 |
|
| +0x0C | float |
flAlphaCandidate2 |
|
| +0x10 | int32 |
nCountdown1 |
|
| +0x14 | int32 |
nCountdown2 |
|
| +0x18 | int32 |
nModeTag1 |
|
| +0x1C | int32 |
nModeTag2 |
|
| +0x20..+0x2C | 4 × float |
flOutBlendW0..2, flOutScaledAlpha |
output side |
| +0x30 | int32 |
nOutModeTag |
|
| +0x34 | float |
flAlphaCandidate3_SlowMo |
slow-mo source |
| +0x38 | int32 |
nField_38 |
|
| +0x3C | int32 |
nSlowMotionEnabled |
LuxBattleCharaMotionFlags (64 bytes)¶
A 64-byte flag bank at chara+0x16D0..+0x170F, indexed by flag id 0..0x3F.
Each flag is a single byte (0 / 1). Guard / hit-state predicates read it;
LuxBattleChara_SetMotionInputFlag @ 0x140304C00 writes it (with
side-effects for special indices). The move bank's slot record carries
two 64-bit "set/clear" masks at slot+0x20 / slot+0x28; on every move
transition, LuxMoveVM_ApplyLaneMotionInputMasks @ 0x1402FD7F0 walks
the bits and calls SetMotionInputFlag for each corresponding flag.
| Flag idx | Chara offset | Name | Role |
|---|---|---|---|
| 0x00 | chara+0x16D0 |
abFlag00_GuardLockReason |
Cleared in special branches. Used by CanTrackOpponentLookAt to decide auto-rotation suppression. |
| 0x01 | chara+0x16D1 |
bFlag01_GuardDirty |
per-frame latch; reset by TickCharaMainSimulation. |
| 0x02 | chara+0x16D2 |
bGuardCrouchStateBase |
Authoritative crouch state. 0=stand, 1=crouch. Set/cleared only by move-VM transitions via ApplyLaneMotionInputMasks. Used as block-direction byte by CheckGuardConditionForHitbox when +0x16FC alt isn't locked. |
| 0x03 | chara+0x16D3 |
bFlag03_Uninterruptible |
uninterruptible state |
| 0x04 | chara+0x16D4 |
bFlag04_TerrainGate |
TerrainGateFlag — CheckOpponentFrontTerrainMatch early-exit. |
| 0x0B | chara+0x16DB |
bFlag0B_HitstunActive |
"in hit-reaction" — locks block-direction to base for high-tagged attacks. |
| 0x0C | chara+0x16DC |
bFlag0C_BlockstunActive |
"in block-stun" — locks block-direction to base for block-tagged attacks. |
| 0x0F | chara+0x16DF |
bFlag0F_PostHitstunActive |
post-hitstun reaction state |
| 0x10 | chara+0x16E0 |
bFlag10_TerrainGateAlt |
secondary terrain gate |
| 0x12 | chara+0x16E2 |
bFlag12_Airborne |
airborne; cleared by SetMotionInputFlag snaps Y to terrain |
| 0x2C | chara+0x16FC |
bAltGuardCrouchState |
Per-frame re-derived crouch state. Reset to 0 each tick; re-computed by UpdateGuardStanceFlags. Used as block-direction byte when +0x1701 alt-locked is set. |
| 0x2D | chara+0x16FD |
bIsGuardingFlag |
"actively guarding this frame" — set by UpdateGuardStanceFlags when guard-input is held + stance-input matches. |
| 0x31 | chara+0x1701 |
bAltGuardLocked |
when set, the alt is authoritative for hit resolution. Reset each tick. |
The bank is a unified motion-state machine: both the move-VM bytecode and the
input system call SetMotionInputFlag(chara, flagIdx, value) to mutate
flags. Several flag indices have side effects:
flag 0x0Bis auto-set when flags{0x0C..0x11, 0x29, 0x35}are set (the cluster mask0x2000200003F000).flag 0x12(airborne) when CLEARED snaps Y to chara+0x3C8 (terrain).flag 0x29when CLEARED also clears flag 0x21 and sets flag 0x30.flag 0x2Awhen SET copies a 4-byte attack-launch state.
See Block direction & ducking-while-guarding for how these flags drive the duck-under-high-attack mechanic.
Hit-detection node structs (KHit)¶
These are the structs that drive the live hit resolver — kicks, punches, hurtboxes, pushboxes, grabs. See Hitbox System for the full call-graph walkthrough.
FKHitNodeBase and FLuxKHitNode are Ghidra-named partial views. The canonical names
in the binary's vtables (KHitBase_vftable @ 0x143E87838, etc.) are KHitBase, KHitSphere,
KHitArea, and KHitFixArea. Runtime storage is mixed-size: Lux_KHitChk_DeserializeLinkedList
@ 0x14030C940 advances Sphere nodes by 0x80 bytes and Area/FixArea nodes by 0xA0 bytes.
The sparse FLuxKHitNode layout in the data-type manager covers the common header plus the
largest FixArea tail.
FKHitNodeBase (36 bytes — header view)¶
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | void* |
vtable |
one of KHitBase / Sphere / Area / FixArea |
| +0x08 | uint64 |
PerAttackerBit |
1ULL << (KindTag & 0x3F) — a single-bit mask. The same value is produced for every subclass; its role depends on context (attacker mask / hurtbox mask / body mask). |
| +0x10 | uint32 |
dwNode_Flags10_WriteOnly |
authored, write-only — no runtime reader. Don't gate or classify on this. |
| +0x14 | uint16 |
wActiveThisFrame |
per-frame geometry gate, written from the MoveVM hot-mask as (hotMask >> KindTag) & 1. hotMask has a permanent floor of 0x3FFFD (slots {0, 2..17} are always on). |
| +0x16 | uint8 |
bStreamTypeTag |
0=Sphere, 1=Area, 2=FixArea |
| +0x17 | uint8 |
bKindTag |
KHit category in [0, ~22), not a skeletal bone id. Drives the +0x08 mask, the PerHurtboxBitmask index, and the strike-vs-throw partition (slots 31, 55 = throw). |
| +0x18 | void* |
next |
intrusive linked-list link |
| +0x20 | int32 |
nNextDeltaBytes |
Relative offset to the next emitted node; 0x80 after Sphere, 0xA0 after Area/FixArea, zero on the tail. |
FLuxKHitNode (variable-size node view)¶
Same header as FKHitNodeBase, plus the largest 0x80-byte tail KHitFixArea uses for its
three reference points and transforms. KHitSphere stops at 0x80 total node bytes;
KHitArea and KHitFixArea consume 0xA0 total bytes.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | uint64 |
data_00 |
shared with FKHitNodeBase::vtable-derived layout |
| +0x08 | uint64 |
CategoryMask |
aliased name for PerAttackerBit / PerHurtboxBit |
| +0x10 | uint32 |
dwField_10 |
aliased Node_Flags10 (write-only) |
| +0x14 | uint16 |
wPerFrameLiveGate |
aliased ActiveThisFrame |
| +0x17 | uint8 |
bKindTag |
aliased KindTag |
| +0x18 | uint64 |
Next |
aliased next |
| +0x20 | int32 |
nNextDeltaBytes |
Relative next-node delta; see header view. |
| +0x28 | byte[8] |
pPad_28 |
padding / ABI gap before subclass fields |
| +0x30 | byte[] |
pSubclassTail |
subclass-specific fields below |
Subclass-specific layouts¶
KHitSphere (StreamTypeTag = 0):
+0x30 FVector BoneLocalCenter (mirrored at +0x40)
+0x50 FVector WorldCenterCurrent (this frame; written by
KHitSphere_UpdateWorldCenter @ 0x14030E1A0)
+0x60 FVector WorldCenterPrevious (last frame; for sweep tests)
+0x70 float Radius (may be scaled by anim cell)
+0x74 float RadiusAuthored
+0x78 float ContactImpulseScale (pushbox contact force)
+0x7C uint32 BoneIndexUe4 (post-Remap)
KHitArea (StreamTypeTag = 1) — SWEPT CAPSULE, double-buffered for CCD:
+0x30 FVector BoneLocalP1
+0x40 FVector BoneLocalP2
+0x50..+0x6F WorldSpaceBufA (P1, P2)
+0x70..+0x8F WorldSpaceBufB (P1, P2)
g_LuxKHitArea_DoubleBufferToggle selects cur vs prev each tick;
the overlap test does 4-way segment/segment CCD across both halves.
+0x90 uint32 BoneIndexUe4_P1
+0x94 uint32 BoneIndexUe4_P2
KHitFixArea (StreamTypeTag = 2) — STATIC OBB from THREE reference points:
+0x30 FVector BoneLocalPoint1 (P1, w=1.0 at +0x3C)
+0x40 FVector BoneLocalPoint2 (P2, w=1.0 at +0x4C)
+0x50 FVector BoneLocalPoint3 (P3, w=1.0 at +0x5C)
+0x60 FVector WorldPoint1
+0x70 FVector WorldPoint2
+0x80 FVector WorldPoint3
+0x90 uint32 BoneIndexUe4
Only KHitSphere carries a verified contact-impulse scale (+0x78), used by the
body / pushbox solver. Area and FixArea nodes are overlap geometry only in the
verified paths.
KHitFixArea's OBB is derived at overlap-test time by Gram-Schmidting (P2-P1) and
(P3-P1) — see hitbox-system.md
for the formula.
Subclass vtables:
| Symbol | Address |
|---|---|
KHitBase_vftable |
0x143E87838 |
KHitSphere_vftable |
0x143E877F0 |
KHitArea_vftable |
0x143E877A8 |
KHitFixArea_vftable |
0x143E87760 |
FActiveAttackSlot (68 bytes)¶
The per-tag active-attack slot stored in ALuxBattleChara_Partial.ActiveAttackSlots
(+0x3D0/+0x3D8). Holds two velocity vectors (slot start and slot end) plus
the chained hash-bucket pointers used by chara->HashBuckets_Data.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | uint8 |
bTag |
1..N attack-tag key |
| +0x04 | float[3] |
flVelocityA_x/y/z |
|
| +0x10 | float[3] |
flVelocityB_x/y/z |
|
| +0x1C | float[3] |
flPositionMid_x/y/z |
|
| +0x28 | float[3] |
flDirectionUnit_x/y/z |
unit direction |
| +0x34 | bool |
bGateStateByte |
|
| +0x38 | int32 |
nGateCountdownFrames |
|
| +0x3C | int32 |
nHashNextBucket |
linked-list next |
| +0x40 | int32 |
nHashThisBucket |
self-bucket marker |
Move-VM structs¶
The move VM has substantial scratch state beyond the well-known opcode scratch. See Move System for the per-opcode semantics; the structs themselves are listed here.
FLuxMoveCommandPlayer (12332 bytes)¶
The per-chara VM context indexed by g_LuxMoveVM_CommandPlayerArray @
0x14470F390 (slot stride 0xC0E). Despite the name, this is not the
ALuxBattleMoveCommandPlayer actor at BattleManager+0x4C0 — it is a fixed
global static array holding the live VM state for each chara.
The Ghidra datatype is a sparse 0x302C struct applied at
g_LuxMoveVM_CommandPlayerArray. Most of the 12 KB is uncovered scratch / cell
buffers; only the fields pinned in Ghidra are listed below.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | void* |
vtable |
|
| +0x08 | void* |
SelfChara |
back-ref to owning ALuxBattleChara |
| +0x10 | void* |
OppChara |
mirror of chara->Opponent (+0x973E8) |
| +0x1C | uint32 |
dwMode |
VM mode |
| +0x20 | uint32 |
dwActivePose |
|
| +0x340 | void* |
FuncPtr340 |
callback ptr used during dispatch |
| +0xCD0 | uint32 |
dwPredicateRefreshEnable |
gate for RefreshConditionFlagRing |
| +0xCE0 | void* |
MoveDefArrayBase |
mirror of g_LuxMoveSystem_MoveDefArrayPerSlot for this chara |
| +0xCF0 | int32 |
nPostATKRemainingDelayFrames |
feeds LuxMoveVM_PostATKDelayGate |
| +0x1988 | uint32 |
dwOverrideFlag |
|
| +0x198C | uint32 |
dwActiveFlag |
|
| +0x1990 | uint32 |
dwParserState |
|
| +0x1998 | void* |
CellArrayPtr |
active move's opcode cell array |
| +0x19C8 | uint32 |
dwVMEnabled |
mirror of chara+0x18 |
| +0x19F0..+0x1A64 | uint32/float[~30 fields] |
dwPredRing_* |
condition-flag ring: Self16EC, Self1727, OppBehaviorInRange, StanceInWindow, etc. — see Ghidra layout for full list |
| +0x2150 | uint32 |
dwMoveState0 |
|
| +0x2158 | uint32 |
dwMoveState1 |
|
| +0x2684..+0x269C | uint32[6] |
move-counters | MoveCount, CurrentMoveIdx, CharaVMPhase, StartFlag, MoveEnded, ReverseFlag |
| +0x26A0..+0x26A8 | 3 × uint32 |
CalcOpA/B/C |
0x50004 calc operands |
| +0x26AC..+0x26B0 | uint32,float |
BTN_Mask, BTN_Time |
0x1xxxx BTN+TIME opcode |
| +0x26B4..+0x26B8 | 2 × uint32 |
ATB_ComboId, ATB_YarareId_0 |
0x40001 ATB |
| +0x26BC..+0x26C8 | 4 × uint32 |
IF_Op/Delta/Subject/Value |
0x50008 IF |
| +0x26CC | uint32 |
Goto_Delta |
0x50003 goto |
| +0x26D0..+0x26D4 | 2 × uint32 |
Rand_Threshold, Rand_JumpDelta |
0x50006 RAND |
| +0x26D8..+0x26E4 | 4 × uint32 |
ATK_Power, ATK_RangeRaw, ATK_Speed, ATK_DirectionMask |
0x40002 ATK |
| +0x2A10..+0x2A24 | uint32[6] |
reservation+cell counters | Reserve_NoWait, AnimSettleFrames, CellLoopCounter, CellCount, Cursor |
| +0x2A28 | char[128] |
pDebugTextBuf |
per-tick debug-trace text — written every tick, never read in shipping (see Dev / Debug Hooks) |
| +0x2AD8 | float |
flRingout_EdgeReach |
|
| +0x2B30..+0x2B38 | 3 × uint32 |
ActiveYarareId, ReactionTimer, ActiveYarareAce |
|
| +0x2B68..+0x2B9C | 4 × uint32 |
Ringout_* |
YarareId, SuccessFlag, DirectionCode |
| +0x2BC4..+0x2BC8 | 2 × uint32 |
PostEffectYarareId, PostEffectBodyPart |
|
| +0x2BF8 | uint32 |
HitIntensity |
|
| +0x2C34 | int32 |
ScaledKnockbackReach |
|
| +0x2C4C | int32 |
PostDispatchHang |
per-hit hang frames |
| +0x2CF4 | float |
unknown/scratch | no dispatcher write was found in the 83-function Yarare static sweep |
| +0x2CF8 | float |
flPrimaryRoll |
primary dispatcher RNG sample |
| +0x2CFC | float |
flSecondaryRoll |
secondary dispatcher RNG sample |
| +0x2D08..+0x2D10 | 3 × int32 |
TimerB43..B45 |
|
| +0x3004 | uint32 |
BodyPartStash |
|
| +0x3018..+0x3028 | 4 × uint32 |
AirMoveIndex, ReactionCode, AirSubFlag, … |
air-juggle subflags |
FLuxMoveVM_OpcodeScratch (96 bytes)¶
The 96-byte scratch buffer the VM fills in once per opcode — see
Move System: VM opcode scratch layout.
Layout matches the +0x26AC..+0x26E4 slice of FLuxMoveCommandPlayer.
FLuxMoveVM_ATKPayload (16 bytes)¶
A 16-byte tuple matching the four ATK-opcode fields — used as a struct return /
local copy for the ATK arm of LuxMoveVM_ExecuteAndDumpOpcode.
| Offset | Type | Name |
|---|---|---|
| +0x00 | uint32 |
dwPower |
| +0x04 | uint32 |
dwRangeRaw |
| +0x08 | uint32 |
dwSpeed |
| +0x0C | uint32 |
dwDirectionMask |
FLuxMoveBank (48 bytes)¶
Per-character move-bank header at chara+0x455C0. Resolved by
LuxMoveVM_ResolveBankSlot @ 0x1402FC400 and read by every
LuxMoveVM_TransitionToMove, SetActiveMoveSlot, and
TickHitResolutionAndBodyCollision site.
| Offset | Size | Type | Field | Notes |
|---|---|---|---|---|
| +0x00 | 8 | qword | header magic / unread | unread by hit pipeline |
| +0x08 | 4 | dword | unknown | |
| +0x0C | 2 | u16 | unknown | |
| +0x0E | 2 | u16 | wEventRecordCount |
count of records in event-record table |
| +0x10 | 4 | u32 | dwAttackCellArrayOffset |
offset to AttackCell array (bank + bank[+0x10] + cellBone * 0x70) |
| +0x14 | 4 | u32 | dwNonAttackDescTableOffset |
offset to non-attack 6-byte descr table (bank + bank[+0x14] + (subIdx & ~0x10000) * 6) |
| +0x18 | 4 | u32 | dwEventRecordTableOffset |
offset to event-record table (stride 0x30, count = wEventRecordCount) |
| +0x1C..+0x2B | 16 | FLuxMoveBankBucket[4] |
bucket0..bucket3 (StartIdx, Count) |
ResolveBankSlot uses (packedAddr >> 12) & 3 to pick a bucket |
| +0x2C | 4 | u32 | reserved / padding | |
| +0x30 | … | array | slot table starts | stride 0x48; (BucketN.StartIdx + slotInBucket) * 0x48 |
FLuxMoveBankBucket (4 bytes): { uint16 StartIdx; uint16 Count; }.
FLuxMoveBankSlotView (72 bytes)¶
Returned by LuxMoveVM_ResolveBankSlot as
bank + (StartIdx + slot) * 0x48 + 0x30. The +0x30 is intentional:
the structure straddles two adjacent slot records so the engine can read
related fields with one dereference. All offsets are relative to the
returned pointer, not to the absolute slot start.
| Offset | Size | Type | Field | Notes |
|---|---|---|---|---|
| +0x00 | 2 | u16 | wAnimationIndex_00 |
animation id used by motion-init-loop in TransitionToMove (was psVar22Base) |
| +0x02 | 2 | u16 | wMotionPlaybackParam_02 |
motion-init param (was unnamed; differs widely between sister slots like 0x13C/0x13D) |
| +0x04 | 2 | i16 | nField_04 |
motion arg (read by TransitionToMove second loop) |
| +0x06 | 2 | u16 | wMotionFlags_06 |
motion flag bits |
| +0x10 | 4 | u32 | dwSubTableOffset_10 |
per-slot offset into a sub-table A (-1 = none) |
| +0x14 | 4 | u32 | dwSubTableOffset_14 |
per-slot offset into a sub-table B |
| +0x1C | 4 | u32 | bytecode offset (alt path) | added to bank base if != -1 |
| +0x20 | 8 | u64 | qwInputMask_20 |
copied to lane->qwInputMaskCopy_448 at TransitionToMove |
| +0x28 | 8 | u64 | qwInputMask_28 |
copied to lane->qwInputMaskCopy_450 |
| +0x30 | 4 | f32 | flAnimLength_30 |
move's anim length (60Hz frames) |
| +0x34 | 2 | i16 | nAnimLengthFlag_34 |
-2 = use computed length from playback speed; otherwise the length itself in 60ths |
| +0x36 | 2 | i16 | nHitWindowStart_36 |
hit-window start frame (cell.MasterWindowStart for primary cell) |
| +0x38 | 4 | u32 | dwBytecodeOffset_38 |
bank-relative bytecode offset for the slot's per-tick / move-start script. Resolved as bank + dwBytecodeOffset_38 when != -1. Both TransitionToMove and ExecuteOpStream read this. |
| +0x3C..+0x46 | 12 | i16[6] | nCellBoneIndexPerVariant[6] |
Variant index → AttackCell index. -1 = no attack on this variant. The lane's dwVariantIndex_460 (default 0) selects which entry to use. See Cell lifetime. |
FLuxMoveDefEntry (16 bytes)¶
One row in the per-character FMoveDef array at
g_LuxMoveSystem_MoveDefArrayPerSlot + CharaKindByte*0x3038. Indexed by
CurrentMoveIdx.
| Offset | Size | Type | Field | Notes |
|---|---|---|---|---|
| +0x00 | 2 | u16 | wField0 |
move-id or flags (unread by StartMoveForChara) |
| +0x02 | 2 | u16 | wStartCellCursor |
initial value for g_LuxMoveSystem_CellCursorPerSlot (= bytecode entry point) |
| +0x04..+0x0F | 12 | (opaque) | tail | unread by StartMoveForChara |
FLuxMoveBankCell (112 bytes)¶
One row in the per-character "move bank". This is the attack-cell the hit
pipeline consumes: chara+0x44058 OwnActiveAttackCell and the per-tick
opponent copy at chara+0x44048 both point at one of these. It holds the per-cell
damage / hitstun / hit-property data; the byte arithmetic is bank + bank[+0x10]
+ cellBone * 0x70. The pointer at chara+0x44058 is set ONCE per
LuxMoveVM_TransitionToMove and stays put for the move's duration —
LuxMoveVM_SetActiveMoveSlot (the function that would re-point it) has no
native callers, only a UFunction wrapper. See
Attack cell for the full
runtime model.
Field offsets verified by tracing reads in LuxBattleChara_ProcessHit @ 0x140342780,
LuxBattle_ApplyDamageFromPendingHit @ 0x1402FF620,
LuxMoveVM_EvaluateMoveTransition @ 0x14033E140,
LuxBattle_ComputeHitReactionParams @ 0x140343B90,
LuxMoveVM_ClassifyHitboxFrameState @ 0x140300620, and
LuxMoveVM_PropagateFieldToHitboxGroup @ 0x140303590.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | uint64 |
u64SlotMask |
which authored hitbox slots are LIVE while this cell is current — ANDed with defender's PerHurtboxBitmask[i] by the classifier |
| +0x08..+0x30 | (opaque) | not read by hit pipeline | |
| +0x32 | uint16 |
wAttackFlags |
bit 0x001 = block-high, 0x002 = block-low, 0x008 = LowAttack, 0x010 = MidAttack, 0x040 = CrouchOnly, 0x080 = HighAttack, 0x200 = Unblockable / GI-immune |
| +0x34 | uint16 |
wInputCond |
move-input precondition mask, fed to LuxMoveVM_EvaluateMoveInputCondition |
| +0x36 | int16 |
nMasterWindowStart |
hit-window start frame (60Hz). ClassifyHitboxFrameState writes chara+0x1980 = 1 while currentAnimFrame < this, 2 while inside, 3 while past |
| +0x38 | int16 |
nMasterWindowEnd |
hit-window end frame. DUAL-USE: also reused by LuxMoveVM_EvaluateAttackRange @ 0x14035F670 as a signed-short reach ceiling — when chara+0x44978 (the projected reach scalar) exceeds this value, the range gate returns out-of-range. One 16-bit scalar serves both timing and reach |
| +0x3A | int16 |
nBaseDamage |
THE damage figure read by ProcessHit, added into attacker+0x3FC. One value per cell — the same value for every shape that hits while this cell is active |
| +0x3C | int16 |
nStunRecoil |
hitstun bucket, written into attacker+0x3E4 |
| +0x3E | uint16 |
wExtraStateFlags |
mirrored verbatim into attacker+0x400 |
| +0x44 | int16 |
nBlockstunFrames |
ComputeHitReactionParams case 1 (blocked) |
| +0x46 | int16 |
nHitstunStandingNormal |
case 4–5 (standing hit) |
| +0x48 | int16 |
nHitstunStandingAir |
case 4–5 (airborne defender) |
| +0x4C | int16 |
nHitstunCrouchNormal |
case 7 (counter-hit / crouch hit) |
| +0x4E | int16 |
nHitstunCrouchAir |
case 7 (airborne crouch) |
| +0x50 | int16 |
nReactionIdStanding |
case 4–5/9 reaction-move id (standing) |
| +0x52 | int16 |
nReactionIdAir |
case 4–5/9 reaction-move id (air) |
| +0x54 | int16 |
nThrowEscapeId |
case 7 throw-escape id |
| +0x5A | uint16 |
wPassthroughTagA |
mirrored to chara+0x210A on slot transition |
| +0x5E | uint16 |
wHitboxGroupBitfield |
mirrored to chara+0x20F6. bits 0..10 = group ID (0..63) selecting one of 4 banks of 16 hit-sub-window entries at DAT_1448554E8 + 0x338 / 0x3B8 / 0x438 / 0x4B8; bits 11..13 mirrored to chara+0x20F2; bits 14..15 mirrored to chara+0x20F0. See Per-cell sub-window timing. |
| +0x60 | uint16 |
wPassthroughTagC |
mirrored to chara+0x20FC |
| +0x62 | int8 |
cI8RangeStandMin |
per-cell stand-stance min reach (signed byte). 0x81 (-127) = "no constraint on this side". Read by LuxMoveVM_EvaluateAttackRange |
| +0x63 | int8 |
cI8RangeStandMax |
per-cell stand-stance max reach (signed byte). 0x81 sentinel as above |
| +0x64 | int8 |
cI8RangeCrouchMin |
per-cell crouch-stance min reach |
| +0x65 | int8 |
cI8RangeCrouchMax |
per-cell crouch-stance max reach |
| +0x66 | int16 |
nReachExtraGate |
per-cell extra reach gate (signed short). -1 or 0 disables. Used in conjunction with the four byte bounds above to gate EvaluateAttackRange |
| +0x68..+0x69 | (opaque) | tail-padding | |
| +0x6A | uint16 |
wRuntimePropagateField |
mutated at runtime by LuxMoveVM_PropagateFieldToHitboxGroup @ 0x140303590 across the 8 cells of a hitbox-group entry. The only cell field known to change after move start; semantics not yet identified |
| +0x6C..+0x6F | (opaque) | trailing pad |
LuxMoveVM_EvaluateAttackRange @ 0x14035F670 (the per-cell reach
gate) is consulted ONLY by LuxBattle_CheckYarareReactionGate case
0x72 (yarare-table reach predicate), NOT by the throw classifier
itself. Returns: 0 = in range; 0xFFFFFFFD = out of range; 0xFFFFFFFE
= cell has no range constraints (all four bytes are 0 or 0x81,
or nReachExtraGate is -1/0); 0xFFFFFFFF = above-ceiling
(chara+0x44978 reach scalar > nMasterWindowEnd short).
FLuxMoveSchedState (96 bytes)¶
Move scheduler state — a per-chara dual-slot system that lets the next move be
queued while the current one is still ticking. The pMoveIdSlot[2],
pPrevMoveId[2], and similar arrays hold one entry per slot.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x08 | uint32 |
dwSelectedSlotIdx |
0 or 1 — which of the two queued slots is current |
| +0x10 | void* |
pChara |
back-ref |
| +0x30 | uint32[2] |
pMoveIdSlot |
per-slot move id |
| +0x38 | uint32[2] |
pPrevMoveId |
per-slot previous move id |
| +0x40 | uint32[2] |
pMoveChangedCounter |
bumps on transition |
| +0x48 | uint16[2] |
pExtraParam0Slot |
per-slot extra arg |
| +0x4C | uint16[2] |
pExtraParam1Slot |
per-slot extra arg |
| +0x50 | void* |
pSubVM |
optional sub-VM ptr |
| +0x5C | uint32 |
dwActiveSlotIdx |
active vs selected (debug aid) |
FLuxMoveStartRequest (108 bytes)¶
The "queue this move" request struct passed into PlayMove / PlayMoveDirect.
Carries its own state-machine field (dwStateMachine), a completion flag,
and two candidate move-id slots so the dispatcher can resolve a request that
spans a transition window.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | void* |
pVtable |
|
| +0x08 | uint32 |
dwStatus |
|
| +0x10 | void* |
pChara |
target chara |
| +0x18 | uint16 |
wMoveId |
|
| +0x1A | uint16 |
wLevelId |
|
| +0x20 | uint32 |
dwMode |
|
| +0x38 | uint32 |
dwStateMachine |
request-internal SM phase |
| +0x54 | uint32 |
dwCompleted |
|
| +0x68 | int16 |
nCandidateMoveIdA |
for candidate-pair resolution |
| +0x6A | int16 |
nCandidateMoveIdB |
FLuxMoveSubFrameRecord (72 bytes)¶
Sub-frame record — a 60→120 Hz interpolation entry recorded per VM tick. The
flRangeStart/flRangeEnd interval gates which sub-frame samples the record
applies to.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | float |
flFrame_0 |
frame index |
| +0x04 | float |
flRangeStart |
sub-frame interval start |
| +0x08 | float |
flRangeEnd |
sub-frame interval end |
| +0x0C | uint32 |
dwField_0C |
|
| +0x10..+0x30 | 5 × uint64 |
field_10..field_30 |
|
| +0x38 | uint32 |
dwField_38 |
|
| +0x3C | int16 |
nCellBoneIndex |
which bone this sub-frame describes |
| +0x40 | uint64 |
field_40 |
tail |
FLuxMoveLane (1128 bytes — was LuxMoveLaneState)¶
Per-lane VM state for the move bytecode interpreter. Three lane blocks live inline at fixed offsets in every chara:
| Offset on chara | Lane | Role |
|---|---|---|
+0x444F0 |
Lane 0 | Primary active move (most strikes) |
+0x44958 |
Lane 1 | Secondary lane (mirror / queued script) |
+0x44DC0 |
Lane 2 | Stance / yarare / hit-reaction |
Stride = 0x468 bytes. The Ghidra type is FLuxMoveLane (originally
typed as LuxMoveLaneState with mostly-undecoded padding). The field map below
reflects what the Cervantes 3B move-extension investigation uncovered, plus
pre-existing instrumentation. chara+0x44068 (ActiveLaneStateCursorPtr)
points at whichever lane is currently driving the move.
Key fields (only the well-understood ones; undecoded scratch is omitted):
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | int16 |
nLaneIdx |
0 / 1 / 2 |
| +0x02 | int16 |
nMoveID |
packed (bank<<12) \| slot; -1 = idle |
| +0x04 | uint32 |
dwScratch_04 |
|
| +0x08 | float |
flAnimFrameCurrent |
THE frame counter — advanced as +0x08 += time_dilation * +0x30 each tick. Truncate for "current frame N". |
| +0x10 | float |
flAnimLength |
total anim frames; from bank cell +0x34 (or computed from playback speed when wAnimLengthFlag_34 == -2) |
| +0x1A..+0x1B | byte[2] |
bByUnused_1A/1B |
|
| +0x1C | int16 |
nSignedFrameCounter |
per-tick frame delta |
| +0x1E..+0x1F | byte[2] |
bByTransitionAlreadyMatchedFlag_1E/1F |
latched after TransitionToMove(samePackedMoveAddr) |
| +0x20 | float |
flAnimFramePrev |
tick-start snapshot of flAnimFrameCurrent |
| +0x24..+0x25 | byte[2] |
bByField_24/25 |
|
| +0x26..+0x27 | byte[2] |
bByScriptInProgress_26/27 |
"primary script running" gate |
| +0x28..+0x29 | byte[2] |
bBySecondaryRunning_28/29 |
"secondary lane script running" gate |
| +0x2C | int32 |
nTransitionFiredMarker_2C |
tx-fired marker; set by CheckMoveTransitionTiming, cleared every tick by FinalizeTickPoseAndState |
| +0x30 | float |
flAnimLengthCopyA_30 |
playback-speed source |
| +0x34 | float |
flAnimLengthCopyB_34 |
playback-speed target |
| +0x38..+0x3F | byte[8] |
pClearedOnTransition_38 |
wiped by TransitionToMove |
| +0x50..+0x51 | byte[2] |
bByScriptInProgressMarker_50/51 |
"Run currently entering" guards |
| +0x52 | uint16 |
wMotionInputBank_52 |
|
| +0x54 | uint16 |
wStepCounter_54 |
reentrant-step guard inside ExecuteOpStream; if > 16 the lane bails into TransitionToMove(0,0) (lane 0) or CommitMoveEnd (lane 1+) |
| +0x56 | int16 |
nLaneIdxMirror_56 |
written by DecodeVariadicStreamArgs |
| +0x5A | int16 |
nNextMoveID_5A |
immediate-transition target — set by DecodeVariadicStreamArgs (CALLCOND TransitionAuthor); fires when flTransitionThreshold_68 is reached |
| +0x5C | int16 |
nMoveIDOverride16FE_5C |
replaces nNextMoveID_5A when chara+0x16FE != 0 |
| +0x5E | int16 |
nMoveIDOverride16EB_5E |
replaces it when chara+0x16EB != 0 (multi-hit lockout) |
| +0x60 | int16 |
nMoveIDOverrideMode2_60 |
replaces it when chara+0x973E8 + 0x132C > 1 (mode 2) |
| +0x64 | uint32 |
dwTransitionStartTime_64 |
|
| +0x68 | float |
flTransitionThreshold_68 |
when other-lane's anim cursor reaches this, CheckMoveTransitionTiming fires TransitionToMove(nNextMoveID_5A) |
| +0x6C | uint16 |
wMotionArgCount_6C |
|
| +0x6E | int16[17] |
anMotionArgs_6E |
motion args copied from the bytecode's CALLCOND arg tail |
| +0x90 | int32 |
nPVarCount_90 |
"extra param" arg count (motion-arg copy from DecodeVariadicStreamArgs for arg-count > 4) |
| +0x94 | int16[16] |
anPVarBuffer_94 |
extra-arg storage |
| +0xB4 | int16 |
nDeferredMoveID_B4 |
deferred-transition target — written by DecodeVariadicStreamArgs when g_LuxMoveVM_DeferredTransitionScheduleFlag != 0 (i.e. inside an OpcodeIf_15 wrapper); drained next tick by OpcodeIf_16 |
| +0xB6 | int16 |
nDeferredLaneIdx_B6 |
which lane the deferred transition targets |
| +0xB8 | int16 |
nDeferredCommitFlag_B8 |
snapshot of g_LuxMoveVM_DeferredTransitionCommitFlag |
| +0xBC | float |
flDeferredStartFrame_BC |
scheduled start frame |
| +0xC0 | float |
flDeferredCurrentFrame_C0 |
snapshot of current frame |
| +0xD8 | FLuxMoveLaneState* |
pMotionPlaybackSlotA_D8 |
small per-lane motion-playback record (see LuxMoveLaneState_Motion below) |
| +0xE0 | FLuxMoveLaneState* |
pMotionPlaybackSlotB_E0 |
second motion slot (upper-body / lower-body split) |
| +0x1F4 | FLuxMoveLane_EffectOp[16] |
aEffectOpTable_1F4 |
16-entry lane effect-op table (see below) |
| +0x438 | qword |
qwField_438 |
input mask scratch |
| +0x440 | qword |
qwField_440 |
input mask scratch |
| +0x448 | qword |
qwInputMaskCopy_448 |
copy of slot's qwInputMask_20 |
| +0x450 | qword |
qwInputMaskCopy_450 |
copy of slot's qwInputMask_28 |
| +0x458 | qword |
qwField_458 |
total-tick counter (monotonic across advances) |
| +0x460 | uint32 |
dwVariantIndex_460 |
bank variant index 0..5 — picks which nCellBoneIndex_3C[N] from the slot view to resolve into an attack cell |
For a "current move frame N / M" HUD overlay, read Lane0->flAnimFrameCurrent
and Lane0->flAnimLength every tick — pure pointer arithmetic, no hooks
required. chara+0x1350 is the MoveStartCounter, bumped on each
TransitionToMove call (handy for "did the move change?" checks without
diffing nMoveID). chara+0x1360 is the LastHitAnimFrame mirror written
by ProcessHit for HUD consumers.
FLuxMoveLane_EffectOp (36 bytes)¶
One entry of the 16-slot effect-op table at FLuxMoveLane->aEffectOpTable_1F4.
Inserted by LuxMoveVM_OpcodeIf_RegisterEffectOpDedup_04 @ 0x1402FD4A0
(CALLCOND funcIdx 0x04 / 0x19 / 0x1A), walked per-tick by
LuxMoveVM_ExecuteOpStream @ 0x1402FDEA0, and dispatched via
LuxMoveVM_DispatchEffectOp @ 0x140376B20 once nTriggerFrame is reached.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | int16 |
nTriggerFrame |
when lane->flAnimFrameCurrent >= nTriggerFrame, fire (-1 = empty slot) |
| +0x02 | byte |
bArgc |
arg count - 1 (passed as argc to DispatchEffectOp) |
| +0x03 | byte |
bFiredFlag |
set on first fire to suppress re-trigger |
| +0x04 | int16[16] |
anArgs |
the args originally pushed onto the inner-VM stack before the registering CALLCOND |
LuxMoveLaneState_Motion (68 bytes — the small motion-playback record)¶
Per-motion-slot playback state. Two of these are pointed to from
FLuxMoveLane->pMotionPlaybackSlotA_D8 / pMotionPlaybackSlotB_E0. (The Ghidra
type is currently FLuxMoveLaneState, distinct from the FLuxMoveLane
above; the legacy name collision is being phased out.)
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | float |
flUnknown00 |
|
| +0x04 | uint |
dwTickCounter |
|
| +0x08 | float |
flAnimFrameCurrent |
|
| +0x0C | float |
flAnimFramePrev |
|
| +0x10 | float |
flAnimLengthFrames |
|
| +0x1A | ushort |
wAtEndFlag |
1 at final frame |
| +0x1C | short |
nFrameDeltaThisTick |
|
| +0x24 | ushort |
wFrameStepFinished |
|
| +0x26 | ushort |
wInTransitionFlag |
|
| +0x30 | float |
flPlaybackSpeedCurrent |
ramps to target |
| +0x34 | float |
flPlaybackSpeedTarget |
|
| +0x38 | float |
flPlaybackSpeedDelta |
|
| +0x3C | float |
flPlaybackSpeedRate |
|
| +0x40 | float |
flPlaybackSpeedCountdown |
FLuxMoveProvider_CapsuleSlot (64 bytes)¶
Same shape as FLuxCapsuleContainer (header + Data/Num/Max), but referenced
from a different legacy code path. The layout is identical to FLuxCapsuleContainer;
the old MoveProvider ownership chain is stale on this build and the live owner is
still unconfirmed.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | byte[48] |
pHeader |
unread |
| +0x30 | void* |
CapsulesData |
pointer to FLuxCapsule* array |
| +0x38 | int32 |
nCapsulesNum |
element count |
| +0x3C | int32 |
nCapsulesMax |
allocator capacity |
Trace-component layouts (extended)¶
The Ghidra DB carries two extended layouts that overlap the ALuxTraceManager and ULuxTraceComponent sections above but reach further into the byte-grid.
FLuxTraceManagerLayout (1032 bytes)¶
Includes the 904-byte AActor base plus the trace-manager-specific tail.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x388 | void* |
pTraceDataAsset |
ULuxTraceDataAsset* |
| +0x398 | void* |
pEffectSlotA |
UParticleSystemComponent* |
| +0x3A0 | void* |
pEffectSlotB |
UParticleSystemComponent* |
| +0x3A8 | void* |
pTraceComponent |
ULuxTraceComponent* |
| +0x3B0 | byte[48] |
pActiveTraceHashBase |
6-bucket active-trace hash |
| +0x400 | int32 |
nKindIndex |
ELuxTraceKindId |
FLuxTraceComponentLayout (1200 bytes)¶
Includes the 1048-byte UActorComponent base plus the trace-component-specific
tail. Field names match the ULuxTraceComponent section
above; this layout is the Ghidra struct definition used for type-aware decompilation.
FTraceActiveParam (48 bytes — full layout)¶
The full-layout version of the FTraceActiveParam documented above. The Ghidra
struct is 48 bytes, matching Active_Impl's parameter size. The earlier section
lists only the hit-relevant fields; all 48 bytes are below.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | uint8 |
bAttackTag |
the only field Active_Impl reads for hit logic |
| +0x04 | uint32 |
dwFlags |
visual |
| +0x08 | uint16 |
wSubSlot |
|
| +0x0C | uint32 |
dwFrames |
trail duration |
| +0x10 | bool |
bUseDefault |
|
| +0x14 | uint32 |
dwReservedInt14 |
|
| +0x18 | uint8 |
bMode |
|
| +0x1C | uint8 |
bEffectParam |
|
| +0x2C | uint32 |
dwReserved_2C |
Frame-bounds triangle entry¶
FLuxFrameBoundsCellBucket_Partial (48 bytes) and FLuxTerrainTriangleEntry (64 bytes)¶
The cell-bucket and triangle-entry types referenced by
Stage / frame spatial acceleration. The Ghidra struct
definitions match the field list documented in that section. FLuxTerrainTriangleEntry
holds a vertex triple plus a pre-baked plane equation (nx,ny,nz,d), so
LuxBattle_IntersectSegmentWithTerrainTriangle @ 0x140390A90 can do the test
in one dot-product per endpoint.
Misc reflected structs¶
FLuxAttackTouchParam (29 bytes)¶
See Move System: enums and small structs —
fired when a hit registers, holds (player, position, hit type, attack type, level, can-down).
FLuxDamageInfo (18 bytes)¶
See Move System: FLuxDamageInfo — HUD/network
hit-event payload (side, damage, total, is_critical, is_limited).
FLuxDataTablePath (24 bytes)¶
The 24-byte hierarchical-path cursor used by every LuxDataTable* writer.
Documented as the vehicle for BattleManager+0x50 config writes — see
Battle Manager: FLuxDataTablePath.
FLuxBattleOptionParam (16 bytes)¶
Reflected as LuxBattleOptionParam by
InitializeLuxBattleOptionParamStruct @ 0x140992DA0. This is a training/options
config struct; it is not the live online input cache and does not feed
ALuxBattleFrameInputLog+0x390 in the observed build.
| Offset | Type | Name | Notes |
|---|---|---|---|
| +0x00 | int32 |
nInputDelayFrame |
Reflected InputDelayFrame property. |
| +0x04 | int32 |
nInputLagMin |
Reflected InputLagMin property. |
| +0x08 | int32 |
nInputLagMax |
Reflected InputLagMax property. |
| +0x0C | int32 |
nInputLagOffset |
Reflected InputLagOffset property. |
Net / Steam structs (unrelated to hitbox work)¶
A handful of online-session structs are also defined for the Steam-online
subsystem — FNamedOnlineSession_Steam (236 bytes), FOnlinePresenceSteam
(496 bytes), FOnlineSessionInfoSteam (88 bytes), FFriendStateRecord
(232 bytes). Listed for completeness; not relevant to gameplay reversing.