# Preamble Copyright 2026 Mickael Bonfill This Specification is released under the [MIT License](https://opensource.org/licenses/MIT). Permission is hereby granted, free of charge, to any person obtaining a copy of this Specification and associated documentation files, to deal in the Specification without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Specification, and to permit persons to whom the Specification is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Specification. The author makes no, and expressly disclaims any, representations or warranties, express or implied, regarding this Specification, including, without limitation: merchantability, fitness for a particular purpose, non-infringement of any intellectual property, correctness, accuracy, completeness, timeliness, and reliability. Under no circumstances will the author or contributors be liable for any damages, whether direct, indirect, special or consequential damages for lost revenues, lost profits, or otherwise, arising from or in connection with this Specification or the use or other dealings in this Specification. Some parts of this Specification are purely informative and so are EXCLUDED from the normative scope of this Specification. The [???](#introduction-conventions) section of the [???](#introduction) defines how these parts of the Specification are identified. Where this Specification uses technical terminology, defined in the [Glossary](#glossary) or otherwise, that refers to enabling technologies not expressly set forth in this Specification, those enabling technologies are EXCLUDED from the normative scope of this Specification. Where this Specification identifies specific sections of external references, only those specifically identified sections define normative functionality. The full text of the MIT License can be found in the [LICENSE](https://github.com/jbltx/ugas/blob/main/LICENSE) file at the root of the repository. # Part I: Foundations ## 1. Introduction ### 1.1 Purpose and Scope The Universal Gameplay Ability System (UGAS) is an open, engine-agnostic specification designed to standardize gameplay logic across game engines and runtime environments. This specification defines the architecture, data structures, and behavioral contracts required to implement a consistent ability system that can be deployed on any game engine or custom runtime, including Unreal Engine, Unity, and Godot. The scope of this specification includes: - Numeric gameplay state representation (Attributes) - Semantic state labeling (Gameplay Tags) - Action definition and execution (Gameplay Abilities) - State mutation mechanisms (Gameplay Effects) - Asynchronous execution patterns (Ability Tasks) - Client feedback systems (Gameplay Cues) - Network synchronization protocols This specification does NOT define: - Rendering or audio implementation details - Physics engine integration specifics - Platform-specific memory management - User interface implementation ### 1.2 Design Philosophy The UGAS specification is founded on three core principles: *Decoupled Gameplay Logic* Traditional gameplay programming relies on imperative state changes within character classes, leading to tightly coupled code where a single modification to a health variable must manually notify UI elements, sound systems, and networking layers. UGAS shifts this paradigm toward a reactive, data-driven architecture where the Actor is merely an avatar—a spatial representation—while the Gameplay Controller(GC) serves as the authoritative state container. *Reactive, Data-Driven Architecture* All state changes flow through a single mutation layer (Gameplay Effects), ensuring that every modification to the game state is tracked, predicted, and synchronized. This approach eliminates expensive per-frame polling of UI elements or AI state machines in favor of event-driven notifications. *Cross-Platform Interoperability* By defining gameplay rules as deterministic, replicable operations on abstract data structures, UGAS enables a unified framework that can be implemented across diverse execution environments. A GC can exist as a C++ component in Unreal Engine, a Data-Oriented Technology Stack (DOTS) entity in Unity, or a scripted component in Godot. ### 1.3 Document Conventions #### Notation This specification uses the following notational conventions: - *Mathematical Notation*: Standard mathematical symbols for summation (Σ), product (Π), and set operations (∈, ⊆, ∩, ∪) - *Pseudocode*: Language-agnostic pseudocode for algorithm descriptions - *Interface Definitions*: Abstract interface declarations using TypeScript-like syntax #### Requirement Levels The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. | Keyword | Meaning | |------------------------------|---------------------------------------------------------------------------| | MUST / REQUIRED / SHALL | Absolute requirement of the specification | | MUST NOT / SHALL NOT | Absolute prohibition | | SHOULD / RECOMMENDED | Valid reasons may exist to ignore, but implications must be understood | | SHOULD NOT / NOT RECOMMENDED | Valid reasons may exist to implement, but implications must be understood | | MAY / OPTIONAL | Truly optional; interoperability must be ensured | ### 1.4 Normative References - RFC 2119: Key words for use in RFCs to Indicate Requirement Levels - IEEE 754: Standard for Floating-Point Arithmetic - JSON Schema: Draft 2020-12 - YAML 1.2 Specification ## 2. Terminology This section provides formal definitions for terms used throughout this specification. Actor An entity within the game world that can possess a Gameplay Controller. Actors MAY have spatial representation, AI behavior, or player control. Avatar The world representation (visual, physical) associated with a Gameplay Controller. The Avatar is the entity that exists in game space and interacts with the physics and rendering systems. Owner The logical owner of a Gameplay Controller. The Owner is responsible for the persistence and lifecycle of the GC. In networked games, the Owner typically corresponds to the authoritative controller of the entity. Attribute A named, typed value representing a quantitative aspect of an Actor’s state. Attributes implement the dual-value pattern with Base Value and Current Value. AttributeSet A logical container that groups related Attributes. AttributeSets provide modular composition of Actor capabilities. Modifier A temporary or permanent adjustment to an Attribute’s value. Modifiers define an operation (Add, AddPost, Multiply, Override) and a magnitude. Tag A hierarchical, unique identifier serving as a conceptual label for Actors, Abilities, and Effects. Tags use dot-notation (e.g., `State.Debuff.Stunned.Magic`). TagContainer A collection of Tags associated with an entity. TagContainers support efficient query operations. TagQuery A predicate expression evaluated against a TagContainer to determine matches. Ability A self-contained unit of logic defining an action an Actor can perform. Abilities are asynchronous, stateful objects with defined lifecycles. AbilitySpec Instance data for a granted Ability, including level, input binding, and runtime parameters. AbilityTask An asynchronous operation within an Ability that pauses execution until a specific trigger condition is met. Effect The mechanism by which Attributes and Tags are modified. Effects are the ONLY authorized mechanism for mutating gameplay state. EffectSpec Lightweight application data for applying an Effect, containing magnitude, level, and context information. EffectContext Runtime context for Effect application, including source Actor, target Actor, hit location, and causal chain information. Cue A client-side feedback element (VFX, SFX, camera effects) triggered by Tags or Effects. Cues are purely cosmetic and do not affect gameplay logic. CueManager Client-side system responsible for instantiating and managing Cue resources. GC (Gameplay Controller) The central component managing an Actor’s Attributes, Tags, Abilities, and Effects. The GC is the authoritative state container for gameplay logic. ## 3. Architectural Overview ### 3.1 Four-Pillar Model The UGAS architecture is predicated on the interaction between four distinct pillars: ┌─────────────────────────────────────────────────────────────────┐ │ GAMEPLAY CONTROLLER │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌───────────────┐ ┌───────────────┐ ┌──────────────────────┐ │ │ │ DATA │ │ SEMANTIC │ │ LOGIC │ │ │ │ LAYER │ │ LAYER │ │ LAYER │ │ │ │ │ │ │ │ │ │ │ │ Attributes │ │ Gameplay Tags│ │ Gameplay Abilities │ │ │ │ Attribute Sets│ │ Tag Containers│ │ Ability Tasks │ │ │ │ │ │ │ │ │ │ │ └──────┬────────┘ └──────┬────────┘ └──────────┬───────────┘ │ │ │ │ │ │ │ └─────────────────┼─────────────────────┘ │ │ │ │ │ ▼ │ │ ┌────────────────────────┐ │ │ │ MUTATION LAYER │ │ │ │ │ │ │ │ Gameplay Effects │ │ │ │ Modifiers │ │ │ │ Execution Calcs │ │ │ └────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ Data Layer (Attributes) Numeric state representation. Attributes store quantitative values such as Health, Mana, Strength, and Speed. All numeric gameplay state MUST be represented through Attributes. Semantic Layer (Tags) Qualitative state representation. Tags describe "what kind" or "in what state" an Actor exists. Tags enable logic gating, ability requirements, and state queries without coupling to specific implementations. Logic Layer (Abilities) Behavioral definitions. Abilities encapsulate the asynchronous, stateful logic of actions Actors can perform. Abilities coordinate with Tasks for complex, multi-stage execution. Mutation Layer (Effects) State change mechanism. Effects are the ONLY authorized mechanism for modifying Attributes or Tags. This restriction ensures all state changes are tracked, predicted, and synchronized. Ability implementations MUST NOT call `Tags.AddTag()`, `Tags.RemoveTag()`, or any equivalent direct tag mutation API. All tag state changes MUST flow through a `GameplayEffect` applied via the GC’s effect application pipeline. This is a deliberate departure from UE4 GAS (which permits "loose tags") and is the property that makes replication of tag state tractable. ### 3.2 Component Relationships ┌─────────────┐ │ ACTOR │ │ (Avatar) │ └──────┬──────┘ │ possesses ▼ ┌─────────────┐ ┌───────────────┐ ┌─────────────┐ │ OWNER │──────────────│ GAMEPLAY │──────────────│ ATTRIBUTE │ │ ACTOR │ owns │ CONTROLLER │ contains │ SETS │ └─────────────┘ └───────┬───────┘ └─────────────┘ │ ┌────────────────┼────────────────┐ │ │ │ ▼ ▼ ▼ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ ABILITIES │ │ TAGS │ │ EFFECTS │ │ (Specs) │ │(Container)│ │ (Active) │ └─────┬─────┘ └───────────┘ └─────┬─────┘ │ │ ▼ ▼ ┌───────────┐ ┌───────────┐ │ TASKS │ │ MODIFIERS │ └───────────┘ └───────────┘ ### 3.3 Execution Model The UGAS execution model follows a deterministic sequence for processing gameplay logic: 1. *Input Processing*: Hardware inputs are mapped to Input Actions, which trigger Ability activation attempts. 2. *Ability Activation*: The GC validates activation requirements (Tags, Costs, Cooldowns) before committing the Ability. 3. *Effect Application*: Abilities apply Effects to Targets. Effects create Modifiers on Attributes and grant/remove Tags. 4. *Attribute Recalculation*: Affected Attributes recalculate their Current Values based on active Modifiers. 5. *Event Dispatch*: OnAttributeChanged events propagate to registered observers. 6. *Cue Triggering*: Tag changes trigger appropriate Gameplay Cues on clients. 7. *Replication*: State changes are replicated to networked clients according to the configured replication mode. ### 3.4 Threading Considerations Implementations SHOULD consider the following threading guidelines: - *Main Thread*: Ability activation, Effect application, and Attribute modification SHOULD occur on the main game thread to ensure deterministic ordering. - *Async Tasks*: AbilityTasks MAY spawn background work but MUST return results to the main thread for state modification. - *Replication*: Network replication MAY occur on dedicated networking threads but MUST synchronize with the main thread for state application. - *Cues*: Gameplay Cue instantiation MAY occur on rendering threads but MUST NOT modify gameplay state. # Part II: Core Components ## 4. Gameplay Controller(GC) ### 4.1 Responsibilities The Gameplay Controlleris the central hub for all gameplay ability logic. An GC implementation MUST: 1. Maintain collections of granted Abilities, active Effects, and owned Tags 2. Manage one or more AttributeSets 3. Process Ability activation requests 4. Apply and remove Gameplay Effects 5. Dispatch events for state changes 6. Support network replication (if applicable) ### 4.2 Ownership Model The GC implements a dual-actor ownership model: Owner Actor The logical owner of the GC. The Owner is responsible for: - GC lifecycle management - Network authority - Persistence across possession changes Avatar Actor The world representation associated with the GC. The Avatar provides: - Spatial position for targeting - Animation and physics integration - Visual representation #### Same-Actor Configuration For simple entities (AI-controlled enemies, destructible objects), the Owner and Avatar MAY be the same Actor: ┌─────────────────────────────┐ │ AI ENEMY │ │ ┌───────────────────────┐ │ │ │ GC │ │ │ │ Owner: this │ │ │ │ Avatar: this │ │ │ └───────────────────────┘ │ └─────────────────────────────┘ #### Split-Actor Configuration For player-controlled characters in networked games, the Owner and Avatar SHOULD be separate to ensure GC persistence across respawns: ┌─────────────────────────────┐ ┌─────────────────────────────┐ │ PLAYER STATE │ │ PLAYER CHARACTER │ │ (Persists entire session) │ │ (Destroyed on death) │ │ ┌───────────────────────┐ │ │ │ │ │ GC │──┼────────┼──▶ Avatar reference │ │ │ Owner: this │ │ │ │ │ └───────────────────────┘ │ └─────────────────────────────┘ └─────────────────────────────┘ ### 4.3 Lifecycle #### Initialization Sequence 1. GC is instantiated on Owner Actor 2. AttributeSets are registered with GC 3. Owner and Avatar references are set 4. Initial Abilities are granted 5. Initial Effects are applied 6. Replication is configured (if networked) #### Possession Handling When Avatar possession changes: 1. Previous Avatar reference is cleared 2. Active Effects targeting Avatar location are re-evaluated 3. New Avatar reference is set 4. Avatar-dependent Abilities are re-validated #### Destruction Cleanup 1. All active Effects are removed 2. All granted Abilities are revoked 3. Event subscriptions are cleared 4. Network replication is terminated ### 4.4 Interface Specification Implementations SHOULD provide an interface for GC discovery: ``` typescript interface IAbilitySystemInterface { /** * Returns the Gameplay Controllerassociated with this entity. * @returns The GC instance, or null if not available */ GetGameplayController(): GameplayController | null; } ``` Actors participating in the ability system MUST implement this interface or provide an equivalent discovery mechanism. ### 4.5 Public API The following methods define the core GC interface: #### Effect Context Creation ``` typescript /** * Creates a new Effect Context for outgoing effects. * @returns A handle to the new context */ MakeEffectContext(): EffectContextHandle; ``` #### Effect Spec Creation ``` typescript /** * Creates an Effect Spec for application. * @param effectClass - The Effect definition to instantiate * @param level - The level at which to apply the effect * @param context - The effect context handle * @returns A handle to the new spec */ MakeOutgoingSpec( effectClass: GameplayEffectClass, level: number, context: EffectContextHandle ): EffectSpecHandle; ``` #### Effect Application ``` typescript /** * Applies an effect to this GC's owner. * @param spec - The effect spec to apply * @param predictionKey - Optional prediction key for client-side prediction * @returns Handle to the active effect, or invalid handle if application failed */ ApplyGameplayEffectToSelf( spec: EffectSpecHandle, predictionKey?: PredictionKey ): ActiveEffectHandle; /** * Applies an effect to a target GC. * * NETWORKED ENVIRONMENTS: A call originating on a client is speculative. * The server MUST validate instigator authority, ability ownership, target * reachability, and effect-class whitelist before executing the authoritative * application. See §13.7 for the full validation pipeline. * * @param target - The target GC * @param spec - The effect spec to apply * @param predictionKey - Optional prediction key for client-side prediction * @returns Handle to the active effect, or invalid handle if application failed */ ApplyGameplayEffectToTarget( target: GameplayController, spec: EffectSpecHandle, predictionKey?: PredictionKey ): ActiveEffectHandle; ``` #### Effect Removal ``` typescript /** * Removes an active effect. * @param handle - Handle to the active effect * @param stacksToRemove - Number of stacks to remove (-1 for all) * @returns True if removal succeeded */ RemoveActiveGameplayEffect( handle: ActiveEffectHandle, stacksToRemove: number = -1 ): boolean; ``` #### Ability Management ``` typescript /** * Grants an ability to this GC. * @param abilityClass - The ability class to grant * @param level - Initial ability level * @param inputID - Optional input binding * @returns Handle to the granted ability spec */ GrantAbility( abilityClass: GameplayAbilityClass, level: number = 1, inputID?: InputID ): AbilitySpecHandle; /** * Attempts to activate an ability. * @param handle - Handle to the ability spec * @returns True if activation succeeded */ TryActivateAbility(handle: AbilitySpecHandle): boolean; ``` ## 5. Attributes ### 5.1 Attribute Data Structure An Attribute MUST implement the following data structure: ``` typescript struct Attribute { /** Permanent value, modified only by Instant effects */ BaseValue: float; /** Dynamically calculated value including all active modifiers */ CurrentValue: float; /** Collection of active modifiers affecting this attribute */ Modifiers: ModifierStack; /** Static configuration for this attribute */ Metadata: AttributeMetadata; } struct AttributeMetadata { /** Unique identifier for this attribute */ Name: string; /** Attribute category */ Category: AttributeCategory; /** Minimum allowed value (optional) */ MinValue?: float | AttributeReference; /** Maximum allowed value (optional) */ MaxValue?: float | AttributeReference; /** Replication configuration */ ReplicationMode: AttributeReplicationMode; } enum AttributeCategory { /** Consumable values (Health, Mana, Stamina) */ Resource, /** Derived statistics (Damage, Defense, Speed) */ Statistic, /** Meta-attributes used for calculations only */ Meta } ``` ### 5.2 Dual-Value Pattern Every Attribute MUST implement the dual-value pattern consisting of Base Value and Current Value. This distinction is the primary mechanism for handling temporary modifications. Base Value The permanent, persistent value of the Attribute. Base Values are modified ONLY by Instant effects and represent permanent changes such as leveling, permanent upgrades, or instant damage/healing. Current Value The dynamically calculated result of the Base Value plus all active temporary Modifiers. Current Values are ephemeral and automatically recalculated when Modifiers are added or removed. | Component | Modification Source | Persistence | |---------------|----------------------|------------------------| | Base Value | Instant Effects only | Persistent (saved) | | Current Value | All Modifier types | Ephemeral (calculated) | ### 5.3 Modifier Pipeline The Current Value calculation MUST follow a standardized pipeline to ensure mathematical consistency across implementations. #### Formula The Current Value $V_{current}$ is calculated as: $$V_{current} = \max\left( V_{min},\ \min\left( V_{max},\ \left( V_{base} + \sum a_i \right) \times \prod_{c \in C} \left(1 + \sum_{k \in c} m_k\right) + \sum b_l \right) \right)$$ Where: - $V_{base}$ = Base Value - $a_i$ = Pre-multiply flat additive modifiers (`Add` operations) - $C$ = the set of distinct Channel values among active `Multiply` modifiers; each modifier without a `Channel` belongs to its own unique implicit singleton channel - $m_k$ = signed bonus magnitude for each `Multiply` modifier (e.g., `+0.25` for a +25% bonus, `−0.25` for a 25% penalty) - $b_l$ = Post-multiply flat additive modifiers (`AddPost` operations; very rare) - $V_{min}$, $V_{max}$ = clamping constraints Note that clamping is not mandatory; the simplified form is: $$V_{current} = \left( V_{base} + \sum a_i \right) \times \prod_{c \in C} \left(1 + \sum_{k \in c} m_k\right) + \sum b_l$$ #### Channel Aggregation The channel product $\prod_{c \in C}\!\left(1 + \sum_{k \in c} m_k\right)$ is how the "damage bucket" design is expressed at the pipeline level: - *Same channel → bonuses ADD.* All `Multiply` modifiers sharing a `Channel` value contribute their magnitudes additively. The channel’s effective factor is `1 + sum of magnitudes`. Two +20% bonuses in the same channel yield ×1.40, not ×1.44. - *Different channels → factors MULTIPLY.* Each channel produces one effective factor; those factors are multiplied together. A ×1.40 channel and a ×1.30 channel yield ×1.82. - *No channel → isolated singleton.* A `Multiply` modifier without a `Channel` is in its own implicit channel, so its contribution is `1 + magnitude` — independent of all other modifiers. This is the primary tool for preventing linear power creep: bonuses from the same source category (e.g., "damage bonuses from gear") are additive within a channel, while bonuses from categorically different sources (e.g., "gear bonuses" vs. "legendary powers") are multiplicative across channels. #### Order of Operations The order of operations is CRITICAL for deterministic results: 1. Sum all flat additive modifiers (`Add`): `flat = ΣAdd` 2. Apply flat additions to Base Value: `value = Base + flat` 3. Group `Multiply` modifiers by `Channel`. For each channel, sum the magnitudes: `channel_factor = 1 + Σm_k` 4. Multiply all channel factors together and apply: `value *= Π channel_factor` 5. Add sum of all post-multiply flat additive modifiers (`AddPost`): `value += ΣAddPost` 6. Apply `Override` modifiers (if any, replacing the result) — see conflict resolution below 7. Apply clamping constraints #### Override Conflict Resolution When multiple active Override modifiers target the same Attribute simultaneously, implementations MUST resolve the conflict deterministically using the following ordered rules: 1. *Priority wins*: The Override modifier from the `GameplayEffect` with the highest `Priority` value replaces the result. Lower-priority Overrides are ignored for that Attribute. 2. *Last-applied wins on tie*: If two or more competing Override modifiers share the same `Priority`, the one from the most recently applied effect wins (LIFO order, determined by application timestamp). `Priority` defaults to `0`. Effects intended to be overrideable by other effects should use lower priority values (e.g. `-10`); effects that must always dominate should use higher values (e.g. `100`). > *Example:* A "Freeze" effect sets `MoveSpeed` Override to `0` at Priority `10`. A "Slow\` effect also sets an Override to `50` at Priority `5`. The Freeze wins because `10 > 5`. If a "Root" effect then sets an Override to `0` at Priority `10`, it ties with Freeze — the more recently applied effect’s Override is used, but the end result is identical. #### Example Calculation Given: - Base Value: 100 - Add Modifier 1: +20 - Add Modifier 2: +10 - Additive Percentage 1: +10% (0.1) - Additive Percentage 2: +15% (0.15) - Multiplicative 1: 1.5× - Multiplicative 2: 2.0× - No Bonus Flat Calculation: Step 1-2: 100 + 20 + 10 = 130 Step 3-4: 130 × (1 + 0.1 + 0.15) = 130 × 1.25 = 162.5 Step 5-6: 162.5 × 1.5 × 2.0 = 487.5 Current Value = 487.5 ### 5.4 Clamping and Bounds Attributes MAY define minimum and maximum constraints. Constraints can be: Static Values Fixed numeric bounds that do not change. ``` yaml Clamping: Min: 0.0 Max: 100.0 ``` Dependent Attribute References Bounds referencing other Attributes, enabling dynamic constraints. ``` yaml Clamping: Min: 0.0 Max: "MaxHealth" # References another attribute ``` When a constraint references another Attribute: 1. The referenced Attribute’s Current Value is used as the bound 2. Changes to the referenced Attribute trigger recalculation of dependent Attributes 3. Circular dependencies MUST NOT be created ### 5.5 Attribute Metadata Attribute Metadata defines static configuration: *Category* - `Resource`: Consumable values that are spent and recovered (Health, Mana, Stamina) - `Statistic`: Derived values used in calculations (Damage, Defense, CritChance) - `Meta`: Internal values used only for calculations, not displayed to players *Replication Flags* - `None`: Not replicated - `OwnerOnly`: Replicated only to owning client - `All`: Replicated to all clients ### 5.6 OnAttributeChanged Event Any change to an Attribute—whether to Base Value or Current Value—MUST trigger an OnAttributeChanged event. #### Event Payload ``` typescript struct AttributeChangedEvent { /** The attribute that changed */ Attribute: AttributeReference; /** Previous current value */ OldValue: float; /** New current value */ NewValue: float; /** The effect that caused the change (if any) */ CausalEffect?: ActiveEffectHandle; /** Source of the change */ Source?: GameplayController; /** Target of the change */ Target: GameplayController; } ``` #### Subscription Model Observers SHOULD register for attribute change notifications: ``` typescript interface IAttributeChangeObserver { OnAttributeChanged(event: AttributeChangedEvent): void; } // Registration GC.RegisterAttributeChangeObserver( attribute: AttributeReference, observer: IAttributeChangeObserver ): void; // Unregistration GC.UnregisterAttributeChangeObserver( attribute: AttributeReference, observer: IAttributeChangeObserver ): void; ``` ### 5.7 Schema Definition ``` yaml Attribute: Name: string # Required: Unique identifier DefaultBaseValue: float # Required: Initial base value Category: enum # Optional: Resource | Statistic | Meta Clamping: # Optional: Value constraints Min: float | string # Static value or attribute reference Max: float | string # Static value or attribute reference ReplicationMode: enum # Optional: None | OwnerOnly | All Metadata: # Optional: Additional configuration DisplayName: string # Human-readable name Description: string # Tooltip description UICategory: string # UI grouping ``` ## 6. Attribute Sets ### 6.1 Purpose and Composition An Attribute Set is a logical container grouping related Attributes. Attribute Sets provide: - *Modularity*: Actors can mix and match sets based on capabilities - *Organization*: Related Attributes are defined together - *Reusability*: Common sets can be shared across Actor types - *Serialization Boundary*: Sets define units for save/load operations ### 6.2 Set Registration with GC Attribute Sets MUST be registered with an GC before use: ``` typescript /** * Registers an attribute set with this GC. * @param attributeSet - The set to register */ GC.RegisterAttributeSet(attributeSet: AttributeSet): void; /** * Unregisters an attribute set from this GC. * @param attributeSet - The set to unregister */ GC.UnregisterAttributeSet(attributeSet: AttributeSet): void; /** * Retrieves a registered attribute set by type. * @returns The attribute set, or null if not registered */ GC.GetAttributeSet(): T | null; ``` ### 6.3 Modular Design Patterns #### Combat Attribute Set ``` yaml $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/attribute_set.json Name: "CombatAttributeSet" Attributes: - Name: "Health" DefaultBaseValue: 100.0 Category: Resource Clamping: Min: 0.0 Max: "MaxHealth" - Name: "MaxHealth" DefaultBaseValue: 100.0 Category: Statistic Clamping: Min: 1.0 - Name: "Mana" DefaultBaseValue: 50.0 Category: Resource Clamping: Min: 0.0 Max: "MaxMana" - Name: "MaxMana" DefaultBaseValue: 50.0 Category: Statistic Clamping: Min: 0.0 - Name: "AttackPower" DefaultBaseValue: 10.0 Category: Statistic - Name: "Defense" DefaultBaseValue: 5.0 Category: Statistic ``` #### Movement Attribute Set ``` yaml $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/attribute_set.json Name: "MovementAttributeSet" Attributes: - Name: "MoveSpeed" DefaultBaseValue: 600.0 Category: Statistic Clamping: Min: 0.0 - Name: "JumpVelocity" DefaultBaseValue: 800.0 Category: Statistic - Name: "GravityScale" DefaultBaseValue: 1.0 Category: Statistic - Name: "AirControl" DefaultBaseValue: 0.5 Category: Statistic Clamping: Min: 0.0 Max: 1.0 ``` #### Vehicle Attribute Set ``` yaml $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/attribute_set.json Name: "VehicleAttributeSet" Attributes: - Name: "EngineTorque" DefaultBaseValue: 500.0 Category: Statistic - Name: "MaxSpeed" DefaultBaseValue: 200.0 Category: Statistic - Name: "TireGrip" DefaultBaseValue: 1.0 Category: Statistic - Name: "Fuel" DefaultBaseValue: 100.0 Category: Resource Clamping: Min: 0.0 Max: "MaxFuel" - Name: "MaxFuel" DefaultBaseValue: 100.0 Category: Statistic ``` ### 6.4 Cross-Set Dependencies Attributes MAY reference Attributes from other registered sets: ``` yaml $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/attribute_set.json Name: "DerivedStatsSet" Dependencies: - "CombatAttributeSet" Attributes: - Name: "EffectiveHealth" DefaultBaseValue: 0.0 Category: Meta DerivedFrom: Expression: "Health * (1 + Defense / 100)" ``` Cross-set references are resolved at runtime. Implementations MUST: 1. Validate all dependencies exist before registration 2. Ensure proper recalculation order when dependencies change 3. Prevent circular dependency chains ### 6.5 Schema Definition ``` yaml AttributeSet: Name: string # Required: Unique set identifier Dependencies: [string] # Optional: Required attribute sets Attributes: [Attribute] # Required: List of attributes Metadata: # Optional: Additional configuration DisplayName: string Description: string ``` ## 7. Gameplay Tags ### 7.1 Hierarchical Naming Convention Gameplay Tags use hierarchical dot-notation to represent semantic categories: Category.Subcategory.Leaf Examples: - `State.Debuff.Stunned.Magic` - `Ability.Type.Melee.Slash` - `DamageType.Physical.Blunt` - `Cooldown.Ability.Fireball` - `GameplayCue.Impact.Fire` #### Naming Rules 1. Each segment MUST use PascalCase 2. Hierarchies SHOULD NOT exceed 5 levels 3. Leaf tags SHOULD be specific; parent tags SHOULD be categorical 4. Reserved prefixes: - `GameplayCue.*` - Cue trigger tags - `Cooldown.*` - Cooldown tracking tags - `State.*` - Actor state tags - `Ability.*` - Ability classification tags - `DamageType.*` - Damage classification tags ### 7.2 Tag Container A Tag Container is a collection of tags associated with an entity. #### Internal Representation A `TagContainer` MUST maintain *reference counts* per tag, not a simple set. Multiple concurrent Effects can grant the same tag; each grant increments the count; each removal decrements it. The tag is considered present only while its count is greater than zero. ``` typescript struct TagContainer { /** * Grant counts for every explicitly-held tag. * A tag is "explicitly present" when its count > 0. * Managed exclusively by the GC Effect application pipeline. */ ExplicitTagCounts: Map; /** * Cumulative grant counts for all explicit tags AND their ancestor tags. * Automatically maintained by AddTag/RemoveTag: adding tag T also * increments the count of every ancestor of T; removing T decrements them. * Used to answer MatchesTag queries in O(1). */ AllTagCounts: Map; } ``` #### Operations ``` typescript interface TagContainer { /** * @internal Reserved for the GC Effect application pipeline. * Ability implementations MUST NOT call this directly. * Grant tags via a GameplayEffect with GrantedTags instead. * * Increments the grant count of `tag` in ExplicitTagCounts and the grant * count of every ancestor of `tag` in AllTagCounts. * Dispatches an OnTagChanged event ONLY when the count transitions 0 → 1 * (i.e. the tag was previously absent). Subsequent grants of the same tag * by additional Effects increment the count silently. */ AddTag(tag: Tag): void; /** * @internal Reserved for the GC Effect application pipeline. * Ability implementations MUST NOT call this directly. * Remove tags by removing the GameplayEffect that granted them. * * Decrements the grant count of `tag` in ExplicitTagCounts and the grant * count of every ancestor of `tag` in AllTagCounts. * MUST NOT decrement below 0; implementations MUST treat an underflow as * a logic error (assert / log error and skip). * Dispatches an OnTagChanged event ONLY when the count transitions 1 → 0 * (i.e. the tag is now fully absent). While the count remains > 1, no * event is dispatched. */ RemoveTag(tag: Tag): void; /** * Returns the current grant count for `tag` in ExplicitTagCounts. * Useful for "how many stacks of Burning are active?" queries. * Returns 0 if the tag is not present. */ GetTagCount(tag: Tag): number; /** Returns true if no explicit tags have a count > 0. */ IsEmpty(): boolean; /** Returns the number of distinct explicit tags with count > 0. */ Count(): number; /** * @internal Reserved for the GC Effect application pipeline. * Sets all counts to 0 and dispatches OnTagChanged for every tag whose * count was > 0. Used during GC teardown only. */ Clear(): void; } ``` ### 7.3 Query Operations | Operation | Map queried | Semantics | Example | |----------------------|---------------------|-----------------------------------------------------------------------------------------|--------------------------------------------------------| | `MatchesTag(T)` | `AllTagCounts` | True if `AllTagCounts[T] > 0` — matches T itself or any descendant of T that is present | Checking for any type of "Stunned" status | | `MatchesTagExact(T)` | `ExplicitTagCounts` | True if `ExplicitTagCounts[T] > 0` — exact tag only, no hierarchy | Immunity to "Stunned.Magic" but not "Stunned.Physical" | | `GetTagCount(T)` | `ExplicitTagCounts` | Returns `ExplicitTagCounts[T]` (0 if absent) | "How many stacks of Burning?" | | `HasAny(Container)` | `AllTagCounts` | True if any tag in Container has `AllTagCounts > 0` | Spell that affects "Undead" OR "Demon" | | `HasAll(Container)` | `AllTagCounts` | True if every tag in Container has `AllTagCounts > 0` | Combo requiring "Chilled" AND "Vulnerable" | | `HasNone(Container)` | `AllTagCounts` | True if no tag in Container has `AllTagCounts > 0` | Ability blocked by any "Immunity" tag | #### Query Examples ``` typescript // Container has: State.Debuff.Stunned.Magic, Status.Burning container.MatchesTag("State.Debuff.Stunned") // true (parent match) container.MatchesTag("State.Debuff.Stunned.Magic") // true (exact match) container.MatchesTag("State.Debuff.Stunned.Physical") // false container.MatchesTagExact("State.Debuff.Stunned") // false (not exact) container.MatchesTagExact("State.Debuff.Stunned.Magic") // true container.HasAny(["Status.Frozen", "Status.Burning"]) // true container.HasAll(["State.Debuff.Stunned.Magic", "Status.Burning"]) // true container.HasAll(["Status.Burning", "Status.Frozen"]) // false ``` ### 7.4 Tag Inheritance and Implicit Tags When a tag is added to a container, the grant counts of all ancestor tags in `AllTagCounts` are incremented by the same amount. When a tag is removed, ancestor counts are decremented symmetrically. This means `MatchesTag` on a parent tag is always consistent with the sum of grants on its descendants: AddTag("State.Debuff.Stunned.Magic") ExplicitTagCounts["State.Debuff.Stunned.Magic"] = 1 AllTagCounts["State.Debuff.Stunned.Magic"] = 1 AllTagCounts["State.Debuff.Stunned"] = 1 ← propagated AllTagCounts["State.Debuff"] = 1 ← propagated AllTagCounts["State"] = 1 ← propagated AddTag("State.Debuff.Stunned.Magic") # second effect grants same tag ExplicitTagCounts["State.Debuff.Stunned.Magic"] = 2 AllTagCounts["State.Debuff.Stunned.Magic"] = 2 AllTagCounts["State.Debuff.Stunned"] = 2 AllTagCounts["State.Debuff"] = 2 AllTagCounts["State"] = 2 RemoveTag("State.Debuff.Stunned.Magic") # first effect expires ExplicitTagCounts["State.Debuff.Stunned.Magic"] = 1 # still present! AllTagCounts["State.Debuff.Stunned.Magic"] = 1 AllTagCounts["State.Debuff.Stunned"] = 1 ... # MatchesTag("State.Debuff.Stunned") → still true, no event dispatched RemoveTag("State.Debuff.Stunned.Magic") # second effect expires ExplicitTagCounts["State.Debuff.Stunned.Magic"] = 0 # now absent AllTagCounts["State.Debuff.Stunned.Magic"] = 0 AllTagCounts["State.Debuff.Stunned"] = 0 ... # OnTagChanged dispatched for the leaf and each ancestor that hit 0 This enables hierarchical queries where `MatchesTag("State.Debuff")` matches any active debuff, and the match remains valid as long as any descendant tag has a count \> 0. ### 7.5 State Representation via Tags Tags are the primary method for representing Actor states. Instead of boolean flags: ``` typescript // Avoid this pattern if (actor.isStunned && !actor.isImmune) { ... } // Use tag queries if (actor.Tags.MatchesTag("State.Debuff.Stunned") && !actor.Tags.MatchesTag("Status.Immune.Stun")) { ... } ``` This decouples the "How" of a state (animation, logic freeze) from the "What" of the state (the Tag). ### 7.6 Schema Definition ``` yaml TagDefinition: Tag: string # Full hierarchical tag name Description: string # Human-readable description AllowMultiple: boolean # Can multiple instances exist? (default: false) DevComment: string # Developer notes ``` Tag definitions MAY be collected in a tag registry: ``` yaml $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_tag.json Tags: - Tag: "State.Debuff.Stunned" Description: "Actor is unable to perform actions" - Tag: "State.Debuff.Stunned.Magic" Description: "Stun caused by magical effect" - Tag: "State.Debuff.Stunned.Physical" Description: "Stun caused by physical impact" - Tag: "Status.Immune.Stun" Description: "Actor is immune to stun effects" ``` ## 8. Gameplay Abilities ### 8.1 Ability Definition A Gameplay Ability is a self-contained unit of logic defining an action an Actor can perform. Unlike simple function calls, Abilities are asynchronous, stateful objects with defined lifecycles. #### Ability Class Structure ``` typescript abstract class GameplayAbility { /** Tags describing this ability */ AbilityTags: TagContainer; /** Tags that block this ability's activation */ BlockedByTags: TagContainer; /** Tags that this ability blocks when active */ BlockAbilitiesWithTags: TagContainer; /** Tags required on owner for activation */ ActivationRequiredTags: TagContainer; /** Tags that prevent activation if present */ ActivationBlockedTags: TagContainer; /** * Tags applied to the owner while this ability is active. * Implementations MUST apply these as an auto-generated Infinite GameplayEffect * on CommitAbility and remove that effect on EndAbility/CancelAbility. * Direct tag mutation is prohibited (see §3.1). */ ActivationOwnedTags: TagContainer; /** Cost effect applied on commit */ CostEffect?: GameplayEffectClass; /** Cooldown effect applied on commit */ CooldownEffect?: GameplayEffectClass; /** Called when ability is activated */ abstract ActivateAbility(context: AbilityContext): void; /** Called when ability ends */ abstract EndAbility(wasCancelled: boolean): void; } ``` #### AbilitySpec (Instance Data) ``` typescript struct AbilitySpec { /** Reference to the ability class */ AbilityClass: GameplayAbilityClass; /** Current level of this ability instance */ Level: number; /** Input action binding (if any) */ InputID?: InputID; /** Handle for identification */ Handle: AbilitySpecHandle; /** Runtime parameters */ Parameters: Map; /** Is currently active? */ IsActive: boolean; /** * Handle to the auto-generated Infinite Effect that grants ActivationOwnedTags. * Set by CommitAbility; cleared by EndAbility/CancelAbility. * Undefined when the ability is not active. */ ActiveOwnedTagsHandle?: ActiveEffectHandle; } ``` ### 8.2 Lifecycle State Machine ┌──────────────┐ │ NotGranted │ └──────┬───────┘ │ Grant ▼ ┌──────────────┐ ┌──────────▶│ Granted │◀──────────┐ │ │ (Inactive) │ │ │ └──────┬───────┘ │ │ │ TryActivate │ │ ▼ │ │ ┌──────────────┐ │ │ │ Activating │───────────┤ │ │ (Validating) │ Fail │ │ └──────┬───────┘ │ │ │ Commit │ │ ▼ │ │ ┌──────────────┐ │ │ │ Active │ │ │ │ (Executing) │ │ │ └──────┬───────┘ │ │ │ End/Cancel │ │ ▼ │ │ ┌──────────────┐ │ └───────────│ Ending │───────────┘ └──────────────┘ ### 8.3 Activation Requirements Before an Ability can activate, the following checks MUST pass: 1. *Granted Check*: Ability must be granted to the GC 2. *Not Already Active*: Ability must not currently be active (unless configured for multiple instances) 3. *Required Tags*: Owner must have all tags in `ActivationRequiredTags` 4. *Blocked Tags*: Owner must NOT have any tags in `ActivationBlockedTags` 5. *Cost Verification*: If CostEffect is defined, owner must have sufficient resources 6. *Cooldown Verification*: Cooldown tag must not be present ``` typescript function CanActivateAbility(spec: AbilitySpec): boolean { const ownerTags = GC.GetOwnedTags(); // Check required tags if (!ownerTags.HasAll(spec.AbilityClass.ActivationRequiredTags)) { return false; } // Check blocked tags if (ownerTags.HasAny(spec.AbilityClass.ActivationBlockedTags)) { return false; } // Check cooldown if (ownerTags.MatchesTag(GetCooldownTag(spec))) { return false; } // Check cost if (!CanAffordCost(spec)) { return false; } return true; } ``` ### 8.4 Commit Phase The Commit phase is the point of no return where resources are consumed and cooldowns begin. Once committed: 1. Cost Effect is applied (resources consumed) 2. Cooldown Effect is applied (cooldown tag granted) 3. Activation Owned Tags are granted 4. Ability proceeds to execution ``` typescript function CommitAbility(spec: AbilitySpec): boolean { // Apply cost if (spec.AbilityClass.CostEffect) { const costSpec = MakeOutgoingSpec(spec.AbilityClass.CostEffect, spec.Level); ApplyGameplayEffectToSelf(costSpec); } // Apply cooldown if (spec.AbilityClass.CooldownEffect) { const cooldownSpec = MakeOutgoingSpec(spec.AbilityClass.CooldownEffect, spec.Level); ApplyGameplayEffectToSelf(cooldownSpec); } // Grant activation tags via an auto-generated Infinite Effect. // Direct tag mutation is prohibited (§3.1); all tag state flows through Effects. if (!spec.AbilityClass.ActivationOwnedTags.IsEmpty()) { const ownedTagsSpec = MakeOwnedTagsEffect(spec.AbilityClass.ActivationOwnedTags, spec.Level); spec.ActiveOwnedTagsHandle = ApplyGameplayEffectToSelf(ownedTagsSpec); } return true; } ``` ### 8.5 Costs and Cooldowns as Effects Costs and Cooldowns are NOT separate variables but are implemented as specialized Gameplay Effects. #### Cost Effect Pattern ``` yaml $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: "GE_Fireball_Cost" DurationPolicy: Instant Modifiers: - Attribute: "Mana" Operation: Add Magnitude: Type: ScalableFloat Value: -50.0 # Negative to subtract ``` #### Cooldown Effect Pattern ``` yaml $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: "GE_Fireball_Cooldown" DurationPolicy: HasDuration Duration: Type: ScalableFloat Value: 5.0 # 5 second cooldown GrantedTags: - "Cooldown.Ability.Fireball" ``` This pattern enables external modification of costs and cooldowns. For example, a "Mana Efficiency" buff could apply a multiplier to all cost effects: ``` yaml $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: "GE_ManaEfficiency_Buff" DurationPolicy: HasDuration Duration: Type: ScalableFloat Value: 30.0 Modifiers: - Attribute: "ManaCostMultiplier" Operation: Multiply Magnitude: Type: ScalableFloat Value: -0.25 # -25% mana cost ``` ### 8.6 Cancellation and Interruption Abilities may be cancelled by: 1. *Self-Cancellation*: Ability logic calls EndAbility(true) 2. *External Cancel*: Another system calls CancelAbility on the GC 3. *Cancel Tags*: An Effect grants a tag in the Ability’s `CancelAbilitiesWithTags` set 4. *Owner Death*: Owner’s Health reaches zero ``` typescript function CancelAbility(handle: AbilitySpecHandle): void { const spec = GetAbilitySpec(handle); if (!spec.IsActive) return; // Remove activation tags by removing the Effect that granted them. // Direct tag mutation is prohibited (§3.1). if (spec.ActiveOwnedTagsHandle) { RemoveActiveGameplayEffect(spec.ActiveOwnedTagsHandle); spec.ActiveOwnedTagsHandle = undefined; } // Call ability's end handler spec.AbilityInstance.EndAbility(true /* wasCancelled */); // Cleanup active tasks CancelAllAbilityTasks(handle); spec.IsActive = false; } ``` ### 8.7 Schema Definition ``` yaml Ability: Name: string # Required: Unique identifier Tags: AbilityTags: [string] # Tags describing this ability BlockedByTags: [string] # Tags that block activation BlockAbilitiesWithTags: [string] # Tags blocked while active CancelAbilitiesWithTags: [string] # Tags cancelled on activation ActivationRequiredTags: [string] # Required for activation ActivationBlockedTags: [string] # Block activation if present ActivationOwnedTags: [string] # Granted while active Cost: string # Effect name for cost Cooldown: string # Effect name for cooldown Tasks: # Sequential task definitions - Type: string # Task type name Params: object # Task-specific parameters Metadata: DisplayName: string Description: string Icon: string ``` ## 9. Gameplay Effects ### 9.1 Effect Structure A Gameplay Effect defines a modification to an Actor’s state. Effects are data-only definitions that SHOULD NOT be subclassed. ``` typescript struct GameplayEffect { /** Unique identifier */ Name: string; /** Duration behavior */ DurationPolicy: DurationPolicy; /** Duration value (if applicable) */ Duration?: MagnitudeDefinition; /** Periodic execution settings */ Period?: PeriodicSettings; /** Attribute modifications */ Modifiers: Modifier[]; /** Complex calculations */ Executions: ExecutionCalculation[]; /** Tags granted while active */ GrantedTags: Tag[]; /** Tags required on target for application */ ApplicationRequiredTags: Tag[]; /** Abilities granted while active */ GrantedAbilities: AbilityGrant[]; /** Execution policy for multiple instances */ ExecutionPolicy: ExecutionPolicy; /** * Override conflict resolution priority. * When multiple active effects apply an Override modifier to the same * Attribute, the effect with the highest Priority value wins. * On equal Priority, last-applied wins (LIFO). * Defaults to 0. Negative values are valid. */ Priority: integer; /** Gameplay cue tags */ GameplayCues: Tag[]; } ``` ### 9.2 Duration Policies | Policy | Base Value | Current Value | Persistence | |---------------|------------|---------------|---------------------------| | `Instant` | Modified | Recalculated | Permanent change | | `HasDuration` | Unchanged | Modified | Temporary (until expiry) | | `Infinite` | Unchanged | Modified | Temporary (until removed) | Instant Effects Modify the Base Value immediately and permanently. The Effect does not remain "active" after application. Classic examples: damage, healing, permanent stat increases. HasDuration Effects Modify the Current Value for a specified duration. When the timer expires, the modifier is removed and the attribute reverts. Classic examples: buffs, debuffs, temporary bonuses. Infinite Effects Modify the Current Value indefinitely until explicitly removed. Classic examples: passive auras, equipment bonuses, persistent status effects. ### 9.3 Periodic Execution Effects with duration (HasDuration or Infinite) MAY execute periodically: ``` typescript struct PeriodicSettings { /** Time between executions */ Period: float; /** Execute immediately on application? */ ExecuteOnApplication: boolean; } ``` Periodic effects behave like repeated Instant effects within a duration container: ``` yaml $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: "GE_Poison" DurationPolicy: HasDuration Duration: Type: ScalableFloat Value: 10.0 Period: Period: 1.0 ExecuteOnApplication: false Modifiers: - Attribute: "Health" Operation: Add Magnitude: Type: ScalableFloat Value: -5.0 # 5 damage per second ``` ### 9.4 Modifier Specification #### 9.4.1 Operations | Operation | Semantics | Pipeline Step | Magnitude convention | |------------|-----------------------------|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `Add` | Pre-multiply flat additive | Step 2 | Absolute delta (e.g., `+10` adds 10) | | `AddPost` | Post-multiply flat additive | Step 5 (very rare) | Absolute delta | | `Multiply` | Channel-aggregated bonus | Step 4 | Signed bonus: `+0.25` = +25%, `−0.25` = −25%. Modifiers in the same `Channel` add their bonuses; channel effective factors multiply across channels. See §5.3 Channel Aggregation. | | `Override` | Replace value | Step 6 | Absolute replacement value | > *Note:* There is no `Divide` operation. A 50% reduction is expressed as `Multiply` with magnitude `−0.5` (i.e., a −50% penalty). This eliminates the divide-by-zero edge case. ``` typescript struct Modifier { /** Target attribute */ Attribute: AttributeReference; /** * Modification operation. * - Add: Pre-multiply flat additive (pipeline step 2) * - AddPost: Post-multiply flat additive (pipeline step 7; very rare) * - Multiply: Multiplicative factor (pipeline step 6) * - Override: Replace the computed value entirely (pipeline step 8) */ Operation: ModifierOperation; /** Magnitude calculation */ Magnitude: MagnitudeDefinition; /** * Optional aggregation channel name for `Multiply` modifiers. * * Semantics (see §5.3 Channel Aggregation for the full formula): * - Modifiers with the SAME Channel add their bonuses together before * the channel's effective factor (1 + sum) is computed. * - Modifiers in DIFFERENT Channels produce independent factors that * multiply against each other. * - A modifier with no Channel is in its own implicit singleton channel, * contributing independently. * * Example — two gear bonuses and one legendary power: * GE_FireDmg: Multiply +0.20 Channel:"DamageBonuses" * GE_EliteDmg: Multiply +0.15 Channel:"DamageBonuses" * GE_Legendary: Multiply +0.50 Channel:"LegendaryPowers" * → effective factor = (1 + 0.20 + 0.15) × (1 + 0.50) = 1.35 × 1.50 = 2.025 * vs. naive stacking: 1.20 × 1.15 × 1.50 = 2.07 (higher, causes power creep) * * Ignored on `Add`, `AddPost`, and `Override` modifiers. */ Channel?: string; } ``` #### 9.4.2 Magnitude Calculation Types ScalableFloat Static or curve-based value. ``` yaml Magnitude: Type: ScalableFloat Value: 25.0 # Static value # OR Curve: "DamageCurve" # Curve lookup CurveInput: "Level" # Curve x-axis ``` AttributeBased Derived from another attribute. ``` yaml Magnitude: Type: AttributeBased BackingAttribute: "Strength" Source: Target # Source | Target Coefficient: 1.5 PreMultiplyAdditive: 0.0 PostMultiplyAdditive: 10.0 # Result = (AttributeValue + PreAdd) * Coefficient + PostAdd ``` CustomCalculation Custom Modifier Magnitude Calculator (MMC). ``` yaml Magnitude: Type: CustomCalculation CalculatorClass: "MMC_CriticalDamage" ``` SetByCaller Runtime-provided value via EffectSpec. ``` yaml Magnitude: Type: SetByCaller DataTag: "Damage.Base" # Lookup key ``` Usage: ``` typescript const spec = MakeOutgoingSpec(damageEffect, level); spec.SetByCallerMagnitude("Damage.Base", calculatedDamage); ApplyGameplayEffectToTarget(target, spec); ``` ### 9.5 Execution Calculations Execution Calculations provide full access to source and target attributes for complex, multi-attribute logic. ``` typescript abstract class ExecutionCalculation { /** Attributes to capture from source */ SourceCaptureDefinitions: AttributeCapture[]; /** Attributes to capture from target */ TargetCaptureDefinitions: AttributeCapture[]; /** Perform the calculation */ abstract Execute( source: CapturedAttributes, target: CapturedAttributes, context: EffectContext ): ModifierResult[]; } struct AttributeCapture { Attribute: AttributeReference; CaptureTime: CaptureTime; // OnApplication | OnExecution } ``` *Capture vs Snapshot Semantics* - `OnApplication`: Attribute value is captured when Effect is first applied - `OnExecution`: Attribute value is captured each time Effect executes Example: Armor Penetration Calculation ``` typescript class ExecCalc_PhysicalDamage extends ExecutionCalculation { SourceCaptureDefinitions = [ { Attribute: "AttackPower", CaptureTime: OnExecution }, { Attribute: "ArmorPenetration", CaptureTime: OnExecution } ]; TargetCaptureDefinitions = [ { Attribute: "Armor", CaptureTime: OnExecution } ]; Execute(source, target, context): ModifierResult[] { const attackPower = source.Get("AttackPower"); const armorPen = source.Get("ArmorPenetration"); const targetArmor = target.Get("Armor"); const effectiveArmor = Math.max(0, targetArmor - armorPen); const damageReduction = effectiveArmor / (effectiveArmor + 100); const finalDamage = attackPower * (1 - damageReduction); return [{ Attribute: "Health", Operation: Add, Magnitude: -finalDamage }]; } } ``` ### 9.6 Execution Policies Execution Policies define how multiple instances of the same Effect interact. This model replaces traditional "stacking" concepts with clearer behavioral semantics. | Policy | Behavior | |-----------------|-------------------------------------------------------------------------| | `RunInParallel` | All instances execute simultaneously; magnitude stacks N times | | `RunInSequence` | Instances queue; executes one after another | | `RunInMerge` | Single logical instance; durations merge (earliest start to latest end) | #### RunInParallel Each instance of the effect runs simultaneously, applying N times the magnitude. Time ───────────────────────────────────▶ Instance 1: ████████████████ Instance 2: ████████████████ Instance 3: ████████████████ Combined magnitude at t=5: 3× base Use case: Stackable damage-over-time effects, multiple buff sources #### RunInSequence Instances queue and execute one after another. Time ───────────────────────────────────▶ Instance 1: ████████████████ Instance 2: ████████████████ Instance 3: ████████████████ Use case: Channeled effects, crowd control chains *Chaining mechanism:* The GC owns the queue for each `RunInSequence` effect class. When the active instance’s duration expires (or it is manually removed), the GC automatically dequeues and begins the next instance, resetting the duration timer. Ability authors do not manage this transition; applying the same effect class while one is already active is sufficient to enqueue. The `OnEffectApplied` / `OnEffectRemoved` delegates fire for each instance individually, so callers can observe the moment one stun ends and the next begins. #### RunInMerge Multiple applications merge into a single logical instance with combined duration. Time ───────────────────────────────────▶ Instance 1: ████████████████ Instance 2: ████████████████ Instance 3: ████████████████ Merged: ████████████████████████████ Use case: Buff refreshing, grace periods ### 9.7 Tag Grants Effects MAY grant Tags while active: ``` yaml $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: "GE_Burning" DurationPolicy: HasDuration Duration: Type: ScalableFloat Value: 5.0 GrantedTags: - "State.Debuff.Burning" - "State.Element.Fire" Modifiers: - Attribute: "Health" Operation: Add Magnitude: Type: ScalableFloat Value: -10.0 ``` When the Effect is applied: 1. `AddTag` is called for each Granted Tag — grant counts increment 2. `OnTagChanged` is dispatched *only* for tags whose count transitions `0 → 1` 3. Gameplay Cues are triggered only on that same `0 → 1` transition When the Effect is removed (duration expires or manual removal): 1. `RemoveTag` is called for each Granted Tag — grant counts decrement 2. `OnTagChanged` is dispatched *only* for tags whose count transitions `1 → 0` 3. Looping Gameplay Cues are stopped only on that same `1 → 0` transition *Consequence for concurrent Effects:* if two Effects both grant `State.Debuff.Burning`, the tag’s count reaches 2. Removing the first Effect decrements to 1 — no event, no Cue change, the character remains visually on fire. Only removing the second Effect decrements to 0, dispatches `OnTagChanged`, and stops the looping Cue. This is the correct behaviour and falls out automatically from ref-counting. ### 9.8 Ability Grants Effects MAY grant Abilities while active: ``` yaml $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: "GE_FireSword_Equipped" DurationPolicy: Infinite GrantedAbilities: - AbilityClass: "GA_FlameStrike" Level: 1 InputID: "Ability.Weapon.Special" RemoveOnEffectRemoval: true ``` This pattern enables equipment-based abilities where unequipping the item removes the Effect and consequently the granted Ability. ### 9.9 EffectSpec and EffectContext #### EffectSpec Structure ``` typescript struct EffectSpec { /** Reference to effect definition */ EffectClass: GameplayEffectClass; /** Level for magnitude calculations */ Level: number; /** Application context */ Context: EffectContextHandle; /** SetByCaller magnitude overrides */ SetByCallerMagnitudes: Map; /** Duration override (if any) */ DurationOverride?: float; /** Period override (if any) */ PeriodOverride?: float; } ``` #### EffectContext Structure ``` typescript struct EffectContext { /** GC that created this effect */ InstigatorGC: GameplayController; /** Actor that caused this effect */ EffectCauser: Actor; /** Ability that applied this effect (if any) */ SourceAbility?: GameplayAbility; /** Object that was the origin (projectile, etc.) */ SourceObject?: Object; /** Hit result for physics-based effects */ HitResult?: HitResult; /** World location for positional effects */ WorldOrigin?: Vector3; } ``` #### Handle Patterns Handles provide lightweight references to specs and active effects: ``` typescript struct EffectSpecHandle { Data: SharedPtr; } struct ActiveEffectHandle { Handle: number; bPassedFiltersAndWasExecuted: boolean; } ``` ### 9.10 Schema Definition ``` yaml # GameplayEffect Definition Schema type: object required: - Name - DurationPolicy properties: Name: type: string description: Unique effect identifier DurationPolicy: type: string enum: - Instant - HasDuration - Infinite Duration: type: object properties: Type: type: string enum: - ScalableFloat - AttributeBased - SetByCaller Value: type: number Period: type: object properties: Period: type: number minimum: 0 ExecuteOnApplication: type: boolean default: false ExecutionPolicy: type: string enum: - RunInParallel - RunInSequence - RunInMerge default: RunInParallel Priority: type: integer default: 0 description: Override conflict priority. Highest value wins when multiple Override modifiers target the same Attribute. Equal priority resolves by last-applied (LIFO). Modifiers: type: array items: type: object required: - Attribute - Operation - Magnitude properties: Attribute: type: string Operation: type: string enum: - Add - AddPost - Multiply - Override Magnitude: type: object Channel: type: string description: > Aggregation channel for Multiply modifiers. Modifiers sharing a Channel add their bonuses; channels multiply against each other. Omit to treat this modifier as an isolated singleton channel. Ignored on Add, AddPost, and Override operations. GrantedTags: type: array items: type: string GrantedAbilities: type: array items: type: object properties: AbilityClass: type: string Level: type: integer default: 1 InputID: type: string RemoveOnEffectRemoval: type: boolean default: true GameplayCues: type: array items: type: string ``` # Part III: Asynchronous Execution ## 10. Ability Tasks ### 10.1 Purpose and Design Ability Tasks are specialized asynchronous nodes that pause ability execution until a specific trigger condition is met. Tasks enable complex, multi-stage abilities to be written in a linear, readable fashion while executing asynchronously across frames or network ticks. Tasks leverage the Observer design pattern for efficiency. Instead of polling a condition every frame, the ability registers a task and goes dormant. When the trigger condition is met, the task "wakes up" the ability and execution continues. ### 10.2 Task Lifecycle ┌─────────────┐ │ Inactive │ └──────┬──────┘ │ Instantiate ▼ ┌─────────────┐ │ Ready │ └──────┬──────┘ │ Activate ▼ ┌─────────────┐ Tick (if needed) ┌───▶│ Active │◀────────────────┐ │ └──────┬──────┘ │ │ │ │ │ ├────────────────────────┘ │ │ Trigger/Complete │ ▼ │ ┌─────────────┐ │ │ Completed │ │ └─────────────┘ │ │ ┌─────────────┐ └────│ Cancelled │ └─────────────┘ *Instantiation*: Task is created with configuration parameters *Activation*: Task registers with relevant systems (timers, events, physics) *Tick* (optional): Some tasks require per-frame updates (see §10.6) *Completion*: Trigger condition met; ability execution resumes *Cancellation*: Task is aborted (ability cancelled, owner died) ### 10.3 Predefined Task Categories | Category | Trigger | Example Tasks | |-------------|--------------------|--------------------------------------| | Temporal | Timer expiry | WaitDelay, WaitGameTime | | Event-Based | Gameplay event | WaitGameplayEvent, WaitTagChanged | | Input-Based | Input state change | WaitInputRelease, WaitInputPressed | | State-Based | Tag change | WaitTagAdded, WaitTagRemoved | | Spatial | Collision/overlap | WaitOverlap, WaitForTarget | | Animation | Montage notify | WaitAnimationEvent, WaitMontageEnded | #### WaitDelay Waits for a specified duration. ``` typescript class WaitDelay extends AbilityTask { Duration: float; OnActivate(): void { this.StartTimer(this.Duration); } OnTimerComplete(): void { this.Completed.Broadcast(); this.EndTask(); } } ``` #### WaitGameplayEvent Waits for a gameplay event with a matching tag. ``` typescript class WaitGameplayEvent extends AbilityTask { EventTag: Tag; OnlyTriggerOnce: boolean; OnActivate(): void { this.Owner.OnGameplayEvent.Subscribe(this.EventTag, this.OnEvent); } OnEvent(payload: GameplayEventData): void { this.EventReceived.Broadcast(payload); if (this.OnlyTriggerOnce) { this.EndTask(); } } } ``` #### WaitInputRelease Waits for an input action to be released. ``` typescript class WaitInputRelease extends AbilityTask { InputID: InputID; OnActivate(): void { this.InputSystem.OnInputReleased.Subscribe(this.InputID, this.OnRelease); } OnRelease(heldDuration: float): void { this.Released.Broadcast(heldDuration); this.EndTask(); } } ``` #### WaitTagAdded Waits for a specific tag to be added to the owner. ``` typescript class WaitTagAdded extends AbilityTask { WaitTag: Tag; OnActivate(): void { if (this.Owner.Tags.MatchesTag(this.WaitTag)) { this.TagFound.Broadcast(); this.EndTask(); return; } this.Owner.OnTagChanged.Subscribe(this.OnTagChanged); } OnTagChanged(tag: Tag, added: boolean): void { if (added && this.WaitTag.Matches(tag)) { this.TagFound.Broadcast(); this.EndTask(); } } } ``` ### 10.4 Custom Task Implementation Custom tasks MUST: 1. Extend the base AbilityTask class 2. Implement OnActivate() for setup 3. Implement cleanup in OnEndTask() 4. Provide delegate/event outputs for ability continuation 5. Handle cancellation gracefully ``` typescript class WaitForHealthThreshold extends AbilityTask { Threshold: float; Comparison: ComparisonType; // LessThan | LessEqual | Greater | GreaterEqual OnActivate(): void { // Check immediately if (this.CheckThreshold()) { this.ThresholdReached.Broadcast(); this.EndTask(); return; } // Subscribe to attribute changes this.Owner.OnAttributeChanged.Subscribe("Health", this.OnHealthChanged); } OnHealthChanged(event: AttributeChangedEvent): void { if (this.CheckThreshold()) { this.ThresholdReached.Broadcast(); this.EndTask(); } } CheckThreshold(): boolean { const health = this.Owner.GetAttributeValue("Health"); switch (this.Comparison) { case LessThan: return health < this.Threshold; case LessEqual: return health <= this.Threshold; case Greater: return health > this.Threshold; case GreaterEqual: return health >= this.Threshold; } } OnEndTask(): void { this.Owner.OnAttributeChanged.Unsubscribe("Health", this.OnHealthChanged); } } ``` ### 10.5 Task Ownership and Cleanup Tasks are owned by the Ability that created them. When an Ability ends: 1. All active Tasks are cancelled 2. Task event subscriptions are cleared 3. Task resources are released ``` typescript function EndAbility(wasCancelled: boolean): void { // Cancel all active tasks for (const task of this.ActiveTasks) { task.Cancel(); } this.ActiveTasks.Clear(); // Remove activation-owned tags by removing the Effect that granted them. // This is the normal (non-cancelled) end path; CancelAbility handles the cancel path. const spec = GC.GetAbilitySpec(this.Handle); if (spec?.ActiveOwnedTagsHandle) { GC.RemoveActiveGameplayEffect(spec.ActiveOwnedTagsHandle); spec.ActiveOwnedTagsHandle = undefined; } // Continue with ability end logic... } ``` ### 10.6 Tick Budgeting and Performance Most tasks are event-driven and impose no per-frame cost: as described in §10.1, an ability registers a task, goes dormant, and is woken only when the trigger fires. A subset of tasks, however, cannot be expressed as a single subscription and require periodic re-evaluation. The clearest example is the *Spatial* category (§10.3): `WaitOverlap` and `WaitForTarget` poll physics queries to detect overlaps or acquire targets. Ticking tasks scale multiplicatively with both ability complexity and actor count. An ability running five concurrent spatial tasks across 100 simultaneous actors performs 500 physics queries per frame. Without throttling, prioritisation, or visibility into per-task cost, this class of task becomes the dominant performance hazard of the system on large-scale titles, while remaining negligible on small ones. This section defines RECOMMENDED mechanisms for bounding that cost. The base `AbilityTask` SHOULD expose the controls described below, and the runtime SHOULD honour them when scheduling task ticks. Event-, state-, and input-driven tasks are never ticked and are unaffected. #### Per-Task Tick Throttling A ticking task SHOULD support a configurable tick interval rather than ticking every frame. Implementations SHOULD expose a `TickInterval` on the base task, expressed in seconds, where `0` means "tick every frame". When `TickInterval > 0`, the runtime MUST NOT tick the task more frequently than the interval; it SHOULD accumulate elapsed time and evaluate once per elapsed interval. ``` typescript abstract class AbilityTask { // Seconds between Tick() evaluations. 0 = every frame (default). TickInterval: float = 0; // Higher values tick first when the per-frame budget is exhausted. Default 0. Priority: int = 0; private accumulated: float = 0; // Called by the runtime each frame for tasks that require ticking. InternalTick(deltaTime: float): void { if (this.TickInterval <= 0) { this.Tick(deltaTime); return; } this.accumulated += deltaTime; if (this.accumulated >= this.TickInterval) { this.Tick(this.accumulated); // pass real elapsed time, not the nominal interval this.accumulated = 0; } } protected abstract Tick(deltaTime: float): void; } ``` Throttling trades responsiveness for cost. The interval SHOULD be chosen per task according to how quickly the observed condition changes and how tolerant the gameplay is to latency: | Category | Recommended Interval | Rationale | |-----------------------------|----------------------|---------------------------------------------------------------------------------| | Spatial (gameplay-critical) | Every frame (0) | Hit detection and targeting where a missed frame is player-visible | | Spatial (ambient / AOE) | 50-100 ms | Lingering area effects and aura acquisition; sub-frame precision is unnecessary | | Temporal | Every frame (0) | Driven by the timer subsystem; cost is already O(1) per task | | Animation | Every frame (0) | Must align with notify windows; no polling cost beyond the montage system | | Event / State / Input | n/a (no tick) | Observer-driven; never ticked (see §10.1) | #### Task Tick Budget and Priority Throttling bounds the cost of an individual task but not the aggregate cost across many actors. Implementations SHOULD additionally support a *per-frame tick budget*: an upper bound on the number of task ticks, or on accumulated tick time, that the runtime executes in a single frame. When the budget is exhausted in a given frame, the runtime SHOULD defer the remaining ticking tasks to a subsequent frame rather than exceeding the budget. Tasks SHOULD declare a `Priority`; when deferring, the runtime SHOULD tick higher-priority tasks first and SHOULD avoid starving any task indefinitely (for example, by ageing deferred tasks toward a higher effective priority). Gameplay-critical tasks, such as player hit detection and targeting, SHOULD be assigned a higher priority than ambient ones. `TickInterval` and `Priority` are conventional, optional parameters applicable to any task; they appear on the Ability schema’s task entries (see Appendix B, Ability Schema Definition). #### Profiling Hooks To make expensive tasks identifiable in production, implementations SHOULD expose profiling hooks around task ticking. At minimum, the runtime SHOULD make the following available per task type: - Number of active instances - Aggregate and per-instance tick time - Effective tick frequency (after throttling) These metrics SHOULD be surfaced through the host engine’s profiler or an equivalent instrumentation channel, so that integrators can attribute frame cost to specific task types and tune `TickInterval`, `Priority`, and the per-frame budget accordingly. ## 11. Input Integration ### 11.1 Command Pattern Overview The UGAS input system implements the Command pattern to decouple hardware inputs from ability execution. This separation enables: - Controller remapping without code changes - Platform-specific input schemes - Input buffering and queuing - Context-driven input switching via Gameplay Tags The input layer defines four formal entity schemas that sit between raw hardware and the ability system: ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Device │─────▶│ Modifier │─────▶│ Action │ │ Input │ │ Pipeline │ │ (InputID) │ └──────────────┘ └──────────────┘ └──────┬───────┘ │ ┌──────────────┐ │ │ Action Set │◀─────┘ │ (context) │ └──────┬───────┘ │ ┌──────────────┐ │ Ability │ │ Activation │ └──────────────┘ The *Mapping* entity connects Device Inputs to Actions, applying Modifiers along the way. Action Sets group Actions into switchable contexts driven by Gameplay Tags on the owning GC. ### 11.2 Input Actions An Input Action is a named, semantic input intent — the logical "what", not the physical "how". Actions decouple gameplay logic from hardware: an ability binds to the Action `Fire`, never to `Mouse.LeftButton`. Each Action declares a value type and a trigger behavior: | Value Type | Description | |------------|-------------------------------------------------------------------------------------------------| | Digital | Boolean on/off. Emits Started/Ongoing/Completed trigger events. | | Axis1D | Single float axis (e.g. throttle, steering). Emits a continuous value each frame while nonzero. | | Axis2D | Two-component vector (e.g. movement direction, camera look). | | Axis3D | Three-component vector (e.g. VR hand position, gyroscope orientation). | | Trigger Behavior | Description | |------------------|----------------------------------------------------------------| | OnPressed | Fire once when input goes from zero to nonzero (default). | | OnReleased | Fire once when input returns to zero. | | WhileHeld | Fire every frame while input is nonzero. | | OnTap | Fire once on press-then-release within `TapThreshold` seconds. | | OnDoubleTap | Fire on two taps within `DoubleTapWindow` seconds. | ``` yaml Name: Fire ValueType: Digital TriggerBehavior: OnPressed ConsumeInput: true Tags: ActionTags: - Input.Type.Combat Metadata: DisplayName: Fire Weapon Category: Combat ``` The `Name` field is the value that `InputID` fields on `GrantedAbilities` (§4) and `WaitInputRelease` tasks (§10.3) resolve to. Implementations MUST use exact string matching between `Action.Name` and `InputID`. `ConsumeInput` controls whether this action consumes the underlying input event. When `true` (the default), lower-priority actions bound to the same hardware input do not receive the event. When `false`, the event propagates. `Tags.ActionTags` enable bulk operations. A Gameplay Effect that grants the tag `Input.Blocked.Combat` could suppress all actions tagged `Input.Type.Combat` without listing them individually. ### 11.3 Action Sets An Action Set is a context-based group of Actions that are active together. When a player enters a vehicle, the `OnFoot` set deactivates and the `InVehicle` set activates — driven by the same tag-based activation rules that govern abilities. ``` yaml Name: OnFoot Actions: - Move - Look - Fire - Aim - Reload - Jump - Sprint Priority: 0 ActivationTags: RequiredTags: - State.Alive BlockedTags: - State.InVehicle - State.Cutscene InputBuffer: Enabled: true BufferWindow: 0.12 MaxBufferSize: 2 ``` #### Activation Rules Action Sets activate and deactivate based on the owning GC’s tag state, evaluated whenever owned tags change: 1. ALL tags in `ActivationTags.RequiredTags` MUST be present on the GC (AND logic). 2. NONE of the tags in `ActivationTags.BlockedTags` MAY be present (OR logic to block). 3. Multiple Action Sets MAY be active simultaneously. When two active sets contain the same Action, the set with the highest `Priority` wins for that Action. 4. When `Exclusive` is `true`, activating this set deactivates all other non-exclusive sets at the same or lower priority. This is appropriate for modal contexts (vehicle controls, cutscenes, menu navigation). The runtime SHOULD store the list of currently active Action Set names on the GC’s `ActiveActionSets` field for debugging and serialization. #### Per-Set Input Buffering Each Action Set MAY override the global input buffer configuration (see §11.7). This enables per-context tuning: - Fighting games: aggressive buffering (0.1s window, 5 buffer size) - Platformers: moderate buffering (0.15s window, 3 buffer size) - Menus: no buffering (avoid accidental double-confirms) - Driving: no buffering (analog inputs are continuous, not event-based) ### 11.4 Device Inputs A Device Input is a canonical, engine-agnostic identifier for a physical input on a hardware device. Device Inputs are referenced inline within Mappings using a `Device` + `Input` pair — they are not standalone entity files, since hardware is a finite catalogue, not game-specific authored data. | Device | Example Inputs | |----------|----------------------------------------------------------------------------------------------------------------------------------| | Keyboard | `Key.Space`, `Key.W`, `Key.LeftShift`, `Key.Escape` | | Mouse | `Mouse.LeftButton`, `Mouse.RightButton`, `Mouse.Axis.X`, `Mouse.Axis.Y`, `Mouse.Scroll` | | Gamepad | `Gamepad.FaceBottom` (A/Cross), `Gamepad.FaceRight` (B/Circle), `Gamepad.LeftStick.X`, `Gamepad.RightTrigger`, `Gamepad.DPad.Up` | | Touch | `Touch.Tap`, `Touch.Region.Left`, `Touch.Swipe` | | Gyro | `Gyro.Pitch`, `Gyro.Yaw`, `Gyro.Roll` | | Custom | Extension point for VR controllers, flight sticks, steering wheels. Uses `CustomDeviceName` for disambiguation. | Implementations MUST bridge canonical Device Input identifiers to their engine-specific equivalents at runtime. The canonical naming convention uses hierarchical dot notation: `{Category}.{Identifier}` (e.g. `Key.Space`, `Gamepad.LeftStick.X`). ### 11.5 Input Mappings A Mapping binds one or more Device Inputs to an Action, within an Action Set and optional platform context. This is the core data file that designers author to define "what button does what." ``` yaml ActionSet: OnFoot Platform: PC Bindings: - Action: Fire Inputs: - Device: Mouse Input: Mouse.LeftButton - Action: Move CompositeInputs: Up: Device: Keyboard Input: Key.W Down: Device: Keyboard Input: Key.S Left: Device: Keyboard Input: Key.A Right: Device: Keyboard Input: Key.D - Action: Look Inputs: - Device: Mouse Input: Mouse.Axis.X - Device: Mouse Input: Mouse.Axis.Y Modifiers: - MouseSensitivity ``` #### Binding Types *Simple binding*: A single Device Input maps to an Action. The most common case. *Chord binding*: Multiple Device Inputs in the `Inputs` array must all be active simultaneously for the binding to fire. Used for modifier keys (Shift+1 for an alternate ability slot). When a chord and a simple binding share an input, the chord SHOULD have a higher `Priority` to avoid the simple binding firing first. *Composite binding*: The `CompositeInputs` object composes multiple digital inputs into an axis value. The directional slots (`Up`, `Down`, `Left`, `Right`, `Forward`, `Backward`) map to a normalized vector suitable for Axis2D or Axis3D actions. This is how WASD keys produce a 2D movement vector on keyboard. #### Platform Filtering When `Platform` is set, the mapping only applies on that platform. Implementations SHOULD resolve platform at load time and discard non-matching mappings. When `Platform` is omitted, the mapping applies universally. Multiple mapping files MAY target the same Action Set with different platforms, providing platform-specific defaults: - `input_mapping_onfoot_pc.yaml` — keyboard + mouse bindings - `input_mapping_onfoot_gamepad.yaml` — gamepad bindings #### Rebinding Bindings with `bIsRebindable: true` (the default) SHOULD be modifiable at runtime through the remapping interface (see §11.8). Bindings with `bIsRebindable: false` are fixed and MUST NOT appear in the player’s controls settings screen. ### 11.6 Input Modifiers An Input Modifier is a reusable processing step applied to raw input values as they flow through the mapping pipeline. Modifiers are standalone entities referenced by name — not inline configuration — enabling sharing across mappings and integration with player settings screens. | Modifier Type | Description | |------------------|---------------------------------------------------------------------------------------------------------------------------------------------------| | DeadZone | Clamps values below `InnerThreshold` to zero and above `OuterThreshold` to 1.0. Shape is `Axial` (per-axis) or `Radial` (magnitude of 2D vector). | | Sensitivity | Linear multiplier applied to the value. Per-axis multipliers available for 2D actions. | | ResponseCurve | Non-linear response curve. Types: `Linear`, `Exponential` (with `Exponent`), `SCurve`, `Custom` (with explicit `CurvePoints`). | | AxisInvert | Inverts one or more axes (`InvertX`, `InvertY`). | | AxisScale | Scales axes by arbitrary factors (`ScaleX`, `ScaleY`, `ScaleZ`). | | AxisSwizzle | Reorders axes (e.g. `SwizzleOrder: "YXZ"` swaps X and Y). | | RadialScaling | Normalizes input to a maximum radius, clamping to the unit circle. | | Normalize | Normalizes the input vector to unit length. | | TriggerThreshold | Converts an analog value to digital: values above `PressThreshold` count as pressed. | | Clamp | Clamps the value between `Min` and `Max`. | | Custom | Engine-specific implementation via `CalculatorClass`. | ``` yaml Name: StickDeadzone Type: DeadZone Params: InnerThreshold: 0.15 OuterThreshold: 0.95 DeadZoneShape: Radial UserConfigurable: true Metadata: DisplayName: Stick Dead Zone ``` #### Pipeline Processing Modifiers in a binding’s `Modifiers` array are processed in order — the output of each modifier feeds into the next. The pipeline runs every frame for axis-type actions and on input events for digital actions. A typical gamepad stick pipeline: 1. Raw stick position → `DeadZone` (eliminate drift) → `RadialScaling` (normalize to unit circle) → processed value reaches the Action. A typical mouse look pipeline: 1. Raw mouse delta → `Sensitivity` (user-configurable multiplier) → processed value reaches the Action. #### User-Configurable Modifiers When `UserConfigurable` is `true`, the runtime SHOULD expose the modifier’s parameters in the player’s settings screen (e.g. a sensitivity slider, an invert-Y toggle, a dead zone adjustment). The modifier’s `Metadata.DisplayName` provides the label for the settings UI. ### 11.7 Input Buffering Input buffering allows players to queue inputs during animations or recovery frames. The buffer configuration is specified per Action Set (see §11.3) or globally: ``` typescript struct InputBufferConfig { /** Enable input buffering */ Enabled: boolean; /** Buffer window in seconds */ BufferWindow: float; /** Maximum buffered inputs */ MaxBufferSize: number; } ``` When input buffering is enabled: 1. Input arrives during "blocked" state (animation, recovery) 2. Input is stored in buffer with timestamp 3. When block ends, buffered inputs are processed in order 4. Expired inputs (beyond buffer window) are discarded ``` typescript function ProcessBufferedInputs(actionSet: ActionSet): void { const config = actionSet.InputBuffer ?? this.GlobalBufferConfig; if (!config.Enabled) return; const now = GetCurrentTime(); // Remove expired inputs this.InputBuffer = this.InputBuffer.filter( input => now - input.Timestamp < config.BufferWindow ); // Process valid inputs for (const input of this.InputBuffer) { if (TryActivateAbilityByInputID(input.ActionName)) { break; // Successfully activated, stop processing } } this.InputBuffer.Clear(); } ``` Buffered inputs MUST only be processed if the Action Set that contains their Action is still active when the buffer is drained. ### 11.8 Remapping Support Input mappings SHOULD be externalizable and modifiable at runtime: ``` typescript interface IInputMapper { /** Get the active bindings for an action */ GetBindingsForAction(action: ActionName): Binding[]; /** Remap a binding to a new device input */ RemapBinding(action: ActionName, oldInput: DeviceInput, newInput: DeviceInput): void; /** Reset all bindings to defaults for an action set */ ResetToDefaults(actionSet: ActionSetName): void; /** Save current mappings to persistent storage */ SaveMappings(): void; /** Load saved mappings from persistent storage */ LoadMappings(): void; } ``` Implementations MUST respect the `bIsRebindable` flag on bindings. Only bindings with `bIsRebindable: true` SHOULD be presented in the controls settings UI and accepted by `RemapBinding`. When a player remaps a binding, the runtime SHOULD check for conflicts (two bindings in the same Action Set using the same Device Input) and either warn the player or swap the conflicting binding. # Part IV: Feedback and Networking ## 12. Gameplay Cues ### 12.1 Design Philosophy Gameplay Cues enforce strict separation between Mechanics and Aesthetics. This separation provides: - *Server Optimization*: Headless servers load no visual/audio resources - *Client Customization*: Visual settings don’t affect gameplay - *Network Efficiency*: Cues are not replicated; only trigger tags are - *Platform Adaptation*: Different platforms can have different cue implementations ### 12.2 Cue Trigger Mechanism Cues are triggered by Tags following the `GameplayCue.*` convention: ``` yaml $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: "GE_FireDamage" DurationPolicy: Instant Modifiers: - Attribute: "Health" Operation: Add Magnitude: Type: ScalableFloat Value: -25.0 GameplayCues: - "GameplayCue.Impact.Fire" ``` When the Effect is applied: 1. Server applies the Effect and modifies attributes 2. `GameplayCue.Impact.Fire` tag is communicated to clients 3. Clients' Cue Managers instantiate the fire impact VFX/SFX ### 12.3 Cue Types *Burst Cues* (Fire-and-Forget) : Triggered once, play to completion, clean themselves up. ``` typescript class GC_Impact_Fire extends GameplayCueBurst { OnExecute(context: CueContext): void { SpawnParticleSystem("PS_FireImpact", context.HitLocation); PlaySound("SFX_FireImpact", context.HitLocation); } } ``` *Looping Cues* (Duration-Bound) : Persist while the triggering Effect is active. ``` typescript class GC_Status_Burning extends GameplayCueLooping { private ParticleComponent: ParticleSystem; OnAdd(context: CueContext): void { this.ParticleComponent = SpawnLoopingParticle("PS_BurningLoop", context.Target); StartLoopingSound("SFX_BurningLoop", context.Target); } OnRemove(): void { this.ParticleComponent.Destroy(); StopLoopingSound("SFX_BurningLoop"); } } ``` ### 12.4 Cue Manager The Cue Manager is a client-side system responsible for: 1. Receiving cue trigger notifications 2. Matching tags to Cue implementations 3. Instantiating and managing Cue resources 4. Pooling frequently-used Cues for performance ``` typescript class GameplayCueManager { private CueRegistry: Map; private ActiveLoopingCues: Map; HandleCueNotify(tag: Tag, context: CueContext, type: CueNotifyType): void { const cueClass = this.CueRegistry.get(tag); if (!cueClass) return; switch (type) { case Execute: const burstCue = this.InstantiateCue(cueClass); burstCue.OnExecute(context); break; case Add: const loopingCue = this.InstantiateCue(cueClass); loopingCue.OnAdd(context); this.ActiveLoopingCues.get(context.EffectHandle).push(loopingCue); break; case Remove: const activeCues = this.ActiveLoopingCues.get(context.EffectHandle); for (const cue of activeCues) { cue.OnRemove(); } this.ActiveLoopingCues.delete(context.EffectHandle); break; } } } ``` ### 12.5 Server Optimization On headless servers: 1. Cue Manager is NOT instantiated 2. Cue assets are NOT loaded 3. Cue trigger tags are still processed for replication 4. Memory footprint is significantly reduced Implementations SHOULD support a headless mode flag: ``` typescript if (!IsHeadlessServer()) { this.CueManager = new GameplayCueManager(); this.CueManager.LoadCueAssets(); } ``` ## 13. Network Replication ### 13.1 Replication Architecture UGAS defines a client-server replication model where: - The server is authoritative for all gameplay state - Clients receive replicated state updates - Clients may predict state changes locally - Server reconciles predicted state with authoritative state ┌──────────────────┐ ┌──────────────────┐ │ SERVER │ │ CLIENT │ │ │ │ │ │ ┌────────────┐ │ Replicate │ ┌────────────┐ │ │ │ GC │──┼───────────▶│ │ GC │ │ │ │(Authority) │ │ │ │ (Proxy) │ │ │ └────────────┘ │ │ └────────────┘ │ │ │ │ │ │ │ Predict │ │ │ │◀───────────┼──(Local Input) │ │ │ │ │ │ │ Reconcile │ │ │ │───────────▶│ │ └──────────────────┘ └──────────────────┘ ### 13.2 Replication Modes | Mode | Effects | Cues | Tags | Attributes | Use Case | |-----------|------------|------|------|------------|-----------------------------| | `Minimal` | None | All | All | None | AI entities, distant actors | | `Mixed` | Owner only | All | All | Owner only | Player characters | | `Full` | All | All | All | All | Single-player, debugging | Minimal Mode Only Cue triggers and Tag changes are replicated. Effects and Attributes are server-only. Suitable for AI entities where clients don’t need full state. Mixed Mode Full replication to the owning client; minimal replication to others. The standard mode for player characters in multiplayer games. Full Mode Complete replication to all clients. Used for single-player games or debugging. Higher bandwidth cost. ### 13.3 Bandwidth Optimization #### Delta Compression Only changed values are transmitted: ``` typescript struct ReplicatedAttributeSet { /** Bitmask of changed attributes since last update */ DirtyMask: uint32; /** Only changed attribute values */ ChangedValues: float[]; } ``` #### Dirty Bit Tracking Attributes track their dirty state: ``` typescript function SetBaseValue(attribute: Attribute, newValue: float): void { if (attribute.BaseValue !== newValue) { attribute.BaseValue = newValue; attribute.bIsDirty = true; this.DirtyAttributes.add(attribute); } } ``` #### Quantization For bandwidth-critical scenarios, attribute values MAY be quantized: ``` typescript struct QuantizedHealth { /** 0-255 representing 0-100% health */ HealthPercent: uint8; } ``` ### 13.4 Client-Side Prediction To eliminate network latency perception, clients predict ability outcomes locally: ``` typescript function TryActivateAbility_Predicted(handle: AbilitySpecHandle): void { // Generate prediction key const predictionKey = GeneratePredictionKey(); // Predict locally const success = TryActivateAbility_Local(handle, predictionKey); if (success) { // Store predicted state this.PredictedActivations.set(predictionKey, { Handle: handle, Timestamp: GetCurrentTime(), State: CaptureState() }); // Send to server Server_TryActivateAbility(handle, predictionKey); } } ``` ### 13.5 Server Reconciliation When server response differs from prediction: ``` typescript function OnServerActivationResponse( predictionKey: PredictionKey, serverSuccess: boolean, serverState: GameplayState ): void { const prediction = this.PredictedActivations.get(predictionKey); if (!prediction) return; if (!serverSuccess) { // Prediction was wrong - rollback RollbackToState(prediction.State); } else { // Prediction was correct - reconcile minor differences ReconcileState(serverState); } this.PredictedActivations.delete(predictionKey); } ``` #### Rollback and Replay For significant discrepancies: 1. Revert to last known authoritative state 2. Re-apply all inputs that occurred since that state 3. Blend visually to avoid jarring corrections ``` typescript function RollbackAndReplay( authoritativeState: GameplayState, inputHistory: Input[] ): void { // 1. Revert state ApplyState(authoritativeState); // 2. Replay inputs for (const input of inputHistory) { if (input.Timestamp > authoritativeState.Timestamp) { SimulateInput(input); } } // 3. Blend if needed if (VisualDiscrepancy > Threshold) { StartVisualBlend(currentVisual, newSimulatedState); } } ``` ### 13.6 Replication Frequency Recommendations | Actor Type | Update Rate | Notes | |------------------------------------------|-------------|-------------------------------------------------------------------------------| | Player Character (LAN / low-latency) | 60-100 Hz | High frequency for responsive feel | | Player Character (mobile / high-latency) | 20-30 Hz | Reduce to manage bandwidth; compensate with aggressive client-side prediction | | Important AI | 30-60 Hz | Moderate frequency | | Distant Actors | 10-20 Hz | Lower frequency acceptable | | Static Objects | On Change | Event-based only | > *High-latency guidance:* On connections with RTT \> 150 ms (common on mobile or cross-region play), implementations SHOULD lower the player-character replication rate to 20-30 Hz and increase prediction window depth accordingly. Attribute and Tag state SHOULD be sent at a lower rate than position to prioritise movement responsiveness. Dead-reckoning or interpolation SHOULD be applied on the receiving end. ### 13.7 Effect Application Authorization `ApplyGameplayEffectToTarget` is the primary mutation surface of the GC pipeline and therefore a critical security boundary in networked environments. #### Core requirement In any networked environment, a call to `ApplyGameplayEffectToTarget` that originates on a client MUST be validated by the server before the effect is executed on authoritative state. Clients MUST NOT be permitted to mutate server-authoritative GC state directly. #### Validation pipeline The server-side validation step MUST check, at minimum: 1. *Instigator authority* — the instigating GC is owned by the requesting client (or is a server-controlled entity). 2. *Ability ownership* — the effect is being applied as part of an ability that the instigator has been granted (i.e., the ability spec exists in the instigator’s granted-ability list). 3. *Target reachability* — the target GC is a legitimate target for the instigator at the time of application (range, line-of-sight, or game-rule checks as appropriate to the title). 4. *Effect class whitelist* — the effect class is one the ability is permitted to apply; implementations SHOULD reject arbitrary `EffectClass` values supplied by the client. If any check fails, the server MUST reject the application and MAY roll back any prediction the client has already applied locally (via the standard reconciliation path in §13.5). #### Predicted applications When a client applies an effect locally as part of a prediction (using a `PredictionKey`), the local application is speculative only. The authoritative application — or its rejection — is determined by the server. Implementations MUST treat predicted effect applications as unconfirmed until the server acknowledges the prediction key. ``` typescript // Client: speculative application const predictionKey = GeneratePredictionKey(); const specHandle = MakeOutgoingSpec(GE_Damage, level, predictionKey); ApplyGameplayEffectToTarget(target.GC, specHandle, predictionKey); // Effect is active locally, but flagged as predicted (unconfirmed). // Server: receives the RPC, validates, then applies authoritatively function Server_ApplyEffect( instigatorGC: GameplayController, targetGC: GameplayController, specHandle: EffectSpecHandle, predictionKey: PredictionKey ): void { // 1. Validate instigator owns the ability that produced this spec if (!ValidateInstigatorAuthority(instigatorGC, specHandle)) { RejectPrediction(predictionKey); return; } // 2. Validate target is reachable / eligible if (!ValidateTarget(instigatorGC, targetGC, specHandle)) { RejectPrediction(predictionKey); return; } // Authoritative application — triggers replication to all clients ApplyGameplayEffectToTarget(targetGC, specHandle); ConfirmPrediction(predictionKey); } ``` #### Authoritative-only effects Some effects MUST only ever be applied by the server (e.g., spawn effects, death effects, anti-cheat corrections). These effects SHOULD be tagged with `Gameplay.Effect.AuthoritativeOnly` and implementations MUST refuse to apply them on a client even if a prediction key is present. # Part V: Reference Implementation ## 14. Implementation Examples ### 14.1 Basic Damage Application #### Effect Definition ``` yaml $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: "GE_BasicDamage" DurationPolicy: Instant Modifiers: - Attribute: "Health" Operation: Add Magnitude: Type: SetByCaller DataTag: "Damage.Amount" GameplayCues: - "GameplayCue.Impact.Generic" ``` #### Application Flow ``` typescript function ApplyDamage(target: GameplayController, damage: float): void { // 1. Create context const context = this.GC.MakeEffectContext(); context.SetEffectCauser(this.Owner); // 2. Create spec const spec = this.GC.MakeOutgoingSpec(GE_BasicDamage, 1, context); // 3. Set damage amount spec.SetByCallerMagnitude("Damage.Amount", -damage); // Negative for subtraction // 4. Apply to target const handle = this.GC.ApplyGameplayEffectToTarget(target, spec); // 5. Check success if (handle.IsValid()) { OnDamageApplied(target, damage); } } ``` #### Attribute Change Handling ``` typescript class HealthObserver implements IAttributeChangeObserver { OnAttributeChanged(event: AttributeChangedEvent): void { const oldValue = event.OldValue; const newValue = event.NewValue; // Update health bar UI this.HealthBar.SetPercent(newValue / this.MaxHealth); // Show damage number const damage = oldValue - newValue; if (damage > 0) { SpawnDamageNumber(damage, event.Target.GetLocation()); } // Check for death if (newValue <= 0) { OnDeath(event.CausalEffect); } } } ``` ### 14.2 Buff/Debuff with Duration #### Temporary Modifier ``` yaml $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: "GE_StrengthBuff" DurationPolicy: HasDuration Duration: Type: ScalableFloat Value: 30.0 ExecutionPolicy: RunInMerge # Refresh duration on reapplication Modifiers: - Attribute: "AttackPower" Operation: Multiply Magnitude: Type: ScalableFloat Value: 0.25 # +25% damage GrantedTags: - "Status.Buff.Strength" GameplayCues: - "GameplayCue.Status.StrengthBuff" ``` #### Visual Cue Integration ``` typescript class GC_Status_StrengthBuff extends GameplayCueLooping { private AuraEffect: ParticleSystem; private BuffIcon: UIWidget; OnAdd(context: CueContext): void { // Spawn visual aura this.AuraEffect = SpawnAttached( "PS_StrengthAura", context.Target, "Spine" ); // Show buff icon in UI this.BuffIcon = ShowBuffIcon("Icon_Strength", context.Duration); // Play activation sound PlaySound("SFX_BuffActivate"); } OnRemove(): void { this.AuraEffect.Destroy(); this.BuffIcon.Remove(); PlaySound("SFX_BuffExpire"); } } ``` ### 14.3 Ability with Cast Time ``` yaml Ability: Name: "GA_Fireball" Tags: AbilityTags: - "Ability.Type.Spell" - "Ability.Element.Fire" ActivationOwnedTags: - "State.Casting" CancelAbilitiesWithTags: - "State.Stunned" ActivationBlockedTags: - "State.Silenced" Cost: "GE_Fireball_Cost" Cooldown: "GE_Fireball_Cooldown" ``` #### Task-Based Implementation ``` typescript class GA_Fireball extends GameplayAbility { CastTime: float = 1.5; ProjectileClass: ProjectileClass; ActivateAbility(context: AbilityContext): void { // 1. Commit resources if (!CommitAbility()) { EndAbility(true); return; } // 2. Play cast animation PlayAnimation("Anim_CastFireball"); // 3. Wait for cast time const waitTask = WaitDelay(this.CastTime); waitTask.OnComplete.Subscribe(this.OnCastComplete); // 4. Listen for interruption const interruptTask = WaitTagAdded("State.Stunned"); interruptTask.OnTagFound.Subscribe(this.OnInterrupted); } OnCastComplete(): void { // Spawn and launch projectile const projectile = SpawnProjectile( this.ProjectileClass, this.GetAvatarLocation(), this.GetAimDirection() ); projectile.SetDamageEffect(GE_FireballDamage); EndAbility(false); } OnInterrupted(): void { // Play fizzle effect TriggerCue("GameplayCue.Ability.Interrupted"); EndAbility(true); } } ``` ### 14.4 Complex Calculation (Armor Penetration) ``` typescript class ExecCalc_ArmorPenetration extends ExecutionCalculation { SourceCaptureDefinitions = [ { Attribute: "AttackPower", CaptureTime: OnExecution }, { Attribute: "ArmorPenetrationFlat", CaptureTime: OnExecution }, { Attribute: "ArmorPenetrationPercent", CaptureTime: OnExecution }, { Attribute: "CriticalChance", CaptureTime: OnExecution }, { Attribute: "CriticalDamage", CaptureTime: OnExecution } ]; TargetCaptureDefinitions = [ { Attribute: "Armor", CaptureTime: OnExecution }, { Attribute: "DamageReduction", CaptureTime: OnExecution } ]; Execute(source, target, context): ModifierResult[] { // Get source stats const attackPower = source.Get("AttackPower"); const armorPenFlat = source.Get("ArmorPenetrationFlat"); const armorPenPercent = source.Get("ArmorPenetrationPercent"); const critChance = source.Get("CriticalChance"); const critDamage = source.Get("CriticalDamage"); // Get target stats const targetArmor = target.Get("Armor"); const damageReduction = target.Get("DamageReduction"); // Calculate effective armor const armorAfterFlat = Math.max(0, targetArmor - armorPenFlat); const effectiveArmor = armorAfterFlat * (1 - armorPenPercent); // Armor damage reduction formula const armorDR = effectiveArmor / (effectiveArmor + 100); // Base damage let damage = attackPower * (1 - armorDR); // Apply critical hit if (RandomFloat() < critChance) { damage *= critDamage; context.SetTag("Hit.Critical"); } // Apply flat damage reduction damage *= (1 - damageReduction); return [{ Attribute: "Health", Operation: Add, Magnitude: -damage }]; } } ``` ## 15. Case Studies ### 15.1 Platformer (Mario-style) #### Movement Attributes ``` yaml $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/attribute_set.json Name: "PlatformerMovementSet" Attributes: - Name: "GravityScale" DefaultBaseValue: 1.0 Category: Statistic - Name: "JumpVelocity" DefaultBaseValue: 1200.0 Category: Statistic - Name: "AirControl" DefaultBaseValue: 0.65 Category: Statistic Clamping: Min: 0.0 Max: 1.0 - Name: "CoyoteTimeDuration" DefaultBaseValue: 0.15 Category: Statistic - Name: "JumpBufferDuration" DefaultBaseValue: 0.1 Category: Statistic - Name: "VerticalVelocity" DefaultBaseValue: 0.0 Category: Meta - Name: "HorizontalSpeed" DefaultBaseValue: 600.0 Category: Statistic ``` #### Jump Ability with Variable Height ``` typescript class GA_Jump extends GameplayAbility { // Handle to the Infinite Effect that grants State.InAir while airborne. // State.Grounded is managed by the physics subsystem via its own Effect, // not by this ability — tag ownership follows responsibility. private inAirHandle: ActiveEffectHandle; ActivateAbility(context: AbilityContext): void { // Check grounded OR coyote time if (!this.Owner.Tags.MatchesTag("State.Grounded") && !this.Owner.Tags.MatchesTag("Status.CoyoteTime")) { EndAbility(true); return; } // Grant State.InAir via an Effect — direct tag mutation is prohibited (§3.1). // GE_InAir is an Infinite Effect with GrantedTags: ["State.InAir"]. // The physics subsystem independently removes its GE_Grounded effect // when it detects the character is no longer on the ground. const inAirSpec = MakeOutgoingSpec(GE_InAir, 1); this.inAirHandle = ApplyGameplayEffectToSelf(inAirSpec); const jumpVelocity = this.Owner.GetAttribute("JumpVelocity"); ApplyImpulse(Vector3.Up * jumpVelocity); // Variable height: wait for button release const releaseTask = WaitInputRelease("Jump"); releaseTask.OnReleased.Subscribe(this.OnJumpReleased); // Wait for landing const landTask = WaitGameplayEvent("Event.Landed"); landTask.OnEvent.Subscribe(this.OnLanded); } OnJumpReleased(heldDuration: float): void { // Short press = cut jump short. // VerticalVelocity is a GAS Attribute kept in sync by the physics Avatar, // so this remains a pure GAS query — no direct physics coupling here. if (this.Owner.GetAttribute("VerticalVelocity") > 0) { // Apply gravity multiplier for shorter jump const cutSpec = MakeOutgoingSpec(GE_JumpCut, 1); ApplyGameplayEffectToSelf(cutSpec); } } OnLanded(): void { // Remove State.InAir by removing the Effect that granted it. // The physics subsystem re-applies its GE_Grounded effect on landing. RemoveActiveGameplayEffect(this.inAirHandle); EndAbility(false); } } ``` #### Power-Up Effects ``` yaml $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: "GE_SuperMushroom" DurationPolicy: Infinite GrantedTags: - "State.PowerUp.Super" Modifiers: - Attribute: "Scale" Operation: Multiply Magnitude: Type: ScalableFloat Value: 1.0 # +100% (doubles size) - Attribute: "Health" Operation: Add Magnitude: Type: ScalableFloat Value: 1.0 # Gain 1 hit point GameplayCues: - "GameplayCue.PowerUp.Super" ``` ### 15.2 Racing (Forza-style) #### Vehicle Attribute Sets ``` yaml $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/attribute_set.json Name: "VehiclePerformanceSet" Attributes: - Name: "EngineTorque" DefaultBaseValue: 400.0 Description: "Base torque in Nm" - Name: "EngineRPM" DefaultBaseValue: 0.0 Category: Meta - Name: "MaxSpeed" DefaultBaseValue: 250.0 Description: "Top speed in km/h" - Name: "TireGripMultiplier" DefaultBaseValue: 1.0 Category: Statistic - Name: "AeroDownforce" DefaultBaseValue: 100.0 Description: "Downforce coefficient" - Name: "TireTemperature" DefaultBaseValue: 80.0 Clamping: Min: 20.0 Max: 150.0 ``` #### Biome-Based Area Effects ``` yaml $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: "GE_Biome_Mud" DurationPolicy: Infinite ApplicationRequiredTags: - "Vehicle" Modifiers: - Attribute: "TireGripMultiplier" Operation: Multiply Magnitude: Type: ScalableFloat Value: -0.6 # -60% grip (retains 40% of normal grip) - Attribute: "MaxSpeed" Operation: Add Magnitude: Type: ScalableFloat Value: -30.0 # Reduce top speed GrantedTags: - "Surface.Mud" --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: "GE_Biome_Asphalt" DurationPolicy: Infinite ApplicationRequiredTags: - "Vehicle" Modifiers: - Attribute: "TireGripMultiplier" Operation: Override Magnitude: Type: ScalableFloat Value: 1.0 GrantedTags: - "Surface.Asphalt" ``` #### Physics Integration ``` typescript class ExecCalc_VehicleTraction extends ExecutionCalculation { SourceCaptureDefinitions = [ { Attribute: "TireGripMultiplier", CaptureTime: OnExecution }, { Attribute: "AeroDownforce", CaptureTime: OnExecution }, { Attribute: "TireTemperature", CaptureTime: OnExecution }, { Attribute: "CurrentSpeed", CaptureTime: OnExecution } ]; Execute(source, target, context): ModifierResult[] { const baseGrip = source.Get("TireGripMultiplier"); const downforce = source.Get("AeroDownforce"); const tireTemp = source.Get("TireTemperature"); const speed = source.Get("CurrentSpeed"); // Downforce increases with speed squared const downforceBonus = (downforce * speed * speed) / 100000; // Tire temperature optimal range: 80-100 let tempMultiplier = 1.0; if (tireTemp < 80) { tempMultiplier = 0.7 + (tireTemp / 80) * 0.3; } else if (tireTemp > 100) { tempMultiplier = 1.0 - ((tireTemp - 100) / 50) * 0.3; } const effectiveTraction = baseGrip * (1 + downforceBonus) * tempMultiplier; return [{ Attribute: "AvailableTraction", Operation: Override, Magnitude: effectiveTraction }]; } } ``` ### 15.3 ARPG (Diablo-style) #### Damage Bucket Architecture The "Damage Bucket" system prevents linear power creep by organizing `Multiply` modifiers into named channels. Modifiers in the same channel add their bonuses; channels multiply against each other. Because the `Channel` mechanism is built into the modifier pipeline (§5.3), the bucket design is expressed *declaratively* in Effect YAML — no custom calculation code required for the stacking logic itself. Three canonical buckets: | Channel | What goes in it | Stacking | |---------------------|----------------------------------------------------------------|--------------------------------------------------| | `"MainStat"` | Stat-derived scaling (e.g. Strength → +damage) | Additive | | `"DamageBonuses"` | Conditional damage bonuses (fire, vs. elites, while healthy …) | Additive | | `"LegendaryPowers"` | Item set / legendary power multipliers | Additive within, ×MainStat ×DamageBonuses across | ``` yaml # GE_Weapon_FireSword.yaml — item that grants a fire damage bonus $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: "GE_Weapon_FireSword" DurationPolicy: Infinite Modifiers: - Attribute: "WeaponDamage" Operation: Multiply Magnitude: Type: ScalableFloat Value: 0.20 # +20% fire damage Channel: "DamageBonuses" ``` ``` yaml # GE_Passive_EliteHunter.yaml — passive skill: +15% damage vs. elites Name: "GE_Passive_EliteHunter" DurationPolicy: Infinite ApplicationRequiredTags: - "Status.FightingElite" Modifiers: - Attribute: "WeaponDamage" Operation: Multiply Magnitude: Type: ScalableFloat Value: 0.15 # +15% damage vs. elites Channel: "DamageBonuses" ``` ``` yaml # GE_Set_LegendaryPower.yaml — set bonus: +50% damage (its own channel) Name: "GE_Set_LegendaryPower" DurationPolicy: Infinite Modifiers: - Attribute: "WeaponDamage" Operation: Multiply Magnitude: Type: ScalableFloat Value: 0.50 # +50% legendary multiplier Channel: "LegendaryPowers" ``` ``` yaml # GE_MainStat_Strength.yaml — applied by the attribute system per point of Strength Name: "GE_MainStat_Strength" DurationPolicy: Infinite Modifiers: - Attribute: "WeaponDamage" Operation: Multiply Magnitude: Type: AttributeBased BackingAttribute: "Strength" Source: Source Coefficient: 0.01 # +1% per Strength point Channel: "MainStat" ``` With all four effects active (Strength 50, fire sword, elite hunter active, legendary set): - `MainStat` channel factor: `1 + (0.01 × 50)` = *×1.50* - `DamageBonuses` channel factor: `1 + 0.20 + 0.15` = *×1.35* - `LegendaryPowers` channel factor: `1 + 0.50` = *×1.50* - Final `WeaponDamage` multiplier: `1.50 × 1.35 × 1.50` = *×3.04* vs. naive unchanelled stacking: `1.50 × 1.20 × 1.15 × 1.50` = *×3.11* — a modest difference at low item counts that compounds severely at higher counts. *Conditional modifiers* (e.g. the Vulnerability bonus) that depend on target state at hit-time still require an `ExecutionCalculation`, but only for the conditional logic — the stacking math is already handled by the pipeline: ``` typescript class ExecCalc_ARPGDamage extends ExecutionCalculation { Execute(source, target, context): ModifierResult[] { // All bucket stacking is already resolved in WeaponDamage's Current Value. const weaponDamage = source.Get("WeaponDamage"); // Only conditional logic needs to live here. const vulnerabilityBonus = target.Tags.MatchesTag("Status.Vulnerable") ? 0.20 : 0.0; return [{ Attribute: "Health", Operation: Add, Magnitude: -weaponDamage * (1 + vulnerabilityBonus) }]; } } ``` #### Combat Tag Queries ``` typescript class GA_Whirlwind extends GameplayAbility { ActivateAbility(context: AbilityContext): void { // This ability tags this.AbilityTags = ["Ability.Type.Melee", "DamageType.Physical"]; // Find targets in radius const targets = GetActorsInRadius(this.Owner.Location, 500); for (const target of targets) { // Check immunities if (target.Tags.MatchesTag("Immunity.Physical")) { // Show immune text SpawnFloatingText(target, "IMMUNE"); continue; } // Apply damage effect const spec = MakeOutgoingSpec(GE_WhirlwindDamage, this.Level); // Check for vulnerability bonus if (target.Tags.MatchesTag("Status.Vulnerable")) { spec.SetByCallerMagnitude("VulnerabilityBonus", 0.2); } ApplyGameplayEffectToTarget(target.GC, spec); } } } ``` #### Procedural Item Effects ``` typescript class ItemEquipSystem { EquipItem(item: Item): void { // Create infinite effect for item stats const itemEffect = GenerateItemEffect(item); // Apply effect const handle = this.GC.ApplyGameplayEffectToSelf(itemEffect); // Store handle for unequip this.EquippedItemEffects.set(item.ID, handle); // Grant item abilities for (const ability of item.GrantedAbilities) { this.GC.GrantAbility(ability.Class, ability.Level, ability.InputID); } } GenerateItemEffect(item: Item): EffectSpec { const effect = new GameplayEffect(); effect.DurationPolicy = Infinite; // Add modifiers for each stat roll for (const stat of item.Stats) { effect.Modifiers.push({ Attribute: stat.AttributeName, Operation: stat.Operation, Magnitude: { Type: ScalableFloat, Value: stat.Value } }); } // Add item tag effect.GrantedTags.push(`Item.Equipped.${item.Slot}`); effect.GrantedTags.push(`Item.Type.${item.Type}`); return MakeOutgoingSpec(effect, 1, MakeEffectContext()); } } ``` ### 15.4 Puzzle (2048-style) #### Grid Cell Attributes ``` yaml $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/attribute_set.json Name: "PuzzleCellSet" Attributes: - Name: "CellValue" DefaultBaseValue: 0.0 Category: Statistic - Name: "GridX" DefaultBaseValue: 0.0 Category: Meta - Name: "GridY" DefaultBaseValue: 0.0 Category: Meta - Name: "MergePriority" DefaultBaseValue: 0.0 Category: Meta ``` #### Move Ability with Tasks ``` typescript class GA_GridMove extends GameplayAbility { Direction: Vector2; ActivateAbility(context: AbilityContext): void { // Task 1: Scan grid const cells = ScanOccupiedCells(); // Sort by direction (front to back) cells.sort((a, b) => GetDirectionPriority(a, b, this.Direction)); // Calculate movements const movements: CellMovement[] = []; const merges: CellMerge[] = []; for (const cell of cells) { const result = CalculateDestination(cell, this.Direction); if (result.CanMove) { movements.push(result); if (result.WillMerge) { merges.push(result.MergeInfo); } } } // Apply movement effects for (const move of movements) { const moveSpec = MakeOutgoingSpec(GE_CellMove, 1); moveSpec.SetByCallerMagnitude("NewX", move.DestX); moveSpec.SetByCallerMagnitude("NewY", move.DestY); ApplyGameplayEffectToTarget(move.Cell.GC, moveSpec); } // Apply merge effects for (const merge of merges) { const mergeSpec = MakeOutgoingSpec(GE_CellMerge, 1); ApplyGameplayEffectToTarget(merge.TargetCell.GC, mergeSpec); // Mark source for destruction via an Effect — direct tag mutation is prohibited (§3.1). // GE_PendingDestroy is an Infinite Effect with GrantedTags: ["Status.PendingDestroy"]. const destroySpec = MakeOutgoingSpec(GE_PendingDestroy, 1); ApplyGameplayEffectToTarget(merge.SourceCell.GC, destroySpec); } // Wait for animations const animTask = WaitDelay(0.2); animTask.OnComplete.Subscribe(this.OnMoveComplete); } OnMoveComplete(): void { // Destroy merged sources DestroyTaggedCells("Status.PendingDestroy"); // Spawn new tile SpawnRandomTile(); // Check win/lose conditions CheckGameState(); EndAbility(false); } } ``` #### Undo via Effect Audit Trail Rather than snapshotting raw cell values, the undo system hooks into the GC Effect application pipeline via `OnBeforeEffectApplied`. Each effect applied during a turn is recorded alongside the pre-apply attribute values it will overwrite. Undoing a turn replays those pre-apply values back through Instant Effects — the undo state is derived entirely from the Effect layer, not from bespoke value captures. ``` typescript interface EffectRecord { TargetGC: GameplayController; Spec: EffectSpec; /** Attribute values captured immediately before this Effect was applied. */ PreApplyValues: Map; } interface TurnRecord { EffectsApplied: EffectRecord[]; } class UndoSystem { private TurnHistory: TurnRecord[] = []; private CurrentTurn: TurnRecord | null = null; /** Called by GA_Move at the start of each player turn. */ BeginTurn(): void { this.CurrentTurn = { EffectsApplied: [] }; } /** * Hook registered on each cell GC as OnBeforeEffectApplied. * The GC pipeline calls this immediately before applying an Effect, * giving us a chance to snapshot the attribute values that will change. */ OnBeforeEffectApplied(targetGC: GameplayController, spec: EffectSpec): void { if (!this.CurrentTurn) return; const preApplyValues = new Map(); for (const modifier of spec.EffectClass.Modifiers) { preApplyValues.set(modifier.Attribute, targetGC.GetAttribute(modifier.Attribute)); } this.CurrentTurn.EffectsApplied.push({ TargetGC: targetGC, Spec: spec, PreApplyValues: preApplyValues }); } /** Called by GA_Move after all effects for the turn have been applied. */ CommitTurn(): void { if (this.CurrentTurn) { this.TurnHistory.push(this.CurrentTurn); this.CurrentTurn = null; } } Undo(): void { if (this.TurnHistory.length === 0) return; const lastTurn = this.TurnHistory.pop()!; // Restore pre-apply values in reverse Effect order. // Each restore is itself an Instant Override Effect — undo flows through // the same pipeline as every other state change. for (const record of [...lastTurn.EffectsApplied].reverse()) { const restoreSpec = MakeOutgoingSpec(GE_RestoreValues, 1); for (const [attr, value] of record.PreApplyValues) { restoreSpec.SetByCallerMagnitude(attr, value); } ApplyGameplayEffectToTarget(record.TargetGC, restoreSpec); } } } ``` `GE_RestoreValues` is an Instant Effect with one `Override` modifier per restored attribute, driven by `SetByCaller` magnitudes. Every undo operation passes through the standard Effect pipeline: it is observable, replicable, and appears in the Effect audit trail exactly like any other state change. # Mathematical Notation ## Variable Naming Conventions | Symbol | Meaning | |----------------------|-------------------------------| | $V$ | Value (generic) | | $V_{base}$ | Base Value of an Attribute | | $V_{current}$ | Current Value of an Attribute | | $V_{min}$, $V_{max}$ | Minimum/Maximum bounds | | $a$ | Additive modifier magnitude | | $p$ | Percentage modifier magnitude | | $m$ | Multiplicative factor | | $t$ | Time variable | | $\Delta_t$ | Time delta | | $n$ | Count/index variable | ## Summation and Product Notation *Summation* ($\sum$): Sum of values over an index range $$\sum_{i=1}^{n} a_i = a_1 + a_2 + \cdots + a_n$$ *Product* ($\prod$): Product of values over an index range $$\prod_{k=1}^{n} m_k = m_1 \times m_2 \times \cdots \times m_n$$ ## Set Theory Notation for Tags | Notation | Meaning | |-------------|---------------------------------------------| | T | A single Tag | | C | A TagContainer (set of Tags) | | T ∈ C | Tag T is a member of Container C | | C₁ ⊆ C₂ | Container C₁ is a subset of C₂ | | C₁ ∩ C₂ | Intersection of two containers | | C₁ ∪ C₂ | Union of two containers | | C₁ ∩ C₂ ≠ ∅ | Containers have at least one common element | # Complete Schema Reference ## Schema URL Versioning Policy All `$schema` URLs in UGAS data files use the pattern: https://raw.githubusercontent.com/jbltx/ugas/{version}/schemas/{schema-name}.json Where `{version}` is the git tag of the UGAS release the data file was authored against (e.g. `v1.0.0-draft.3`). Each released version tag MUST maintain stable schema URLs — schemas at a given version tag MUST NOT be modified after release. Data files SHOULD pin to the exact UGAS version they were authored against. Tooling that processes UGAS data files SHOULD validate against the schema URL declared in `$schema`, and SHOULD fail clearly when the URL is unreachable or the schema is invalid, rather than silently skipping validation. ## GameplayController Schema Definition ``` yaml # Gameplay Controller Interface Schema Definition # Based on UGAS Specification v1.0.0-draft.3 - Section 4 type: object required: - OwnerActor - AttributeSets properties: OwnerActor: type: object description: Logical owner of the GC (responsible for lifecycle, network authority, persistence) properties: ActorID: type: string description: Unique identifier for the owner actor ActorType: type: string description: Type of the owner actor AvatarActor: type: object description: World spatial representation (optional, can be same as Owner) properties: ActorID: type: string description: Unique identifier for the avatar actor ActorType: type: string description: Type of the avatar actor AttributeSets: type: array items: type: object properties: Name: type: string description: AttributeSet identifier Attributes: type: array items: type: object properties: Name: type: string BaseValue: type: number CurrentValue: type: number minItems: 1 description: Registered attribute containers GrantedAbilities: type: array items: type: object required: - AbilityClass properties: AbilityClass: type: string description: Ability class identifier Level: type: integer default: 1 minimum: 1 description: Ability level InputID: type: string description: >- Input binding identifier. References an Action Name from the input layer. When the bound Action triggers, the GC calls TryActivateAbility for this grant. Handle: type: string description: Unique handle for this granted ability instance bIsActive: type: boolean default: false description: Whether the ability is currently active description: All abilities granted to this GC ActiveEffects: type: array items: type: object required: - Handle - EffectClass properties: Handle: type: string description: Unique identifier for this active effect EffectClass: type: string description: GameplayEffect class reference Duration: type: number description: Remaining duration in seconds (-1 for infinite) Stacks: type: integer minimum: 1 default: 1 description: Number of effect stacks StartTime: type: number description: Timestamp when effect was applied Level: type: integer minimum: 1 default: 1 InstigatorGC: type: string description: Reference to the GC that caused this effect description: Currently active effects applied to this GC ActiveActionSets: type: array items: type: string description: Currently active ActionSet names (derived from tag evaluation at runtime) OwnedTags: type: array items: type: string pattern: "^[A-Z][a-zA-Z0-9]*(\\.[A-Z][a-zA-Z0-9]*)*$" description: Current semantic state tags (hierarchical dot notation) ReplicationMode: type: string enum: [Minimal, Mixed, Full, None] default: Mixed description: "Replication strategy: Minimal (only cues & tags for AI), Mixed (full to owner, minimal to others for players), Full (complete to all for single-player/spectators), None (no replication for server-only)" bIsActive: type: boolean default: true description: Whether this GC is currently active Metadata: type: object description: Optional metadata for display and debugging properties: DisplayName: type: string Description: type: string Tags: type: array items: type: string DebugCategory: type: string ``` ## Attribute Schema Definition ``` yaml # Attribute Definition Schema # Based on UGAS Specification v1.0.0-draft.3 - Appendix B type: object required: - Name - DefaultBaseValue properties: Name: type: string description: Unique identifier for this attribute DefaultBaseValue: type: number description: Initial base value Category: type: string enum: [Resource, Statistic, Meta] default: Statistic Clamping: type: object properties: Min: oneOf: - type: number - type: string description: Attribute reference Max: oneOf: - type: number - type: string description: Attribute reference ReplicationMode: type: string enum: [None, OwnerOnly, All] default: All Metadata: type: object properties: DisplayName: type: string Description: type: string UICategory: type: string Icon: type: string ``` ## AttributeSet Schema Definition ``` yaml # AttributeSet Definition Schema # Based on UGAS Specification v1.0.0-draft.3 - Appendix B type: object required: - Name - Attributes properties: Name: type: string description: Unique set identifier Dependencies: type: array items: type: string description: Required attribute sets Attributes: type: array items: $ref: "#/definitions/Attribute" Metadata: type: object properties: DisplayName: type: string Description: type: string definitions: Attribute: type: object required: - Name - DefaultBaseValue properties: Name: type: string description: Unique identifier for this attribute DefaultBaseValue: type: number description: Initial base value Category: type: string enum: [Resource, Statistic, Meta] default: Statistic Clamping: type: object properties: Min: oneOf: - type: number - type: string Max: oneOf: - type: number - type: string ReplicationMode: type: string enum: [None, OwnerOnly, All] default: All Metadata: type: object properties: DisplayName: type: string Description: type: string UICategory: type: string Icon: type: string ``` ## Ability Schema Definition ``` yaml # Ability Definition Schema # Based on UGAS Specification v1.0.0-draft.3 - Appendix B type: object required: - Name properties: Name: type: string description: Unique identifier for this ability Tags: type: object properties: AbilityTags: type: array items: type: string description: Tags that describe this ability BlockedByTags: type: array items: type: string description: Tags that prevent this ability from running BlockAbilitiesWithTags: type: array items: type: string description: While this ability is active, block abilities with these tags CancelAbilitiesWithTags: type: array items: type: string description: Cancel abilities with these tags when this ability activates ActivationRequiredTags: type: array items: type: string description: Tags required on the GC to activate this ability ActivationBlockedTags: type: array items: type: string description: Tags that block activation of this ability ActivationOwnedTags: type: array items: type: string description: Tags granted to the GC while this ability is active Cost: type: string description: Reference to cost GameplayEffect Cooldown: type: string description: Reference to cooldown GameplayEffect Tasks: type: array items: type: object required: - Type properties: Type: type: string description: Task type identifier Params: type: object description: Task-specific parameters TickInterval: type: number description: Seconds between task ticks; 0 means every frame. Only meaningful for ticking tasks. Priority: type: integer description: Tick scheduling priority when the per-frame tick budget is exhausted; higher ticks first. Metadata: type: object properties: DisplayName: type: string Description: type: string Icon: type: string ``` ## Effect Schema Definition ``` yaml # GameplayEffect Definition Schema # Based on UGAS Specification v1.0.0-draft.3 - Appendix B type: object required: - Name - DurationPolicy properties: Name: type: string description: Unique effect identifier DurationPolicy: type: string enum: - Instant - HasDuration - Infinite Duration: $ref: "#/$defs/MagnitudeDefinition" Period: type: object properties: Period: type: number minimum: 0 description: Time interval for periodic execution ExecuteOnApplication: type: boolean default: false description: Whether to execute immediately on application ExecutionPolicy: type: string enum: - RunInParallel - RunInSequence - RunInMerge default: RunInParallel Priority: type: integer default: 0 description: >- Override conflict resolution priority. When multiple active effects apply an Override modifier to the same Attribute, the effect with the highest Priority value wins. On equal Priority, last-applied wins (LIFO). Negative values are valid. Modifiers: type: array items: $ref: "#/$defs/Modifier" Executions: type: array items: type: object properties: CalculatorClass: type: string description: Custom calculation class reference GrantedTags: type: array items: type: string description: Tags granted while this effect is active ApplicationRequiredTags: type: array items: type: string description: Tags required on target for effect to apply GrantedAbilities: type: array items: type: object properties: AbilityClass: type: string description: Ability class to grant Level: type: integer default: 1 description: Level of the granted ability InputID: type: string description: Optional input binding RemoveOnEffectRemoval: type: boolean default: true description: Remove ability when effect expires GameplayCues: type: array items: type: string description: Visual/audio cues to trigger $defs: MagnitudeDefinition: type: object required: - Type properties: Type: type: string enum: - ScalableFloat - AttributeBased - CustomCalculation - SetByCaller Value: type: number description: Static value for ScalableFloat Curve: type: string description: Curve table reference CurveInput: type: string description: Input parameter for curve evaluation BackingAttribute: type: string description: Attribute to use for AttributeBased magnitude Source: type: string enum: - Source - Target description: Which GC to read the backing attribute from Coefficient: type: number default: 1 description: Multiplicative coefficient PreMultiplyAdditive: type: number default: 0 description: Value added before multiplication PostMultiplyAdditive: type: number default: 0 description: Value added after multiplication CalculatorClass: type: string description: Custom calculation class for CustomCalculation type DataTag: type: string description: Tag for SetByCaller data lookup Modifier: type: object required: - Attribute - Operation - Magnitude properties: Attribute: type: string description: Target attribute to modify Operation: type: string enum: - Add - AddPost - Multiply - Override description: >- Mathematical operation to apply. Add: pre-multiply flat additive (pipeline step 2, before percentage and multiply steps). AddPost: post-multiply flat additive (pipeline step 7, after all multiply steps; very rare). Multiply: multiplicative factor at step 6 — use a reciprocal magnitude (e.g. 0.5) instead of a Divide operation. Override: replaces the computed result at step 8. Magnitude: $ref: "#/$defs/MagnitudeDefinition" Channel: type: string description: >- Optional named aggregation channel. Modifiers in the same channel sum together; modifiers in different channels multiply against each other. Used for damage-bucket systems (see §15.3). Defaults to the global channel if omitted. ``` ## Tag Schema Definition ``` yaml # Tag Registry Schema # Based on UGAS Specification v1.0.0-draft.3 - Appendix B type: object properties: Tags: type: array items: type: object required: - Tag properties: Tag: type: string pattern: "^[A-Z][a-zA-Z0-9]*(\\.[A-Z][a-zA-Z0-9]*)*$" description: Hierarchical tag in dot notation (e.g., State.Debuff.Stunned) Description: type: string AllowMultiple: type: boolean default: false DevComment: type: string ``` # References and Citations ## BibTeX Entries ``` bibtex @online{epicgames_gas, author = {{Epic Games}}, title = {Understanding the Unreal Engine Gameplay Ability System}, year = {2024}, url = {https://dev.epicgames.com/documentation/en-us/unreal-engine/understanding-the-unreal-engine-gameplay-ability-system}, urldate = {2026-02-03} } @online{tranek_gasdoc, author = {Dan Tranek}, title = {GASDocumentation: Understanding Unreal Engine's GameplayAbilitySystem}, year = {2024}, url = {https://github.com/tranek/GASDocumentation}, urldate = {2026-02-03} } @online{unity_gas, author = {{Unity Technologies}}, title = {Unity Gameplay Ability System}, year = {2024}, url = {https://github.com/sjai013/unity-gameplay-ability-system}, urldate = {2026-02-03} } @online{godot_attributes, author = {{OctoD}}, title = {Godot Gameplay Attributes}, year = {2024}, url = {https://github.com/OctoD/godot_gameplay_attributes}, urldate = {2026-02-03} } @book{gregory_engine, author = {Jason Gregory}, title = {Game Engine Architecture}, edition = {3rd}, publisher = {A K Peters/CRC Press}, year = {2018}, isbn = {978-1138035454} } @online{gambetta_prediction, author = {Gabriel Gambetta}, title = {Client-Side Prediction and Server Reconciliation}, year = {2021}, url = {https://www.gabrielgambetta.com/client-side-prediction-server-reconciliation.html}, urldate = {2026-02-03} } @online{gaffer_sync, author = {Glenn Fiedler}, title = {State Synchronization}, year = {2019}, url = {https://gafferongames.com/post/state_synchronization/}, urldate = {2026-02-03} } ``` # Document History | Version | Date | Author | Changes | |---------|---------------|-----------------|-----------------------| | 1.0 | February 2026 | Mickael Bonfill | Initial specification | *End of Specification* --- # Schema Definitions The following YAML schemas define the data format for each UGAS entity type. ## Attribute Schema ```yaml # Attribute Definition Schema # Based on UGAS Specification v1.0.0-draft.3 - Appendix B type: object required: - Name - DefaultBaseValue properties: Name: type: string description: Unique identifier for this attribute DefaultBaseValue: type: number description: Initial base value Category: type: string enum: [Resource, Statistic, Meta] default: Statistic Clamping: type: object properties: Min: oneOf: - type: number - type: string description: Attribute reference Max: oneOf: - type: number - type: string description: Attribute reference ReplicationMode: type: string enum: [None, OwnerOnly, All] default: All Metadata: type: object properties: DisplayName: type: string Description: type: string UICategory: type: string Icon: type: string ``` ## Attribute Set Schema ```yaml # AttributeSet Definition Schema # Based on UGAS Specification v1.0.0-draft.3 - Appendix B type: object required: - Name - Attributes properties: Name: type: string description: Unique set identifier Dependencies: type: array items: type: string description: Required attribute sets Attributes: type: array items: $ref: "#/definitions/Attribute" Metadata: type: object properties: DisplayName: type: string Description: type: string definitions: Attribute: type: object required: - Name - DefaultBaseValue properties: Name: type: string description: Unique identifier for this attribute DefaultBaseValue: type: number description: Initial base value Category: type: string enum: [Resource, Statistic, Meta] default: Statistic Clamping: type: object properties: Min: oneOf: - type: number - type: string Max: oneOf: - type: number - type: string ReplicationMode: type: string enum: [None, OwnerOnly, All] default: All Metadata: type: object properties: DisplayName: type: string Description: type: string UICategory: type: string Icon: type: string ``` ## Gameplay Ability Schema ```yaml # Ability Definition Schema # Based on UGAS Specification v1.0.0-draft.3 - Appendix B type: object required: - Name properties: Name: type: string description: Unique identifier for this ability Tags: type: object properties: AbilityTags: type: array items: type: string description: Tags that describe this ability BlockedByTags: type: array items: type: string description: Tags that prevent this ability from running BlockAbilitiesWithTags: type: array items: type: string description: While this ability is active, block abilities with these tags CancelAbilitiesWithTags: type: array items: type: string description: Cancel abilities with these tags when this ability activates ActivationRequiredTags: type: array items: type: string description: Tags required on the GC to activate this ability ActivationBlockedTags: type: array items: type: string description: Tags that block activation of this ability ActivationOwnedTags: type: array items: type: string description: Tags granted to the GC while this ability is active Cost: type: string description: Reference to cost GameplayEffect Cooldown: type: string description: Reference to cooldown GameplayEffect Tasks: type: array items: type: object required: - Type properties: Type: type: string description: Task type identifier Params: type: object description: Task-specific parameters TickInterval: type: number description: Seconds between task ticks; 0 means every frame. Only meaningful for ticking tasks. Priority: type: integer description: Tick scheduling priority when the per-frame tick budget is exhausted; higher ticks first. Metadata: type: object properties: DisplayName: type: string Description: type: string Icon: type: string ``` ## Gameplay Controller Schema ```yaml # Gameplay Controller Interface Schema Definition # Based on UGAS Specification v1.0.0-draft.3 - Section 4 type: object required: - OwnerActor - AttributeSets properties: OwnerActor: type: object description: Logical owner of the GC (responsible for lifecycle, network authority, persistence) properties: ActorID: type: string description: Unique identifier for the owner actor ActorType: type: string description: Type of the owner actor AvatarActor: type: object description: World spatial representation (optional, can be same as Owner) properties: ActorID: type: string description: Unique identifier for the avatar actor ActorType: type: string description: Type of the avatar actor AttributeSets: type: array items: type: object properties: Name: type: string description: AttributeSet identifier Attributes: type: array items: type: object properties: Name: type: string BaseValue: type: number CurrentValue: type: number minItems: 1 description: Registered attribute containers GrantedAbilities: type: array items: type: object required: - AbilityClass properties: AbilityClass: type: string description: Ability class identifier Level: type: integer default: 1 minimum: 1 description: Ability level InputID: type: string description: >- Input binding identifier. References an Action Name from the input layer. When the bound Action triggers, the GC calls TryActivateAbility for this grant. Handle: type: string description: Unique handle for this granted ability instance bIsActive: type: boolean default: false description: Whether the ability is currently active description: All abilities granted to this GC ActiveEffects: type: array items: type: object required: - Handle - EffectClass properties: Handle: type: string description: Unique identifier for this active effect EffectClass: type: string description: GameplayEffect class reference Duration: type: number description: Remaining duration in seconds (-1 for infinite) Stacks: type: integer minimum: 1 default: 1 description: Number of effect stacks StartTime: type: number description: Timestamp when effect was applied Level: type: integer minimum: 1 default: 1 InstigatorGC: type: string description: Reference to the GC that caused this effect description: Currently active effects applied to this GC ActiveActionSets: type: array items: type: string description: Currently active ActionSet names (derived from tag evaluation at runtime) OwnedTags: type: array items: type: string pattern: "^[A-Z][a-zA-Z0-9]*(\\.[A-Z][a-zA-Z0-9]*)*$" description: Current semantic state tags (hierarchical dot notation) ReplicationMode: type: string enum: [Minimal, Mixed, Full, None] default: Mixed description: "Replication strategy: Minimal (only cues & tags for AI), Mixed (full to owner, minimal to others for players), Full (complete to all for single-player/spectators), None (no replication for server-only)" bIsActive: type: boolean default: true description: Whether this GC is currently active Metadata: type: object description: Optional metadata for display and debugging properties: DisplayName: type: string Description: type: string Tags: type: array items: type: string DebugCategory: type: string ``` ## Gameplay Effect Schema ```yaml # GameplayEffect Definition Schema # Based on UGAS Specification v1.0.0-draft.3 - Appendix B type: object required: - Name - DurationPolicy properties: Name: type: string description: Unique effect identifier DurationPolicy: type: string enum: - Instant - HasDuration - Infinite Duration: $ref: "#/$defs/MagnitudeDefinition" Period: type: object properties: Period: type: number minimum: 0 description: Time interval for periodic execution ExecuteOnApplication: type: boolean default: false description: Whether to execute immediately on application ExecutionPolicy: type: string enum: - RunInParallel - RunInSequence - RunInMerge default: RunInParallel Priority: type: integer default: 0 description: >- Override conflict resolution priority. When multiple active effects apply an Override modifier to the same Attribute, the effect with the highest Priority value wins. On equal Priority, last-applied wins (LIFO). Negative values are valid. Modifiers: type: array items: $ref: "#/$defs/Modifier" Executions: type: array items: type: object properties: CalculatorClass: type: string description: Custom calculation class reference GrantedTags: type: array items: type: string description: Tags granted while this effect is active ApplicationRequiredTags: type: array items: type: string description: Tags required on target for effect to apply GrantedAbilities: type: array items: type: object properties: AbilityClass: type: string description: Ability class to grant Level: type: integer default: 1 description: Level of the granted ability InputID: type: string description: Optional input binding RemoveOnEffectRemoval: type: boolean default: true description: Remove ability when effect expires GameplayCues: type: array items: type: string description: Visual/audio cues to trigger $defs: MagnitudeDefinition: type: object required: - Type properties: Type: type: string enum: - ScalableFloat - AttributeBased - CustomCalculation - SetByCaller Value: type: number description: Static value for ScalableFloat Curve: type: string description: Curve table reference CurveInput: type: string description: Input parameter for curve evaluation BackingAttribute: type: string description: Attribute to use for AttributeBased magnitude Source: type: string enum: - Source - Target description: Which GC to read the backing attribute from Coefficient: type: number default: 1 description: Multiplicative coefficient PreMultiplyAdditive: type: number default: 0 description: Value added before multiplication PostMultiplyAdditive: type: number default: 0 description: Value added after multiplication CalculatorClass: type: string description: Custom calculation class for CustomCalculation type DataTag: type: string description: Tag for SetByCaller data lookup Modifier: type: object required: - Attribute - Operation - Magnitude properties: Attribute: type: string description: Target attribute to modify Operation: type: string enum: - Add - AddPost - Multiply - Override description: >- Mathematical operation to apply. Add: pre-multiply flat additive (pipeline step 2, before percentage and multiply steps). AddPost: post-multiply flat additive (pipeline step 7, after all multiply steps; very rare). Multiply: multiplicative factor at step 6 — use a reciprocal magnitude (e.g. 0.5) instead of a Divide operation. Override: replaces the computed result at step 8. Magnitude: $ref: "#/$defs/MagnitudeDefinition" Channel: type: string description: >- Optional named aggregation channel. Modifiers in the same channel sum together; modifiers in different channels multiply against each other. Used for damage-bucket systems (see §15.3). Defaults to the global channel if omitted. ``` ## Gameplay Tag Schema ```yaml # Tag Registry Schema # Based on UGAS Specification v1.0.0-draft.3 - Appendix B type: object properties: Tags: type: array items: type: object required: - Tag properties: Tag: type: string pattern: "^[A-Z][a-zA-Z0-9]*(\\.[A-Z][a-zA-Z0-9]*)*$" description: Hierarchical tag in dot notation (e.g., State.Debuff.Stunned) Description: type: string AllowMultiple: type: boolean default: false DevComment: type: string ``` ## Input Action Schema ```yaml # Input Action Definition Schema # Based on UGAS Specification v1.0.0-draft.3 - Section 11 type: object required: - Name - ValueType properties: Name: type: string description: Unique identifier for this action, referenced by InputID on granted abilities ValueType: type: string enum: - Digital - Axis1D - Axis2D - Axis3D description: >- Semantic value type this action produces. Digital actions emit trigger events (Started/Ongoing/Completed). Axis actions emit a continuous value each frame while nonzero. TriggerBehavior: type: string enum: - OnPressed - OnReleased - WhileHeld - OnTap - OnDoubleTap default: OnPressed description: >- When this action fires its trigger event. OnPressed fires once on activation. WhileHeld fires every frame while input is nonzero. OnTap requires press-then-release within TapThreshold. TapThreshold: type: number minimum: 0 default: 0.2 description: Maximum hold duration (seconds) for OnTap and OnDoubleTap DoubleTapWindow: type: number minimum: 0 default: 0.3 description: Maximum gap (seconds) between two taps for OnDoubleTap ConsumeInput: type: boolean default: true description: Whether this action consumes the input event, preventing lower-priority actions from receiving it Tags: type: object properties: ActionTags: type: array items: type: string description: Tags describing this action for bulk queries (e.g. disable all combat inputs) Metadata: type: object properties: DisplayName: type: string description: Localization-friendly name shown in control remapping UI Description: type: string Icon: type: string Category: type: string description: UI grouping for settings screens (e.g. Movement, Combat) ``` ## Input Action Set Schema ```yaml # Input Action Set Definition Schema # Based on UGAS Specification v1.0.0-draft.3 - Section 11 type: object required: - Name - Actions properties: Name: type: string description: Unique identifier for this action set Actions: type: array items: type: string description: Reference to an Action Name minItems: 1 description: Actions available when this set is active Priority: type: integer default: 0 description: >- When multiple sets are active and contain the same Action, the highest priority wins. Enables layering (e.g. a Dialogue set overlays OnFoot). ActivationTags: type: object properties: RequiredTags: type: array items: type: string description: Tags required on the owning GC for this set to be active (AND logic) BlockedTags: type: array items: type: string description: Tags that deactivate this set if any are present on the GC Exclusive: type: boolean default: false description: >- If true, activating this set deactivates all other non-exclusive sets at the same or lower priority. Useful for modal contexts like vehicles or cutscenes. InputBuffer: type: object description: Per-set input buffering configuration, overrides the global InputBufferConfig properties: Enabled: type: boolean default: false BufferWindow: type: number minimum: 0 default: 0.15 description: Buffer window in seconds MaxBufferSize: type: integer minimum: 1 default: 3 Metadata: type: object properties: DisplayName: type: string Description: type: string ``` ## Input Mapping Schema ```yaml # Input Mapping Definition Schema # Based on UGAS Specification v1.0.0-draft.3 - Section 11 type: object required: - ActionSet - Bindings properties: ActionSet: type: string description: The ActionSet this mapping belongs to Platform: type: string enum: - PC - Console - Mobile - VR description: >- Optional platform filter. When set, these bindings only apply on the specified platform. Omit for universal bindings. Bindings: type: array items: $ref: "#/$defs/Binding" minItems: 1 Metadata: type: object properties: DisplayName: type: string Description: type: string $defs: DeviceInput: type: object required: - Device - Input properties: Device: type: string enum: - Keyboard - Mouse - Gamepad - Touch - Gyro - Custom description: Device family Input: type: string description: >- Canonical input identifier within the device family, using a standardized naming convention independent of any engine (e.g. Key.Space, Mouse.LeftButton, Gamepad.FaceBottom) CustomDeviceName: type: string description: When Device is Custom, the specific hardware identifier (e.g. FlightStick, SteeringWheel) CompositeInputs: type: object description: >- Composes multiple digital inputs into an axis value. Used for WASD-to-2D-vector, arrow-keys-to-axis, etc. properties: Up: $ref: "#/$defs/DeviceInput" Down: $ref: "#/$defs/DeviceInput" Left: $ref: "#/$defs/DeviceInput" Right: $ref: "#/$defs/DeviceInput" Forward: $ref: "#/$defs/DeviceInput" Backward: $ref: "#/$defs/DeviceInput" Binding: type: object required: - Action properties: Action: type: string description: Reference to an Action Name Inputs: type: array items: $ref: "#/$defs/DeviceInput" minItems: 1 description: >- Device input(s) for this binding. A single entry is a simple binding. Multiple entries create a chord (all must be active simultaneously). CompositeInputs: $ref: "#/$defs/CompositeInputs" Modifiers: type: array items: type: string description: Ordered list of Modifier references applied to the raw input value (pipeline order) Priority: type: integer default: 0 description: >- When multiple bindings map to the same Action within the same ActionSet, the highest priority binding that is satisfiable wins. HoldThreshold: type: number minimum: 0 description: >- Seconds the input must be held before the binding activates. Useful for distinguishing tap (interact) from hold (pick up). bIsRebindable: type: boolean default: true description: Whether the player can change this binding in controls settings Tags: type: object properties: RequiredTags: type: array items: type: string description: Additional tag requirements beyond the ActionSet's ActivationTags ``` ## Input Modifier Schema ```yaml # Input Modifier Definition Schema # Based on UGAS Specification v1.0.0-draft.3 - Section 11 type: object required: - Name - Type properties: Name: type: string description: Unique identifier for this modifier Type: type: string enum: - DeadZone - Sensitivity - ResponseCurve - AxisInvert - AxisScale - AxisSwizzle - RadialScaling - Normalize - TriggerThreshold - Clamp - Custom description: The processing operation this modifier performs Params: type: object description: Type-specific parameters properties: InnerThreshold: type: number minimum: 0 maximum: 1 description: "DeadZone: values below this threshold are clamped to zero" OuterThreshold: type: number minimum: 0 maximum: 1 description: "DeadZone: values above this threshold are clamped to 1.0" DeadZoneShape: type: string enum: - Axial - Radial description: "DeadZone: Axial applies per-axis, Radial applies to magnitude of 2D vector" Multiplier: type: number description: "Sensitivity: linear multiplier (1.0 = no change)" MultiplierX: type: number description: "Sensitivity: per-axis X multiplier for 2D actions" MultiplierY: type: number description: "Sensitivity: per-axis Y multiplier for 2D actions" CurveType: type: string enum: - Linear - Exponential - SCurve - Custom description: "ResponseCurve: curve shape" Exponent: type: number description: "ResponseCurve: exponent for Exponential curves (1.0 = linear, 2.0 = quadratic)" CurvePoints: type: array items: type: object properties: In: type: number Out: type: number description: "ResponseCurve: explicit input-to-output mapping points for Custom curves" InvertX: type: boolean default: false description: "AxisInvert: invert horizontal axis" InvertY: type: boolean default: false description: "AxisInvert: invert vertical axis" ScaleX: type: number description: "AxisScale: horizontal scale factor" ScaleY: type: number description: "AxisScale: vertical scale factor" ScaleZ: type: number description: "AxisScale: depth scale factor (for Axis3D)" SwizzleOrder: type: string description: "AxisSwizzle: axis reordering (e.g. YXZ to swap X and Y)" MaxRadius: type: number description: "RadialScaling: maximum radius for normalization" PressThreshold: type: number minimum: 0 maximum: 1 default: 0.5 description: "TriggerThreshold: analog value above which a trigger counts as pressed" Min: type: number description: "Clamp: minimum value" Max: type: number description: "Clamp: maximum value" CalculatorClass: type: string description: "Custom: engine-specific class implementing the modifier logic" UserConfigurable: type: boolean default: false description: >- Whether this modifier's params are exposed in the player's settings screen. When true, the runtime SHOULD present relevant params (e.g. sensitivity slider, invert-Y toggle) in the controls menu. Metadata: type: object properties: DisplayName: type: string description: Name shown in settings UI when UserConfigurable Description: type: string ``` --- # Schema Examples The following YAML files are concrete examples of UGAS entity definitions. ## Example: Damage Effect ```yaml # Example: Damage Effect # Demonstrates an instant effect that reduces health $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: SimpleDamageEffect DurationPolicy: Instant Modifiers: - Attribute: Health Operation: Add Magnitude: Type: ScalableFloat Value: -25.0 GrantedTags: - State.Damaged GameplayCues: - GameplayCue.Character.Damage ``` ## Example: Fire Action ```yaml # Example: Fire Action (Shooter) # Demonstrates a digital combat action with press-to-activate trigger behavior $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: Fire ValueType: Digital TriggerBehavior: OnPressed ConsumeInput: true Tags: ActionTags: - Input.Type.Combat Metadata: DisplayName: Fire Weapon Description: Pull the trigger on the equipped weapon Category: Combat ``` ## Example: Fireball Ability ```yaml # Example: Fireball Ability # Demonstrates an offensive magic ability with tag-based requirements $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_ability.json Name: FireballAbility Tags: AbilityTags: - Ability.Magic.Fireball - Ability.Offensive BlockedByTags: - State.Dead ActivationRequiredTags: - State.CanCast ActivationBlockedTags: - State.Silenced - State.Stunned - State.Disarmed.Magic ActivationOwnedTags: - State.Casting - State.Busy Cost: ManaCostEffect_25 Cooldown: FireballCooldown_5s Tasks: - Type: PlayMontage Params: MontageToPlay: Anim_CastFireball PlayRate: 1.0 - Type: WaitGameplayEvent Params: EventTag: Event.Montage.CastPoint - Type: SpawnProjectile Params: ProjectileClass: Projectile_Fireball Speed: 2000.0 Gravity: -980.0 Metadata: DisplayName: Fireball Description: Launch a ball of fire at the target, dealing damage on impact Icon: ui/icons/fireball.png ``` ## Example: Health Attribute ```yaml # Example: Health Attribute # Demonstrates a Resource type attribute with clamping $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/attribute.json Name: Health DefaultBaseValue: 100.0 Category: Resource Clamping: Min: 0 Max: MaxHealth # Reference to MaxHealth attribute ReplicationMode: All Metadata: DisplayName: Health Points Description: Character's current life force UICategory: Vital Stats Icon: ui/icons/health.png ``` ## Example: Onfoot Action Set ```yaml # Example: OnFoot Action Set (Shooter) # Demonstrates tag-driven activation with input buffering $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action_set.json Name: OnFoot Actions: - Move - Look - Fire - Aim - Reload - Jump - Sprint - Interact - CycleWeapon Priority: 0 ActivationTags: RequiredTags: - State.Alive BlockedTags: - State.InVehicle - State.Cutscene InputBuffer: Enabled: true BufferWindow: 0.12 MaxBufferSize: 2 Metadata: DisplayName: On Foot Description: Standard FPS controls while on foot ``` ## Example: Onfoot Pc Mapping ```yaml # Example: OnFoot PC Mapping (Shooter) # Demonstrates keyboard+mouse bindings with WASD composition and modifier references $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_mapping.json ActionSet: OnFoot Platform: PC Bindings: - Action: Fire Inputs: - Device: Mouse Input: Mouse.LeftButton - Action: Aim Inputs: - Device: Mouse Input: Mouse.RightButton - Action: Reload Inputs: - Device: Keyboard Input: Key.R - Action: Move CompositeInputs: Up: Device: Keyboard Input: Key.W Down: Device: Keyboard Input: Key.S Left: Device: Keyboard Input: Key.A Right: Device: Keyboard Input: Key.D - Action: Look Inputs: - Device: Mouse Input: Mouse.Axis.X - Device: Mouse Input: Mouse.Axis.Y Modifiers: - MouseSensitivity - Action: Jump Inputs: - Device: Keyboard Input: Key.Space - Action: Sprint Inputs: - Device: Keyboard Input: Key.LeftShift - Action: Interact Inputs: - Device: Keyboard Input: Key.E - Action: CycleWeapon Inputs: - Device: Mouse Input: Mouse.Scroll Metadata: DisplayName: On Foot (Keyboard + Mouse) Description: Default PC bindings for on-foot shooter gameplay ``` ## Example: Stick Deadzone Modifier ```yaml # Example: Stick Deadzone Modifier # Demonstrates a user-configurable radial dead zone for gamepad analog sticks $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_modifier.json Name: StickDeadzone Type: DeadZone Params: InnerThreshold: 0.15 OuterThreshold: 0.95 DeadZoneShape: Radial UserConfigurable: true Metadata: DisplayName: Stick Dead Zone Description: Inner dead zone radius for analog sticks ``` ## Example: Tag Registry ```yaml # Example: Tag Registry # Demonstrates hierarchical tag definitions for a game $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_tag.json Tags: # State tags - Tag: State.Alive Description: Entity is alive and active AllowMultiple: false - Tag: State.Dead Description: Entity is dead AllowMultiple: false - Tag: State.Combat Description: Entity is in combat AllowMultiple: false # Debuff tags - Tag: State.Debuff.Stunned Description: Entity is stunned and cannot act AllowMultiple: false - Tag: State.Debuff.Silenced Description: Entity cannot cast spells AllowMultiple: false # Buff tags - Tag: State.Buff.Regenerating Description: Entity is regenerating health over time AllowMultiple: false # Ability tags - Tag: Ability.Magic.Fireball Description: Fireball spell ability AllowMultiple: false - Tag: Ability.Offensive Description: Ability that deals damage AllowMultiple: true ``` --- # Genre Pack Templates The following YAML files are schema-conformant template entities from the genre packs. Copy a pack's entities as a starting point for a new game in that genre. ## Genre Pack: action ### Ability Ground Pound ```yaml # Ground pound: slam down from the air, damaging every enemy in a small radius on impact. # Only usable while airborne; reuses GE_ContactDamage and skips Immunity.Contact targets. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_ability.json Name: GA_GroundPound Tags: AbilityTags: - Ability.Type.Attack ActivationRequiredTags: - State.InAir ActivationBlockedTags: - State.Grounded Tasks: - Type: WaitGameplayEvent Params: EventTag: Event.Landed - Type: ApplyEffectToActorsInRadius Params: Radius: 200.0 EffectClass: GE_ContactDamage IgnoreTargetsWithTag: Immunity.Contact Metadata: DisplayName: Ground Pound Description: Slam to the ground, damaging nearby enemies on impact Icon: ui/icons/ground_pound.png ``` ### Ability Jump ```yaml # Variable-height jump (the signature platformer ability), from the §15.1 case study. # Activation gate is State.Grounded OR Status.CoyoteTime - that OR is evaluated in the ability's # activation logic, since tag lists are ANDed and cannot express it declaratively. # Task order is the happy path: GE_JumpCut is only applied if VerticalVelocity > 0 at release. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_ability.json Name: GA_Jump Tags: AbilityTags: - Ability.Type.Movement Tasks: - Type: ApplyEffectToOwner Params: EffectClass: GE_InAir - Type: WaitInputRelease Params: InputID: Jump - Type: ApplyEffectToOwner Params: EffectClass: GE_JumpCut - Type: WaitGameplayEvent Params: EventTag: Event.Landed - Type: RemoveEffectFromOwner Params: EffectClass: GE_InAir - Type: RemoveEffectFromOwner Params: EffectClass: GE_JumpCut Metadata: DisplayName: Jump Description: Leap upward; hold for a higher jump, release early to cut it short Icon: ui/icons/jump.png ``` ### Attribute Set ```yaml # Platformer movement attribute set: jump feel, air control, plus vitals and size for power-ups. # Extends the core spec's attribute model (no core attribute is redefined here). $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/attribute_set.json Name: PlatformerMovementSet Attributes: # --- Jump & movement feel (tuned for "game feel"; read by the movement abilities) --- - Name: GravityScale DefaultBaseValue: 1.0 Category: Statistic Metadata: DisplayName: Gravity Scale Description: Multiplier on world gravity; raised by GE_JumpCut for variable-height jumps UICategory: Movement - Name: JumpVelocity DefaultBaseValue: 1200.0 Category: Statistic Metadata: DisplayName: Jump Velocity Description: Upward impulse applied by GA_Jump UICategory: Movement - Name: AirControl DefaultBaseValue: 0.65 Category: Statistic Clamping: Min: 0.0 Max: 1.0 Metadata: DisplayName: Air Control Description: Fraction (0..1) of ground control retained while airborne UICategory: Movement - Name: HorizontalSpeed DefaultBaseValue: 600.0 Category: Statistic Metadata: DisplayName: Run Speed Description: Ground movement speed; raised while the invincibility star is active UICategory: Movement - Name: CoyoteTimeDuration DefaultBaseValue: 0.15 Category: Statistic Metadata: DisplayName: Coyote Time Description: Grace window (s) after leaving a ledge during which a jump still counts UICategory: Movement - Name: JumpBufferDuration DefaultBaseValue: 0.1 Category: Statistic Metadata: DisplayName: Jump Buffer Description: Window (s) before landing during which a jump input is queued UICategory: Movement - Name: MaxJumpCount DefaultBaseValue: 1.0 Category: Statistic Metadata: DisplayName: Max Jumps Description: Jumps allowed before touching ground; raise to 2 for a double jump UICategory: Movement - Name: VerticalVelocity DefaultBaseValue: 0.0 Category: Meta Metadata: DisplayName: Vertical Velocity Description: Live vertical velocity kept in sync by the physics avatar; read by GA_Jump UICategory: Telemetry # --- Form & vitals (targeted by power-ups and hazard damage) --- - Name: Scale DefaultBaseValue: 1.0 Category: Statistic Metadata: DisplayName: Character Scale Description: Visual/collision size; doubled by the Super power-up via the SizeBonuses channel UICategory: Form - Name: MaxHealth DefaultBaseValue: 3.0 Category: Statistic Metadata: DisplayName: Maximum Health UICategory: Vitals - Name: Health DefaultBaseValue: 1.0 Category: Resource Clamping: Min: 0 Max: MaxHealth ReplicationMode: All Metadata: DisplayName: Health Description: Hit points; reaching 0 costs a life UICategory: Vitals - Name: Lives DefaultBaseValue: 3.0 Category: Resource Clamping: Min: 0 Metadata: DisplayName: Lives Description: Remaining lives; game over at 0 UICategory: Vitals Metadata: DisplayName: Platformer Movement Set Description: Jump/movement feel attributes plus form and vitals for an action-platformer character ``` ### Effect Contact Damage ```yaml # Contact damage: one hit point removed on touch. Used both ways - by hazards/enemies striking # the player, and by GA_GroundPound striking enemies. Targets holding Immunity.Contact are # skipped by the applying ability (see GA_GroundPound). $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_ContactDamage DurationPolicy: Instant Modifiers: - Attribute: Health Operation: Add Magnitude: Type: ScalableFloat Value: -1.0 GameplayCues: - GameplayCue.Character.Hit ``` ### Effect Grounded ```yaml # Grounded state: granted while the character stands on the ground. # Owned by the physics subsystem - it applies this on landing and removes it on takeoff. # Tag ownership follows responsibility (§3.1): abilities never mutate State.Grounded directly. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_Grounded DurationPolicy: Infinite GrantedTags: - State.Grounded ``` ### Effect In Air ```yaml # Airborne state: granted by GA_Jump on takeoff and removed on landing. # Grants State.InAir so other abilities (e.g. GA_GroundPound) can gate on being airborne. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_InAir DurationPolicy: Infinite GrantedTags: - State.InAir ``` ### Effect Invincibility Star ```yaml # Invincibility star: a timed power-up that ignores contact damage and boosts run speed. # HasDuration so it self-expires; grants Immunity.Contact (consumed by hazard/attack effects). # Channel SpeedBonuses: factor = 1 + 0.3 = 1.3x HorizontalSpeed while active. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_InvincibilityStar DurationPolicy: HasDuration Duration: Type: ScalableFloat Value: 10.0 Modifiers: - Attribute: HorizontalSpeed Operation: Multiply Magnitude: Type: ScalableFloat Value: 0.3 Channel: SpeedBonuses GrantedTags: - State.PowerUp.Invincible - Immunity.Contact GameplayCues: - GameplayCue.PowerUp.Star ``` ### Effect Jump Cut ```yaml # Variable-height jump cut: applied when the jump button is released while still rising. # Raises gravity (the §15.1 "gravity multiplier for shorter jump") so the character falls # sooner. GA_Jump removes it on landing alongside GE_InAir. # Channel JumpControl uses the additive-within-channel convention: factor = 1 + 1.5 = 2.5x gravity. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_JumpCut DurationPolicy: Infinite Modifiers: - Attribute: GravityScale Operation: Multiply Magnitude: Type: ScalableFloat Value: 1.5 Channel: JumpControl ``` ### Effect Super Mushroom ```yaml # Super power-up: doubles the character's size and grants one extra hit point. # From the §15.1 case study. The Scale modifier sits in the SizeBonuses channel, where the # additive-within-channel convention reads Value 1.0 as +100%: factor = 1 + 1.0 = 2.0x size. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_SuperMushroom DurationPolicy: Infinite GrantedTags: - State.PowerUp.Super Modifiers: - Attribute: Scale Operation: Multiply Magnitude: Type: ScalableFloat Value: 1.0 Channel: SizeBonuses - Attribute: Health Operation: Add Magnitude: Type: ScalableFloat Value: 1.0 GameplayCues: - GameplayCue.PowerUp.Super ``` ### Gameplay Controller ```yaml # Worked example: a "Super" platformer hero standing on the ground after grabbing a mushroom. # CurrentValue columns show the result of the active GE_SuperMushroom: # Scale: base 1.0 x SizeBonuses(1 + 1.0 = 2.0) = 2.0 # Health: base 1 + 1 (mushroom) = 2.0 $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_controller.json OwnerActor: ActorID: Hero_Platformer_01 ActorType: PlayerCharacter AttributeSets: - Name: PlatformerMovementSet Attributes: - Name: GravityScale BaseValue: 1.0 CurrentValue: 1.0 - Name: JumpVelocity BaseValue: 1200.0 CurrentValue: 1200.0 - Name: AirControl BaseValue: 0.65 CurrentValue: 0.65 - Name: HorizontalSpeed BaseValue: 600.0 CurrentValue: 600.0 - Name: Scale BaseValue: 1.0 CurrentValue: 2.0 - Name: MaxHealth BaseValue: 3.0 CurrentValue: 3.0 - Name: Health BaseValue: 1.0 CurrentValue: 2.0 - Name: Lives BaseValue: 3.0 CurrentValue: 3.0 GrantedAbilities: - AbilityClass: GA_Jump Level: 1 InputID: Jump - AbilityClass: GA_GroundPound Level: 1 InputID: GroundPound ActiveEffects: - Handle: eff-grounded EffectClass: GE_Grounded Duration: -1 - Handle: eff-super-mushroom EffectClass: GE_SuperMushroom Duration: -1 OwnedTags: - State.Alive - State.Grounded - State.PowerUp.Super ReplicationMode: Mixed bIsActive: true Metadata: DisplayName: Super Hero (grounded) Description: Worked-example platformer hero showing power-up attribute aggregation on Scale and Health DebugCategory: action-pack-example ``` ### Input Action Set Onfoot ```yaml # On-foot action set: the default action set for a platformer character. # Bundles all traversal and combat inputs available while the character is alive. # Input buffering is enabled with a generous 0.15 s window and depth of 3, which # smooths out rapid jump-chains and coyote-time sequences typical of the genre. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action_set.json Name: OnFoot Actions: - Move - Jump - GroundPound Priority: 0 ActivationTags: RequiredTags: - State.Alive InputBuffer: Enabled: true BufferWindow: 0.15 MaxBufferSize: 3 Metadata: DisplayName: On Foot Description: Default movement and combat inputs for a grounded or airborne platformer hero ``` ### Input Actions ```yaml # Input actions for the action / platformer genre pack. # Move is a continuous 2D axis (WhileHeld) for analog stick and WASD movement. # Jump is digital OnPressed; the ability itself (GA_Jump) uses a WaitInputRelease task # to implement variable jump height, so the action only needs to fire the initial trigger. # GroundPound is digital OnPressed; the gameplay controller gates it to State.InAir via # the binding's RequiredTags, not the action definition. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: Move ValueType: Axis2D TriggerBehavior: WhileHeld ConsumeInput: true Tags: ActionTags: - Ability.Type.Movement Metadata: DisplayName: Move Description: Horizontal movement along the ground or in the air Category: Movement --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: Jump ValueType: Digital TriggerBehavior: OnPressed ConsumeInput: true Tags: ActionTags: - Ability.Type.Movement Metadata: DisplayName: Jump Description: Leap upward; hold for a higher arc, release early to cut short Icon: ui/icons/jump.png Category: Movement --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: GroundPound ValueType: Digital TriggerBehavior: OnPressed ConsumeInput: true Tags: ActionTags: - Ability.Type.Attack - Ability.Type.Movement Metadata: DisplayName: Ground Pound Description: Slam downward while airborne, dealing damage on impact Icon: ui/icons/ground_pound.png Category: Movement ``` ### Input Mapping Onfoot Gamepad ```yaml # Gamepad bindings for the OnFoot action set (Console platform). # Move uses the left stick with a radial dead-zone modifier for clean directional input. # Jump is mapped to FaceBottom (A / Cross) and GroundPound to LeftTrigger; the trigger # binding is gated behind State.InAir so it only fires while airborne. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_mapping.json ActionSet: OnFoot Platform: Console Bindings: - Action: Move Inputs: - Device: Gamepad Input: Gamepad.LeftStick.X - Device: Gamepad Input: Gamepad.LeftStick.Y Modifiers: - StickDeadzone - Action: Jump Inputs: - Device: Gamepad Input: Gamepad.FaceBottom - Action: GroundPound Inputs: - Device: Gamepad Input: Gamepad.LeftTrigger Tags: RequiredTags: - State.InAir Metadata: DisplayName: On Foot (Gamepad) Description: Standard console gamepad layout for platformer on-foot controls ``` ### Input Mapping Onfoot Pc ```yaml # PC keyboard bindings for the OnFoot action set. # Move uses a WASD composite that the runtime promotes to an Axis2D vector. # Jump maps to Space; GroundPound maps to S but is gated behind State.InAir so # it only activates when airborne (prevents conflict with the downward Move axis). $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_mapping.json ActionSet: OnFoot Platform: PC Bindings: - Action: Move CompositeInputs: Up: Device: Keyboard Input: Key.W Down: Device: Keyboard Input: Key.S Left: Device: Keyboard Input: Key.A Right: Device: Keyboard Input: Key.D - Action: Jump Inputs: - Device: Keyboard Input: Key.Space - Action: GroundPound Inputs: - Device: Keyboard Input: Key.S Tags: RequiredTags: - State.InAir Metadata: DisplayName: On Foot (Keyboard) Description: WASD + Space keyboard layout for the platformer on-foot controls ``` ### Input Modifiers ```yaml # Input modifiers for the action / platformer genre pack. # StickDeadzone applies a radial dead-zone to analog stick input, filtering out # drift near the center (Inner 0.15) and snapping to full deflection near the edge # (Outer 0.95). Marked UserConfigurable so players can tune thresholds in settings. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_modifier.json Name: StickDeadzone Type: DeadZone Params: DeadZoneShape: Radial InnerThreshold: 0.15 OuterThreshold: 0.95 UserConfigurable: true Metadata: DisplayName: Stick Dead Zone Description: Radial dead-zone for analog sticks; filters center drift and snaps near full deflection ``` ### Tag Registry ```yaml # Action / platformer genre tag taxonomy. ADDITIVE: introduces genre-specific tags only, # including the platformer movement states under the core State.* namespace as used by the # §15.1 case study. It does not redefine the core State.Alive / State.Combat / State.Dead # lifecycle tags - those are referenced, not declared here. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_tag.json Tags: # --- Movement states (granted by Effects; tag ownership follows responsibility, §3.1) --- - Tag: State.Grounded Description: Character is standing on ground; owned by the physics subsystem (GE_Grounded) AllowMultiple: false - Tag: State.InAir Description: Character is airborne; granted by GA_Jump (GE_InAir), removed on landing AllowMultiple: false - Tag: Status.CoyoteTime Description: Brief grace window after leaving a ledge during which a jump still counts AllowMultiple: false - Tag: Status.JumpBuffered Description: A jump input was queued just before landing AllowMultiple: false # --- Power-up states (granted by power-up Effects) --- - Tag: State.PowerUp.Super Description: Enlarged "super" form; survives one extra hit AllowMultiple: false - Tag: State.PowerUp.Fire Description: Fire form; enables a ranged attack AllowMultiple: false - Tag: State.PowerUp.Invincible Description: Temporary invincibility (star); ignores contact damage AllowMultiple: false # --- Ability classification --- - Tag: Ability.Type.Movement Description: Locomotion ability (jump, dash, climb) AllowMultiple: false - Tag: Ability.Type.Attack Description: Offensive ability (stomp, ground pound, projectile) AllowMultiple: false # --- Hazards & combat (the action-adventure surface) --- - Tag: DamageType.Contact Description: Damage from touching an enemy or hazard AllowMultiple: false - Tag: DamageType.Pit Description: Falling out of bounds; typically instant AllowMultiple: false - Tag: Immunity.Contact Description: Ignores incoming contact damage (e.g. while invincible) AllowMultiple: false ``` ## Genre Pack: racing ### Ability Drift ```yaml # Drift ability: hold to slide. Grants Vehicle.State.Drifting and refills boost via GE_DriftCharge, # trading cornering grip for nitro charge. Ends when the input is released. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_ability.json Name: GA_Drift Tags: AbilityTags: - Ability.Type.Handling ActivationRequiredTags: - Vehicle ActivationBlockedTags: - Vehicle.State.SpunOut ActivationOwnedTags: - Vehicle.State.Drifting Tasks: - Type: ApplyEffectToOwner Params: EffectClass: GE_DriftCharge - Type: WaitInputRelease Metadata: DisplayName: Drift Description: Hold to slide through corners, converting grip into boost charge Icon: ui/icons/drift.png ``` ### Ability Nitro Boost ```yaml # Nitro boost ability: spend the boost resource for a short burst of power. # Costs boost (GE_NitroCost) and goes on cooldown (GE_NitroCooldown); applies GE_NitroBoost to self. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_ability.json Name: GA_NitroBoost Tags: AbilityTags: - Ability.Type.Boost ActivationRequiredTags: - Vehicle - Race.Phase.Racing ActivationBlockedTags: - Vehicle.State.SpunOut Cost: GE_NitroCost Cooldown: GE_NitroCooldown Tasks: - Type: ApplyEffectToOwner Params: EffectClass: GE_NitroBoost Metadata: DisplayName: Nitro Boost Description: Burn boost charge for a burst of torque and top speed Icon: ui/icons/nitro.png ``` ### Attribute Set ```yaml # Vehicle performance attribute set: drivetrain, aero, tires, and the boost resource. # Extends the core spec's attribute model (no core attribute is redefined here). $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/attribute_set.json Name: VehiclePerformanceSet Attributes: # --- Drivetrain (the power the engine puts to the wheels) --- - Name: EngineTorque DefaultBaseValue: 400.0 Category: Statistic Metadata: DisplayName: Engine Torque Description: Base drive torque in Nm; raised by tuning upgrades UICategory: Drivetrain - Name: MaxSpeed DefaultBaseValue: 250.0 Category: Statistic Metadata: DisplayName: Top Speed Description: Top speed in km/h; lowered by heavy/low-grip surfaces, raised by boost UICategory: Drivetrain # --- Aerodynamics & tires (feed the traction pipeline) --- - Name: AeroDownforce DefaultBaseValue: 100.0 Category: Statistic Metadata: DisplayName: Aero Downforce Description: Downforce coefficient; scales effective traction with speed (see spec section 4) UICategory: Chassis - Name: TireGripMultiplier DefaultBaseValue: 1.0 Category: Statistic Metadata: DisplayName: Tire Grip Description: Aggregated grip; target of the Surface and Upgrades channels (see spec section 4) UICategory: Tires - Name: TireTemperature DefaultBaseValue: 80.0 Category: Statistic Clamping: Min: 20.0 Max: 150.0 Metadata: DisplayName: Tire Temperature Description: Tire core temperature in degrees C; grip peaks in the 80-100 window UICategory: Tires # --- Boost resource (spent by the nitro ability, refilled by drifting) --- - Name: MaxBoost DefaultBaseValue: 100.0 Category: Statistic Metadata: DisplayName: Maximum Boost UICategory: Boost - Name: Boost DefaultBaseValue: 100.0 Category: Resource Clamping: Min: 0 Max: MaxBoost ReplicationMode: All Metadata: DisplayName: Boost Description: Nitro charge spent to activate GA_NitroBoost UICategory: Boost # --- Runtime physics state (driven each frame by the simulation / traction calc) --- - Name: EngineRPM DefaultBaseValue: 0.0 Category: Meta Metadata: DisplayName: Engine RPM UICategory: Telemetry - Name: CurrentSpeed DefaultBaseValue: 0.0 Category: Meta Metadata: DisplayName: Current Speed Description: Live ground speed in km/h read by the traction calculation UICategory: Telemetry - Name: AvailableTraction DefaultBaseValue: 1.0 Category: Meta Metadata: DisplayName: Available Traction Description: Output of ExecCalc_VehicleTraction; couples grip, downforce, and tire temperature UICategory: Telemetry Metadata: DisplayName: Vehicle Performance Set Description: Drivetrain, aerodynamics, tires, boost resource, and runtime telemetry for a racing vehicle ``` ### Effect Biome Asphalt ```yaml # Surface area effect: dry asphalt. Overrides the Surface-channel grip to the clean 1.0 baseline, # clearing any prior off-track penalty. Infinite while the vehicle is on the surface. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_Biome_Asphalt DurationPolicy: Infinite ApplicationRequiredTags: - Vehicle Modifiers: - Attribute: TireGripMultiplier Operation: Override Magnitude: Type: ScalableFloat Value: 1.0 Channel: Surface GrantedTags: - Surface.Asphalt ``` ### Effect Biome Mud ```yaml # Surface area effect: driving onto mud. Cuts grip in the "Surface" channel and lowers top speed. # Infinite while the vehicle is on the surface; applied/removed by the track's area volumes. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_Biome_Mud DurationPolicy: Infinite ApplicationRequiredTags: - Vehicle Modifiers: - Attribute: TireGripMultiplier Operation: Multiply Magnitude: Type: ScalableFloat Value: -0.6 Channel: Surface - Attribute: MaxSpeed Operation: Add Magnitude: Type: ScalableFloat Value: -30.0 GrantedTags: - Surface.Mud ``` ### Effect Drift Charge ```yaml # Drift charge: while sliding, periodically refill the boost resource. # HasDuration + Period demonstrates timed, repeating execution (refreshed each tick GA_Drift holds). $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_DriftCharge DurationPolicy: HasDuration Duration: Type: ScalableFloat Value: 5.0 Period: Period: 0.5 ExecuteOnApplication: false Modifiers: - Attribute: Boost Operation: Add Magnitude: Type: ScalableFloat Value: 10.0 GrantedTags: - Vehicle.State.Drifting ``` ### Effect Nitro Boost ```yaml # Nitro boost: a short burst of extra power applied by GA_NitroBoost. # HasDuration so it self-expires; grants Vehicle.State.Boosting for cues and ability gating. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_NitroBoost DurationPolicy: HasDuration Duration: Type: ScalableFloat Value: 3.0 Modifiers: - Attribute: EngineTorque Operation: Add Magnitude: Type: ScalableFloat Value: 120.0 - Attribute: MaxSpeed Operation: Add Magnitude: Type: ScalableFloat Value: 40.0 GrantedTags: - Vehicle.State.Boosting GameplayCues: - GameplayCue.Vehicle.NitroFlame ``` ### Effect Traction Update ```yaml # Traction recompute: couples the declarative grip stat to the non-linear physics. # An ExecutionCalculation reads grip, downforce, speed, and tire temperature and writes # AvailableTraction. Instant; the simulation re-applies it each physics step. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_Traction_Update DurationPolicy: Instant Executions: - CalculatorClass: ExecCalc_VehicleTraction ``` ### Effect Tuning Sport ```yaml # Sport tuning package: a permanent performance upgrade fitted to the vehicle. # Raises drivetrain output (flat) and grip in the "Upgrades" channel of the traction pipeline. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_Tuning_Sport DurationPolicy: Infinite ApplicationRequiredTags: - Vehicle Modifiers: - Attribute: EngineTorque Operation: Add Magnitude: Type: ScalableFloat Value: 80.0 - Attribute: MaxSpeed Operation: Add Magnitude: Type: ScalableFloat Value: 20.0 - Attribute: TireGripMultiplier Operation: Multiply Magnitude: Type: ScalableFloat Value: 0.10 Channel: Upgrades GrantedTags: - Vehicle.Tuned ``` ### Gameplay Controller ```yaml # Worked example: a Sport-class car mid-race, on mud, with the sport tuning package fitted. # CurrentValue columns show the result of the two infinite effects below: # TireGripMultiplier: base 1.0 x Surface(1 - 0.6 = 0.40) x Upgrades(1 + 0.10 = 1.10) = 0.44 # MaxSpeed: base 250 + Tuning(+20) + Mud(-30) = 240.0 # EngineTorque: base 400 + Tuning(+80) = 480.0 $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_controller.json OwnerActor: ActorID: Vehicle_SportCar_01 ActorType: PlayerVehicle AttributeSets: - Name: VehiclePerformanceSet Attributes: - Name: EngineTorque BaseValue: 400.0 CurrentValue: 480.0 - Name: MaxSpeed BaseValue: 250.0 CurrentValue: 240.0 - Name: TireGripMultiplier BaseValue: 1.0 CurrentValue: 0.44 - Name: AeroDownforce BaseValue: 100.0 CurrentValue: 100.0 - Name: TireTemperature BaseValue: 80.0 CurrentValue: 95.0 - Name: MaxBoost BaseValue: 100.0 CurrentValue: 100.0 - Name: Boost BaseValue: 100.0 CurrentValue: 60.0 GrantedAbilities: - AbilityClass: GA_NitroBoost Level: 1 InputID: Boost - AbilityClass: GA_Drift Level: 1 InputID: Drift ActiveEffects: - Handle: eff-tuning-sport EffectClass: GE_Tuning_Sport Duration: -1 - Handle: eff-biome-mud EffectClass: GE_Biome_Mud Duration: -1 OwnedTags: - Vehicle - Vehicle.Class.Sport - Vehicle.Tuned - Surface.Mud - Race.Phase.Racing ReplicationMode: Mixed bIsActive: true Metadata: DisplayName: Sport Car (tuned, on mud) Description: Worked-example vehicle showing traction-pipeline aggregation on TireGripMultiplier DebugCategory: racing-pack-example ``` ### Input Action Set Racing ```yaml # Racing action set: groups all on-track driving inputs. # Active only while the vehicle is racing and not spun out. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action_set.json Name: RacingOnTrack Actions: - Steer - Throttle - Brake - Boost - Drift - Look Priority: 0 ActivationTags: RequiredTags: - Vehicle - Race.Phase.Racing BlockedTags: - Vehicle.State.SpunOut Metadata: DisplayName: Racing Description: Core driving input set active during on-track racing ``` ### Input Actions ```yaml # Input actions for the racing genre pack. # Steer, Throttle, Brake are analog axes; Boost and Drift are digital triggers; # Look provides a free-look camera axis while driving. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: Steer ValueType: Axis1D TriggerBehavior: WhileHeld Tags: ActionTags: - Driving Metadata: DisplayName: Steer Description: Horizontal steering axis (-1 full left, +1 full right) Category: Driving --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: Throttle ValueType: Axis1D TriggerBehavior: WhileHeld Tags: ActionTags: - Driving Metadata: DisplayName: Throttle Description: Analog throttle input (0 idle, 1 full power) Category: Driving --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: Brake ValueType: Axis1D TriggerBehavior: WhileHeld Tags: ActionTags: - Driving Metadata: DisplayName: Brake Description: Analog brake input (0 no braking, 1 full braking) Category: Driving --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: Boost ValueType: Digital TriggerBehavior: OnPressed Tags: ActionTags: - Driving Metadata: DisplayName: Boost Description: Activate nitro boost Category: Driving --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: Drift ValueType: Digital TriggerBehavior: WhileHeld Tags: ActionTags: - Driving Metadata: DisplayName: Drift Description: Hold to initiate and sustain a drift through corners Category: Driving --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: Look ValueType: Axis2D TriggerBehavior: WhileHeld Tags: ActionTags: - Driving Metadata: DisplayName: Look Description: Free-look camera axis for glancing around while driving Category: Driving ``` ### Input Mapping Racing Gamepad ```yaml # Gamepad bindings for the RacingOnTrack action set (Console platform). # Maps standard gamepad controls to driving actions with appropriate modifiers. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_mapping.json ActionSet: RacingOnTrack Platform: Console Bindings: - Action: Throttle Inputs: - Device: Gamepad Input: Gamepad.RightTrigger Modifiers: - TriggerDeadzone - Action: Brake Inputs: - Device: Gamepad Input: Gamepad.LeftTrigger Modifiers: - TriggerDeadzone - Action: Steer Inputs: - Device: Gamepad Input: Gamepad.LeftStick.X Modifiers: - StickDeadzone - SteeringSensitivity - Action: Boost Inputs: - Device: Gamepad Input: Gamepad.FaceBottom - Action: Drift Inputs: - Device: Gamepad Input: Gamepad.LeftShoulder - Action: Look Inputs: - Device: Gamepad Input: Gamepad.RightStick Modifiers: - StickDeadzone Metadata: DisplayName: Racing Gamepad Description: Default gamepad mapping for on-track racing controls ``` ### Input Modifiers ```yaml # Input modifiers shared by racing input mappings. # StickDeadzone and TriggerDeadzone clean up analog noise; # SteeringSensitivity applies an S-curve for progressive steering feel. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_modifier.json Name: StickDeadzone Type: DeadZone Params: DeadZoneShape: Radial InnerThreshold: 0.15 OuterThreshold: 0.95 UserConfigurable: true Metadata: DisplayName: Stick Dead Zone Description: Radial dead zone for analog stick inputs --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_modifier.json Name: TriggerDeadzone Type: DeadZone Params: DeadZoneShape: Axial InnerThreshold: 0.05 OuterThreshold: 0.98 UserConfigurable: false Metadata: DisplayName: Trigger Dead Zone Description: Axial dead zone for trigger inputs --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_modifier.json Name: SteeringSensitivity Type: ResponseCurve Params: CurveType: SCurve UserConfigurable: true Metadata: DisplayName: Steering Sensitivity Description: S-curve response for progressive steering feel ``` ### Tag Registry ```yaml # Racing genre tag taxonomy. ADDITIVE: this registry introduces genre-specific tags only. # It does not redefine core State.* / Ability.* tags - those are referenced, not declared here. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_tag.json Tags: # --- Track surfaces (granted by area/biome effects; drive the grip pipeline) --- - Tag: Surface.Asphalt Description: Dry sealed track; full baseline grip AllowMultiple: false - Tag: Surface.Mud Description: Soft off-track surface; heavy grip and speed penalty AllowMultiple: false - Tag: Surface.Gravel Description: Loose run-off surface; moderate grip penalty AllowMultiple: false - Tag: Surface.Ice Description: Frozen surface; severe grip penalty AllowMultiple: false - Tag: Surface.Wet Description: Rain-soaked surface; reduced grip and aquaplaning risk AllowMultiple: false # --- Vehicle classification (target gating + class-specific tuning) --- - Tag: Vehicle Description: Entity is a controllable vehicle; required by surface/area effects AllowMultiple: false - Tag: Vehicle.Class.Sport Description: Balanced road/sports car AllowMultiple: false - Tag: Vehicle.Class.Kart Description: Lightweight kart; high agility, low top speed AllowMultiple: false - Tag: Vehicle.Class.Formula Description: Open-wheel racer; high downforce and top speed AllowMultiple: false - Tag: Vehicle.Tuned Description: A performance tuning upgrade is installed AllowMultiple: false # --- Vehicle dynamic states (granted by abilities and timed effects) --- - Tag: Vehicle.State.Boosting Description: Nitro boost is currently active AllowMultiple: false - Tag: Vehicle.State.Drifting Description: Vehicle is in a controlled slide, building boost charge AllowMultiple: false - Tag: Vehicle.State.SpunOut Description: Vehicle has lost control; blocks driver abilities AllowMultiple: false # --- Ability classification --- - Tag: Ability.Type.Boost Description: Consumes the boost resource for a burst of performance AllowMultiple: false - Tag: Ability.Type.Handling Description: Driving technique that alters grip or builds resource AllowMultiple: false # --- Race progression / status --- - Tag: Race.Phase.Countdown Description: Pre-start countdown; driver inputs locked AllowMultiple: false - Tag: Race.Phase.Racing Description: Race is live and timed AllowMultiple: false - Tag: Race.Phase.Finished Description: Vehicle has crossed the finish line AllowMultiple: false - Tag: Race.Status.FinalLap Description: The leader has started the last lap AllowMultiple: false ``` ## Genre Pack: rpg ### Ability Basic Attack ```yaml # Default single-target weapon strike. Free (no cost), short montage, applies weapon damage. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_ability.json Name: GA_BasicAttack Tags: AbilityTags: - Ability.Type.Melee - DamageType.Physical ActivationBlockedTags: - State.Dead - State.Debuff.Stunned Tasks: - Type: PlayMontage Params: MontageToPlay: Anim_BasicAttack PlayRate: 1.0 - Type: WaitGameplayEvent Params: EventTag: Event.Montage.HitWindow - Type: ApplyEffectToTarget Params: EffectClass: GE_BasicAttackDamage Metadata: DisplayName: Basic Attack Description: Strike a single target for weapon damage Icon: ui/icons/basic_attack.png ``` ### Ability Whirlwind ```yaml # Signature melee AoE: spins, striking every enemy in radius with the basic-attack damage effect. # Models the GA_Whirlwind tag-query example from the core spec (section 15.3). $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_ability.json Name: GA_Whirlwind Tags: AbilityTags: - Ability.Type.Melee - DamageType.Physical ActivationRequiredTags: - State.Combat ActivationBlockedTags: - State.Dead - State.Debuff.Stunned ActivationOwnedTags: - State.Combat Cost: GE_WhirlwindCost Cooldown: GE_WhirlwindCooldown Tasks: - Type: PlayMontage Params: MontageToPlay: Anim_Whirlwind PlayRate: 1.0 - Type: WaitGameplayEvent Params: EventTag: Event.Montage.HitWindow - Type: ApplyEffectToActorsInRadius Params: Radius: 500.0 EffectClass: GE_BasicAttackDamage IgnoreTargetsWithTag: Immunity.Physical Metadata: DisplayName: Whirlwind Description: Spin in place, striking all nearby enemies for weapon damage Icon: ui/icons/whirlwind.png ``` ### Attribute Set ```yaml # RPG core attribute set: primary stats, derived combat stats, vitals, and progression. # Extends the core spec's attribute model (no core attribute is redefined here). $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/attribute_set.json Name: RPGCoreAttributes Attributes: # --- Primary attributes (player-allocated, drive everything else) --- - Name: Strength DefaultBaseValue: 10.0 Category: Statistic Metadata: DisplayName: Strength Description: Increases physical weapon damage via the MainStat channel UICategory: Primary - Name: Dexterity DefaultBaseValue: 10.0 Category: Statistic Metadata: DisplayName: Dexterity Description: Increases attack speed and critical hit chance UICategory: Primary - Name: Intelligence DefaultBaseValue: 10.0 Category: Statistic Metadata: DisplayName: Intelligence Description: Increases spell damage and maximum mana UICategory: Primary - Name: Vitality DefaultBaseValue: 10.0 Category: Statistic Metadata: DisplayName: Vitality Description: Increases maximum health UICategory: Primary # --- Vitals (resources, clamped to their derived maxima) --- - Name: MaxHealth DefaultBaseValue: 100.0 Category: Statistic Metadata: DisplayName: Maximum Health UICategory: Vitals - Name: Health DefaultBaseValue: 100.0 Category: Resource Clamping: Min: 0 Max: MaxHealth ReplicationMode: All Metadata: DisplayName: Health Description: Current life; reaching 0 kills the character UICategory: Vitals - Name: MaxMana DefaultBaseValue: 50.0 Category: Statistic Metadata: DisplayName: Maximum Mana UICategory: Vitals - Name: Mana DefaultBaseValue: 50.0 Category: Resource Clamping: Min: 0 Max: MaxMana ReplicationMode: All Metadata: DisplayName: Mana Description: Resource spent to activate abilities UICategory: Vitals # --- Derived combat statistics --- - Name: WeaponDamage DefaultBaseValue: 10.0 Category: Statistic Metadata: DisplayName: Weapon Damage Description: Aggregated outgoing damage; target of the damage-bucket channels (see spec section 4) UICategory: Combat - Name: Armor DefaultBaseValue: 0.0 Category: Statistic Metadata: DisplayName: Armor Description: Flat physical damage reduction UICategory: Combat - Name: CritChance DefaultBaseValue: 0.05 Category: Statistic Clamping: Min: 0 Max: 1 Metadata: DisplayName: Critical Hit Chance Description: Probability (0..1) that an attack critically strikes UICategory: Combat - Name: CritDamage DefaultBaseValue: 1.5 Category: Statistic Metadata: DisplayName: Critical Hit Damage Description: Multiplier applied on a critical strike UICategory: Combat # --- Progression --- - Name: Level DefaultBaseValue: 1.0 Category: Meta Metadata: DisplayName: Character Level UICategory: Progression - Name: Experience DefaultBaseValue: 0.0 Category: Resource Clamping: Min: 0 Metadata: DisplayName: Experience Description: Accumulates toward the next level UICategory: Progression Metadata: DisplayName: RPG Core Attributes Description: Primary stats, vitals, derived combat stats, and progression for an action-RPG character ``` ### Effect Basic Attack Damage ```yaml # Instant damage dealt by a weapon strike: subtracts the source's fully-aggregated # WeaponDamage (after all damage-bucket channels resolve) from the target's Health. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_BasicAttackDamage DurationPolicy: Instant Modifiers: - Attribute: Health Operation: Add Magnitude: Type: AttributeBased BackingAttribute: WeaponDamage Source: Source Coefficient: -1.0 GameplayCues: - GameplayCue.Character.Damage ``` ### Effect Mainstat Strength ```yaml # MainStat scaling: each point of Strength adds +1% WeaponDamage in the "MainStat" channel. # Mirrors the ARPG damage-bucket design in the core spec (section 15.3). $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_MainStat_Strength DurationPolicy: Infinite Modifiers: - Attribute: WeaponDamage Operation: Multiply Magnitude: Type: AttributeBased BackingAttribute: Strength Source: Source Coefficient: 0.01 Channel: MainStat ``` ### Effect Regeneration ```yaml # Timed health regeneration buff: restores 5 Health every second for 5 seconds. # Demonstrates HasDuration + periodic execution. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_Regeneration DurationPolicy: HasDuration Duration: Type: ScalableFloat Value: 5.0 Period: Period: 1.0 ExecuteOnApplication: false Modifiers: - Attribute: Health Operation: Add Magnitude: Type: ScalableFloat Value: 5.0 GrantedTags: - State.Buff.Regenerating ``` ### Effect Weapon Firesword ```yaml # Equippable item effect: a Fire Sword that grants +20% damage in the "DamageBonuses" channel. # Infinite duration while equipped; grants item/element tags consumed by other systems. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_Weapon_FireSword DurationPolicy: Infinite Modifiers: - Attribute: WeaponDamage Operation: Multiply Magnitude: Type: ScalableFloat Value: 0.20 Channel: DamageBonuses GrantedTags: - Item.Equipped.Weapon - Item.Type.Sword - DamageType.Fire ``` ### Gameplay Controller ```yaml # Worked example: a Diablo-style Barbarian hero, mid-build. # WeaponDamage shows the damage-bucket result of the two active effects below: # base 10 x MainStat(1 + 0.01*50 = 1.50) x DamageBonuses(1 + 0.20 = 1.20) = 18.0 $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_controller.json OwnerActor: ActorID: Hero_Barbarian_01 ActorType: PlayerCharacter AttributeSets: - Name: RPGCoreAttributes Attributes: - Name: Strength BaseValue: 50.0 CurrentValue: 50.0 - Name: Vitality BaseValue: 20.0 CurrentValue: 20.0 - Name: MaxHealth BaseValue: 200.0 CurrentValue: 200.0 - Name: Health BaseValue: 200.0 CurrentValue: 200.0 - Name: WeaponDamage BaseValue: 10.0 CurrentValue: 18.0 - Name: Level BaseValue: 5.0 CurrentValue: 5.0 GrantedAbilities: - AbilityClass: GA_BasicAttack Level: 1 InputID: BasicAttack - AbilityClass: GA_Whirlwind Level: 5 InputID: Ability1 ActiveEffects: - Handle: eff-mainstat-strength EffectClass: GE_MainStat_Strength Duration: -1 - Handle: eff-weapon-firesword EffectClass: GE_Weapon_FireSword Duration: -1 OwnedTags: - State.Alive - State.Combat - Class.Barbarian - Item.Equipped.Weapon - Item.Type.Sword ReplicationMode: Mixed bIsActive: true Metadata: DisplayName: Barbarian (Level 5) Description: Worked-example hero showing damage-bucket aggregation on WeaponDamage DebugCategory: rpg-pack-example ``` ### Input Action Set Onfoot ```yaml # RPG on-foot action set. Active while the character is alive. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action_set.json Name: OnFoot Actions: - BasicAttack - Ability1 - Move - Look Priority: 0 ActivationTags: RequiredTags: - State.Alive Metadata: DisplayName: On Foot Description: Standard RPG controls for exploration and combat ``` ### Input Actions ```yaml # Input actions for the RPG genre pack. # BasicAttack is the primary left-click attack. Ability1 is the first hotbar slot. # Move and Look are standard 2D axes for character movement and camera control. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: BasicAttack ValueType: Digital TriggerBehavior: OnPressed ConsumeInput: true Tags: ActionTags: - Ability.Type.Attack Metadata: DisplayName: Basic Attack Description: Primary melee or ranged attack Category: Combat --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: Ability1 ValueType: Digital TriggerBehavior: OnPressed ConsumeInput: true Tags: ActionTags: - Ability.Type.Active Metadata: DisplayName: Ability 1 Description: First ability hotbar slot Category: Combat --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: Move ValueType: Axis2D TriggerBehavior: WhileHeld ConsumeInput: true Tags: ActionTags: - Ability.Type.Movement Metadata: DisplayName: Move Description: Directional movement vector Category: Movement --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: Look ValueType: Axis2D TriggerBehavior: WhileHeld ConsumeInput: true Tags: ActionTags: - Ability.Type.Movement Metadata: DisplayName: Look Description: Camera orientation Category: Movement ``` ### Input Mapping Onfoot Pc ```yaml # RPG PC keyboard+mouse bindings for on-foot gameplay. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_mapping.json ActionSet: OnFoot Platform: PC Bindings: - Action: BasicAttack Inputs: - Device: Mouse Input: Mouse.LeftButton - Action: Ability1 Inputs: - Device: Keyboard Input: Key.1 - Action: Move CompositeInputs: Up: Device: Keyboard Input: Key.W Down: Device: Keyboard Input: Key.S Left: Device: Keyboard Input: Key.A Right: Device: Keyboard Input: Key.D - Action: Look Inputs: - Device: Mouse Input: Mouse.Axis.X - Device: Mouse Input: Mouse.Axis.Y Modifiers: - MouseSensitivity Metadata: DisplayName: On Foot (Keyboard + Mouse) Description: Default PC bindings for RPG exploration and combat ``` ### Input Modifiers ```yaml # Reusable input modifiers for the RPG genre pack. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_modifier.json Name: MouseSensitivity Type: Sensitivity Params: Multiplier: 1.0 MultiplierX: 1.0 MultiplierY: 1.0 UserConfigurable: true Metadata: DisplayName: Mouse Sensitivity Description: Overall mouse look sensitivity ``` ### Tag Registry ```yaml # RPG genre tag taxonomy. ADDITIVE: this registry introduces genre-specific tags only. # It does not redefine core State.* / Ability.* tags — those are referenced, not declared here. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_tag.json Tags: # --- Damage types (used by abilities, immunities, and conditional effects) --- - Tag: DamageType.Physical Description: Physical (weapon) damage AllowMultiple: false - Tag: DamageType.Fire Description: Fire elemental damage AllowMultiple: false - Tag: DamageType.Cold Description: Cold elemental damage AllowMultiple: false - Tag: DamageType.Lightning Description: Lightning elemental damage AllowMultiple: false - Tag: DamageType.Poison Description: Poison damage, typically applied over time AllowMultiple: false # --- Ability classification --- - Tag: Ability.Type.Melee Description: Close-range physical ability AllowMultiple: false - Tag: Ability.Type.Ranged Description: Projectile or distance physical ability AllowMultiple: false - Tag: Ability.Type.Spell Description: Magic ability that consumes mana AllowMultiple: false # --- Combat status (drive conditional damage buckets and immunities) --- - Tag: Status.Vulnerable Description: Target takes increased damage while present AllowMultiple: false - Tag: Status.FightingElite Description: Source is engaged with an elite enemy AllowMultiple: false - Tag: Immunity.Physical Description: Target ignores incoming physical damage AllowMultiple: false - Tag: Immunity.Fire Description: Target ignores incoming fire damage AllowMultiple: false # --- Items (granted by equip effects) --- - Tag: Item.Equipped.Weapon Description: A weapon is equipped in the main hand AllowMultiple: false - Tag: Item.Equipped.Armor Description: A body armor piece is equipped AllowMultiple: false - Tag: Item.Type.Sword Description: Item is a sword AllowMultiple: false - Tag: Item.Rarity.Legendary Description: Item is of legendary rarity AllowMultiple: false # --- Character classes --- - Tag: Class.Barbarian Description: Strength-based melee class AllowMultiple: false - Tag: Class.Sorcerer Description: Intelligence-based caster class AllowMultiple: false - Tag: Class.Rogue Description: Dexterity-based skirmisher class AllowMultiple: false ``` ## Genre Pack: shooter ### Ability Aim ```yaml # Aim down sights: hold to apply GE_ADS (tighter cone, slower movement, State.Aiming), release to # remove it. Same hold-then-WaitInputRelease shape as the platformer jump. Cannot aim mid-reload. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_ability.json Name: GA_Aim Tags: AbilityTags: - Ability.Type.Aim ActivationRequiredTags: - Weapon.Equipped ActivationBlockedTags: - State.Reloading Tasks: - Type: ApplyEffectToOwner Params: EffectClass: GE_ADS - Type: WaitInputRelease Params: InputID: Aim - Type: RemoveEffectFromOwner Params: EffectClass: GE_ADS Metadata: DisplayName: Aim Down Sights Description: Hold to aim for a tighter bullet cone at the cost of movement speed Icon: ui/icons/aim.png ``` ### Ability Fire ```yaml # Primary fire: trace under the crosshair and apply ballistic damage to the hit target. # Costs one round (GE_FireCost: Ammo -1) - the cost gate is what prevents firing on an empty # magazine - and goes on a per-shot cooldown (GE_FireCooldown, duration = 1 / FireRate) that paces # the automatic fire rate. Cannot fire mid-reload. GE_FireCost and GE_FireCooldown are trivial # (a flat resource cost and a cooldown tag) and are referenced by name rather than shipped as files. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_ability.json Name: GA_Fire Tags: AbilityTags: - Ability.Type.Fire - DamageType.Ballistic ActivationRequiredTags: - Weapon.Equipped ActivationBlockedTags: - State.Reloading Cost: GE_FireCost Cooldown: GE_FireCooldown Tasks: - Type: WaitTargetData Params: TargetingMethod: HitscanTrace MaxRange: 200.0 - Type: ApplyEffectToTarget Params: EffectClass: GE_BallisticDamage Metadata: DisplayName: Fire Description: Fire the equipped weapon at the target under the crosshair Icon: ui/icons/fire.png ``` ### Ability Reload ```yaml # Reload: enter the reloading state, wait the reload window, then transfer rounds from the reserve # into the magazine. Blocked while already reloading. The "only reload if not full and the reserve # is non-empty" check (Ammo < MagazineSize AND ReserveAmmo > 0) is evaluated in activation logic, # since attribute comparisons cannot be expressed as activation tags. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_ability.json Name: GA_Reload Tags: AbilityTags: - Ability.Type.Reload ActivationRequiredTags: - Weapon.Equipped ActivationBlockedTags: - State.Reloading Tasks: - Type: ApplyEffectToOwner Params: EffectClass: GE_Reloading - Type: WaitDelay Params: Duration: 2.0 - Type: ApplyEffectToOwner Params: EffectClass: GE_Reload Metadata: DisplayName: Reload Description: Reload the equipped weapon, drawing rounds from the reserve Icon: ui/icons/reload.png ``` ### Attribute Set ```yaml # Shooter combat attribute set: survivability, the weapon-handling "feel" stats, and the # ammo economy. Extends the core spec's attribute model (no core attribute is redefined here). # Base values describe a default automatic rifle so the set is playable as shipped. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/attribute_set.json Name: ShooterCombatSet Attributes: # --- Survivability (shield absorbs first, then health; both regenerate out of combat) --- - Name: MaxHealth DefaultBaseValue: 100.0 Category: Statistic Metadata: DisplayName: Maximum Health UICategory: Survivability - Name: Health DefaultBaseValue: 100.0 Category: Resource Clamping: Min: 0 Max: MaxHealth ReplicationMode: All Metadata: DisplayName: Health Description: Hit points; reaching 0 is a down/kill. Damage spills here after Shield is depleted UICategory: Survivability - Name: MaxShield DefaultBaseValue: 50.0 Category: Statistic Metadata: DisplayName: Maximum Shield UICategory: Survivability - Name: Shield DefaultBaseValue: 50.0 Category: Resource Clamping: Min: 0 Max: MaxShield ReplicationMode: All Metadata: DisplayName: Shield Description: Regenerating overshield that absorbs incoming damage before Health UICategory: Survivability # --- Movement --- - Name: MoveSpeed DefaultBaseValue: 600.0 Category: Statistic Metadata: DisplayName: Movement Speed Description: Ground movement speed; lowered while aiming down sights (GE_ADS) UICategory: Movement # --- Weapon handling (the gunplay "feel" stats; target of the damage/accuracy channels) --- - Name: WeaponDamage DefaultBaseValue: 25.0 Category: Statistic Metadata: DisplayName: Weapon Damage Description: Fully-aggregated per-shot damage read by GE_BallisticDamage (after all channels resolve) UICategory: Weapon - Name: FireRate DefaultBaseValue: 10.0 Category: Statistic Metadata: DisplayName: Fire Rate Description: Rounds per second; the per-shot cooldown (GE_FireCooldown) is 1 / FireRate UICategory: Weapon - Name: Spread DefaultBaseValue: 2.0 Category: Statistic Metadata: DisplayName: Spread Description: Bullet-cone half-angle in degrees; tightened by aiming (Aim channel) and attachments (Attachments channel) UICategory: Weapon - Name: HeadshotMultiplier DefaultBaseValue: 2.0 Category: Statistic Metadata: DisplayName: Headshot Multiplier Description: Damage multiplier applied by the hit-resolution calc when the target is hit on Hitzone.Head UICategory: Weapon - Name: EffectiveRange DefaultBaseValue: 50.0 Category: Statistic Metadata: DisplayName: Effective Range Description: Distance in metres before damage falloff begins; extended by barrel attachments UICategory: Weapon # --- Ammo economy (magazine spent by firing, refilled from the reserve on reload) --- - Name: MagazineSize DefaultBaseValue: 30.0 Category: Statistic Metadata: DisplayName: Magazine Size Description: Rounds per magazine; the upper clamp on Ammo UICategory: Ammo - Name: Ammo DefaultBaseValue: 30.0 Category: Resource Clamping: Min: 0 Max: MagazineSize ReplicationMode: All Metadata: DisplayName: Loaded Ammo Description: Rounds in the current magazine; spent by GA_Fire (GE_FireCost), refilled by GA_Reload UICategory: Ammo - Name: ReserveAmmo DefaultBaseValue: 90.0 Category: Resource Clamping: Min: 0 ReplicationMode: All Metadata: DisplayName: Reserve Ammo Description: Spare rounds drawn into the magazine on reload UICategory: Ammo - Name: ReloadTime DefaultBaseValue: 2.0 Category: Statistic Metadata: DisplayName: Reload Time Description: Seconds to complete a reload; the GA_Reload wait window UICategory: Ammo # --- Runtime state (driven each frame by the simulation / hit-resolution calc) --- - Name: DistanceToTarget DefaultBaseValue: 0.0 Category: Meta Metadata: DisplayName: Distance To Target Description: Live distance in metres to the traced target; compared against EffectiveRange for falloff UICategory: Telemetry - Name: EffectiveDamage DefaultBaseValue: 0.0 Category: Meta Metadata: DisplayName: Effective Damage Description: Output of ExecCalc_HitResolution after hitzone multiplier and range falloff; for telemetry/UI UICategory: Telemetry Metadata: DisplayName: Shooter Combat Set Description: Survivability, weapon-handling feel stats, ammo economy, and runtime telemetry for a first/third-person shooter ``` ### Effect Ads ```yaml # Aim down sights: held while the aim input is down (applied/removed by GA_Aim). # Tightens the bullet cone and slows movement. Both modifiers use the additive-within-channel # convention: factor = 1 + Value, so Spread x (1 - 0.75) = x0.25 and MoveSpeed x (1 - 0.3) = x0.70. # Spread sits in the Aim channel; the Attachments channel (GE_AttachmentBarrel) multiplies across it. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_ADS DurationPolicy: Infinite Modifiers: - Attribute: Spread Operation: Multiply Magnitude: Type: ScalableFloat Value: -0.75 Channel: Aim - Attribute: MoveSpeed Operation: Multiply Magnitude: Type: ScalableFloat Value: -0.3 Channel: Aim GrantedTags: - State.Aiming GameplayCues: - GameplayCue.Weapon.ADS ``` ### Effect Attachment Barrel ```yaml # Barrel/muzzle attachment: a loadout upgrade that tightens the cone and extends range. # Infinite while fitted. The Spread modifier sits in the Attachments channel - a different channel # from GE_ADS's Aim channel - so the two multiply: base Spread x Aim x Attachments (see the # worked example in gameplay_controller.yaml). EffectiveRange is a flat additive bonus. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_AttachmentBarrel DurationPolicy: Infinite Modifiers: - Attribute: Spread Operation: Multiply Magnitude: Type: ScalableFloat Value: -0.5 Channel: Attachments - Attribute: EffectiveRange Operation: Add Magnitude: Type: ScalableFloat Value: 30.0 GrantedTags: - Weapon.Attachment.Barrel ``` ### Effect Ballistic Damage ```yaml # Damage from a single bullet hit, applied to the traced target by GA_Fire. Shooter damage is not a # single static subtraction: it couples the source's aggregated WeaponDamage with range falloff # (DistanceToTarget vs EffectiveRange), the hitzone multiplier (x HeadshotMultiplier on Hitzone.Head), # and shield-before-health absorption - so it runs through a custom Execution. The calc honors # Immunity.Ballistic targets and writes EffectiveDamage (Meta) for telemetry/UI. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_BallisticDamage DurationPolicy: Instant Executions: - CalculatorClass: ExecCalc_HitResolution GameplayCues: - GameplayCue.Weapon.Impact ``` ### Effect Equip Rifle ```yaml # Equips the default automatic rifle. Infinite; grants the weapon-loadout tags that gate the # fire/reload/aim abilities. The rifle's stat block is the ShooterCombatSet base values, so this # effect carries no modifiers - swapping weapons means swapping which equip effect is active. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_EquipRifle DurationPolicy: Infinite GrantedTags: - Weapon.Equipped - Weapon.Type.Rifle - Weapon.FireMode.Auto GameplayCues: - GameplayCue.Weapon.Equip ``` ### Effect Reload ```yaml # Magazine transfer, applied instantly at the end of the reload. The amount moved depends on the # live Ammo, MagazineSize, and ReserveAmmo, so it cannot be a static modifier - it runs through a # custom Execution that tops the magazine up to MagazineSize and decrements ReserveAmmo by the # rounds loaded (capped by what's left in reserve). $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_Reload DurationPolicy: Instant Executions: - CalculatorClass: ExecCalc_MagazineReload ``` ### Effect Reloading ```yaml # In-progress reload state. HasDuration so it self-expires after the reload window; grants # State.Reloading, which blocks GA_Fire and GA_Aim. GA_Reload applies this, waits the duration, # then applies GE_Reload (the instant magazine transfer). Duration mirrors the ReloadTime attribute. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_effect.json Name: GE_Reloading DurationPolicy: HasDuration Duration: Type: ScalableFloat Value: 2.0 GrantedTags: - State.Reloading GameplayCues: - GameplayCue.Weapon.Reload ``` ### Gameplay Controller ```yaml # Worked example: a player aiming a rifle that has a barrel attachment fitted. # CurrentValue columns show the result of the three active infinite effects below. The Spread # aggregation is the genre's signature: two channels multiply across each other. # Spread: base 2.0 x Aim(1 - 0.75 = 0.25) x Attachments(1 - 0.5 = 0.50) = 0.25 # MoveSpeed: base 600 x Aim(1 - 0.3 = 0.70) = 420.0 # EffectiveRange: base 50 + 30 (barrel) = 80.0 $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_controller.json OwnerActor: ActorID: Soldier_FPS_01 ActorType: PlayerCharacter AttributeSets: - Name: ShooterCombatSet Attributes: - Name: MaxHealth BaseValue: 100.0 CurrentValue: 100.0 - Name: Health BaseValue: 100.0 CurrentValue: 100.0 - Name: MaxShield BaseValue: 50.0 CurrentValue: 50.0 - Name: Shield BaseValue: 50.0 CurrentValue: 50.0 - Name: MoveSpeed BaseValue: 600.0 CurrentValue: 420.0 - Name: WeaponDamage BaseValue: 25.0 CurrentValue: 25.0 - Name: FireRate BaseValue: 10.0 CurrentValue: 10.0 - Name: Spread BaseValue: 2.0 CurrentValue: 0.25 - Name: HeadshotMultiplier BaseValue: 2.0 CurrentValue: 2.0 - Name: EffectiveRange BaseValue: 50.0 CurrentValue: 80.0 - Name: MagazineSize BaseValue: 30.0 CurrentValue: 30.0 - Name: Ammo BaseValue: 30.0 CurrentValue: 30.0 - Name: ReserveAmmo BaseValue: 90.0 CurrentValue: 90.0 - Name: ReloadTime BaseValue: 2.0 CurrentValue: 2.0 GrantedAbilities: - AbilityClass: GA_Fire Level: 1 InputID: Fire - AbilityClass: GA_Reload Level: 1 InputID: Reload - AbilityClass: GA_Aim Level: 1 InputID: Aim ActiveEffects: - Handle: eff-equip-rifle EffectClass: GE_EquipRifle Duration: -1 - Handle: eff-attachment-barrel EffectClass: GE_AttachmentBarrel Duration: -1 - Handle: eff-ads EffectClass: GE_ADS Duration: -1 OwnedTags: - State.Alive - Team.Friendly - Weapon.Equipped - Weapon.Type.Rifle - Weapon.FireMode.Auto - Weapon.Attachment.Barrel - State.Aiming ReplicationMode: Mixed bIsActive: true Metadata: DisplayName: FPS Soldier (aiming, rifle + barrel) Description: Worked-example shooter character showing two-channel Spread aggregation while aiming DebugCategory: shooter-pack-example ``` ### Input Action Set Onfoot ```yaml # On-Foot action set: the default input context for a shooter character on the ground. # Groups all nine actions (combat, movement, general) into a single set that is active # whenever the player is alive and not inside a vehicle or cutscene. Input buffering is # enabled with a tight 0.12 s window (max 2 queued events) so that rapid fire/reload # sequences feel responsive without swallowing unintended repeats. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action_set.json Name: OnFoot Actions: - Fire - Aim - Reload - Move - Look - Jump - Sprint - Interact - CycleWeapon Priority: 0 ActivationTags: RequiredTags: - State.Alive BlockedTags: - State.InVehicle - State.Cutscene Exclusive: false InputBuffer: Enabled: true BufferWindow: 0.12 MaxBufferSize: 2 Metadata: DisplayName: On Foot Description: Default on-foot input context covering combat, movement, and general interactions ``` ### Input Actions ```yaml # Shooter genre input actions. Each document defines a single action that an ability or # movement system can listen for. Actions are grouped into Action Sets (see # input_action_set_onfoot.yaml) and bound to hardware in Input Mappings. # Digital actions produce trigger events; Axis2D actions produce a continuous 2D vector. --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: Fire ValueType: Digital TriggerBehavior: OnPressed ConsumeInput: true Tags: ActionTags: - Ability.Type.Fire - DamageType.Ballistic Metadata: DisplayName: Fire Description: Primary fire - pull the trigger Icon: ui/icons/fire.png Category: Combat --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: Aim ValueType: Digital TriggerBehavior: WhileHeld ConsumeInput: true Tags: ActionTags: - Ability.Type.Aim Metadata: DisplayName: Aim Down Sights Description: Hold to aim down sights for reduced spread and slower movement Icon: ui/icons/aim.png Category: Combat --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: Reload ValueType: Digital TriggerBehavior: OnPressed ConsumeInput: true Tags: ActionTags: - Ability.Type.Reload Metadata: DisplayName: Reload Description: Reload the currently equipped weapon Icon: ui/icons/reload.png Category: Combat --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: Move ValueType: Axis2D TriggerBehavior: WhileHeld ConsumeInput: true Tags: ActionTags: - Ability.Type.Movement Metadata: DisplayName: Move Description: Omnidirectional ground movement Icon: ui/icons/move.png Category: Movement --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: Look ValueType: Axis2D TriggerBehavior: WhileHeld ConsumeInput: true Tags: ActionTags: - Ability.Type.Movement Metadata: DisplayName: Look Description: Camera / aim direction Icon: ui/icons/look.png Category: Movement --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: Jump ValueType: Digital TriggerBehavior: OnPressed ConsumeInput: true Tags: ActionTags: - Ability.Type.Movement Metadata: DisplayName: Jump Description: Jump or vault over obstacles Icon: ui/icons/jump.png Category: Movement --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: Sprint ValueType: Digital TriggerBehavior: WhileHeld ConsumeInput: true Tags: ActionTags: - Ability.Type.Movement Metadata: DisplayName: Sprint Description: Hold to sprint at increased speed Icon: ui/icons/sprint.png Category: Movement --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: Interact ValueType: Digital TriggerBehavior: OnPressed ConsumeInput: true Tags: ActionTags: [] Metadata: DisplayName: Interact Description: Context-sensitive interaction (doors, pickups, objectives) Icon: ui/icons/interact.png Category: General --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_action.json Name: CycleWeapon ValueType: Digital TriggerBehavior: OnPressed ConsumeInput: true Tags: ActionTags: - Ability.Type.Fire Metadata: DisplayName: Cycle Weapon Description: Switch to the next weapon in the loadout Icon: ui/icons/cycle_weapon.png Category: Combat ``` ### Input Mapping Onfoot Gamepad ```yaml # Console gamepad mapping for the OnFoot action set. # Analog triggers use a TriggerThreshold modifier so partial pulls are ignored below 0.5. # Both sticks pass through a radial dead zone; the right stick additionally gets an # AimAssistCurve (exponential response) for smoother aim control on a gamepad. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_mapping.json ActionSet: OnFoot Platform: Console Bindings: - Action: Fire Inputs: - Device: Gamepad Input: Gamepad.RightTrigger Modifiers: - TriggerThreshold - Action: Aim Inputs: - Device: Gamepad Input: Gamepad.LeftTrigger Modifiers: - TriggerThreshold - Action: Reload Inputs: - Device: Gamepad Input: Gamepad.FaceLeft - Action: Move Inputs: - Device: Gamepad Input: Gamepad.LeftStick.X - Device: Gamepad Input: Gamepad.LeftStick.Y Modifiers: - StickDeadzone - RadialScaling - Action: Look Inputs: - Device: Gamepad Input: Gamepad.RightStick.X - Device: Gamepad Input: Gamepad.RightStick.Y Modifiers: - StickDeadzone - AimAssistCurve - Action: Jump Inputs: - Device: Gamepad Input: Gamepad.FaceBottom - Action: Sprint Inputs: - Device: Gamepad Input: Gamepad.LeftStickPress - Action: Interact Inputs: - Device: Gamepad Input: Gamepad.FaceTop - Action: CycleWeapon Inputs: - Device: Gamepad Input: Gamepad.DPad.Right Metadata: DisplayName: On Foot - Gamepad Description: Console gamepad bindings for the on-foot action set with aim assist and dead zones ``` ### Input Mapping Onfoot Pc ```yaml # PC keyboard + mouse mapping for the OnFoot action set. # Move uses a WASD composite; Look uses raw mouse axes with a MouseSensitivity modifier # so the player can tune X/Y sensitivity independently in the settings screen. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_mapping.json ActionSet: OnFoot Platform: PC Bindings: - Action: Fire Inputs: - Device: Mouse Input: Mouse.LeftButton - Action: Aim Inputs: - Device: Mouse Input: Mouse.RightButton - Action: Reload Inputs: - Device: Keyboard Input: Key.R - Action: Move CompositeInputs: Up: Device: Keyboard Input: Key.W Down: Device: Keyboard Input: Key.S Left: Device: Keyboard Input: Key.A Right: Device: Keyboard Input: Key.D - Action: Look Inputs: - Device: Mouse Input: Mouse.Axis.X - Device: Mouse Input: Mouse.Axis.Y Modifiers: - MouseSensitivity - Action: Jump Inputs: - Device: Keyboard Input: Key.Space - Action: Sprint Inputs: - Device: Keyboard Input: Key.LeftShift - Action: Interact Inputs: - Device: Keyboard Input: Key.E - Action: CycleWeapon Inputs: - Device: Mouse Input: Mouse.Scroll Metadata: DisplayName: On Foot - PC Description: Keyboard and mouse bindings for the on-foot action set ``` ### Input Modifiers ```yaml # Reusable input modifiers for the shooter genre. Referenced by name in Input Mappings. # StickDeadzone and MouseSensitivity are player-configurable; the others are tuning knobs # owned by the designer. All modifiers sit in the per-binding modifier pipeline and run # in the order they are listed on each binding. --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_modifier.json Name: StickDeadzone Type: DeadZone Params: DeadZoneShape: Radial InnerThreshold: 0.15 OuterThreshold: 0.95 UserConfigurable: true Metadata: DisplayName: Stick Dead Zone Description: Radial dead zone for analog sticks - ignores drift below 15% and saturates above 95% --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_modifier.json Name: MouseSensitivity Type: Sensitivity Params: Multiplier: 1.0 MultiplierX: 1.0 MultiplierY: 1.0 UserConfigurable: true Metadata: DisplayName: Mouse Sensitivity Description: Per-axis mouse sensitivity multiplier exposed in the player settings screen --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_modifier.json Name: TriggerThreshold Type: TriggerThreshold Params: PressThreshold: 0.5 UserConfigurable: false Metadata: DisplayName: Trigger Threshold Description: Analog trigger press threshold - values below 0.5 are treated as released --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_modifier.json Name: AimAssistCurve Type: ResponseCurve Params: CurveType: Exponential Exponent: 2.5 UserConfigurable: false Metadata: DisplayName: Aim Assist Curve Description: Exponential response curve for gamepad aiming - slower near center, faster at extremes --- $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/input_modifier.json Name: RadialScaling Type: RadialScaling Params: MaxRadius: 1.0 UserConfigurable: false Metadata: DisplayName: Radial Scaling Description: Normalizes stick input to a maximum radius of 1.0 for consistent diagonal movement ``` ### Tag Registry ```yaml # Shooter genre tag taxonomy. ADDITIVE: introduces genre-specific tags only. # It does not redefine the core lifecycle tags (State.Alive / State.Combat / State.Dead) - # those are referenced by the pack's entities, not declared here. $schema: https://raw.githubusercontent.com/jbltx/ugas/v1.0.0-draft.3/schemas/gameplay_tag.json Tags: # --- Weapon-handling states (granted by Effects; tag ownership follows responsibility) --- - Tag: State.Aiming Description: Aiming down sights; granted by GE_ADS, removed when the aim input is released AllowMultiple: false - Tag: State.Reloading Description: A reload is in progress; granted by GE_Reloading, blocks firing and aiming AllowMultiple: false - Tag: State.Sprinting Description: Tactical sprint state (owned by the movement system); documented for a sprint extension AllowMultiple: false # --- Weapon classification & loadout (granted by equip / attachment Effects) --- - Tag: Weapon.Equipped Description: A weapon is equipped and ready; gate for the fire/reload/aim abilities AllowMultiple: false - Tag: Weapon.Type.Rifle Description: Automatic rifle AllowMultiple: false - Tag: Weapon.Type.Pistol Description: Sidearm AllowMultiple: false - Tag: Weapon.Type.Shotgun Description: Close-range spread weapon AllowMultiple: false - Tag: Weapon.Type.Sniper Description: Long-range precision rifle AllowMultiple: false - Tag: Weapon.FireMode.Auto Description: Fully automatic fire while the trigger is held AllowMultiple: false - Tag: Weapon.FireMode.SemiAuto Description: One shot per trigger pull AllowMultiple: false - Tag: Weapon.FireMode.Burst Description: A fixed burst of rounds per trigger pull AllowMultiple: false - Tag: Weapon.Attachment.Barrel Description: A muzzle/barrel attachment is fitted; granted by GE_AttachmentBarrel AllowMultiple: false # --- Ability classification --- - Tag: Ability.Type.Fire Description: Primary fire ability AllowMultiple: false - Tag: Ability.Type.Reload Description: Reload ability AllowMultiple: false - Tag: Ability.Type.Aim Description: Aim-down-sights ability AllowMultiple: false - Tag: Ability.Type.Movement Description: Locomotion ability (sprint, slide, vault) AllowMultiple: false # --- Damage types --- - Tag: DamageType.Ballistic Description: Kinetic projectile / hitscan damage AllowMultiple: false - Tag: DamageType.Explosive Description: Area damage from grenades and rockets AllowMultiple: false # --- Hitzones (drive the headshot damage multiplier) --- - Tag: Hitzone.Head Description: Hit landed on the head; the hit-resolution calc applies HeadshotMultiplier AllowMultiple: false - Tag: Hitzone.Body Description: Hit landed on the torso; baseline damage AllowMultiple: false - Tag: Hitzone.Limb Description: Hit landed on a limb; typically reduced damage AllowMultiple: false # --- Teams & immunity --- - Tag: Team.Friendly Description: Same team as the source; used to skip friendly fire AllowMultiple: false - Tag: Team.Hostile Description: Opposing team; a valid damage target AllowMultiple: false - Tag: Immunity.Ballistic Description: Ignores incoming ballistic damage (e.g. spawn protection); honored by the hit-resolution calc AllowMultiple: false ```