Now · Updated 2552.07.02.21.29

Editor Tasks

Things that need to be done in the Unreal Editor. Compile first before opening the editor.

Things that need to be done in the Unreal Editor. Compile first before opening the editor.

When a section here lands, remove it. Past completed work is recorded in Docs/Progress.md under "Completed."


#TP Left-Hand IK Control Rig — CR_MC_TP_LeftHandIK

Replaces the FABRIK LHIK on the TP body with a Control Rig (Two Bone IK, better elbow/pole control). Auto-generated via the systemlink-unreal bridge; the graph is built + saved. One step must be done by hand because scripting it crashes the editor (RigVM add_member_variable assert — see Docs/Footguns.md / memory feedback_controlrig_python_membervar_crash).

Asset: /Game/SystemLink/Characters/MasterChief/Rigs/CR_MC_TP_LeftHandIK Already scripted + saved: preview mesh = MasterChief (TP), 91-bone hierarchy imported; graph = BeginExecution (Forwards Solve) → LeftArmTwoBoneIK (RigUnit_TwoBoneIKSimplePerItem) with ItemA=upperarm_l, ItemB=lowerarm_l, EffectorItem=hand_l.

  • [x] 1. Add 3 rig variables (done by hand — scripting add_member_variable crashes the editor):
  • LeftHandTarget/Transform, PoleVector/Vector, Alpha/Float. (Public.)

  • [x] 2. Bind pins to the variables (scripted via bridge, compiled + saved): Effector ← LeftHandTarget,
  • PoleVector ← PoleVector, Weight ← Alpha. Bindings are variable-getter nodes → links (not a pin property).

  • [x] 2b. Apply bool gate (scripted): an If node drives Weight = Apply ? Alpha : 0, so Apply=false
  • fully disables the IK. ⚠ Confirm the Apply variable's Default Value is ticked TRUE in the Variables panel — the bridge can't read variable defaults, and the intent is on-by-default.

  • [x] World-space composition — now done INSIDE the rig. LHIK_TP_Snapshot stores the target in
  • hand_r bone space (FSLLeftHandIKTargets.EffectorBoneSpace_HandR), not world/component. The rig now has GetHandR (Get Transform of hand_r, GlobalSpace) → ComposeEffector (Effector = LeftHandTarget * hand_r, UE relative×parent order) → IK Effector. So LeftHandTarget carries the bone-space offset; no transform math needed in the ABP. (Assumes weapons use hand_r as RightHandBoneName — true for all current weapons.)

  • [ ] 3. TP ABP hookup (now trivial). Drop a Control Rig node in the TP AnimGraph set to
  • CR_MC_TP_LeftHandIK and feed straight from LHIK_TP_Snapshot:

    • LeftHandTargetEffectorBoneSpace_HandR
    • AlphaAlpha (gate with bIsValid / the existing Should Apply LHIK if desired)
    • PoleVector ← elbow hint (optional; default (0,0,1) for now)

Retire the FABRIK path once this matches.

  • [ ] 4. Tune (visual). Left-arm PrimaryAxis/SecondaryAxis sign (defaults (1,0,0)/(0,1,0)) and the
  • PoleVector direction (default (0,0,1), kind=Direction) usually need adjusting so the elbow points naturally — check in the CR preview / PIE.


#Grenades — shootable pickups + per-grenade death drop

C++ done (uncommitted), needs a build + a couple of tunables + test. Full design: Docs/GrenadeSystem.md → "Extras". Also folds in the USLGrenadeIndicator::InitializeInitializeIndicator rename (build only).

  • [ ] 1. Rebuild SystemLinkEditor (close editor first) — new ISLDamageable interface + new
  • UPROPERTYs; Live Coding won't surface them. Covers the grenade changes AND the Initialize rename.

  • [ ] 2. Set tunables on BP_SL_GrenadePickup_Frag (defaults are fine to start): DetonateDamageThreshold
  • (1 = almost any hit pops it), ChainDetonationRadius (350; 0 = no chaining), HitCollision sphere radius (14 cm — match the frag mesh).

  • [ ] 3. Post-rebuild BP check (bridge or in-editor): recompile WBP_SL_GrenadeIndicator +
  • WBP_SL_HUDWidget → confirm zero errors (verifies nothing in BP called the old Initialize) and that the C4263/C4264 warnings are gone.

  • [ ] 4. Test (2 players, listen server): shoot a placed/dropped grenade → detonates with full blast +
  • cue; kills credit the shooter; a cluster chains; die holding grenades → one pickup per grenade scatters → collect back (listen for collect-sound spam on a tight pile). Radial falloff full inside InnerRadius → 0 at OuterRadius.


