$npx -y skills add thedivergentai/GD-Agentic-Skills --skill godot-game-loop-time-trialExpert patterns for racing mechanics, checkpoint tracking, and ghost recording/playback in Godot 4. Use when building racing games, speed-run platformers, or arcade trials.
| 1 | ## Godot 4.7 Baseline |
| 2 | |
| 3 | - Expert patterns in this skill target **Godot 4.7+** (stable, 2026-06-18). |
| 4 | - Consult the [Godot 4.7 migration guide](https://docs.godotengine.org/en/4.7/tutorials/migrating/upgrading_to_godot_4.7.html) when upgrading projects from 4.6. |
| 5 | - **NEVER** assume 4.6 defaults (stretch mode, audio area_mask, RichTextLabel percent flags) without checking 4.7 migration notes. |
| 6 | |
| 7 | # Time Trial Loop: Arcade Precision |
| 8 | |
| 9 | > [!NOTE] |
| 10 | > **Resource Context**: This module provides expert patterns for **Time Trial Loops**. Accessed via Godot Master. |
| 11 | |
| 12 | ## Architectural Thinking: The "Validation-Chain" Pattern |
| 13 | |
| 14 | A Master implementation treats Time Trials as a **State-Validated Sequence**. Recording a time is easy; ensuring the player didn't cheat via shortcuts requires a strictly ordered `CheckpointManager`. |
| 15 | |
| 16 | ### Core Responsibilities |
| 17 | - **TimeTrialManager**: The central clock. Validates checkpoint order and handles "Best Lap" logic. |
| 18 | - **GhostRecorder**: Captures high-frequency transform data. Uses delta-time timestamps for frame-independent playback. |
| 19 | - **Checkpoint**: Spatial triggers that notify the Manager. |
| 20 | |
| 21 | ## Expert Code Patterns |
| 22 | |
| 23 | ### 1. Robust Checkpoint Validation |
| 24 | Prevent "Shortcut Cheating" by requiring checkpoints to be cleared in numerical order. |
| 25 | |
| 26 | ```gdscript |
| 27 | # time_trial_manager.gd snippet |
| 28 | func pass_checkpoint(index): |
| 29 | if index == current_checkpoint_index + 1: |
| 30 | current_checkpoint_index = index |
| 31 | _emit_split_time() |
| 32 | ``` |
| 33 | |
| 34 | ### 2. Space-Efficient Ghosting |
| 35 | Avoid recording every frame. Sample the player's position at a fixed rate (e.g., 10Hz) and use **Linear Interpolation (lerp)** during playback to fill the gaps. |
| 36 | |
| 37 | ```gdscript |
| 38 | # ghost_replayer.gd (Conceptual) |
| 39 | func _process(delta): |
| 40 | # Uses linear interpolation for smooth 60fps+ playback from 10hz data |
| 41 | var target_pos = frame_a.p.lerp(frame_b.p, weight) |
| 42 | ``` |
| 43 | |
| 44 | ## Master Decision Matrix: Data Storage |
| 45 | |
| 46 | | Format | Best For | Implementation | |
| 47 | | :--- | :--- | :--- | |
| 48 | | **Dictionary Array** | Prototyping | Simple `[{t: 0.1, p: pos}, ...]` | |
| 49 | | **Typed Array** | Performance | `PackedVector3Array` for positions. | |
| 50 | | **JSON/Binary** | Saving | `FileAccess.get_var()` to save ghost files. | |
| 51 | |
| 52 | ## NEVER Do |
| 53 | |
| 54 | - **NEVER use OS.get_ticks_msec() for ultra-precise race timing** — Millisecond resolution is too coarse for high-end racing games. Use `Time.get_ticks_usec()` for microsecond precision. |
| 55 | - **NEVER rely exclusively on _process() for finish line triggers** — Visual frames can skip during lag. Always evaluate physical overlaps in `_physics_process()` to guarantee detection within the fixed physics step. |
| 56 | - **NEVER evaluate Area3D overlaps immediately after instantiation** — The physics server requires at least one physics frame to synchronize. `await get_tree().physics_frame` before checking for players. |
| 57 | - **NEVER scale a CollisionShape3D on a checkpoint non-uniformly** — This breaks the underlying SAT collision math. Always scale the internal shape resource (e.g., `BoxShape3D.size`) instead. |
| 58 | - **NEVER use TCP (reliable) for syncing positions in multiplayer racing** — Congestion algorithms cause huge spikes. Use `ENetMultiplayerPeer` with `TRANSFER_MODE_UNRELIABLE` for high-frequency position updates. |
| 59 | - **NEVER trust client-side finish line/lap crossing** — Always validate triggers on the authoritative server using `multiplayer.is_server()` to prevent cheating. |
| 60 | - **NEVER use standard float equality (==) for record lap times** — Use `is_equal_approx()` to account for precision loss in accumulated time variables. |
| 61 | - **NEVER hardcode input checks without flushing the buffer** — For frame-perfect boost/stop responses, call `Input.flush_buffered_events()` to ensure the engine has processed the latest raw input. |
| 62 | - **NEVER allocate new Vector3 arrays inside fast path-following loops** — This triggers the garbage collector. Use `PackedVector3Array` to maintain a contiguous memory block. |
| 63 | - **NEVER use dynamic string paths ($"../Checkpoint") in tight loops** — Lookups are slow. Use `@onready` to cache node references during initialization. |
| 64 | - **NEVER record the whole player object for ghosts** — Only record core transforms (position/rotation). Recording the whole object is memory-intensive and unnecessary for visual ghosts. |
| 65 | - **NEVER give the ghost collision** — It should be a purely visual indicator (e.g., semi-transparent) to avoid disrupting the player's line. |
| 66 | - **NEVER neglect checkpoint sequencing** — Don't just check if the player hit the finish line. Verify they passed every intermediate checkpoint in the correct order. |
| 67 | - **NEVER use Area3D without monitoring optimization** — Checkpoints should only look for the `Player` physics layer to minimize the number of physics overlap calculations. |
| 68 | - **NEVER use standard lerp for ghost rotation** — |