- Fixed Blender export: remove FBX armature modifier before scaling,
then transform_apply(scale=True) before creating new armature
- Model now exports at correct 1.8 unit height (verified in Blender)
- SkinnedPlayerModel now polls MovementStateMachine in _process
to drive animations based on movement state
- Player 1 gets Miku at origin, others get procedural humanoid at y=-0.9
Pipeline:
1. Downloaded TDA-style Miku model from Sketchfab (19MB FBX, 38K verts)
2. Imported to Blender, scaled to Godot humanoid proportions (1.8u tall)
3. Created clean 17-bone humanoid armature (Hips/Spine/Chest/Head/Arms/Legs)
4. Auto-weight-painted mesh to armature
5. Added Idle (breath bounce) and Run (cycle) animations
6. Exported as GLB with embedded animations (3MB)
New:
- SkinnedPlayerModel class: loads GLB with own armature+animations
- Player 1 spawns with Miku model, others get procedural humanoid
- Completely separate from procedural model system
Files:
- assets/characters/skins/miku_rigged_final.glb (3MB, 48K verts, 17 bones, 2 anims)
- characters/skinned_player_model.gd
- Removed old procedural GLB attempts
Skin System:
- PlayerSkin resource class (model_path, color_tint, etc.)
- SkinManager with default skins: Default, Red Team, Forest, Miku
- HumanoidModel.apply_skin() supports both GLB model replacement
and procedural color tinting
- Procedural meshes auto-hide when GLB model is loaded
Miku Skin:
- 43-part procedural model generated in Blender (headless)
- Twin tails, headphones, thigh-high socks, idol outfit
- Teal/cyan color scheme with red accents
- Exported to assets/characters/skins/miku.glb
- Applied to player 1 in test level builder
Pipeline:
- Blender 5.0.1 + MCP addon installed
- Bridge script for socket-based Blender control
- docs/3D_ASSET_PIPELINE.md documents full workflow
Problems fixed:
- Player spawn moved from inside central platform to open area (z=20)
- Removed broken internal stairs inside tower (disconnected ramps)
- Removed corner towers, walkways, stepping stones, wall-run corridor,
wall-climb surfaces, grapple anchors — all were overlapping or unreachable
- Reduced arena from 96x96 to 80x80 for tighter gameplay
- Crates properly spaced (1.5u apart), no overlaps or intersections
- Reduced from 6 spawns to 4 (corners only), each with 2 L-shaped cover
walls and clear exit routes — no more 3-wall traps
- Spawn alcoves use 2 walls max with open diagonal exits
- Dummies placed in open areas away from geometry:
TargetDummy at (0,-25), KillableDummy at (25,0),
WalkingDummy patrols z=-30 from x=-20 to 20
- Central platform simplified: 16x16 base (2u) + 8x8 upper (4u)
- 4 cardinal ramps properly connect ground to platform edge
- Cover walls placed in open lanes, not blocking movement
- Barrels placed along outer walls, not in pathways
- Fix tab character in _build_elevated_walkways() call (line 63)
- Fix untyped 'pos' variable in grapple pillar loop (line 376)
- Remove fog_sky_affinity (Godot 4.3+ only property)
- Replace preload() with load() for runtime script loading
- Replace 'is DoubleBarrelShotgun' type check with duck-typing
- Fix 'is not OfflineMultiplayerPeer' syntax in grenade spawn
- Replace absf() with abs() in state_machine, state_air, state_ground
- Update test_level.tscn to remove stale UID reference
All fixes verified: PARSE OK and scene runs without script errors
on Godot 4.2.1 headless.
96x96 arena with multi-level layout, grapple points, wall-run/climb
surfaces, 6 team spawns with anti-camping cover, and varied movement
challenges.
Map Features:
- Central multi-level structure: 20x20 platform (3u), 10x10 tower (6u),
14x14 top platform (9u) with crown cover walls and internal stairs
- 4 corner towers (5u high) with railings and top platforms
- 4 elevated mid-side walkways (3u) with railings connecting center to edges
- 8 ramps: cardinal to central, diagonal to corners, to walkways
- Quarter-pipe curve (8 segments) near NE corner
- 4 parkour stepping stone chains (NW, NE, SW, SE quadrants)
- Wall-run corridor (N side) with parallel walls, obstacles, and floor gap
- Wall-run surfaces on all 4 outer wall sections
- Wall-climb surfaces around central tower and mid-map (warm color)
- 4 grapple anchor pillars (15u tall) with visible tops at cardinal points
- Cover system: low walls, crate clusters, corridor walls, structural pillars
- Speed corridor with side rails, speed bumps, and floor markings
- 6 spawn alcoves (3 red: NW/SW/West, 3 blue: NE/SE/East) with cover walls
- Anti-spawn-camping: each alcove has 2+ exits and blocking walls
- Floor accent tiles in central area
- Atmospheric sky with procedural sky material and fog
- Directional sun + fill light + center omni + 6 spawn accent lights
- Decorative: barrels, hazard stripes, accent beams, spawn area lights
- Full multiplayer spawning, weapon system, HUD, entities preserved
- All movement states: ground, air, wall_run, wall_climb, wall_cling,
slide, dash, grapple
Parse verified clean with Godot 4.2.1 headless.
Root cause: builder created plain Node instances for the state machine
and states, so _machine.player = self in PlayerMovementController
crashed with 'Invalid assignment ... on base object of type Node'.
Now each node gets its real script attached before _ready() runs.
player_movement_controller.gd: use Node + get_node() instead of
typed MovementStateMachine + $ shorthand so runtime set_script paths
don't crash on @implicit_ready
- state_wall_run.gd: typed ray_origin/ray_end as Vector3
- test_level_builder.gd: restructure builder to put MovementStateMachine
+ state children under Player, attach camera under Player too
- player_movement_controller.gd: @onready uses get_node so type is not inferred
- _build_player: creates Player (CharacterBody3D) with capsule,
MovementStateMachine + 6 state children, attaches movement script
- Calls player._ready() manually after set_script (GDScript
set_script does not auto-trigger _ready)
- Camera parented under Player so it follows movement
- Restores _build_camera() which was accidentally dropped
- test_level.tscn: minimal Node3D root (zero SubResource references)
- debug/test_level_builder.gd: runtime floor/walls/lighting/camera/UI
shapes/meshes created from BoxShape3D/BoxMesh — no .tscn subresources
- project.godot: revert features to 4.6 (installed build is 4.6.3)
- Removed all SubResource BoxShape3D references from test_level.tscn
- Changed PackedStringArray feature from 4.6 to 4.2
Scene is now pure nodes + transforms, no external resource IDs
- scenes/test_level/test_level.tscn — playable test environment (30x30 floor + 4 walls + ramp)
Floor at y=-1, Player spawn at (0, 1.5, 8), FpsCamera at head height (y=1.7)
DirectionalLight + WorldEnvironment + UI label overlay (speed/state/chain)
- debug/debug_test_env.sh — launch wrapper for manual playtesting
No code changes: raw physics testbed. Intended to be opened as first scene in Godot window.
Closes#2 (test-level scene task)
- state_dash.gd: Basis*Vector4 → Basis*Vector3 (no Vec4 in Godot 4.2)
- state_ground.gd: shape_owner_get_shape(0) → (0, 0); typed Variant vars; typed ray/normal
- state_slide.gd: same shape API fix + unique shape vars per branch
- state_wall_run.gd: split 'and' line into single typed bool expression