- Third-person camera chain is now HeadPivot > OrbitPivot > ShoulderOffset
(0.55m right, 0.12m up) > SpringArm(2.6m, 14 deg) — the character sits
slightly left of screen centre over-the-shoulder instead of dead centre
behind. Aim is unchanged (still the first-person camera).
- Hold Alt in third person to orbit the camera freely around the character
with the mouse (yaw wraps, pitch clamped -69..+29 deg); the character
keeps facing and aiming where it was. Release Alt and the camera springs
back behind the shoulder.
- debug/orbit_capture.gd: screenshots the OTS framing, a driven orbit, and
the spring-back for visual regression.
Co-Authored-By: Claude Fable 5 <[email protected]>
Third-person player characters now animate every action with readable
intent (all networked, cel style preserved):
- Look-around: upper body follows the owner's camera pitch — distributed
over spine/neck/head so aiming up/down reads on the whole silhouette
(driven by the already-synced HeadPivot rotation on remotes)
- Reload: the baked PistolReload clip plays as a one-shot whenever the
equipped weapon starts reloading (local edge-detect poll -> new synced
action counter replays it on every peer)
- Grenade throw: new Throw clip baked from the library's overhead swing
(Sword_Attack retarget); triggers on throw, synced the same way
- Shot recoil: visible kick on the shooter's model (shoulders snap back,
forearms rise, fast decay) driven by the existing fire-effects RPC —
remote players' shots now look like shots
- Slide: trailing arm braces against the ground for balance on top of the
feet-first pose
- Armed idle confirmed in capture: weapon held at ready, not arms-down
Plumbing: synced_action/synced_action_seq added to all three player
spawners' replication configs; ACTIONS table on the model maps names to
clips + lock times.
Verified: 18 clips resolve (incl Throw), smoke 0 failures, movement
11/11, acoustics PASSED; third-person run + armed idle captures clean.
Co-Authored-By: Claude Fable 5 <[email protected]>
New Acoustics system (globals/acoustics.gd):
- Occlusion: rays from listener to source collect up to 3 occluding bodies;
each surface's material adds transmission loss and lowers the lowpass
cutoff. A gunshot through wood stays warm (-8dB, 1.4kHz), brick muffles
hard (-15dB, 600Hz), concrete harder (-18dB, 450Hz), metal keeps more
highs (-11dB, 1kHz), glass barely muffles (-5dB, 2.6kHz). Applied via
each AudioStreamPlayer3D's attenuation filter.
- Early reflections: loud sounds (gunfire, explosions) probe 8 directions
for nearby walls; each close surface plays a quiet, delayed (path/343ms),
material-filtered copy from the mirror point — shots audibly slap back
off structures so players can triangulate. Sub-30ms echoes are skipped
(they perceptually merge with the direct sound).
- Surface classification: builder-set acoustic_material meta -> node-name
keywords -> metallic-material inference -> concrete default; cached.
Integration:
- AudioManager.play_3d applies occlusion at fire time and re-filters all
playing positional sounds every 0.12s (walking behind a wall mid-sound
audibly muffles it); flight loops opt in via "acoustic_occluded" group
- Remote players' gunshots were entirely SILENT — rpc_play_fire_effects now
plays the right weapon's shot at the shooter's position, occluded and
reflected like everything else
- Remote players' footsteps were also silent — now positional + occluded,
cadence scaled to their speed
- Test level tagged: brick arena walls, wooden platforms/obstacles, metal
wall-run corridor/rails/dash platforms; dust2 sandstone reads as brick
Tests: debug/acoustics_test.gd (12 checks: per-material loss ordering,
multi-wall stacking, reflection delay/filter, name classification) — all
pass; movement 11/11; smoke 0 failures.
Co-Authored-By: Claude Fable 5 <[email protected]>
Audio:
- All ballistic weapons now fire real firearm recordings (The Free Firearm
Sound Library, CC0): AK-47 uses actual AK-47 shots, M4=AR-15, MP7=PPSh,
AWP=Mosin Nagant, DMR=Savage 10, shotgun=Mossberg/Nova, nailgun=Ruger .22,
rocket/mortar layered with real booms. Plasma keeps its energy sound.
- Reload is real lever-action foley instead of interface clicks
- Projectile flight loops localized: unit_size 50->6, max_db 6->0,
max_distance 45 — no more mortar/rocket drone in every player's ear;
loops also got proper loop_end (was a zero-length loop)
- Flight loops lowpassed into soft whooshes instead of sci-fi engine drones
- Menu music converted to OGG with native full-track looping (47s seamless,
replaces the broken manual WAV loop points)
Animations (movement-shooter pass):
- Rebaked miku.glb with 4 new clips from the CC0 library: Grapple (swim
reach — reads as a swing), PistolIdle (armed idle with weapon up),
PistolShoot, PistolReload
- Armed idle: standing with a weapon now uses PistolIdle instead of
arms-at-sides
- Slide pose overhaul: legs kick out front (lead/trail thighs, knees
straighten) — feet-first slide instead of "sitting in a crouch"
- Wall run: forward drive pitch + inner arm reaches out to the wall
- Per-transition blend times: dash/hit/jump cut fast (0.05-0.08s),
locomotion cross-fades smoothly (0.2-0.25s); CrouchWalk speed-scales
Fixes:
- Third-person camera no longer renders the first-person viewmodel layer
(the gun/arms floated in front of the third-person view)
Verified: 11/11 movement tests, smoke test 0 failures (17 clips resolved),
third-person run screenshot shows grounded animated model, no floating gun.
Co-Authored-By: Claude Fable 5 <[email protected]>
Audio (all CC0 — Kenney packs + OpenGameArt, see assets/sounds/SOURCES.md):
- Replace every procedural synth WAV with forged sounds (ffmpeg pitch/layer
mixes): distinct fire sounds per weapon with 2-3 variations, explosions,
footsteps x5, land/jump/dash/vault, knife swing, hit/kill confirms,
bullet impacts, reload, flight loops, UI hover/click/confirm/error,
match jingles, menu music
- AudioManager.stream_for(): AudioStreamRandomizer per sound id — every
weapon now gets variation + pitch randomization on each shot
- Explosions and bullet impacts play positional audio; landing has its own
sound instead of a pitched footstep; ambient wind bed on every map
Visuals:
- ExplosionVFX: shared cel-shaded burst (white-hot stepped core, ink
shockwave ring, star spikes, flat smoke puffs) replaces the orange
sphere in both local and remote-replay paths
- Comic star muzzle flashes on all weapons; unified cel tracer bolts with
ink outlines across hitscan/shotgun/remote paths
- Impact decals: hard-stepped ink-splat gradients instead of soft airbrush
- Toon shading on first-person view models + arms, third-person weapons,
and the procedural humanoid fallback (no hull outlines on FBX weapons —
their hard normals tear the inverted hull)
- Fix: giant soft "blob" highlight on floors — toon rim/specular disabled
on level-geometry materials (pre-existing artifact since the cel commit)
- Fix: own third-person weapon rendered into the first-person camera
(shadows-only now covers first_person_mode)
UI:
- UITheme: comic theme on the root window — Bangers display font (OFL),
paper panels with thick ink borders + hard drop shadows, papaya accent,
themed buttons/inputs/popups; hover/click sounds on all menu buttons
- Main menu: tilted comic wordmark, sunset toon diorama, looping menu music
- Match HUD: themed scoreboard/killfeed/kill counter
Viewports:
- 4x MSAA project-wide + viewmodel/diorama viewports (crisp ink lines)
- Viewmodel camera near plane 0.01; third-person camera FOV syncs settings
- debug/visual_capture.gd: dev tool to screenshot menu + level for review
Verified: 11/11 movement tests, spawn smoke test 0 failures, before/after
screenshot comparison of menu and level.
Co-Authored-By: Claude Fable 5 <[email protected]>
- SkinnedPlayerModel: generic play_oneshot() (generalizes the Land lock),
Hit and Dance in the clip table (Dance loops), restartable clips
- Hit flinch: victims visibly react on every peer's screen via the existing
damage broadcast — no new RPC
- Dance emote on B: toggles while grounded and idle, breaks on any movement;
synced to remotes via synced_is_dancing (all three spawner configs)
- Anime radial speed-lines overlay (canvas shader): fades in past ~1.2x walk
speed, spikes on dash, widescreen-corrected clear center
- New input action 'emote' (B) in project.godot
11/11 FSM tests, spawn smoke 0 failures.
Co-Authored-By: Claude Fable 5 <[email protected]>
- Air: direct steering bends existing velocity toward input without changing
its magnitude (air_steer_rate), on top of Quake accelerate with raised
gains (air_strafe_accel 55->90, per-tick cap 1.2->2.5). No-input air drag
nearly removed so held momentum carries. Steering skips opposing input to
avoid flipping through zero.
- Dash: charge system on the machine — dash_charges (2) spend instantly,
each recharges in dash_cooldown (2 s). get_dash_cooldown_remaining() now
reports time to the NEXT charge (0 while one is banked) so existing HUDs
keep working; get_dash_charges() added for pips.
- Tests: dash tests updated to the charge API + new coverage for
spend-2-then-recharge; 11/11 pass, spawn smoke clean.
Co-Authored-By: Claude Fable 5 <[email protected]>
- Generated CC0-style prototype grid textures (0.5 m cells) and a shared
LevelMaterials factory: world-space triplanar mapping over every code-built
level (test level, dust2, procedural arena) with the existing color coding
kept as tints. Readable surfaces at speed instead of flat color boxes;
materials are cached per tint so identical surfaces batch.
- .gdignore + .gitignore the raw downloaded asset packs in addons/ whose
overlong paths broke Godot import scans and spammed git warnings.
- Replaced the rotted FSM test file (invalid call(t) on Callables, stateless
stub nodes, hung forever without quitting) with a real suite: 10 tests
covering state registration, jump bookkeeping, wall-run momentum
preservation and upward-carry cap, per-player dash cooldown (regression for
the old shared static), dash speed stacking, chain soft cap, landing
events, and slide entry momentum. New headless entrypoint:
godot --headless --path . -s res://movement/tests/run_fsm_tests.gd
Co-Authored-By: Claude Fable 5 <[email protected]>
- Quake-style air-strafe accelerate (real speed gain from strafing; external
speed from dash/rockets never clamped)
- Wall run: keeps entry momentum (incl. upward carry), accelerates along the
wall instead of hard-setting velocity, gravity fades in over the run
- Slide: slope physics (gravity projected along floor accelerates downhill),
flat-ground decel instead of multiplicative friction, entry boost, direct
slide->wallrun and slide->dash transitions
- Dash: cooldown 10s -> 2s, per-player (was a static shared across instances),
momentum fully kept on exit, FOV punch event
- Grapple: taut pendulum with active rope control (W reels in, S pays out),
release keeps swing energy
- Wall climb: carries upward momentum in, jump-off kick, shared vault helper
- Crouch capsule: single owner in the machine, smoothly lerped, ceiling check;
camera eye height follows via crouch factor (no more head snapping)
- Landing: machine emits 'land' events scaled by impact; camera dip + pitched
down thud; footsteps get pitch variation
- Camera rig: event-driven (land dip, dash FOV kick, vault pitch impulse),
slide roll tilt
- Model: Jump vs Fall split by vertical velocity, Land one-shot on heavy
landings, wall-run body lean (synced to remotes via synced_wall_side)
Co-Authored-By: Claude Fable 5 <[email protected]>
Layer a procedural SkeletonModifier3D (ShooterPoseModifier) on top of the base
clip so the character reads like a movement-shooter avatar:
- Directional lean: banks into the movement direction (right/left/back) and
blends smoothly for diagonals, driven by velocity relative to facing.
- Slide: leans the torso back and pitches the head up to look forward, instead
of the base clip's forward-torso/legs-out "spine break".
- Airborne: plays a composed Jump pose (weapon ready) rather than a flailing
fall.
- Weapon hold: the base clip already keeps the arms down (weapon at the hip);
on ADS both arms lift and swing in toward centre-front to aim. ADS is read
from the active weapon and synced (synced_is_ads) so remote players raise
their weapons too.
The controller feeds movement direction + ADS to SkinnedPlayerModel.set_locomotion()
each frame (works for local and remote via synced velocity/rotation). All
rotations are authored in skeleton space (fwd=+Z, up=+Y, right=-X) and converted
per-bone; tuning constants are at the top of ShooterPoseModifier.
Verified by rendering the poses (idle/strafe/back/slide/ads) in a real Godot
viewport. Smoke test 30/30.
Co-Authored-By: Claude Fable 5 <[email protected]>
The first-person head-shrink (SkeletonModifier3D) left the camera looking at
the inside of the neck/torso/hair. Replaced it: the owner's model now renders
shadows-only in first person (clean FPS view, still fully animated and visible
to others and in shadows).
Added a third-person toggle (V / toggle_camera_view) so you can actually see
your own animated model — the easiest way to verify a new skin's animations:
- over-the-shoulder SpringArm3D camera (wall-collision aware) on the local
player; press V to swap, press again to return
- firing still uses the first-person camera, so aim is unchanged
- reveals the owner's model via set_owner_visible() on both SkinnedPlayerModel
and HumanoidModel; hides the first-person weapon viewmodel while in TP
- death forces first person so the toggle can't fight the death cam
Smoke test extended to cover the toggle (camera boom created, becomes current,
round-trips) — 28/28 checks pass for both color and GLB skins.
Co-Authored-By: Claude Fable 5 <[email protected]>
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]>
- Add miku_rigged_animated.glb (3.27MB) with 18-bone skeleton and 6 animations
(Idle, Walk, Run, Jump, Crouch, Death) via Blender auto-rig script
- Add tools/rig_and_animate.py: reusable Blender script for rigging any humanoid
mesh with Mixamo-compatible bone naming
- Update SkinnedPlayerModel: scale_factor, first-person mode, animation state
matching (Idle/Walk/Run/Jump/Crouch/Death)
- Update level_runtime.gd: use skinned model for local player, procedural
humanoid for remote players
- Update skin_manager.gd and test_level_builder.gd to use animated model
- Fix Godot 4.2.1 'is not Type' syntax in 5 weapon files
- Add editor/import_miku_to_tscn.gd for editor-based GLB import
- 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.