references/migration-notes.md
# Migration notes: godot-genre-sports
Incremental upgrade for topics this skill covers. Apply **one hop**, stabilize/test, then next. Never skip hops.
If the project is **< 4.0**, follow [godot-version-migration](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-version-migration/SKILL.md) era bridges (legacy → 3→4) until 4.0, then these hops. Official 3→4: [Upgrading from Godot 3 to Godot 4](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.html).
## 4.0 → 4.1
Official: [Upgrading to Godot 4.1](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.1.html)
- `Area3D.priority` type is `int` (was `float`).
- `PhysicsDirectSpaceState3D.collide_shape` returns `Array[Vector3]`.
- `Geometry3D.segment_intersects_convex` takes `Array[Plane]`.
- `AnimationNode._process` requires new `test_only` parameter; `blend_input`/`blend_node` gain optional `test_only`.
- `AnimationNodeStateMachinePlayback.get_travel_path` returns `Array[StringName]`.
## 4.1 → 4.2
Official: [Upgrading to Godot 4.2](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.2.html)
- Many `AnimationPlayer`/`AnimationTree` APIs moved to `AnimationMixer` base.
- `method_call_mode` → `callback_mode_method`; `playback_process_mode`/`process_callback` → `callback_mode_process`.
- `playback_active` → `active` on mixer; `AnimationTree.tree_root` typed as `AnimationRootNode`.
- `AnimationPlayer.seek` gains optional `update_only`.
## 4.2 → 4.3
Official: [Upgrading to Godot 4.3](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.3.html)
- If the genre uses TileMap, migrate to TileMapLayer nodes before relying on layer APIs.
- If the genre ships multiplayer, upgrade all peers to 4.3 together (SceneMultiplayer protocol).
- `PhysicsShapeQueryParameters3D.motion` is `Vector3` (was `Vector2`).
- `Animation` interpolate / `track_find_key` gain `backward`/`limit` options.
- `AnimationMixer._post_process_key_value` object arg is `uint64`.
- `Skeleton3D.bone_pose_changed` → `skeleton_updated`; `BoneAttachment3D.on_bone_pose_update` → `on_skeleton_update`.
- Capture mode replaced; see Migrating Animations 4.0→4.3 article for blend/time semantics.
## 4.3 → 4.4
Official: [Upgrading to Godot 4.4](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.4.html)
- `SoftBody3D.set_point_pinned` gains optional `insert_at`.
## 4.4 → 4.5
Official: [Upgrading to Godot 4.5](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.5.html)
- Jolt: `physics/jolt_physics_3d/simulation/areas_detect_static_bodies` removed — Areas always report static overlaps; filter via layers/masks.
## 4.5 → 4.6
Official: [Upgrading to Godot 4.6](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.6.html)
- Retune Environment glow/fog if the genre leans on bloom-heavy looks.
- New projects default 3D physics engine to **Jolt** — existing projects keep prior setting; verify Jolt differences before shipping.
- `AnimationPlayer` `assigned_animation` / `autoplay` / `current_animation` are `StringName` (C# binary break).
- `get_queue` returns `StringName[]`.
## 4.6 → 4.7
Official: [Upgrading to Godot 4.7](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.7.html)
- Confirm project stretch mode and AudioStreamPlayer area_mask after opening in 4.7.
- Jolt: `WorldBoundaryShape3D.plane.d` sign convention flipped vs 4.6 — negate if boundaries moved.
- Jolt: `SoftBody3D` default mass is 1 kg for the body (not 0 → per-point auto mass); retune stiffness/damping.
- Jolt: `Area3D` reports overlaps with `SoftBody3D` — adjust layers/masks if undesired.
- `Animation.length` uses double metadata (C# impact).
- `AnimationNodeBlendSpace1D/2D.add_blend_point` optional `name`.
- `LookAtModifier3D.relative` default is `false` (was `true`).
references/skill-chain.md
# Skill Chain
| Phase | Skills | Purpose |
|-------|--------|---------|
| 1. Physics | `physics-bodies`, `vehicle-wheel-3d` | Ball bounce, friction, player collisions |
| 2. AI | `steering-behaviors`, `godot-state-machine-advanced` | Formations, marking, flocking |
| 3. Anim | `godot-animation-tree-mastery` | Blended running, shooting, tackling |
| 4. Input | `input-mapping` | Contextual buttons (Pass/Tackle share button) |
| 5. Camera | `godot-camera-systems` | Dynamic broadcast view, zooming on action |
scripts/body_part_hitbox.gd
class_name BodyPartHitbox extends Area3D
enum Part { HEAD, TORSO, LEGS }
@export var part_type: Part
func _on_ball_entered(ball: RigidBody3D) -> void:
match part_type:
Part.HEAD:
apply_header_force(ball)
Part.TORSO:
apply_chest_trap(ball)
Part.LEGS:
apply_kick_force(ball)
scripts/magnus_ball_physics.gd
extends RigidBody3D
class_name MagnusBallPhysics
## Expert Physical Ball (Godot 4.7).
## Implements realistic bounce, friction, and the Magnus Effect (spin-lift).
@export var magnus_coefficient: float = 0.5
func _physics_process(delta: float) -> void:
# Magnus Effect: Force perpendicular to both velocity and spin
var spin = angular_velocity
var velocity = linear_velocity
# Expert Pattern: Cross product determines lift direction
var magnus_force = spin.cross(velocity) * magnus_coefficient
# Apply central force to simulate aerodynamic lift/curve
apply_central_force(magnus_force)
## [SKILL NOTICE]: Use 'PhysicsMaterial' on the RigidBody3D for
## friction and bounce. Use 'Magnus Effect' for realistic curve balls.
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/classes/class_rigidbody3d.html
# - https://docs.godotengine.org/en/stable/classes/class_physicsmaterial.html
# - https://docs.godotengine.org/en/stable/tutorials/scripting/idle_and_physics_processing.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-physics-3d/SKILL.md — spin/force application on RigidBody3D
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-sports/SKILL.md
# =============================================================================
scripts/pass_predictor.gd
class_name PassPredictor extends Node3D
func is_lane_clear(target_pos: Vector3) -> bool:
var space_state := get_world_3d().direct_space_state
var query := PhysicsRayQueryParameters3D.create(global_position, target_pos)
query.collision_mask = 1 # Environment/Opponents
var result := space_state.intersect_ray(query)
return result.is_empty() # Path is clear if no collision
scripts/sports_ball_physics.gd
# skills/genre-sports/scripts/sports_ball_physics.gd
extends RigidBody3D
## Sports Ball Physics (Expert Pattern)
## Implements Magnus Effect (curve) and air drag for realistic ball flight.
class_name SportsBallPhysics
@export var drag_coefficient: float = 0.01
@export var magnus_strength: float = 0.5
@export var air_density: float = 1.2
func _ready() -> void:
# Ensure continuous collision detection for fast balls
custom_integrator = true
continuous_cd = true
contact_monitor = true
max_contacts_reported = 3
func _integrate_forces(state: PhysicsDirectBodyState3D) -> void:
var velocity = state.linear_velocity
var speed = velocity.length()
if speed < 0.1: return
# Drag Force: Fd = -0.5 * p * v^2 * Cd * A (simplified)
# Direction opposite to velocity
var drag_force = -velocity.normalized() * (0.5 * air_density * speed * speed * drag_coefficient)
state.apply_central_force(drag_force)
# Magnus Effect: Fm = S(w x v)
# Cross product of angular velocity and linear velocity
var angular_vel = state.angular_velocity
if angular_vel.length_squared() > 1.0:
var magnus_force = angular_vel.cross(velocity) * magnus_strength
state.apply_central_force(magnus_force)
## EXPERT USAGE:
## Attach to a RigidBody3D. Set Linear/Angular Damp to 0 in inspector
## (since we apply custom drag). Shoot ball with 'apply_impulse'.
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/tutorials/physics/rigid_body.html
# - https://docs.godotengine.org/en/stable/classes/class_rigidbody3d.html
# - https://docs.godotengine.org/en/stable/classes/class_physicsdirectbodystate3d.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-physics-3d/SKILL.md — RigidBody continuous CD and custom integrators
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-monte-carlo-balancer/SKILL.md — ballistic curve/stat bands when tuning shots
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-sports/SKILL.md
# =============================================================================
scripts/sports_character.gd
class_name SportsCharacter extends CharacterBody3D
@onready var anim_tree: AnimationTree = $AnimationTree
func _physics_process(_delta: float) -> void:
# Extract root motion from the current animation state
var root_motion := anim_tree.get_root_motion_position()
# Apply to velocity for physics-synced movement
velocity = (global_transform.basis * root_motion) / _delta
move_and_slide()
scripts/sports_patterns.gd
# sports_patterns.gd
extends Node
# 1. Physics-Safe Ball Impulses
# EXPERT NOTE: Forces and impulses must be applied in the physics step to ensure stability.
func kick_ball(ball: RigidBody3D, force_vector: Vector3) -> void:
ball.apply_central_impulse(force_vector)
# 2. Custom Physics Materials (Bounciness)
# EXPERT NOTE: Set restitution programmatically for consistent bounce across surfaces.
func setup_ball_bounce(body: RigidBody3D, bounce: float) -> void:
var mat := PhysicsMaterial.new()
mat.bounce = bounce
body.physics_material_override = mat
# 3. Dynamic Joypad Assignment
# EXPERT NOTE: Safely detect and assign connected controllers for local multiplayer/team play.
func get_active_controllers() -> Array[int]:
return Input.get_connected_joypads()
# 4. Fast Distance Checking (Passing AI)
# EXPERT NOTE: Use distance_squared_to to bypass expensive square root calculations in loops.
func find_closest_teammate(me: Node3D, team: Array[Node3D]) -> Node3D:
return team.reduce(func(best, p):
return p if me.global_position.distance_squared_to(p.global_position) < \
me.global_position.distance_squared_to(best.global_position) else best
)
# 5. Authoritative Score RPC
# EXPERT NOTE: Client requests; server validates physics and position before updating score.
@rpc("any_peer", "call_local", "reliable")
func request_goal_validation(ball_rid: RID, pos: Vector3) -> void:
if multiplayer.is_server():
# Validate that the ball actually crossed the plane in physics space
print("Server validated goal at: ", pos)
# 6. Unreliable State Syncing
# EXPERT NOTE: Send raw bytes via UDP for minimum latency in fast sports movement.
func sync_player_movement(data: PackedByteArray) -> void:
multiplayer.send_bytes(data, 0, MultiplayerPeer.TRANSFER_MODE_UNRELIABLE)
# 7. Safe Controller Haptics
# EXPERT NOTE: Trigger vibration feedback for heavy tackles or successful shots.
func trigger_impact_vibration(device_id: int) -> void:
Input.start_joy_vibration(device_id, 0.6, 1.0, 0.2)
# 8. AI Rubber-Banding Speed
# EXPERT NOTE: Adjust AI agent speed dynamically based on player distance to maintain tension.
func apply_rubber_band(agent_rid: RID, player_dist: float, factor: float) -> void:
var speed := 5.0 + (player_dist * factor)
NavigationServer3D.agent_set_max_speed(agent_rid, speed)
# 9. Physics Server Body State Integration
# EXPERT NOTE: Manually calculate velocities for rigid bodies without breaking physics steps.
func _integrate_forces(state: PhysicsDirectBodyState3D) -> void:
# state.linear_velocity = custom_vector
pass
# 10. Normalizing Joystick Input
# EXPERT NOTE: Always normalize input to prevent diagonal movement from being faster.
func get_move_direction(device_id: int) -> Vector2:
var input := Vector2(
Input.get_joy_axis(device_id, JOY_AXIS_LEFT_X),
Input.get_joy_axis(device_id, JOY_AXIS_LEFT_Y)
)
return input.normalized() if input.length() > 0.1 else Vector2.ZERO
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/classes/class_physicsmaterial.html
# - https://docs.godotengine.org/en/stable/tutorials/inputs/controllers_gamepads_joysticks.html
# - https://docs.godotengine.org/en/stable/tutorials/networking/high_level_multiplayer.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-input-handling/SKILL.md — joypad axes and vibration helpers
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-multiplayer-networking/SKILL.md — authoritative goal RPC + unreliable bytes
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-adapt-single-to-multiplayer/SKILL.md — promote local patterns to net authority
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-sports/SKILL.md
# =============================================================================
scripts/sports_umpire_logic.gd
extends Node
class_name SportsUmpireLogic
## Expert Sports Umpire (Godot 4.7).
## Manages game state and scoring using a State Machine and Area3D signals.
enum State { PRE_GAME, ACTIVE, POST_GOAL, GAME_OVER }
var current_state: State = State.PRE_GAME
var score: int = 0
func _on_goal_area_body_entered(body: Node3D) -> void:
if current_state == State.ACTIVE and body is RigidBody3D:
_process_goal()
func _process_goal() -> void:
score += 1
current_state = State.POST_GOAL
# Expert Pattern: Use SceneTreeTimer for non-blocking delays
await get_tree().create_timer(2.0).timeout
current_state = State.ACTIVE
## [SKILL NOTICE]: Use 'Area3D' for spatial triggers (goals/bounds)
## and an 'enum' State Machine to manage rule logic.
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/classes/class_area3d.html
# - https://docs.godotengine.org/en/stable/classes/class_scenetreetimer.html
# - https://docs.godotengine.org/en/stable/tutorials/scripting/scene_tree.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-state-machine-advanced/SKILL.md — PRE_GAME/ACTIVE/POST_GOAL match phases
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-signal-architecture/SKILL.md — goal Area3D body_entered event wiring
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-sports/SKILL.md
# =============================================================================
scripts/stat_modifier_powerup.gd
extends Node
class_name StatModifierPowerup
## Expert Powerup System (Godot 4.7).
## Manages temporary stat buffs with automatic duration reverting.
var base_stats: Dictionary = { "speed": 10.0, "power": 5.0 }
var active_stats: Dictionary = base_stats.duplicate()
func apply_buff(stat_id: String, multiplier: float, duration: float) -> void:
if not active_stats.has(stat_id): return
active_stats[stat_id] = base_stats[stat_id] * multiplier
# Expert Pattern: Bind signal to pass context to the timeout callback
var timer = get_tree().create_timer(duration)
timer.timeout.connect(_revert_buff.bind(stat_id))
func _revert_buff(stat_id: String) -> void:
active_stats[stat_id] = base_stats[stat_id]
## [SKILL NOTICE]: Use 'bind()' on timer signals to pass specific
## stat IDs back to the callback for clean duration management.
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/classes/class_scenetreetimer.html
# - https://docs.godotengine.org/en/stable/tutorials/scripting/idle_and_physics_processing.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-monte-carlo-balancer/SKILL.md — buff duration/multiplier fairness sims
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-signal-architecture/SKILL.md — timer.timeout.bind context for buff expiry
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-sports/SKILL.md
# =============================================================================
scripts/team_manager.gd
# skills/genre-sports/scripts/team_manager.gd
extends Node
## Team AI Manager (Expert Pattern)
## Manages formations and assigns roles (Attacker, Defender) dynamically.
## Prevents "Kindergarten Soccer" (everyone chasing the ball).
class_name TeamManager
enum Strategy { ATTACK, DEFEND }
@export var formation_anchor: Node3D
@export var players: Array[Node3D] # AI Controllers
@export var ball: RigidBody3D
var current_strategy: Strategy = Strategy.DEFEND
var formation_slots: Array[Node3D] = []
func _ready() -> void:
# Collect formation slots (markers)
for child in formation_anchor.get_children():
if child is Marker3D:
formation_slots.append(child)
func _physics_process(delta: float) -> void:
if not ball: return
# 1. Determine Strategy
if _team_has_possession():
current_strategy = Strategy.ATTACK
else:
current_strategy = Strategy.DEFEND
# 2. Move Anchor
# Anchor generally follows ball but stays on team's side or moves upfield
var target_pos = ball.global_position
if current_strategy == Strategy.DEFEND:
target_pos.z *= 0.5 # Stay closer to goal
formation_anchor.global_position = formation_anchor.global_position.lerp(target_pos, delta)
# 3. Assign Roles
var best_player = _find_closest_player_to_ball()
for i in range(players.size()):
var player = players[i]
if player == best_player:
# Press the ball
player.set_target(ball.global_position)
player.set_state("CHASE")
else:
# Go to formation slot
if i < formation_slots.size():
player.set_target(formation_slots[i].global_position)
player.set_state("FORMATION")
func _team_has_possession() -> bool:
# Logic to check if any team member is controlling the ball
return false
func _find_closest_player_to_ball() -> Node3D:
var best: Node3D = null
var min_dist = INF
for p in players:
var d = p.global_position.distance_to(ball.global_position)
if d < min_dist:
min_dist = d
best = p
return best
## EXPERT USAGE:
## Setup Player AI with set_target/set_state methods.
## Create Formation Node with Marker3D children.
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/classes/class_marker3d.html
# - https://docs.godotengine.org/en/stable/tutorials/navigation/navigation_using_navigationagents.html
# - https://docs.godotengine.org/en/stable/tutorials/3d/using_transforms.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-navigation-pathfinding/SKILL.md — agents path to formation slots
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-state-machine-advanced/SKILL.md — CHASE vs FORMATION player states
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-sports/SKILL.md
# =============================================================================
SKILL.md
---
name: godot-genre-sports
description: "Expert blueprint for sports games (FIFA, NBA 2K, Rocket League, Tony Hawk) covering physics-based ball interaction, team AI formations, contextual input, and match umpire/score authority. Broadcast framing routes to godot-camera-systems. Use when building soccer, basketball, hockey, racing sports, or arcade sports games. Keywords ball physics, magnus effect, formation AI, team tactics, contextual controls, steering behaviors."
---
## NEVER Do (Expert Anti-Patterns)
### Physics & Ball Interaction
- NEVER parent the ball directly to a player Transform; strictly keep it a standalone `RigidBody3D` and use `apply_central_impulse()` for **realistic dribble physics**.
- NEVER allow the ball to "Tunnel" through goals; strictly enable **Continuous CD** (`continuous_cd = true`) on the ball's properties for high-velocity validation.
- NEVER scale a `CollisionShape3D` non-uniformly; strictly adjust the resource radius to preserve the internal **moment of inertia**.
- NEVER apply impulses in `_process()`; strictly use `_physics_process()` or `_integrate_forces()` to prevent visual jitter.
- NEVER use a single collision shape for characters; strictly use **layered shapes** for Head, Torso, and Legs to enable headers and chest-traps.
### Match & Team AI
- NEVER allow all AI to chase the ball ("Kindergarten Soccer"); strictly implement **Formation Slots** (Defense/Attack) where only the closest 1-2 players engage.
- NEVER use perfect goalkeeper reflexes; strictly add a **Reaction Delay** (0.2s-0.5s) and an "Error Rate" based on shot angle and velocity.
- NEVER ignore **Root Motion** for movement; strictly use `AnimationTree` with root motion to ensure momentum and turns are visually grounded.
- NEVER trust client-side goal validations; strictly require the **Authoritative Server** to validate physics and score logic.
### Implementation & Sync
- NEVER rely on the default physics tick rate (60 TPS) for fast-moving ballistics; strictly increase **physics_ticks_per_second** (e.g., to 120 or 240) to prevent tunneling.
- NEVER leave **Physics Interpolation** disabled if you want broadcast-quality smoothness; enable it in Project Settings to smooth ball transforms between ticks on high-refresh monitors.
- NEVER skip **vector normalization** on joystick input; strictly normalize to prevent diagonal movement from being 1.4x faster.
- NEVER handle contextual buttons with `is_action_pressed()`; strictly use a **ContextManager** to determine if Button A means "Pass", "Tackle", or "Switch".
- NEVER evaluate an `Area3D` goal trigger immediately; strictly `await get_tree().physics_frame` to allow the Physics Server to sync.
## Ball Possession Decision Tree
| Feel | Approach | Rule |
|------|----------|------|
| **Arcade / magnetic** | Soft follow or short-range spring toward feet | Still **never** reparent the ball to the player Transform; keep `RigidBody3D` authoritative |
| **Sim / impulse dribble** | Kick slightly ahead with `apply_central_impulse()` each touch | Prefer **MANDATORY** ball scripts below; enable `continuous_cd` |
Default for this skill: **impulse dribble**. Magnetic stickiness is a last resort for pure arcade genres and must remain a free RigidBody.
---
## 🛠 Expert Components (scripts/)
> **MANDATORY** — read the script that matches the task before coding:
> - Goals / match phases → [sports_umpire_logic.gd](scripts/sports_umpire_logic.gd)
> - Full flight model (drag + Magnus via `_integrate_forces`) → [sports_ball_physics.gd](scripts/sports_ball_physics.gd)
> - Lean Magnus-only curve (simpler attach) → [magnus_ball_physics.gd](scripts/magnus_ball_physics.gd) — choose **one** ball script, not both
> - Formations / kindergarten-soccer fix → [team_manager.gd](scripts/team_manager.gd)
> - Temporary buffs / powerups → [stat_modifier_powerup.gd](scripts/stat_modifier_powerup.gd)
> - Shared impulse/score helpers → [sports_patterns.gd](scripts/sports_patterns.gd)
>
> **Broadcast camera**: not implemented in this skill’s `scripts/`. Use peer [godot-camera-systems](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-camera-systems/SKILL.md) for broadcast framing / zoom-on-action.
>
> **Do NOT Load** every sports script for one task.
### Ball Physics (pick one)
- [sports_ball_physics.gd](scripts/sports_ball_physics.gd) - High-fidelity Magnus + air drag via custom integrator (`continuous_cd` ready).
- [magnus_ball_physics.gd](scripts/magnus_ball_physics.gd) - Leaner Magnus-only force helper when drag is handled elsewhere.
### Match / Team / Meta
- [sports_umpire_logic.gd](scripts/sports_umpire_logic.gd) - Goal Area3D + match state machine (PRE_GAME / ACTIVE / POST_GOAL).
- [team_manager.gd](scripts/team_manager.gd) - Formation Slots and team strategy switching.
- [stat_modifier_powerup.gd](scripts/stat_modifier_powerup.gd) - Temporary player/stat modifiers for arcade powerups.
- [sports_patterns.gd](scripts/sports_patterns.gd) - Physics-safe impulses and authoritative scoring helpers.
---
## Skill Chain
| Phase | Skills | Purpose |
|-------|--------|---------|
| 1. Physics | `godot-physics-3d` | Ball bounce, friction, player collisions |
| 2. AI | `godot-state-machine-advanced`, `godot-navigation-pathfinding` | Formations, marking, avoidance |
| 3. Anim | `godot-animation-tree-mastery` | Blended running, shooting, tackling |
| 4. Input | `godot-input-handling` | Contextual buttons (Pass/Tackle share button) |
| 5. Camera | `godot-camera-systems` | Broadcast view / zoom-on-action (**peer skill**, not local scripts) |
## Architecture Overview
### 1. The Ball (Physics Core)
The most important object. Must feel right.
```gdscript
# ball.gd
extends RigidBody3D
@export var drag_coefficient: float = 0.5
@export var magnus_effect_strength: float = 2.0
func _integrate_forces(state: PhysicsDirectBodyState3D) -> void:
# Apply Air Drag
var velocity = state.linear_velocity
var speed = velocity.length()
var drag_force = -velocity.normalized() * (drag_coefficient * speed * speed)
state.apply_central_force(drag_force)
# Magnus Effect (Curve)
var spin = state.angular_velocity
var magnus_force = spin.cross(velocity) * magnus_effect_strength
state.apply_central_force(magnus_force)
```
### 2. Team AI (Formations)
AI players don't just run at the ball. They run to *positions* relative to the ball/field.
```gdscript
# team_manager.gd
extends Node
enum Strategy { ATTACK, DEFEND }
var current_strategy: Strategy = Strategy.DEFEND
var formation_slots: Array[Node3D] # Markers parented to a "Formation Anchor"
func update_tactics(ball_pos: Vector3) -> void:
# Move the entire formation anchor
formation_anchor.position = lerp(formation_anchor.position, ball_pos, 0.5)
# Assign best player to each slot
for player in players:
var best_slot = find_closest_slot(player)
player.set_target(best_slot.global_position)
```
### 3. Match Manager
The referee logic.
```gdscript
# match_manager.gd
var score_team_a: int = 0
var score_team_b: int = 0
var match_timer: float = 300.0
enum State { KICKOFF, PLAYING, GOAL, END }
func goal_scored(team: int) -> void:
if team == 0: score_team_a += 1
else: score_team_b += 1
current_state = State.GOAL
play_celebration()
await get_tree().create_timer(5.0).timeout
reset_positions()
current_state = State.KICKOFF
```
## Key Mechanics Implementation
### Contextual Input
"A" button does different things depending on context.
```gdscript
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("action_main"):
if has_ball:
pass_ball()
elif is_near_ball:
slide_tackle()
else:
switch_player()
```
### Steering Behaviors
For natural movement (Seek, Flee, Arrive).
```gdscript
func seek(target_pos: Vector3) -> Vector3:
var desired_velocity = (target_pos - global_position).normalized() * max_speed
var steering = desired_velocity - velocity
return steering.limit_length(max_force)
```
## Godot-Specific Tips
* **NavigationServer3D**: Essential for avoiding obstacles (other players/referee).
* **AnimationTree (BlendSpace2D)**: Crucial for sports. You need smooth blending between Idle -> Walk -> Jog -> Sprint in all directions.
* **PhysicsMaterial**: Tune `bounce` and `friction` on the Ball and Field colliders carefully.
## Common Pitfalls
1. **AI Bunching**: All 22 players running at the ball (Kindergarten Soccer). **Fix**: Use Formation Slots. Only 1-2 players "Press" the ball; others cover space.
2. **Magnetic Ball**: Ball sticks to player too perfectly. **Fix**: Use a "Dribble" mechanic where the player kicks the ball slightly ahead physics-wise, rather than parenting it.
3. **Unfair Goalies**: Goalie reacts instantly. **Fix**: Add a "Reaction Time" delay and "Error Rate" based on shot speed/stats.
## Advanced Sports Meta-Systems
Professional implementation of animation synchronization, spatial intelligence, and collision filtering.
### 1. Root-Motion-Transition (AnimationTree)
Utilize the `AnimationMixer` class (and its derivatives like `AnimationTree`) to extract root motion from complex animations. This ensures that the character's physical displacement is driven directly by the animation data, preventing "skating" and ensuring momentum is visually grounded during high-speed turns or shots.
```gdscript
class_name SportsCharacter extends CharacterBody3D
@onready var anim_tree: AnimationTree = $AnimationTree
func _physics_process(_delta: float) -> void:
# Extract root motion from the current animation state
var root_motion := anim_tree.get_root_motion_position()
# Apply to velocity for physics-synced movement
velocity = (global_transform.basis * root_motion) / _delta
move_and_slide()
```
### 2. Contextual-Pass-Prediction (Raycasts)
To predict if a passing lane is clear, configure a `PhysicsRayQueryParameters3D` object and use `PhysicsDirectSpaceState3D.intersect_ray()`. This allows the AI or player assist to verify unobstructed paths to teammates before committing to an action.
```gdscript
class_name PassPredictor extends Node3D
func is_lane_clear(target_pos: Vector3) -> bool:
var space_state := get_world_3d().direct_space_state
var query := PhysicsRayQueryParameters3D.create(global_position, target_pos)
query.collision_mask = 1 # Environment/Opponents
var result := space_state.intersect_ray(query)
return result.is_empty() # Path is clear if no collision
```
### 3. Layered-Hitbox Pattern
Configure `Area3D` nodes with specific `collision_layer` and `collision_mask` properties to filter interactions. By assigning different layers for the ball and specific body parts (Head, Torso, Legs), you can accurately detect contextual overlaps for headers, chest-traps, or slide tackles.
```gdscript
class_name BodyPartHitbox extends Area3D
enum Part { HEAD, TORSO, LEGS }
@export var part_type: Part
func _on_ball_entered(ball: RigidBody3D) -> void:
match part_type:
Part.HEAD:
apply_header_force(ball)
Part.TORSO:
apply_chest_trap(ball)
Part.LEGS:
apply_kick_force(ball)
```
**Expert Tip**: For the "Root Motion" system, ensure the `AnimationTree` property `deterministic` is set to true to ensure consistent displacement across different hardware.
## Deep recipes (on demand)
| Topic | Reference / script |
|-------|-------------------|
| Skill chain & phase routing | [skill-chain.md](references/skill-chain.md) |
## Reference
> Progressive disclosure: open Official Documentation links only when researching a specific API; load Related Skills when routing to a peer domain — do not preload the whole lattice.
### Official Documentation
- [Physics introduction](https://docs.godotengine.org/en/stable/tutorials/physics/physics_introduction.html) — Collision layers/masks, continuous CD, and when RigidBody vs CharacterBody fits sports players and balls.
- [Using RigidBody](https://docs.godotengine.org/en/stable/tutorials/physics/rigid_body.html) — Impulse/force timing, custom integrators, and contact monitoring for kick/dribble without parenting the ball.
- [RigidBody3D](https://docs.godotengine.org/en/stable/classes/class_rigidbody3d.html) — `continuous_cd`, damp, and `apply_central_impulse` / force APIs for high-speed ballistics.
- [PhysicsDirectBodyState3D](https://docs.godotengine.org/en/stable/classes/class_physicsdirectbodystate3d.html) — `_integrate_forces` state for Magnus/drag custom forces without fighting the solver.
- [PhysicsMaterial](https://docs.godotengine.org/en/stable/classes/class_physicsmaterial.html) — Bounce/friction overrides for ball and pitch surfaces.
- [Collision shapes (3D)](https://docs.godotengine.org/en/stable/tutorials/physics/collision_shapes_3d.html) — Correct sphere/capsule sizing so inertia and layered hitboxes stay physically valid.
- [Physics interpolation](https://docs.godotengine.org/en/stable/tutorials/physics/interpolation/physics_interpolation_introduction.html) — Broadcast-smooth ball/player transforms between elevated physics ticks.
- [Idle and physics processing](https://docs.godotengine.org/en/stable/tutorials/scripting/idle_and_physics_processing.html) — Keep impulses and match rules in `_physics_process` / integrate paths to avoid jitter.
- [Ray-casting](https://docs.godotengine.org/en/stable/tutorials/physics/ray-casting.html) — Pass-lane and tackle assist queries via `PhysicsDirectSpaceState3D`.
- [Using AnimationTree](https://docs.godotengine.org/en/stable/tutorials/animation/animation_tree.html) — BlendSpace locomotion and root-motion extraction for grounded cuts and shots.
- [Controllers, gamepads, and joysticks](https://docs.godotengine.org/en/stable/tutorials/inputs/controllers_gamepads_joysticks.html) — Normalized stick axes, device IDs, and vibration for contextual Pass/Tackle/Switch.
- [High-level multiplayer](https://docs.godotengine.org/en/stable/tutorials/networking/high_level_multiplayer.html) — Authoritative goal validation and unreliable movement sync for competitive matches.
### Related Skills
#### Prerequisites
- [godot-project-foundations](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-project-foundations/SKILL.md) — Autoloads, physics tick/interpolation project settings, and scene layout before ball/team systems land.
- [godot-physics-3d](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-physics-3d/SKILL.md) — RigidBody3D, layers/masks, and continuous collision patterns the ball and layered hitboxes depend on.
- [godot-input-handling](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-input-handling/SKILL.md) — Action maps and device routing so one button can mean Pass, Tackle, or Switch by context.
#### Complements
- [godot-animation-tree-mastery](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-animation-tree-mastery/SKILL.md) — Root-motion BlendSpaces and deterministic mixer setup for sprint/shot/tackle without skating.
- [godot-camera-systems](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-camera-systems/SKILL.md) — Broadcast framing, zoom-on-action, and follow rigs for pitch-scale presentation.
- [godot-navigation-pathfinding](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-navigation-pathfinding/SKILL.md) — NavigationAgent/mesh avoidance so formation runners do not stack through teammates.
- [godot-state-machine-advanced](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-state-machine-advanced/SKILL.md) — Match phases (kickoff/play/goal/end) and per-player chase vs formation states.
- [godot-raycasting-queries](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-raycasting-queries/SKILL.md) — Masked space queries for clear passing lanes and tackle prediction.
- [godot-multiplayer-networking](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-multiplayer-networking/SKILL.md) — Server-authoritative score RPCs and transfer modes for fast sports snapshots.
- [godot-adapt-single-to-multiplayer](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-adapt-single-to-multiplayer/SKILL.md) — Authority split and reconciliation when promoting local kickabouts to online matches.
- [godot-signal-architecture](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-signal-architecture/SKILL.md) — Goal, possession, and UI event buses without coupling umpire logic to every player node.
- [godot-3d-world-building](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-world-building/SKILL.md) — Stadium collision, pitch surfaces, and LOD props around the playable field.
#### Downstream / consumers
- [godot-monte-carlo-balancer](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-monte-carlo-balancer/SKILL.md) — Simulate player/attribute asymmetry, keeper reaction error bands, and rubber-band AI so match outcomes stay competitive.
- [godot-genre-racing](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-racing/SKILL.md) — Adjacent high-speed physics genre patterns when the sport leans vehicle/arcade (e.g. Rocket League-style).
#### Master
- [godot-master](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-master/SKILL.md) — Library router and mirrored module entry; open when discovering which Domain Skill owns a cross-cutting sports concern.