#Sidearm Shooting

Prerequisite: build SystemLinkEditor first — the sidearm fire C++ (active-fire-weapon routing + actor-owned ammo) must be compiled. Detail + rationale: Docs/SidearmMode.md §8.1–8.2.

Minimum to get a shot firing = tasks 1–3.

Tasks 1–4 done via the Unreal MCP bridge on 2026-06-09 (registered tag name is
SLTags.States.Weapon.SidearmActive, not the shorthand below; pistol muzzle socket is Muzzle).
Note: tasks 1–4 edited Blueprint CDOs / data assets through Python + save_asset — if a PIE test
shows stale values, recompile the affected BPs in-editor to be safe. Tasks 5–6 (cosmetics, HUD) remain.
  • [x] 1. Create BP_GA_SL_Pistol_PrimaryFire — duplicate BP_GA_SL_AssualtRifle_PrimaryFire
  • (parent USLGameplayAbility_Fire). CDO:

    • bIsPrimaryFire = true
    • RequiredWeaponClass = BP_SL_WeaponActor_Pistol ✓ (was still pointing at the AR actor)
    • ActivationRequiredTags = SLTags.States.Weapon.SidearmActive
    • ActivationBlockedTags = Dead, Meleeing (inherited from AR dup — kept) ✓
    • ActivationOwnedTags = SLTags.States.Weapon.Firing
    • Triggers[0] = { Tag: Events.Weapon.PrimaryFire, Source: GameplayEvent }
  • [x] 2. Block primary while sidearm drawn — added SLTags.States.Weapon.SidearmActive to
  • ActivationBlockedTags on BP_GA_SL_AssualtRifle_PrimaryFire and BP_GA_SL_Shotgun_PrimaryFire ✓.

  • [x] 3. Grant the abilityBP_GA_SL_Pistol_PrimaryFire added to AS_AbilitySet_Default ✓.
  • (Fire abilities for equipped weapons are granted on equip via FireMode.FireAbilityClass; the sidearm is a separate always-carried slot that never runs through GrantWeaponAbilities, so it MUST be granted via the ability set — hence no FireAbilityClass set on DA_SL_Pistol.)

  • [x] 4. Sidearm weapon data assetDA_SL_Pistol.PrimaryFireMode filled: bEnabled=true,
  • FireMode=SingleShot, RoundsPerMinute=400, BaseDamage=26, MaxAmmo=12, MuzzleSocketName=Muzzle, DamageMultipliers=defaults. No AmmoDecrementEffect (actor-owned ammo). FireCueTag/ImpactCueTag still empty — see task 5. (Values are tuning placeholders — adjust to taste.)

  • [~] 5. Fire cosmeticspartially done (borrowing AR FX) 2026-06-09.
    • DA_SL_Pistol.PrimaryFireMode.FireCueTag/ImpactCueTag set to the AR cues
    • (GameplayCue.Weapon.AssaultRifle.PrimaryFire / .Impact) → observers + impact covered.

    • Still open: verify the pistol BP's OnLocallyPredictedShotFired plays FP muzzle flash + sound
    • for the shooter (the cue excludes the local player). If the AR-duplicate didn't carry it, wire it (reuse AR FP muzzle/sound). Then pistol-specific FX (GC_SL_Pistol_PrimaryFire, own muzzle Niagara + fire sound) as polish.

  • [ ] 6. HUD sidearm ammo indicator (separate) — bind SidearmWeapon->OnAmmoChanged to a fixed
  • indicator. The C++ already keeps CurrentAmmo correct + replicated; this just displays it.

  • [ ] 7. FP fire animationsDECIDED: state-machine, not montages (shotgun pattern). Add a
  • Firing state to ABP_FP_Pistol (arm recoil) and ABP_Pistol (gun-mesh slide), gated by the Firing tag / bIsFiring; TP equivalents in ABP_TP_Pistol. Apply shotgun lessons: "Always Reset on Entry" on the non-looping fire state + an Inertialization node downstream (see feedback_animbp_state_reset_on_entry). Beepers authors clips in Maya (none exist yet).

    • C++ tie-in: DA_SL_Pistol.PrimaryFireMode.PostFireDelay is currently 0; a Firing state needs
    • dwell time, so single-shot likely needs PostFireDelay > 0 (shotgun uses it to hold the pump). Set once the fire-clip length is known.

Test (listen server, 2 players): RT alone fires the primary; hold LT then RT fires the sidearm; sidearm ammo drains on its own count; primary ammo untouched while sidearm is out; host sees a client's sidearm shots deal the sidearm's damage (the untested damage-RPC path).