# Actor Behavior System Design ## Goal Build a reusable actor behavior system for lobby actors and mini games. Behaviors are configured from JSON and can describe skills, actions, visual effects, projectiles, buffs, and logic effects. Client and server load the same JSON files; the server runs only authoritative logic, while the client consumes presentation events and synchronized authoritative results. The first implementation scope is the shared core, example configuration, and automated tests. It does not directly integrate the lobby actor controller or any mini game UI yet. ## Current Project Context - Unity client shared framework code lives in `Client/Assets/Framework/Shared`. - `Server/Framework.Shared/Framework.Shared.csproj` compiles the same shared source files from the client tree. - Mini game Core code follows the same pattern: a `scripts~` source directory is compiled by both client and server projects. - The existing virtual HUD input work intentionally stops at raw joystick and action button events. This behavior system sits after that input layer and turns input into behavior requests. - Existing character JSON uses `Client/Assets/Resources/Config`, while the server needs direct file/string loading for the same JSON content. ## Recommended Approach Add a pure C# shared module under: ```text Client/Assets/Framework/Shared/ActorBehavior/ ``` The module namespace is: ```csharp XWorld.Framework.ActorBehavior ``` The shared core does not reference Unity. It owns configuration models, deterministic runtime state, timeline triggering, projectile movement, buff ticking, logic effect target selection, and structured event output. Unity-specific work is kept outside the core. Client code will later adapt `TimelineAnimation`, `TimelineEffect`, and projectile presentation events to `Animator`, prefab loading, and visual effects. Server code will later adapt authoritative logic events to real game attributes, snapshots, and network broadcasts. ## Alternatives Considered ### Shared Core First This is the selected approach. It matches the existing shared framework design, keeps client and server behavior rules in one place, and lets lobby and mini games adopt the same API later. Trade-off: the first version proves the system through tests and example configuration, not through a visible lobby skill. ### Per Mini Game Behavior Core Each mini game could package its own behavior runtime. This isolates game modules, but it duplicates fighter/mage style character configuration and makes lobby reuse weak. ### Unity Client First The client could implement skills directly with Unity animation, prefab, and collision APIs, then the server could be added later. This would show visuals quickly, but it would bind rules to Unity object lifetimes and make server authority hard to add cleanly. ## Core Components ### BehaviorConfigCatalog Owns one actor type configuration set, such as `fighter`. Inputs: - `fighter_skill.json` - `fighter_flyobj.json` - `fighter_buff.json` Responsibilities: - Parse JSON strings. - Validate duplicate ids, missing references, invalid durations, invalid shape parameters, and unsupported enum values. - Expose behavior, projectile, buff, and logic effect definitions by id. ### BehaviorWorld Owns runtime simulation for many actors and projectiles. Responsibilities: - Register multiple `BehaviorConfigCatalog` instances. - Add, remove, and update actor runtime state. - Switch an actor's active catalog by actor type. - Start and stop behaviors. - Tick active behavior timelines. - Tick projectile movement and collision. - Tick buffs by duration, interval, and repeat count. - Drain generated `BehaviorEvent` instances. Primary API shape: ```csharp var catalog = BehaviorConfigCatalog.LoadFromJson(type, skillJson, flyObjJson, buffJson); var world = new BehaviorWorld(new BehaviorWorldOptions { Mode = BehaviorRunMode.ServerLogic }); world.RegisterCatalog(catalog); world.AddActor(actorState); world.TryStartBehavior(actorId, "slash", BehaviorInput.FromDirection(x, z)); world.Tick(deltaTime); IReadOnlyList events = world.DrainEvents(); ``` ### ActorBehaviorController Represents behavior state for one actor inside `BehaviorWorld`. Responsibilities: - Track the actor's active catalog type. - Track the current active behavior and elapsed time. - Enforce that only one active behavior runs at a time. - Allow explicit special interruption when the current behavior permits interruption. - Own active buffs for that actor. Behavior control: ```csharp world.TryStartBehavior(actorId, "slash", BehaviorInput.FromDirection(1f, 0f)); world.StopBehavior(actorId, BehaviorStopReason.Stunned); world.TryStartBehavior(actorId, "stun_fall", BehaviorInput.None); ``` ### Projectile Runtime Projectiles are independent runtime objects configured by id. Responsibilities: - Spawn from behavior timelines or logic effects. - Update position by configured movement mode. - Check collision through configured collision shapes. - Run projectile-owned timelines. - Trigger logic effects on collision or on configured times. First version movement modes: - `Line` - `Parabola` - `Homing` First version collision mode: - Sphere collision with radius and hit limit. ### Buff Runtime Buffs are special attached effects. Responsibilities: - Track duration. - Tick at configured intervals. - Respect repeat count. - Apply stacking behavior. - Trigger configured logic effects on add, tick, or remove. First version stacking modes: - `RefreshDuration` - `IgnoreIfExists` - `StackIndependent` ### Logic Effect Runtime Logic effects select targets by shape and request one or more business effects. First version shapes: - `Sector` - `Sphere` - `Ring` - `Line` - `Locked` First version effect operations: - `Attribute`: request an attribute delta, such as `hp = -30`. - `Buff`: request applying a configured buff id. - `Projectile`: request spawning a configured projectile id. The core does not own the real business attribute table. It emits structured events or calls an adapter so lobby and mini games can decide how `hp`, `moveSpeed`, `score`, or custom attributes are applied. ## JSON Configuration Each actor type owns three files. File names use the actor type prefix: ```text fighter_skill.json fighter_flyobj.json fighter_buff.json ``` All files include: - `type`: actor type name, such as `fighter`. - `version`: integer config version. ### Behavior JSON ```json { "type": "fighter", "version": 1, "behaviors": [ { "id": "slash", "duration": 0.8, "canBeInterrupted": true, "input": "DirectionOrTarget", "timeline": [ { "time": 0.0, "kind": "Animation", "animation": "slash_01", "duration": 0.8 }, { "time": 0.12, "kind": "Effect", "prefab": "Assets/Game/Art/Effect/slash.prefab", "duration": 0.5 }, { "time": 0.25, "kind": "LogicEffect", "effectId": "slash_hit" }, { "time": 0.32, "kind": "Projectile", "flyObjectId": "blade_wave" } ], "logicEffects": [ { "id": "slash_hit", "shape": "Sector", "radius": 2.4, "angle": 90, "target": "Enemy", "effects": [ { "type": "Attribute", "attribute": "hp", "value": -30 } ] } ] } ] } ``` ### Projectile JSON ```json { "type": "fighter", "version": 1, "flyObjects": [ { "id": "blade_wave", "duration": 1.5, "movement": { "kind": "Line", "speed": 6.0 }, "collision": { "shape": "Sphere", "radius": 0.4, "hitLimit": 1 }, "timeline": [ { "time": 0.0, "kind": "Effect", "prefab": "Assets/Game/Art/Effect/blade_wave.prefab", "duration": 1.5 }, { "time": 0.0, "kind": "LogicEffect", "effectId": "blade_wave_hit", "trigger": "OnCollision" } ], "logicEffects": [ { "id": "blade_wave_hit", "shape": "Locked", "target": "HitActor", "effects": [ { "type": "Attribute", "attribute": "hp", "value": -20 }, { "type": "Buff", "buffId": "slow_01" } ] } ] } ] } ``` ### Buff JSON ```json { "type": "fighter", "version": 1, "buffs": [ { "id": "slow_01", "duration": 3.0, "tickInterval": 1.0, "repeatCount": 3, "stacking": "RefreshDuration", "logicEffects": [ { "id": "slow_tick", "shape": "Locked", "target": "Owner", "effects": [ { "type": "Attribute", "attribute": "moveSpeed", "value": -0.2 } ] } ] } ] } ``` ## Input Model `BehaviorInput` supports: - `None` - `Direction` - `TargetPosition` - `TargetActor` The existing virtual joystick and action pad can later map raw input events to this input model. Examples: - Joystick direction plus attack button maps to `Direction`. - Drag-release skill maps to `Direction` or `TargetPosition`. - Target lock skill maps to `TargetActor`. ## Run Modes ### ServerLogic The server mode runs authoritative logic: - Behavior start and stop. - Timeline logic events. - Projectile movement and collision. - Buff add, tick, and remove. - Logic effect target selection. - Attribute, buff, and projectile requests. It does not execute animation or effect presentation. It may still emit presentation events for logging or optional network relay, but these are not authoritative. ### ClientPresentation The client presentation mode consumes behavior state and events for visuals: - Animation timeline events. - Effect timeline events. - Projectile presentation spawn and movement. - Buff visual add/remove markers. It does not perform authoritative target selection or attribute application. ### ClientPredict Client prediction may be added after the core is stable. It can run the same timeline and movement logic locally, then reconcile against server events or snapshots. The first version can define the enum value but does not need full reconciliation. ## Event Model The core emits `BehaviorEvent` values. Events are deterministic data records, not callbacks into Unity. First version event kinds: - `BehaviorStarted` - `BehaviorStopped` - `TimelineAnimation` - `TimelineEffect` - `ProjectileSpawned` - `ProjectileMoved` - `ProjectileHit` - `BuffAdded` - `BuffTicked` - `BuffRemoved` - `LogicEffectApplied` - `AttributeEffectRequested` Server and client consume the same event stream differently: - Client consumes `TimelineAnimation`, `TimelineEffect`, and projectile presentation events. - Server consumes `LogicEffectApplied`, `AttributeEffectRequested`, buff events, and projectile spawn/hit events. - Network integration will later broadcast authoritative events and snapshots from the server to clients. ## Targeting And Filtering Actors tracked by the core have minimal deterministic state: - Actor id. - Catalog type. - Position. - Forward direction. - Team or faction id. - Alive/enabled flag. - Optional radius for collision and target selection. Logic effect target filters: - `Self` - `Ally` - `Enemy` - `All` - `Owner` - `HitActor` The first version keeps filtering simple and deterministic. Complex gameplay rules can be added through adapter decisions later. ## Error Handling Configuration loading fails with clear validation messages for: - Missing `type`. - Mismatched type across skill, projectile, and buff JSON. - Duplicate behavior, projectile, buff, or logic effect ids. - Timeline entries with negative time. - Timelines referencing missing logic effect ids. - Behaviors referencing missing projectile ids. - Logic operations referencing missing projectile or buff ids. - Unsupported enum strings. - Invalid shape parameters, such as negative radius or sector angle outside `(0, 360]`. Runtime behavior: - Starting an unknown behavior returns failure and emits no events. - Starting a second behavior while one is active returns failure unless the current behavior was explicitly stopped or interrupted. - Non-interruptible behaviors ignore special stop requests except terminal reasons like `Death`. - Projectiles expire when duration ends or hit limit is reached. - Buffs expire when duration ends or repeat count is reached. ## Tests Add xUnit tests in `Server/Framework.Shared.Tests`. Required coverage: - Loads a complete fighter config from three JSON strings. - Rejects mismatched config type names. - Rejects duplicate ids and missing references. - Allows only one active behavior per actor. - Allows special stop and behavior switch when the current behavior is interruptible. - Triggers timeline animation, effect, logic effect, and projectile entries at configured times. - Selects targets for sector, sphere, ring, line, and locked shapes. - Updates line, parabola, and homing projectile movement. - Triggers projectile sphere collision and hit-limited expiry. - Ticks buffs by duration, interval, and repeat count. - Emits logic effect requests for attribute, buff, and projectile operations. - In `ServerLogic`, authoritative logic events are produced and presentation execution is skipped. - In `ClientPresentation`, presentation events are produced and authoritative attribute requests are skipped. ## Acceptance Criteria - A caller can load a `fighter` behavior catalog from `fighter_skill.json`, `fighter_flyobj.json`, and `fighter_buff.json`. - A `BehaviorWorld` can register multiple actor type catalogs. - Each actor can switch its active behavior catalog by type name. - A caller can start a behavior by actor id and behavior id with direction or target input. - One actor cannot run two active behaviors at the same time. - Special logic can stop or interrupt behavior and then start another behavior when rules allow it. - Behavior timelines can emit animation, effect, projectile, buff, and logic events. - Projectiles can move independently and trigger logic on collision. - Buffs can attach to actors, tick repeatedly, and expire. - Logic effects support sector, sphere, ring, line, and locked target selection. - Server logic can run without Unity animation, prefab, or effect dependencies. - Client code can consume the same event stream later for animation, prefab, and effect playback.