tail wag works

This commit is contained in:
2026-05-25 18:37:42 +03:00
parent 22ed4f7722
commit 98d579f733
8 changed files with 208 additions and 55 deletions
+59
View File
@@ -17,7 +17,15 @@ I am a **Godot 4.6+ expert**. I follow current best practices for GDScript, scen
| `player.gd` | Movement, charge, turning logic |
| `camera_spring_arm.gd` | Mouse-look pivot (`Node3D` + `SpringArm3D`) |
| `camera_follow.gd` | Smooth camera follow (`Camera3D`) |
| `matador.gd` | Matador AI (wander / ragdoll state machine) |
| `matador_spawn.gd` | Spawns N matadors at random arena positions |
| `debug_params.gd` | Runtime-tunable parameter registry (autoload `DP`) |
| `Assets/` | Raw 3D assets (`.glb`, `.fbx`) |
| `Blender/` | Blender source files, animation scripts, FBX exports |
| `Blender/create_matador_anims.py` | Creates walk + idle animations and exports FBX |
| `Blender/render_anim_preview.py` | Renders animation preview PNGs for visual verification |
| `Blender/preview/` | Output directory for animation preview images |
| `tests/` | Headless test scripts (screenshot capture, logic validation) |
| `addons/rider-plugin/` | JetBrains Rider IDE integration |
## Tech choices
@@ -48,6 +56,57 @@ I am a **Godot 4.6+ expert**. I follow current best practices for GDScript, scen
- Keep `_physics_process` deterministic and frame-rate independent (always multiply by `delta`)
- Prefer signals over direct node references for decoupling
## Animation & bone conventions
### Blender rig (matador_v02)
- **Armature bones:** `matador` (root) → `COG``chest``head`, `collarbone_L/R``arm_L/R``forearm_L/R``hand_L/R`, `leg_L/R``shin_L/R``foot_L/R`
- **Rotation mode:** All animated bones use **XYZ Euler** (set in `create_matador_anims.py`)
- **Bone-local axes:** Swing (forward/back) is on **local X**. Arm lowering from T-pose is on **local Y** (positive = left arm down, negative = right arm down). Knee bend is **positive X** (shins only bend backward).
- **Walk cycle:** 30 frames at 30 fps = 1 second loop. Legs swing ±25° X, knees bend 030° X (only when leg is back), arms swing ±15° X opposite phase to legs, arms lowered 55° Y from T-pose, elbows constant 25° X bend.
- **Idle pose:** Arms lowered 55° Y, elbows 25° X, everything else at rest.
### FBX export settings
- `primary_bone_axis = 'Y'`, `secondary_bone_axis = 'X'`
- `apply_scale_options = 'FBX_SCALE_ALL'`
- `add_leaf_bones = False`
- `bake_anim_use_all_actions = True`, `bake_anim_use_nla_strips = False`
### Godot animation names
After FBX import, animations appear as `Armature|walk` and `Armature|idle` in AnimationPlayer.
### Verification workflow
1. Edit bones/animations in Blender (`create_matador_anims.py`)
2. Run render preview: `blender --background Blender/matador_v02.blend --python Blender/render_anim_preview.py`
3. Inspect PNGs in `Blender/preview/` — check bone orientations **before** exporting to Godot
4. Export FBX (done automatically by `create_matador_anims.py`)
5. Re-import in Godot and test in-game
## Running tests
```bash
# All tests (lint + logic + gameplay assertions):
bash run_tests.sh
# Individual tests:
gdlint *.gd tests/*.gd # GDScript lint (gdtoolkit, installed via uv)
godot --headless --script tests/logic_test.gd # pure logic, ~2 s
godot --headless --script tests/gameplay_test.gd # full scene, bone sanity, ~4 s
# Gameplay test with real rendering (inspect frame strip visually):
godot --script tests/gameplay_test.gd # opens a window, saves real screenshots
# Output: tests/output/gameplay/motion_00..04.png, ragdoll_trigger.png, ragdoll_result.png
```
### What the gameplay test catches
| Check | What it detects |
|---|---|
| Tail Verlet chain spread | Tail bunching — consecutive nodes collapsed to same position |
| Ragdoll bone positions finite + within 15 m | Matador limbs exploding after ragdoll impulse |
| Ragdoll Y position > 10 | Bones falling through the floor |
| Frame strip (visual, manual) | Leg IK quality, animation glitches, anything that looks wrong in motion |
Headless screenshots will be blank (Forward Plus has no display). Run without `--headless` to get real frame strips.
## Godot 4.x specifics
- `wrapf` / `wrap` instead of manual modulo for angles