For agents and new contributors. Read this first — it tells you where everything lives. For netcode theory, see
docs/systems/netcode-architecture.md. For art/naming, seedocs/contributing/conventions.md.
SlopArena/
├── client/Unity/Assets/Scripts/Shared/ ← REAL files (Unity compiles these directly)
│ ├── Characters/ ← MankiData, FightGuyData (CharacterRegistry)
│ ├── Abilities/ ← AbilityFactory, ServerAbility implementations
│ ├── Simulation.cs ← SimulateTick(): one tick of movement + combat
│ ├── SpellResolver.cs← Hitbox spawn/Tick: sphere-capsule collision math
│ ├── CharacterState.cs← Per-tick entity state (pos, vel, cooldowns, deaths)
│ ├── CharacterDefinition.cs ← Data-driven characters: stats, abilities, hitboxes
│ ├── AttackData.cs ← HitboxEvent, AttackStage, AbilityData structs
│ ├── CombatMath.cs ← Knockback, facing, damage scaling
│ ├── ServerSimulation.cs ← Wraps Simulation + SpellResolver for server tick
│ ├── CharacterStatePacket.cs ← UDP packet (39 bytes), FromState/Serialize
│ ├── ClientInputPacket.cs ← Client->server input (14 bytes)
│ ├── InputState.cs ← Normalized input (MoveX/Y, flags, ActiveSlot)
│ ├── BakedAnimationData.cs← Offline-baked bone positions per frame
│ ├── ServerSkeleton.cs← FBX/GLB JSON parser for skeleton data
│ ├── ArenaDefinition.cs← Arena data (platforms, spawns, kill height)
│ └── MovementProfiles.cs← (deleted — dead code)
│
├── src/Shared/ ← SYMLINKS → client/Unity/Assets/Scripts/Shared/ (shared code root)
│ ├── Abilities/ ← ServerAbility implementations
│ │ ├── ServerAbility.cs ← Base class: OnStart/Tick/OnEnd lifecycle
│ │ ├── AbilityFactory.cs ← Maps AbilityTypeId to concrete implementations
│ │ ├── MankiLmbCombo.cs ← Manki LMB: 3-hit combo with lunge
│ │ ├── MankiRoundBomb.cs ← Manki Q: hold-to-aim parabolic bomb
│ │ ├── MankiAerosolFlame.cs ← Manki RMB: hold-to-charge flamethrower
│ │ ├── MankiBazooka.cs ← Manki R: rise-aim-fire bazooka
│ │ └── MankiOverclock.cs ← Manki F: self-buff 8s
│ ├── Simulation.cs ← shared logic
│ ├── SpellResolver.cs ← hitbox collision math
│ ├── CharacterState.cs ← entity state
│ └── ... (all files are symlinks to client/Unity/Assets/Scripts/Shared/)
│
├── client/Unity/ ← Unity game client
│ └── Assets/Scripts/
│ ├── Runtime/
│ │ ├── Entities/ ← PlayerRenderer, StatusBillboard, WeaponAttach
│ │ ├── World/ ← MatchBase, TrainingMatch, PvPMatch (match orchestration)
│ │ ├── Simulation/ ← LocalSimulationBridge, NetworkSimulationBridge, ISimulationBridge
│ │ ├── Network/ ← NetworkClient (UDP, Connect/SendInput/ReceiveStates)
│ │ ├── UI/ ← MatchConfig, MainMenuController, LobbyController, CharSelectController, StageSelectController, HUDManager
│ │ ├── Input/ ← InputController (Unity Input → InputState)
│ │ ├── Camera/ ← CameraMount, AimCameraMount (orbit + aim camera)
│ │ ├── Combat/ ← CombatFeedback, AimHandler, AimIndicator
│ │ └── Animation/ ← CharacterAnimationConfig (ScriptableObject)
│ ├── Editor/
│ │ └── SlopArenaAnimatorGenerator.cs ← Generates AnimatorControllers
│ └── Shared/ ← REAL FILES (source of truth for Shared code)
│ ├── Characters/ ← MankiData, FightGuyData
│ ├── Abilities/ ← AbilityFactory, ServerAbility impls
│ └── ... (all Shared code)
│
├── src/
│ ├── Shared/ ← SYMLINKS to client/Unity/Assets/Scripts/Shared/
│ ├── Server/ ← Headless .NET server (MatchInstance, UDP loop)
│ └── ServerApp/ ← Prototype test server
│
├── tests/
│ └── Shared.Tests/ ← xUnit tests (ServerSimulation, SpellResolver, etc.)
│
├── docs/ ← All documentation
├── data/ ← Baked binary data (.arena, _skeleton.bin)
└── tools/ ← Python scripts, build tools
┌─ CLIENT (Unity) ──────────────────────────────────────────────┐ │ │ │ MainMenu → Lobby → CharSelect → StageSelect │ │ ↓ (MatchConfig: Mode, PlayerClass, ArenaName, ServerIP) │ │ MatchBase.Start() → OnMatchStart() │ │ ├── TrainingMatch → LocalSimulationBridge.Tick(inputs) │ │ │ └── ServerSimulation.Tick() │ │ │ ├── PreTickAbilities() / SimulateMovement() │ │ │ └── SpellResolver.Tick() │ │ └── PvPMatch → NetworkSimulationBridge.Tick(inputs) │ │ ├── NetworkClient.SendInput() │ │ └── NetworkClient.ReceiveStates() │ │ │ │ InputController.Poll() → InputState │ │ PlayerRenderer.ApplyServerState(state) │ │ └── UpdateAnimationState() → Animator transitions │ └──────────────────────────────────────────────────────────────────┘
| Convention | Meaning | Example |
|---|---|---|
PX, PY, PZ |
World position (Y=up) | state.PX |
VX, VY, VZ |
World velocity | state.VY (jump velocity) |
ushort durations |
ALL durations in ticks (1/60s) | DashCooldownTicks = 56 |
_fieldName |
Private instance field | _serverTick |
EntityId |
ulong unique ID per entity |
player=1, opponent=2, NPCs=100+ |
Tick suffix |
Duration in ticks | StunTicks, DurationTicks |
Def suffix |
Definition struct | _charDef, HurtboxBoneDef |
→ Shared/Characters/MankiData.cs or FightGuyData.cs
Movementstruct: speed, jump, gravity, dashHurtboxBoneDefs[]: bone-attached hurtbox spheresLMB/RMB/Q/E/R/Fabilities:AbilitySpecwithAbilityTypeIdandParams
→ Shared/Characters/MankiData.cs → the ability's Params dictionary
- Tunable parameters like
lunge_duration,explosion_damage,charge_threshold - No code recompilation needed for balance changes
- Logic lives in
Shared/Abilities/<CharacterName><AbilityName>.cs
→ Shared/Characters/MankiData.cs → the ability's Stages[].HitboxEvents[]
TriggerTick: when during the animation the hitbox spawnsDurationTicks: how long it livesRadius: hitbox size (sphere) or capsule radius
- Don't use
UnityEngine.*inShared/— it breaks the pure C# contract. UseSystem.MathF. - Durations are
ushortticks, notfloatseconds —_timer -= deltais wrong. - Don't modify
CharacterDefinition.csvalues without understanding them — source of truth for balance and hit registration. ServerApp/andServer/are two different servers —Server/is the real one (MatchInstance).ServerApp/is a prototype stub. UseServer/.Shared/is built as a netstandard2.1 DLL — rundotnet build src/Shared/after editing Shared code. Auto-copies toclient/Unity/Assets/Plugins/SlopArena.Shared/via post-build target.- Cooldown struct persistence —
CharacterStateis a value type. Always_states[id] = stateafter modifying cooldowns, otherwise the change is discarded. - Dash duration comes from
MovementStats.DashDurationTicks— not the constSimulation.DashDurationTicks. Character definition is authoritative. - Proportional friction is asymptotic —
VelocityDeadZone(0.015) inApplyVelocityDeadZone()snaps horizontal velocity to 0. Applied after ground friction and air drag. MatchConfigis static — it persists across scene loads. CallMatchConfig.Reset()inMainMenuController.OnEnableso stale values from a previous match don't leak into the next one.
→ Full guide: docs/characters/adding-a-new-character.md
→ Quick version: add CharacterClass enum value → write BuildXxx() in CharacterDefinition.cs → register in BuildRegistry() → add AbilitySpec.Description for each ability slot → create CharacterAnimationConfig ScriptableObject.
# Build Shared library (run after any src/Shared/ change)
dotnet build src/Shared/ --nologo
# Run simulation unit tests
dotnet test tests/Shared.Tests/ --nologo
# Run headless server
dotnet run --project src/Server/| Doc | Covers |
|---|---|
docs/systems/animation-system.md |
Unity Animator: 1-layer trigger-driven, generator tool, pitfalls |
docs/systems/netcode-architecture.md |
Server-authoritative model, prediction, reconciliation |
docs/systems/ability-architecture.md |
ServerAbility pattern, lifecycle, creating new abilities |
docs/systems/combat-systems.md |
Universal combat mechanics |
docs/contributing/conventions.md |
Art direction, animation naming, bone naming |
docs/characters/adding-a-new-character.md |
Full pipeline for new characters |
docs/superpowers/specs/2026-07-09-menu-ui-flow-design.md |
Menu flow design: MainMenu → Lobby → CharSelect → StageSelect |
docs/plans/match-architecture.md |
MatchBase/ISimulationBridge seam design |
CLAUDE.md |
Coding rules (Shared/ purity, tick-based, no engine physics in Shared/) |