Melee Drive · Guns · Singularity (Lyra / Plugify)
Status: DEFERRED · 2026-10-26 — resume after own Melee Drive foundation ships · tag-gate → GAS map added 2026-11-05
Authority: FS-CMB-002…006 · GDD One Flow · two verbs · singularity design
Host: Lyra 5.8 · Plugify: Melee Drive → TFAMelee; Flow+Singularity → TFASingularity; guns → TFAWeapons; camera/stance/ASC host → TFACombat spine — do not fork LyraGame (02-gfp-cookbook.md Plugify lock)
This file is self-contained. Implement from Bible + FS locks here — no external video or third-party action-system follow-along required.
Provenance (optional): tag-gate / action-DA patterns absorbed from a C++ Action System reference (Game Code Mastery); mapped onto Lyra GAS under TF Plugify. Do not add UACSAction / UACSActionComponent.
Park order: (1) Studio Melee Drive foundation → (2) resume this file (Motion Warp · ANS capsule/box · Counter · gun matrix). Flow / Singularity design stays locked; this is the parked implementer path.
Hit registration lock (2026-11-02): Hybrid auth FS-ARCH-013 · Demo = Anim Notify + reach capsule/box · Full polish = optional weapon-socket sweep (bUseSocketSweep). Spec: 2026-11-02-melee-hit-registration-design.md. ANS “box-trace” below = Demo capsule/box path — not range auto-connect.
Air Melee Drive lock (2026-11-02): Jump / double-jump air → Strike legal; reuse ground directional slash montages; disable root motion / Motion Warp while airborne. Downward slash = later art (not Demo exit). Spec: 2026-11-02-air-melee-drive-design.md · FS-CMB-002.
Motion Matching lock (2026-11-02): Demo + Full Pose Search: motion-matching-demo-and-full.md · kanban Demo demo-motion-matching · Full combat / melee. This Melee Drive doc stays montage + Motion Warp + notify hits for Melee Drive; Full strike choosers are §6 of the MM doc after Melee foundation.
TBD-056 — Flow toughness (no Block): Locked 2026-11-05 — Health-only dump. Shield/Armor chips that do not reduce Health do not dump Flow. Spec/FS-CMB-004.
Player fantasy: Terra Firma Dual Armory — Melee Drive and Precision Fire (guns) share one Kinetic Flow ring; Singularity is the climax; Ultimate stays separate. Includes Anim Notify hit detects and an Arkham-style Counter window with UI hooks.
0. Verification — generic melee pitfalls → TF locks
Section titled “0. Verification — generic melee pitfalls → TF locks”| Generic melee pitfall | Terra Firma lock | Adaptation |
|---|---|---|
Patch host LyraGame.Build.cs / dump types in game module |
GFP packing FS-ARCH-010 / Plugify | Melee types in TFAMelee; Flow/Singularity in TFASingularity; MotionWarping deps on the owning GFP |
| Custom freestyle action component as the combat runtime | Lyra ASC + TFA naming | Prefer UTFAMeleeDriveCombatComponent (helpers only), UTFAGameplayAbility_MeleeStrike, UTFAGameplayAbility_Counter — abilities are the verbs |
| Unbounded combo score | FS-CMB-004 tiers | Combo feeds Kinetic Flow tiers (0–15 / 15–30 / 30+ Singularity); Asymmetric credits (melee kill +2 · gun body +1 · headshot +2) |
TakeDamage on hit |
Hybrid hit auth / GAS | Apply Gameplay Effect via ASC (server validates); presentation may predict |
| Ambient magnet / Tick target pull | FS-CMB-002 | Warp only on explicit Attack press toward a scored target while grounded. Forbidden: ambient magnet · any root/warp while airborne |
| Counter missing / wrong warp target | Extends Melee Drive | Counter is melee verb; warp to ActiveAttackingEnemy; success ticks Flow +1 (or kill). Guns: §7 |
| Guns on a second combo meter | One Flow · two verbs | §7 gun interaction matrix is required |
0.5 Tag gates → GAS (Melee Drive foundation map)
Section titled “0.5 Tag gates → GAS (Melee Drive foundation map)”Tag-gated start/stop, montage windows, and cancel rules are Gameplay Ability concerns on the hero ASC. UTFAMeleeDriveCombatComponent owns target pick, Counter window, and Flow credit helpers — not a second action runtime.
Concept map
Section titled “Concept map”| Pattern | TF / Lyra GAS | Bible / FS lock |
|---|---|---|
| Action data asset | UGameplayAbility in TFAMelee (+ family DA for reach / montages) |
Plugify · FS-ARCH-010 |
| Action component | ASC on TFACombat spine + UTFAMeleeDriveCombatComponent |
GFP cookbook · no sibling #include |
| Action Tag | Ability AbilityTags / InputTag via AbilitySet |
Lyra IMC |
| Granted active tags | ActivationOwnedTags (or duration GE) |
e.g. State.Melee.Striking |
| Blocked tags | ActivationBlockedTags |
e.g. ADS blocks Counter — §7 |
| Cancel into | CancelAbilitiesWithTag / cancel sets |
land mid-swing · stance switch |
| CanStart / tag satisfy | CanActivateAbility + stance / grounded / Counter-window checks |
FS hybrid camera · FS-CMB-002 |
| Start / Stop | ActivateAbility / EndAbility |
server GE auth · FS-ARCH-013 |
| Default / available actions | Experience AbilitySet inject — not permanent LyraGame defaults |
Demo Experience |
| Play montage | AbilityTask_PlayMontageAndWait + ANS hit window |
FS-ARCH-013 · FS-CMB-002 |
| “Combo” / special-state attack | Counter window + stance tags; Kinetic Flow is kill-tier meter, not an unbounded action combo score | FS-CMB-004…005 |
| Jump / dodge as actions | Movement / parkour / Dodge abilities elsewhere — Melee Drive does not re-own Jump | TFACombat / Carbon absorb |
| Kill → climax | Message TFA.Combat.KillConfirmed (locked) → TFASingularity subscribes |
FS-CMB-005 · Plugify |
Do not: ship a parallel action component next to ASC · Tick FindBestCombatTarget as a magnet · merge Ultimate into Flow · follow a Soulslike attribute/hitbox series as TF architecture (vitality, hit-reg, Firehand already locked elsewhere).
Pseudocode — tag gates (Strike vs Counter vs ADS)
Section titled “Pseudocode — tag gates (Strike vs Counter vs ADS)”// On AbilitySet / GA defaults (editor or C++ ctor):GA_MeleeStrike: AbilityTags += Ability.Melee.Strike ActivationOwnedTags += State.Melee.Striking ActivationBlockedTags += State.Melee.Countering // optional mutex // CancelAbilitiesWithTag as needed for soft cancels
GA_Counter: AbilityTags += Ability.Melee.Counter ActivationOwnedTags += State.Melee.Countering ActivationBlockedTags += State.Weapon.ADS // §7 — suppress while ADS ActivationRequiredTags+= State.Melee.CounterWindow // set when AI opens window
CanActivateAbility(GA): if GA is Strike and owner is WallRunning → use grounded Melee Drive rules if GA is Strike and airborne → ALLOW (same slash montage; no Motion Warp) // air lock if GA is Counter and Melee Drive.bSuppressCounterWhileAiming and HasTag(ADS) → FAIL if GA is Counter and not Melee Drive.bIsCounterWindowActive → FAIL return Super / tag checksPseudocode — Activate Strike (details → §3–§4)
Section titled “Pseudocode — Activate Strike (details → §3–§4)”ActivateAbility(MeleeStrike): if not CommitAbility → EndAbility; return ensure melee stance / TPP (hybrid camera message) // FS hybrid Target = Melee Drive.FindBestCombatTarget(Range, Cone) // ONLY here — never Tick if Target and Character is GROUNDED and MotionWarp: Warp.AddOrUpdateWarpTarget(CombatTarget, offset toward Target) else: // airborne or no target: play slash in place — no warp / no root pull PlayMontageAndWait(DirectionalSlash) // ANS window → Melee Drive.ExecuteMeleeHitTrace → GE on authority (§3) // confirmed kill → message KillConfirmed (do NOT AddFlowKillCredit only locally)EndAbility: clear warp target; clear State.Melee.StrikingPseudocode — Flow credit / Singularity (details → §8)
Section titled “Pseudocode — Flow credit / Singularity (details → §8)”// TFAMelee / TFAWeapons (server, after validated kill):BroadcastMessage(TFA.Combat.KillConfirmed, {Instigator, Victim})
// TFASingularity (subscriber — no #include of Melee/Weapons):OnKillConfirmed: ComboScore += 1 Tier = tier_from(ComboScore) // 1: <15, 2: 15–29, 3: >=30 Broadcast FlowChanged for OmniVisor if Tier == 3: ActivateAbility(GA_TFA_SingularityTakedown) // auto, no player button // on resolve → ResetFlow; Ultimate untouched (FS-CMB-006)
OnWhiffOrFailedCounter: // and TBD-056 Health-only damage-break ResetFlow(+0.5s stagger)Pseudocode — Experience grants (details → §6)
Section titled “Pseudocode — Experience grants (details → §6)”// Demo / Ladder Experience Action Sets — not a parallel DefaultActions arrayAbilitySet DAS_MeleeDriveCombat: grant GA_MeleeStrike ← InputTag.Ability.Melee grant GA_Counter ← InputTag.Ability.CounterHero components (Game Feature Action): UTFAMeleeDriveCombatComponent UMotionWarpingComponent// Flow meter + Singularity GA live in TFASingularity GFP (message-driven)1. Intent (player fantasy)
Section titled “1. Intent (player fantasy)”Precision Fire (FPP) ──kill +1──┐ ├──► Kinetic Flow ring ──► Overclocked ──► Singularity (TPP takedown)Melee Drive (TPP) ──kill +1──┘Counter (TPP) ──success +1 (or kill)─┘
Ultimate (Shiv) = separate meter — never merged with Flow- Melee Drive = Melee Drive: stick-aimed strike, optional Motion Warp to best cone target on Attack, reach capsule/box on Anim Notify (Demo); Full = Motion Matching clip choice + optional socket sweep — same notify auth.
- Precision Fire = guns: manual aim, Firehand Bus; kills tick the same Flow.
- Counter = timed intercept when an AI opens a window; Arkham prompt UI.
- Singularity = auto at Flow Tier 3 — not a player button.
2. Module dependency (Plugify GFPs, not LyraGame)
Section titled “2. Module dependency (Plugify GFPs, not LyraGame)”Do not replace LyraGame.Build.cs wholesale. Melee types live in TFAMelee.Build.cs; Flow/Singularity in TFASingularity.Build.cs; camera/stance/ASC host stays on TFACombat spine. Example (TFAMelee):
PublicDependencyModuleNames.AddRange(new string[]{ "Core", "CoreUObject", "Engine", "GameplayAbilities", "GameplayTags", "GameplayTasks", "MotionWarping", // Melee Drive strike / counter snap "TFACore", // tags / messages / shared types // LyraAbilitySystem / CommonGame as already required by your GFP // Do NOT PublicDepend TFAWeapons or TFASingularity — Core messages only});Enable the Motion Warping plugin in the .uproject / experience enable list (same rule as other ADDON/vendor plugins — document in ch.01).
3. Melee Drive combat component
Section titled “3. Melee Drive combat component”Owns: target pick, melee hit trace, Counter window helpers, interim Flow credit helpers (not Ultimate).
TFAMeleeDriveCombatComponent.h
Section titled “TFAMeleeDriveCombatComponent.h”#pragma once
#include "CoreMinimal.h"#include "Components/ActorComponent.h"#include "GameplayTagContainer.h"#include "TFAMeleeDriveCombatComponent.generated.h"
class UAbilitySystemComponent;struct FGameplayEffectSpecHandle;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTFACounterWindowChanged, AActor*, AttackingEnemy);DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnTFAFlowChanged, int32, ComboScore, int32, FlowTier);
UCLASS(ClassGroup=(TFA), meta=(BlueprintSpawnableComponent))class TFACOMBAT_API UTFAMeleeDriveCombatComponent : public UActorComponent{ GENERATED_BODY()
public: UTFAMeleeDriveCombatComponent();
/** Best target in stick/facing cone — used on Attack press only (not ambient pull). */ UFUNCTION(BlueprintCallable, Category = "TFA|MeleeDrive") AActor* FindBestCombatTarget(float MaxDistance, float MaxAngle) const;
/** Anim Notify → sweeping box along weapon sockets. Applies GAS GE on authority. */ UFUNCTION(BlueprintCallable, Category = "TFA|MeleeDrive") void ExecuteMeleeHitTrace(FVector BoxHalfSize, FName SocketStart, FName SocketEnd, TSubclassOf<UGameplayEffect> DamageEffectClass, float BaseDamageMagnitude);
UFUNCTION(BlueprintCallable, Category = "TFA|MeleeDrive") void OpenCounterWindow(AActor* AttackingEnemy);
UFUNCTION(BlueprintCallable, Category = "TFA|MeleeDrive") void CloseCounterWindow();
/** True if window open and enemy still valid — does not close window. */ UFUNCTION(BlueprintCallable, Category = "TFA|MeleeDrive") bool AttemptCounter();
/** +1 Flow combo (melee kill, gun kill, or successful counter). May trip Singularity. */ UFUNCTION(BlueprintCallable, Category = "TFA|MeleeDrive") void AddFlowKillCredit();
UFUNCTION(BlueprintCallable, Category = "TFA|MeleeDrive") void ResetFlow(bool bApplyStagger);
UFUNCTION(BlueprintPure, Category = "TFA|MeleeDrive") int32 GetCurrentCombo() const { return CurrentComboScore; }
UFUNCTION(BlueprintPure, Category = "TFA|MeleeDrive") int32 GetFlowTier() const; // 1 / 2 / 3
UPROPERTY(BlueprintAssignable, Category = "TFA|MeleeDrive") FOnTFACounterWindowChanged OnCounterWindowOpened;
UPROPERTY(BlueprintAssignable, Category = "TFA|MeleeDrive") FOnTFACounterWindowChanged OnCounterWindowClosed;
UPROPERTY(BlueprintAssignable, Category = "TFA|MeleeDrive") FOnTFAFlowChanged OnFlowChanged;
protected: UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "TFA|MeleeDrive") FGameplayTag CombatInProgressTag;
UPROPERTY(BlueprintReadOnly, Category = "TFA|MeleeDrive") int32 CurrentComboScore = 0;
UPROPERTY(BlueprintReadOnly, Category = "TFA|MeleeDrive") TObjectPtr<AActor> ActiveAttackingEnemy;
UPROPERTY(BlueprintReadOnly, Category = "TFA|MeleeDrive") bool bIsCounterWindowActive = false;
/** When true, gun ADS / Firehand suppress Counter input (see §7). */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "TFA|MeleeDrive|Guns") bool bSuppressCounterWhileAiming = true;};TFAMeleeDriveCombatComponent.cpp (core excerpts)
Section titled “TFAMeleeDriveCombatComponent.cpp (core excerpts)”#include "TFAMeleeDriveCombatComponent.h"#include "GameFramework/Character.h"#include "Kismet/KismetSystemLibrary.h"#include "AbilitySystemComponent.h"#include "AbilitySystemBlueprintLibrary.h"#include "GameplayEffectTypes.h"
UTFAMeleeDriveCombatComponent::UTFAMeleeDriveCombatComponent(){ PrimaryComponentTick.bCanEverTick = false; CombatInProgressTag = FGameplayTag::RequestGameplayTag(TEXT("State.Combat.InProgress"));}
int32 UTFAMeleeDriveCombatComponent::GetFlowTier() const{ if (CurrentComboScore >= 30) return 3; // Singularity gate if (CurrentComboScore >= 15) return 2; // Overclocked return 1;}
void UTFAMeleeDriveCombatComponent::AddFlowKillCredit(){ CurrentComboScore++; OnFlowChanged.Broadcast(CurrentComboScore, GetFlowTier()); // Tier 3 → grant / activate GA_TFA_SingularityTakedown (server) — see FS-CMB-005}
void UTFAMeleeDriveCombatComponent::ResetFlow(bool bApplyStagger){ CurrentComboScore = 0; OnFlowChanged.Broadcast(0, 1); // Optional: apply stagger GE 0.5s when bApplyStagger (whiff / damage) — FS-CMB-004}
AActor* UTFAMeleeDriveCombatComponent::FindBestCombatTarget(float MaxDistance, float MaxAngle) const{ ACharacter* OwnerChar = Cast<ACharacter>(GetOwner()); if (!OwnerChar) return nullptr;
FVector SearchOrigin = OwnerChar->GetActorLocation(); FVector InputDirection = OwnerChar->GetLastMovementInputVector(); if (InputDirection.IsNearlyZero()) { InputDirection = OwnerChar->GetActorForwardVector(); }
TArray<TEnumAsByte<EObjectTypeQuery>> ObjectTypes; ObjectTypes.Add(UEngineTypes::ConvertToObjectType(ECC_Pawn)); TArray<AActor*> ActorsToIgnore; ActorsToIgnore.Add(OwnerChar); TArray<AActor*> OutActors;
UKismetSystemLibrary::SphereOverlapActors( GetWorld(), SearchOrigin, MaxDistance, ObjectTypes, nullptr, ActorsToIgnore, OutActors);
AActor* BestTarget = nullptr; float HighestScore = -1.f;
for (AActor* Actor : OutActors) { if (!Actor) continue; FVector TargetDir = (Actor->GetActorLocation() - SearchOrigin).GetSafeNormal2D(); const float DotToTarget = FVector::DotProduct(InputDirection.GetSafeNormal2D(), TargetDir); const float AngleToTarget = FMath::RadiansToDegrees(FMath::Acos(FMath::Clamp(DotToTarget, -1.f, 1.f))); if (AngleToTarget > MaxAngle) continue;
const float DistanceNormalized = FVector::Dist(SearchOrigin, Actor->GetActorLocation()) / MaxDistance; const float Score = DotToTarget * (1.f - DistanceNormalized); if (Score > HighestScore) { HighestScore = Score; BestTarget = Actor; } } return BestTarget;}
void UTFAMeleeDriveCombatComponent::ExecuteMeleeHitTrace( FVector BoxHalfSize, FName SocketStart, FName SocketEnd, TSubclassOf<UGameplayEffect> DamageEffectClass, float BaseDamageMagnitude){ ACharacter* OwnerChar = Cast<ACharacter>(GetOwner()); if (!OwnerChar || !OwnerChar->GetMesh() || !DamageEffectClass) return;
// Prefer authority for apply; client may predict VFX only (FS-ARCH-013) if (OwnerChar->GetLocalRole() != ROLE_Authority && !OwnerChar->HasAuthority()) { // LocalPredicted: still run trace for feel; server GE is source of truth }
const FVector StartPos = OwnerChar->GetMesh()->GetSocketLocation(SocketStart); const FVector EndPos = OwnerChar->GetMesh()->GetSocketLocation(SocketEnd);
TArray<TEnumAsByte<EObjectTypeQuery>> ObjectTypes; ObjectTypes.Add(UEngineTypes::ConvertToObjectType(ECC_Pawn)); TArray<AActor*> ActorsToIgnore; ActorsToIgnore.Add(OwnerChar); TArray<FHitResult> OutHits;
const bool bHit = UKismetSystemLibrary::BoxTraceMultiForObjects( GetWorld(), StartPos, EndPos, BoxHalfSize, FRotator::ZeroRotator, ObjectTypes, false, ActorsToIgnore, EDrawDebugTrace::ForDuration, OutHits, true);
if (!bHit) return;
UAbilitySystemComponent* SourceASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(OwnerChar); if (!SourceASC) return;
TArray<AActor*> DamagedActors; for (const FHitResult& Hit : OutHits) { AActor* HitActor = Hit.GetActor(); if (!HitActor || DamagedActors.Contains(HitActor)) continue; DamagedActors.Add(HitActor);
UAbilitySystemComponent* TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(HitActor); if (!TargetASC) continue;
FGameplayEffectContextHandle Ctx = SourceASC->MakeEffectContext(); Ctx.AddSourceObject(OwnerChar); Ctx.AddHitResult(Hit, true); FGameplayEffectSpecHandle Spec = SourceASC->MakeOutgoingSpec(DamageEffectClass, 1.f, Ctx); if (Spec.IsValid()) { Spec.Data->SetSetByCallerMagnitude( FGameplayTag::RequestGameplayTag(TEXT("Data.Damage")), BaseDamageMagnitude); SourceASC->ApplyGameplayEffectSpecToTarget(*Spec.Data.Get(), TargetASC); } // On confirmed kill (death GE / vitality), call AddFlowKillCredit() once — shared with guns }}
void UTFAMeleeDriveCombatComponent::OpenCounterWindow(AActor* AttackingEnemy){ if (!AttackingEnemy) return; ActiveAttackingEnemy = AttackingEnemy; bIsCounterWindowActive = true; OnCounterWindowOpened.Broadcast(ActiveAttackingEnemy);}
void UTFAMeleeDriveCombatComponent::CloseCounterWindow(){ OnCounterWindowClosed.Broadcast(ActiveAttackingEnemy); ActiveAttackingEnemy = nullptr; bIsCounterWindowActive = false;}
bool UTFAMeleeDriveCombatComponent::AttemptCounter(){ if (bIsCounterWindowActive && IsValid(ActiveAttackingEnemy)) { return true; } ResetFlow(true); // failed counter = whiff-class reset return false;}FS-CMB-002 note: FindBestCombatTarget must only be called from Attack / Counter ActivateAbility, never from Tick as a magnet.
4. Melee strike ability (Motion Warp on press)
Section titled “4. Melee strike ability (Motion Warp on press)”Subclass ULyraGameplayAbility (or TFA wrapper) inside TFAMelee (not the thin Combat spine).
Activate (excerpt)
Section titled “Activate (excerpt)”void UTFAGameplayAbility_MeleeStrike::ActivateAbility(...){ Super::ActivateAbility(...); if (!CommitAbility(...)) { EndAbility(...); return; }
ACharacter* Character = Cast<ACharacter>(ActorInfo->AvatarActor.Get()); UTFAMeleeDriveCombatComponent* Combat = Character ? Character->FindComponentByClass<UTFAMeleeDriveCombatComponent>() : nullptr; UMotionWarpingComponent* Warp = Character ? Character->FindComponentByClass<UMotionWarpingComponent>() : nullptr;
// Camera: ensure melee stance / TPP (FS hybrid) before warp // LoadoutComponent->SetActiveStance(Melee) or Gameplay Message
if (Combat && Warp) { if (AActor* Target = Combat->FindBestCombatTarget(StrikeRange, TargetAngleCone)) { const FVector TargetLoc = Target->GetActorLocation(); const FVector Offset = (Character->GetActorLocation() - TargetLoc).GetSafeNormal2D() * TargetStrikingOffset; Warp->AddOrUpdateWarpTargetFromLocationAndRotation( MotionWarpTargetName, TargetLoc + Offset, (TargetLoc - Character->GetActorLocation()).Rotation()); } // Do NOT AddFlowKillCredit here — only on confirmed hit/kill (or counter success) }
// Play montage + wait (AbilityTask_PlayMontageAndWait) …}Montage setup (editor):
- Notify State Motion Warping · Warp Target Name =
CombatTarget· Skew Warp · span lunge frames only. - Notify State ANS_MeleeHitDetection · tick →
ExecuteMeleeHitTrace(sockets e.g.hand_r/ weapon tip, box half-size, GE class).
5. Counter ability
Section titled “5. Counter ability”void UTFAGameplayAbility_Counter::ActivateAbility(...){ Super::ActivateAbility(...); ACharacter* Character = Cast<ACharacter>(ActorInfo->AvatarActor.Get()); auto* Combat = Character ? Character->FindComponentByClass<UTFAMeleeDriveCombatComponent>() : nullptr; auto* Warp = Character ? Character->FindComponentByClass<UMotionWarpingComponent>() : nullptr;
// Gun rule: if aiming and bSuppressCounterWhileAiming → EndAbility fail (see §7) if (!Combat || !Combat->AttemptCounter() || !Warp) { EndAbility(..., true, true); return; }
AActor* Attacker = Combat->ActiveAttackingEnemy; // prefer stored window enemy over re-scan if (Attacker) { const FVector WarpLoc = Attacker->GetActorLocation() + (Character->GetActorLocation() - Attacker->GetActorLocation()).GetSafeNormal2D() * 100.f; Warp->AddOrUpdateWarpTargetFromLocationAndRotation( MotionWarpTargetName, WarpLoc, (Attacker->GetActorLocation() - Character->GetActorLocation()).Rotation()); }
Combat->CloseCounterWindow(); // On successful counter hit/kill → Combat->AddFlowKillCredit(); // Play CounterMontage …}Bugfix: Counter must warp to ActiveAttackingEnemy, not FindBestCombatTarget(600, 360) (that can pick the wrong pawn).
6. Experience / input / UI / AI (editor)
Section titled “6. Experience / input / UI / AI (editor)”- Components on hero:
UTFAMeleeDriveCombatComponent+UMotionWarpingComponent. DAS_MeleeDriveCombat(LyraAbilitySet):- MeleeStrike →
InputTag.Ability.Melee(or Attack) - Counter →
InputTag.Ability.Counter
- MeleeStrike →
- Inject set into Demo / Ladder Experience Action Sets (not permanent LyraGame default if you need Mod-off strip).
W_CounterPrompt: bindOnCounterWindowOpened/Closed; project attacker to screen.- AI wind-up:
OpenCounterWindow(Self)at start;CloseCounterWindowat commit frame / interrupt.
OmniVisor: Flow ring reads OnFlowChanged (tiers). Ultimate glyph stays separate.
7. Gun play interaction (Asymmetric Flow) — required
Section titled “7. Gun play interaction (Asymmetric Flow) — required”Approach A = Ult / Energy streak only (FS-CMB-007) — not this table.
| Situation | Camera / stance | Melee Drive / Counter | Flow |
|---|---|---|---|
| Hip / ADS gun body kill | FPP Precision Fire | Melee ability not active | +1 |
| Hip / ADS gun headshot kill | FPP Precision Fire | Melee ability not active | +2 |
| Press Melee while gun drawn | Pull TPP (Square/X / Melee Strike) | MeleeStrike ability + optional warp | Melee kill +2 |
Hold ADS (bSuppressCounterWhileAiming) |
FPP | Counter input ignored (locked — not buffered) | — |
| Counter window open, player hip-firing | Still may Counter if not ADS | Counter forces TPP intercept | Non-fatal +1 / fatal +2 |
| Overclocked (Tier 2) | Either verb | +30% move applies to gun strafe and melee | Shared |
| Singularity (Tier 3) | Always TPP takedown | Guns holstered / locked for sequence | Reset to 0 after |
| Whiff melee / failed Counter / Health damage | — | — | Flow 0 + 0.5 s stagger |
| Shield/Armor-only chip (no Health loss) | — | — | Flow unchanged (TBD-056) |
| Gun dry-fire / miss | FPP | Not a Flow whiff | No dump |
Implementation hooks for guns
Section titled “Implementation hooks for guns”// On Firehand confirmed kill (server) — production path:BroadcastMessage(TFA.Combat.KillConfirmed, {Instigator, Victim, bHeadshot});// TFASingularity applies Asymmetric credit — do NOT call MeleeDrive AddFlowKillCredit from Weapons
// When entering ADS:if (MeleeDriveCombat->bSuppressCounterWhileAiming){ // Block InputTag.Ability.Counter via ASC tag State.Weapon.ADS}
// When activating MeleeStrike from gun stance:Loadout->RequestMeleeStance(); // camera director → TPP// then MeleeStrike ActivateAbility as aboveDo not: give guns a second / fourth combo meter · dump Flow on every missed shot · Motion-Warp the camera while in ADS · merge Ultimate charge into Flow · #include TFAMelee from TFAWeapons.
8. Singularity bridge
Section titled “8. Singularity bridge”When GetFlowTier() becomes 3 on a kill credit:
- Server activates
GA_TFA_SingularityTakedown(no player button). - Camera forced TPP for Burst Storm (
DA_TFA_SingularityBursts). - On resolve →
ResetFlow(false). - Ultimate meter untouched (FS-CMB-006).
9. Demo exit checks
Section titled “9. Demo exit checks”- Mix gun + melee kills under Asymmetric Flow to 30 without Health damage-break → one Singularity → Flow 0.
- All-gun or all-melee reaches same gate (gun needs more body kills).
- Counter window shows prompt; success credits Flow; fail dumps Flow.
- ADS ignores Counter.
- Shield-only chip does not dump Flow.
- No
TakeDamagepath for player melee power — GE only. - KillConfirmed → TFASingularity; components under Plugify GFPs (
TFAMelee·TFASingularity·TFAWeapons); spine onTFACombat.
10. GDD / bible pointers
Section titled “10. GDD / bible pointers”- GDD: 1b · Combat Mechanics & TTK (One Flow · two verbs) · Demo combat spine
- Spec: 2026-10-26-singularity-one-flow-two-verbs-design.md
- Bible: 22-demo.md · 02-gfp-cookbook.md · Kanban
demo-singularity-flow