feat: Sketchfab-to-game character pipeline, FP/TP animated views, client-auth netcode, audio system

Character pipeline (tools/):
- sketchfab_import.py: search/download via Sketchfab Download API with
  license/attribution tracking (SKETCHFAB_API_TOKEN)
- autorig.py: headless Blender auto-rig — fits a Mixamo-named skeleton
  to unrigged humanoids, binds automatic weights
- merge_animations.py: merges the shared Mixamo-skeleton animation
  library onto any rigged character, strips root motion, canonical names
- pipeline.py: one command chains download -> rig -> animate -> register

In-game skin system:
- SkinManager autoload reads skins.json (auto-written by the pipeline)
- SkinnedPlayerModel rewritten: canonical clips with fallback chains,
  blend times, speed-scaled locomotion, weapon bone attachment
- First-person: full animated body for the owner, head hidden via
  SkeletonModifier3D; third-person: full model for other players
- Skin selector in main menu; skin id synced in multiplayer
- Fixed GLBLoader crash (GLTFDocument.get_animation_count doesn't exist)

Multiplayer sync overhaul:
- Movement is now client-authoritative: the owning peer simulates
  locally (no input round-trip), server keeps health/kills/death
- Remote players interpolate synced_position/velocity with
  extrapolation and snap-on-teleport
- Knockback/impulses routed to the simulating peer

Audio:
- AudioManager autoload: SFX/Weapons/Footsteps/UI/Music buses, pooled
  3D players, variation + pitch randomization, auto-registration from
  assets/sounds

Docs: 3D_ASSET_PIPELINE.md rewritten end-to-end, new ASSET_SOURCES.md
(non-procedural animation/map/sound sources) and SOUND_DESIGN.md.
Verified with debug/spawn_smoke_test.gd (headless: 24/24 checks pass).

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Nicholas Butzke
2026-07-06 02:28:58 -04:00
co-authored by Claude Fable 5
parent 552338112e
commit d05477c135
52 changed files with 1933 additions and 466 deletions
+31
View File
@@ -61,6 +61,9 @@ func _ready() -> void:
_add_button(_ui_vbox, "Settings", _on_settings_pressed)
_add_button(_ui_vbox, "Exit Game", _on_exit_pressed)
# Skin selector (skins come from SkinManager: built-ins + skins.json)
_build_skin_selector(_ui_vbox)
# ==========================================
# LEVEL SELECTOR PANEL
# ==========================================
@@ -355,6 +358,34 @@ func _add_button(parent: Container, text: String, callback: Callable) -> void:
btn.pressed.connect(callback)
parent.add_child(btn)
func _build_skin_selector(parent: Container) -> void:
var skin_mgr = get_node_or_null("/root/SkinManager")
if not skin_mgr:
return
var row = HBoxContainer.new()
row.add_theme_constant_override("separation", 12)
parent.add_child(row)
var lbl = Label.new()
lbl.text = "Skin"
lbl.add_theme_font_size_override("font_size", 24)
row.add_child(lbl)
var opt = OptionButton.new()
opt.custom_minimum_size = Vector2(240, 44)
var ids: Array = skin_mgr.get_skin_ids()
ids.sort()
for i in range(ids.size()):
var skin = skin_mgr.get_skin(ids[i])
opt.add_item(skin.skin_name, i)
opt.set_item_metadata(i, ids[i])
if ids[i] == skin_mgr.active_skin_id:
opt.select(i)
opt.item_selected.connect(func(idx: int):
skin_mgr.set_active_skin(opt.get_item_metadata(idx))
)
row.add_child(opt)
func _on_back_pressed() -> void:
_level_selector_panel.hide()
_ui_vbox.show()