Now · Updated Jun 25, 2026
CurrentFocus — AI Session Primer
CurrentFocus — AI Session Primer
Update this file at the end of each session. It is the first thing to read at the start of a new one.
#Active Branch
grenade-initial-imp — branched from main after the sidearm feature was merged (PR #26, commit 3c8d779). Hand grenades, Increment 1 (frag). The C++ foundation is done + committed; the rest is editor/BP work. Sidearm is fully done + merged (incl. BUG-021 draw-race + BUG-022 holster-pose freeze, both fixed).
#What We're Building
Hand Grenades. Full plan: Docs/GrenadeSystem.md. Frag-first, simple throw, no cooking. GAS throw ability + replicated projectile (bouncing ProjectileMovement, timed fuse → sphere-sweep GE radial damage + explosion cue). Grenade count = replicated int32 on the character. Increment 2 = plasma/switching/polish.
#Where We Left Off
2026-06-23 — grenade Increment 1 is ~90% done. Most of the 2026-06-19 "remaining" checklist has since been built (assets are in the tree, uncommitted). Recon this session reconciled the doc to reality.
BUG-024 (2026-06-24) — equip sound heard map-wide. Remote players' equip sound played at full volume
everywhere (most visible on respawn). RCA (corrected): the equip GameplayCueNotify_Burst plays
Rifle_Raise_Cue(which HAS attenuation) withDoNotAttach, so it spawns atCueParameters.Location—
which ExecuteEquipCue never set → played at world origin (0,0,0), within earshot of the whole compact
TestMap. (NOT a Play Sound 2D node — there is no sound node; first guess was wrong.) **C++ fix (sufficient,
needs rebuild):ExecuteEquipCuenow setsCueParams.Location/Normal. Robustness DONE:** equip cues'
burst-soundAttachPolicy→ AttachToTarget (both AR + Shotgun, via bridge). Full RCA:BugTracker.mdBUG-024. Also new reusable
SA_Default3Dattenuation (/Game/SystemLink/Audio/Attenuation/) for future 3D sounds.
Audio audit (2026-06-25): swept all Play Sound 2D (C++ + BP). C++ clean (sidearm draw/holster gated).
BPs:WBP_SL_RespawnOverlay(UI, fine),BP_SL_MasterChief(correct SC_/SC_TP_ local-2D + attenuated-3D
pairs — fine). One finding →BP_GA_SL_Grenade_Throw:SC_FragThrowwas played 2D in a Local-Predicted
ability → listen-host heard every player's throw. Decision: thrower-only feedback (gate local). Done:
cleared the now-meaninglessSA_Default3DoffSC_FragThrow(bridge). PENDING BP STEP: gate the throw's
Play Sound 2Dnode behindIs Locally Controlled(Branch, True → play) so only the thrower hears it.
Grenade C++ — DONE + committed (a308b5e, then polished in ee1109a): tags, USLGrenadeDataAsset + ASLGrenadeProjectile, ASLCharacterBase grenade count (GrenadeCount + CurrentGrenadeData, both replicated) + OnGrenadeCountChanged delegate + ThrowActiveGrenade() + loadout DefaultGrenade + controller GrenadeAction. ee1109a MP polish: BeginGrenadeThrow() schedules the authoritative spawn on a character world timer (ThrowReleaseTime) — fixes BUG-023 (clients couldn't throw: the spawn was on a Events.Grenade.Release anim notify and the server doesn't tick a remote client's montage). Plus cosmetic SpinMovement, Halo-style accelerating FuseLight, grenade blocks the Pawn channel (bounces off players, thrower excluded at launch).
Grenade — built but UNCOMMITTED (the bulk of the old checklist):
BP_GA_SL_Grenade_Throw(ability graph) — items 1+2: exists, granted inAS_AbilitySet_Default.
GC_SL_Grenade_Frag_Explosioncue — item 4: exists;GameplayCue.Grenade.Frag.Explosionregistered.
- FP+TP throw montages (
AM_MC_FP_Frag_Throw,AM_MC_TP_Frag_Throw),DA_SL_Grenade_Fragfilled — item 3.
- Real frag mesh (
Frag/SM_Modern_Weapons_Grenade_01+ materials) + realBP_SL_GrenadeProjectile_Frag—
item 7. Bounce/explosion audio (SC_FragExplosion, SC_Frag_Bounce, tinks).
IMC_Defaultmapped (item 6),DA_DefaultLoadoutupdated.
- Beyond plan:
GE_SL_Grenade_Cooldown,CS_ExplosionShakecamera shake.
- Uncommitted C++ refinement (
SLGrenadeProjectile.*,SLGrenadeDataAsset.h): replicates the whole
GrenadeData (was just FuseDuration) so clients sync spin + blink; new cosmetic BounceSound field + OnProjectileBounced. ⚠ Needs a rebuild to go live (editor/Live-Coding won't surface the new field).
Grenade pickup + death-drop — C++ DONE (2026-06-23), ⚠ pending rebuild + BP:
ASLGrenadePickup(World/SLGrenadePickup.h/.cpp) — auto-collect pickup mirroringASLAmmoPickup. Tops
up the character's count for its GrenadeData type (clamped to MaxCount); ignores characters whose active type doesn't match or who are already full (left in world). No prompt — collects on overlap.
USLGrenadeDataAsset::DroppedPickupClass— which pickup BP to spawn on death (mirrors the weapon data's
field). Null = drop nothing.
ASLCharacterBase::DropGrenadesOnDeath()— spawns ONE pickup carrying the full current count, launches it
with the same DropSpawnOffset/DropLaunchPitch as weapon drops, then zeroes the count. Called from USLGameplayAbility_Death right after DropAllWeaponsOnDeath().
- Editor steps: (a) rebuild (new class + UPROPERTYs — Live Coding won't surface them); (b) create
GrenadeAmount); (c) setDA_SL_Grenade_Frag.DroppedPickupClass = BP_SL_GrenadePickup_Frag; (d) test:
BP_SL_GrenadePickup_Frag (subclass ASLGrenadePickup, set bounce mesh + GrenadeData=DA_SL_Grenade_Frag
place one → walk over → count rises (capped at MaxCount, ignored when full); die holding grenades → a pickup drops carrying the count → collect it back.
Grenade — GENUINELY OPEN:
- HUD grenade-count widget — the one checklist item with nothing built. Pure BP, bind
OnGrenadeCountChanged (delegate + GetGrenadeCount()/GetCurrentGrenadeData() all ready in C++).
- Rebuild to compile the uncommitted
GrenadeData-replication +BounceSoundC++.
- Verify the BUG-023 BP rewire:
BP_GA_SL_Grenade_Throwmust callBeginGrenadeThrowonce after the
count gate (NOT route the Release notify / a Delay into ThrowActiveGrenade). Can't confirm from the binary — needs a 2-player PIE throw test (host AND client must both spawn a grenade).
- Commit the WIP at a clean save point (large uncommitted pile: grenade
.uassets + the C++ refinement).
⚠ Build env note: one UBT build flagged a banned MSVC toolchain (14.40–14.43 → install 14.44.35207).
Seen once; if a full "close editor + build" fails on the toolchain, install the recommended MSVC component.
The dated lists further down are historical context from earlier sessions.
Sidearm — done & committed:
- C++ foundation: tags,
FP/TPSidearmMesh, replicatedSidearmWeapon,SetSidearmWeapon,
LoadDefaultLoadout spawns DefaultSidearmClass, SLAnimState.bIsSidearmActive, loadout/data-asset fields.
BP_GA_SL_SidearmMode(hold-LT draw, pure tag manager) + LT input wiring.
- Mesh visibility via
USLWeaponsComponent— now poll-driven inBuildAnimSnapshots(BUG-014: the
one-shot tag-event path was unreliable on the listen-server host for proxies; see feedback_gas_tag_callback_patterns Rule 3).
- Sidearm Blend AnimGraph node — custom C++ node (
FAnimNode_SidearmBlend+ new
SystemLinkCoreEditor module) compositing sidearm-over-lowered-primary at clavicle_r, gated by Is Sidearm Active. Committed + pushed (2234ce7). Doc: SidearmMode.md §4.1.
- TP sidearm ABP worked out (idle / run-walk / crouch) using the node.
Sidearm — shooting (VERIFIED ✅ 2026-06-09):
- C++ (
GetActiveFireWeaponindirection + actor-owned ammo, option A) acrossUSLWeaponsComponent+
USLGameplayAbility_Fire — committed. Primary fire unchanged when no sidearm is drawn. Doc: SidearmMode.md §8.1; memory project_sidearm_fire_routing.
- Editor tasks 1–4 done via the Unreal MCP bridge:
BP_GA_SL_Pistol_PrimaryFire
(RequiredWeaponClass=Pistol actor, ActivationRequiredTags=SidearmActive), AR+Shotgun primary blocked while SidearmActive, pistol fire ability granted via AS_AbilitySet_Default (sidearm is a separate always-carried slot — never runs through GrantWeaponAbilities, so it MUST be set-granted), DA_SL_Pistol.PrimaryFireMode filled (single-shot, 400 RPM, 26 dmg, 12 ammo, Muzzle socket, no AmmoDecrementEffect).
- PIE-verified (listen server, 2 players): single-shot fire, independent sidearm ammo, and the
host-validates-client-damage RPC path (GetActiveFireWeaponData() resolves the sidearm server-side). Committed as d5e8ddf.
Weapon Action Lock — States.Weapon.Busy (done 2026-06-13). GAS-native lock so exclusive actions block other weapon actions. Native tag added (SLTags.h/.cpp) + full BP matrix wired via the bridge (fire/melee/sidearm/equip × 13 abilities). Exclusive actions own Busy; everything blocks Busy; switching also blocks Firing (fixes "switch to pistol mid-AR-fire → pistol full-autos"). Equip OWNS but does NOT block Busy (FP+TP equip run concurrently — blocking would mutually cancel them). Full spec + matrix + caveats: Docs/WeaponActionLock.md.
Pistol full-auto (BUG-018) — root-caused + fixed. Was the switch-mid-AR-fire path: the AR's full-auto fire ability is one looping activation that re-resolves GetActiveFireWeapon each iteration, so drawing the sidearm mid-loop fired the pistol at AR cadence. Fixed by the Firing block on SidearmMode (the AR loop owns Firing continuously, so the draw is blocked). No separate clean-draw full-auto bug — pistol reads GetActiveFireMode = SingleShot and fires once. Also fixed a latent bug: StopPrimaryFire used GetPrimaryFireMode() (equipped) instead of GetActiveFireMode(true) (active) → swapped to active (spam-tap fix). See BUG-018.
RefireLock split + sidearm action-lock fixes — DONE & PIE-verified ✅ (2026-06-13). All three C++ changes rebuilt and live: StopPrimaryFire uses GetActiveFireMode; RequestEquip blocks main-weapon swap/pickup while sidearm drawn; RefireLock split (native tag + RefireLockDuration field + fire-ability loose-tag management). BP re-wire done via bridge (SidearmMode + 6 equips block on RefireLock not Firing). DA_Shotgun.RefireLockDuration = 0.2 (PostFireDelay 0.5) — switch to pistol 0.2s after a shot while re-fire stays gated. Tested good, feels good. Full design: Docs/WeaponActionLock.md.
Design decisions (2026-06-14): Sidearm now drops on death as a pickup (supersedes BUG-013's "destroy"). Pistol pickup: same type → ammo refill on the sidearm; different → swap into sidearm slot, old drops as a pickup.
Drop-all-on-death + sidearm pickup routing — DONE in C++, ⚠ PENDING REBUILD (2026-06-14):
ASLCharacterBase::DropAllWeaponsOnDeath()drops allCarriedWeapons+ the sidearm (clears slots);
called from USLGameplayAbility_Death before OnDeathStarted (deterministic, authority).
ASLWeaponPickup::OnCollected/AcceptPickupbranch onWeaponData->bIsSidearm→ route to the sidearm
slot (same type = ammo via AddAmmo; different = SetSidearmWeapon, which drops the old). Data-driven, so placed AND dropped/death-dropped pistols route correctly.
DA_SL_Pistol.bIsSidearm= True (confirmed).DroppedPickupClass= None (needs the pistol pickup BP).
Rebuilt + wired (2026-06-14): C++ live. BP_SL_WeaponPickup_Pistol created (/Game/SystemLink/Pickups/Weapons/Pistol/, dup of Shotgun2) with WeaponActorClass = BP_SL_WeaponActor_Pistol; DA_SL_Pistol.DroppedPickupClass → it.
REMAINING (Beepers, editor):
- Set the pistol pickup's display mesh —
BP_SL_WeaponPickup_Pistolstill shows the shotgun mesh (SCS
component from the dup; not reachable via bridge). Set it to the pistol mesh (SK_Pistol) — unless the pickup shows the weapon actor's mesh at runtime, in which case it's already right (check on placement).
- Remove weapon-drop nodes from the
BP_SL_MasterChiefOnDeathStartedoverride — C++ drops everything
now; leftover BP drops are redundant no-ops. Keep any non-drop death logic (ragdoll/score/etc.).
- Test: die → all carried weapons + the sidearm drop as pickups; pick up same pistol → ammo refill;
different pistol → swaps into sidearm slot + old drops; pistol pickups route to the sidearm slot (not main inventory); placing BP_SL_WeaponPickup_Pistol in the level is collectable.
ACTIVE TASK — Pistol HUD (2026-06-15): sidearm ammo indicator — pistol icon + ammo count. C++ scaffolding landed (see "Sidearm HUD" below); BP authoring + data-asset icon are the remaining editor steps. Reticle swap on draw (OnSidearmActiveChanged, now edge-correct) is the sibling task — the indicator is persistent (keyed off OnSidearmChanged actor validity), while the reticle swap still keys off the draw/holster OnSidearmActiveChanged toggle.
Sidearm HUD — C++ done (2026-06-15): reuses the existing USLAmmoWidget (its SetWeapon(actor) binds the actor's OnAmmoChanged + pushes the initial value — the sidearm is an ASLWeaponActor, so no new widget class needed). USLHUDWidget gains an optional SidearmAmmoWidget (BindWidgetOptional) + an InitializeSidearmIndicator(Character) helper, called from ASLPlayerCharacter::InitializeLocalPlayerHUD alongside the ammo strip. Visibility is persistent — shown whenever the player has a sidearm (drawn or holstered), hidden only when there's no sidearm — driven off OnSidearmChanged actor validity. Icon comes from WeaponData->WeaponIcon via the new OnWeaponSet BIE on USLAmmoWidget. Remaining (editor): author the sidearm indicator BP (USLAmmoWidget subclass: Image + ammo Text, implement OnAmmoChanged + OnWeaponSet), bind it as SidearmAmmoWidget in the HUD BP, set DA_SL_Pistol.WeaponIcon. See Docs/SidearmMode.md §12.
OPEN THREADS (pick up here — as of 2026-06-11):
- ⚠ REBUILD PENDING — 4 uncommitted C++ changes, not yet compiled. Close editor → build → reopen
ASLCharacterBase::GetActiveWeaponMesh()—BlueprintPure, view+slot-aware (FP/TP × sidearm/main).USLWeaponsComponent::OnSidearmActiveChanged(bool)—BlueprintAssignable, edge-detected fromFSLWeaponViewDriverBaseanim-layer swap fix (BUG-016) — targetedUnlinkAnimClassLayersinsteadSLGameplayAbility_Melee::GetWeaponData()→GetActiveFireWeaponData()(pistol-whip, #2).
(Live Coding won't register the new UFUNCTIONs/delegate for BP):
Attach fire FX to this. ApplyPistolFireEffects already works; optionally simplify it onto this.
UpdateSidearmMeshVisibility. For the reticle swap (#5).
of LinkAnimClassLayers(nullptr), so swapping the main weapon no longer drops the sidearm layer.
(Note: the controller's sidearm-redraw change was reverted to Started — net-zero diff there.)
- Pistol-whip melee (replaces auto-holster). Melee while sidearm drawn = pistol-whip: pistol stays
out, swing uses the pistol's melee values. C++ done + rebuilt (#1). EDITOR STEP — DONE ✅ (2026-06-13): verified BP_GA_SL_Melee.CancelAbilitiesWithTag is empty (no SLTags.Abilities.SidearmMode), so melee no longer cancels sidearm mode. Full tag config confirmed: AbilityTags=Abilities.Melee, ActivationOwnedTags=States.Character.Meleeing, ActivationBlockedTags=States.Character.Dead+Meleeing. ANIM (remaining): pistol-whip melee state gated on bIsMeleeing && bIsSidearmActive (Always Reset on Entry + Inertialization + MeleeImpact notify); put the DisableLHIK curve on the pistol melee clip so off-hand IK releases during the swing (Docs/LeftHandIK.md). Doc: SidearmMode.md top callout.
- Cues — DONE ✅. Pistol-specific cues live: tags
GameplayCue.Weapon.Pistol.PrimaryFire/.Impact
registered, GC_SL_Pistol_PrimaryFire/_Impact tagged, DA_SL_Pistol Fire/ImpactCueTag repointed (cleared the AR duplicate-tag collision — BUG-015). FP/TP/local/non-local all route through ApplyPistolFireEffects in BP_WeaponLibrary. PIE-verified. PostFireDelay = 0.2 placeholder (tune to the fire clip; AM_Pistol_Fire is 0.667s).
- TP crouch-fire anim (task #6). FP fire anim DONE. TP: firing while crouched stands the character up
bIsFiringare onAnimStateSnapshot.
— needs crouch-aware Firing. Either a CrouchFiring state gated on bIsCrouched (crouch-fire clip), or upper-body-only fire via Layered Blend Per Bone (lower body keeps crouch, no extra clip). bIsCrouched
- Reticle swap (task #7) — DONE ✅ (2026-06-15). C++:
OnSidearmActiveChanged(edge-correct) +
USLWeaponsComponent::GetActiveReticleClass() (BlueprintPure: sidearm's PrimaryReticleClass while SidearmActive, else equipped's; falls back to equipped's when the active weapon has none) + SwapReticle no-ops on an unchanged class. BP wired in WBP_SL_HUDWidget (Initialize Variables / Bind to Equip Event / Bind to Sidearm Events groups): equip + OnSidearmActiveChanged + OnSidearmChanged all route through SwapReticle(GetActiveReticleClass(), GetOwningPlayer), so draw/holster AND pickup-swap-while-drawn all update correctly. DA_SL_Pistol.PrimaryReticleClass set. Verified working. Full reference: SidearmMode.md §13.
- Verify after build: BUG-016 (pistol anims persist through Shotgun↔AR swap) + pistol-whip melee feel.
- Later: sidearm draw is INSTANT —
SidearmDrawDuration/SidearmDrawingare unused scaffolding until
a draw anim exists (then gate fire on not-SidearmDrawing); HUD sidearm ammo indicator (bind SidearmWeapon->OnAmmoChanged); BUG-013 sidearm-on-death cleanup (still open).
Sidearm DRAW — DONE & feels good ✅ (2026-06-14). Full draw landed: gameplay windup + fire-block + observer-correct animation + None-guard. FSLAnimState has bIsSidearmDrawing + bIsSidearmHolstering, both PURELY COSMETIC anim drivers latched in BuildAnimSnapshots off the rising (draw) / falling (holster) edge of the replicated SidearmActive tag — so observers see them. Decoupled from the SidearmDrawing gameplay tag, which does the fire-block (owning client + server only).
- Gameplay:
BP_GA_SL_SidearmMode§3 windup graph (Commit-gated Add +OnEndAbilityRemove,
Should Replicate=FALSE, Is Valid(GetSidearmWeaponData) guard on the Delay duration); SidearmDrawing in BP_GA_SL_Pistol_PrimaryFire.ActivationBlockedTags; DA_SL_Pistol.SidearmDrawDuration=0.5.
- Observer fix:
bIsSidearmDrawingrising-edge latch offSidearmActive(heldSidearmDrawDuration,
fallback SidearmDrawCosmeticFallback 0.5s) — the loose tag doesn't replicate to proxies.
- New BP accessors on
USLGameplayAbility:GetSidearmWeapon()/GetSidearmWeaponData()(null-safe).
- Full spec:
Docs/SidearmMode.md§3 + §4.2.
HOLSTER — DONE ✅ (2026-06-15, cosmetic). Pose + mesh now both hold through the holster tail. Implementation (all cosmetic — gameplay returns to the primary the instant SidearmActive drops):
- Pose: the
Sidearm Blendnode'sIs Sidearm Activepin is driven by `Is Sidearm Active OR Is
Sidearm Holstering (OR node off Break SLAnim State) in ABP_MC_TP_Pistol — keeps the sidearm branch blended in during the tail (the node's bIsSidearmActive alone would crossfade it out over BlendTime`).
- Mesh: BuildAnimSnapshots now calls `UpdateSidearmMeshVisibility(bIsSidearmActive | bIsSidearmHolstering)` |
|---|
(SLCharacterBase.cpp:658) so the pistol stays visible until the holster anim ends instead of popping out.
- Duration is now per-weapon: new
USLWeaponDataAsset::SidearmHolsterDuration(default 0.5s, tooltip
flagged PURELY COSMETIC). The falling-edge latch reads it (SLCharacterBase.cpp:644), falling back to the character's SidearmHolsterCosmeticTime (0.3s) when no weapon/data resolves. Set DA_SL_Pistol.SidearmHolsterDuration to the holster-clip length (matches SidearmDrawDuration since the holster is the draw played in reverse).
- Draw + holster sounds (local-only, data-asset driven): new
USLWeaponDataAsset::DrawSoundand
HolsterSound (USoundBase, tooltips flagged PURELY COSMETIC / LOCAL-ONLY). Played via UGameplayStatics::PlaySound2D in BuildAnimSnapshots on the draw rising edge / holster falling edge, gated on IsLocallyControlled() — not replicated, observers don't hear them (the gate also prevents a listen-server host hearing remote players' sounds). Draw sound was MOVED out of BP_GA_SL_SidearmMode onto the data asset so the ability stays weapon-agnostic/reusable. Editor step: delete the draw-sound nodes from BP_GA_SL_SidearmMode, then set DA_SL_Pistol.DrawSound + .HolsterSound.
- Delegate-edge fix:
UpdateSidearmMeshVisibilitynow edge-detectsOnSidearmActiveChangedon the
gameplay SidearmActive tag (IsSidearmActive()), NOT the visibility flag — otherwise the reticle-swap delegate would fire ~0.5s late (at the end of the cosmetic tail) instead of when gameplay returns to the
| primary. Mesh visibility still uses `active | holstering`; the broadcast uses the tag. |
|---|
- ⚠ New UPROPERTYs (
SidearmHolsterDuration,DrawSound,HolsterSound) → **full rebuild + editor
restart** (Live Coding won't surface the fields), then set the data-asset values.
Holster currently reuses the draw clip in reverse and feels good. Optional later: a dedicated draw/holster ABP state pair (bIsSidearmDrawing / bIsSidearmHolstering flags + windup timing are already in place).
Side work (non-branch): the ClaudeFace repo has uncommitted Loom changes this session — per-model
substrate signature, Face retired, manual-posture minimum-dwell, session-start auto-open hook. See
Docs/WorkingWithClaude.md(2026-06-10 addendum) + memoryproject_claude_face.
Melee — fully done:
- Input,
BP_GA_SL_Melee, ability-set grant, fire-block tag — done.
- ABP melee state for AR + Shotgun + Unarmed (FP + TP), Always Reset on Entry, Inertialization.
MeleeImpactAnimNotify on FP + TP melee anims.
- C++ features: backstab multiplier,
MeleeImpactCueTag,OnMeleeWallHitBIE, camera shakes
(Swing/Hit/WallHit on weapon data asset — swing only plays on clean miss), timer-driven authority damage, C++-owned ability end timer, eye-origin sphere sweep.
- All melee values (damage, range, radius, backstab, shakes, timing) moved to
USLWeaponDataAsset.
- Unarmed-as-default-weapon:
DefaultUnarmedDataonUSLWeaponsComponent,GetEquippedWeaponData()
fallback, sway/lag/obstruction/scale all routed through it when unarmed.
FirstPersonMeshScalemoved from character to weapon data asset, re-applied on every weapon swap.
MuzzleSocketno-mesh warning fixed inResolveMuzzleLocation.
Weapon Clipping — done (FP): Docs/WeaponClipping.md. Optional leftover: AR FP ABP retract additive.
Respawn stuck-bug — FIXED. See RespawnSystem.md.
#Future-work doc also written this branch (not part of melee work)
Docs/MobilityAssistModule.md — full plan for Doom Eternal-style double jump + dash via an equippable Mobility Assist Module (MMA-3). Added as item #6 in Docs/Progress.md backlog. Not in scope for the melee-ability branch.
#What Was Done This Session (2026-05-19)
TP shotgun ABP — non-looping fire anim fix
- Symptom: shotgun fire animation in
ABP_MC_TP_Shotgun'sFiringstate played once on first shot, then never again on subsequent shots. AR equivalent worked fine.
- Root cause: non-looping asset players in state-machine states retain their time position across re-entries. After the first play, the asset player sits at t=end. Re-entering the state finds nothing left to play.
- Fix: enabled "Always Reset on Entry" on the
Firingstate. Asset player resets to t=0 on every state entry. Looping anims (AR, crouch) don't hit this because they wrap around continuously.
- Memory pointer:
feedback_animbp_state_reset_on_entry.md— this fix is documented for future state-machine work.
Inertialization node added — ABP_MC_TP_Shotgun AnimGraph: the Not Firing → Firing transition used Inertial Blend logic but had no Inertialization node downstream, producing a warning on quit. Single Inertialization node placed between the state machine output and the Layered Blend Per Bone.
FSLAnimState::bIsFirstPerson added
- New
UPROPERTY(BlueprintReadOnly)bool on the anim snapshot, sourced fromWeaponsComponent->IsFirstPersonView()
- Always false on remote/non-locally-controlled pawns — observers naturally see TP anim paths
- Wired in
BuildAnimSnapshots()alongside existing bools
- Use case: shared shotgun ABP branches between FP and TP fire animations on this bool
Sequencer animation authoring workflow documented
Docs/SequencerAuthoringWorkflow.md— full pipeline (create sequence from Content Browser, drag mesh assets directly, convert to Spawnable before adding constraints, bake to AnimSequence)
- Recovery from the
TransformableComponentHandleharvest crash (caused by Possessable bindings + constraints) — lesson learned the hard way mid-session whenLS_TP_Shotgun_Idle2.uassetcorrupted and crashed the editor on save
- Memory pointer:
feedback_sequencer_spawnables.md
#What's Next (in order)
#Hit Detection Refinement — complete ✅ (2026-05-20)
Per-bone collision via Physics Asset + custom ECC_WeaponTrace channel landed and verified end-to-end. Hit.BoneName populates with real bone names; bone-group anchors + per-weapon damage multipliers wired through both authoritative damage sites.
Implementation summary:
- Custom trace channel
ECC_WeaponTrace(Default Response: Block, mirrorsECC_Visibilitybehavior). Channel landed onECC_GameTraceChannel1, aliased inTypes/SLCollisionChannels.h.
- Capsule ignores
ECC_WeaponTrace(Custom collision preset onBP_SL_MasterChief). Physics Asset bodies onSK_MasterChiefblock it via the SkeletalMeshComponent's Custom collision preset.
- C++ swap: all four trace call sites moved from
ECC_Visibility→ECC_WeaponTrace(Server_ProcessShotsline trace + sphere sweep,ExecuteFire_ListenServerHostline trace,TracePredictedHitline trace).
ESLBoneGroupenum (Body/Head/Arms/Legs),FSLDamageMultipliersstruct onFSLWeaponFireMode(defaults: Body 1.0, Head 2.0, Arms 0.75, Legs 0.75),BoneGroupAnchorsmap +GetBoneGroup()parent-chain walk onASLCharacterBase.
- Debug prints gated on
bDebug(on the fire ability and weapons component independently). Prints bone name + resolved group + multiplier + final damage on every confirmed hit.
Editor configuration on BP_SL_MasterChief.BoneGroupAnchors (~5 entries — anchor walk handles descendants):
neck_01→ Head,upperarm_l/r→ Arms,thigh_l/r→ Legs
#Multi-Slot Weapon HUD — complete ✅ (2026-05-22)
Halo 3-style stacked weapon-slot strip. Both carried weapons show live ammo simultaneously; equipped weapon on top (active scale/opacity), secondary below (dimmed). Ammo updates instantly on shots and pickups. Legacy SwapAmmoWidget path fully removed.
Key implementation notes for future reference:
RebuildSlotsadds the equipped weapon first (guarantees it's at container index 0 — no reorder pass needed)
HandleEquippedWeaponChangedcallsRebuildSlots(notUpdateActiveSlot) — ensures fresh ammo values at equip time
AddAmmoon the equipped-weapon path callsSetCurrentAmmoafterSetNumericAttributeBaseto broadcast the delegate (bypassesPostGameplayEffectExecute)
- See
Docs/WeaponsSystem.md→ Ammo Display for the full architecture
#Queued next (in order)
- Melee — ✅ done.
- Weapon Clipping — ✅ done (FP). Optional leftover: AR FP ABP retract additive + TP eval (likely ignore).
- Secondary Weapon — Left-Hand Sidearm — IN PROGRESS. Foundation + draw + mesh + TP animation done & committed. Open: shooting (C++ written, uncommitted/untested → build, editor tasks, PIE, commit). Then HUD ammo indicator + death cleanup (BUG-013).
- Melee polish — minor remaining: unarmed melee anim, SFX assets, camera-shake asset assignment.
- Menus / CommonUI foundation — next after sidearm. Design + footguns fully documented in
Docs/UISystem.md. Layer scaffolding in place; missing input data, controller data assets, C++ base classes, pop API, pause wiring. SeeDocs/Progress.md#5.
#Reference for any future new weapon
Docs/NewWeaponChecklist.md — end-to-end checklist. AR is the reference pattern; duplicate and customize.
#Reference for any future new weapon
Docs/NewWeaponChecklist.md — end-to-end checklist (tags, meshes, sockets, data asset, abilities, cues, animations, HUD, pickup, test). Use as the canonical guide when adding a new weapon. AR is the reference pattern; duplicate and customize.
#Deferred shotgun polish (still outstanding from previous branch, not blocking new work)
- Shotgun fire cosmetics —
BP_GA_SL_Shotgun_PrimaryFire → OnLocallyPredictedShotFiredstill uses AR sound + AR muzzle flash Niagara. Swap when shotgun-specific assets exist.
- Shell ejection — designed but not implemented. Plan: Niagara mesh emitter + GameplayCue (
GC_SL_Shotgun_ShellEject) triggered from a pump-anim AnimNotify sendingSLTags.Events.Weapon.ShellEject. AddShellEjectSocketto the shotgun skeletal mesh.
- FP shotgun fire animation polish — barrel-snap-up + pump cycle on the FP weapon mesh.
#Test Checklist
Authoritative checklist lives in Docs/Shotgun.md → Test Checklist. Update it there as items pass. For the recent multiplayer-correctness fixes (cosmetic gating, trace channel parity, RPC batching, cancelable fire ability, cue contract), follow Docs/FireAbilityNetworkTesting.md.
#Key Docs for This Work
| Doc | When to read |
|---|---|
Docs/Shotgun.md | Full spec — data asset values, tag names, asset locations, build order, authoritative test checklist |
Docs/WeaponsSystem.md | How the weapon system works end-to-end (includes "Authoring a Fire Cue" contract) |
Docs/WeaponFireAbility.md | Fire ability internals |
Docs/FireAbilityNetworkTesting.md | PIE procedure for verifying multiplayer correctness of the fire flow |
Docs/WeaponEquip.md | Equip ability pattern |
Docs/Progress.md | Full project feature list and backlog |
#State of the Shotgun (2026-05-19)
- FP idle animation in place — looks correct, hand grip and weapon position good
- TP fire/pump animation working via state machine in
ABP_MC_TP_Shotgun(Firing state, non-looping anim, Always Reset on Entry + Inertialization)
bIsFirstPersonavailable onAnimStateSnapshotfor FP/TP branching inside shared ABPs
- Fire cosmetics still cloned from AR (
OnLocallyPredictedShotFireduses AR sound + muzzle flash) — deferred polish
- Shell ejection — designed (Niagara mesh emitter + GameplayCue + AnimNotify), not implemented — deferred polish
#Deferred Cleanup (do in a content-pass session, not mid-feature)
#Assualt → Assault content rename
14 .uasset files still carry the Assualt typo. Rename each in the Editor's Content Browser (right-click → Rename) so reference updates are automatic; do NOT rename on disk. After the renames, do a single project search-and-replace across Docs/ for "Assualt" → "Assault" to clean up doc references.
Code-referenced (most important — named as the parent BP to duplicate in Docs/Shotgun.md and Docs/WeaponFireAbility.md):
Content/SystemLink/AbilitySystem/Abilities/Weapons/AssaultRifle/BP_GA_SL_AssualtRifle_PrimaryFire
Character animations (6 — AssaultRifle/ and AssaultRifle/FP/ subfolders):
assualt-rifle-idle,assualt-rifle-idle-2,assualt-rifle-idle-breathing,assualt-rifle-run,FP/assualt-rifle-idle
Content/SystemLink/Characters/MasterChief/Anims/Library/Animations1/AssaultRifle/assualt-rifle-idle
Weapon visuals (6):
Content/SystemLink/Weapons/AssaultRifle/Mesh/M_AssualtRifleClip
…/Materials/assault-rifle-clip_M_AssualtRifleClip_BaseColor+_Normal+_OcclusionRoughnessMetallic
…/Materials/HUD/TX_AssualtRifleHud
…/UI/assualt-rifle-reticle+_Resized
#Equip-ability tag pattern — reconciled ✅ (2026-05-22)
| All three sources now use the canonical registered pattern `SLTags.Abilities.Equip.<FirstPerson | ThirdPerson>.<Weapon>`: |
|---|
SLGameplayAbility_Equip.hdoc comment updated
Docs/Shotgun.mdCDO setup section updated
Config/DefaultGameplayTags.iniis the source of truth (was already correct)
#What NOT to Touch This Session
USLGameplayAbility_FireC++ — pellet loop is done, don't re-open it
- Weapon swap HUD — deferred, not part of this branch