From 0d0e0602ed89ef6950c27657889a9dcceb0cc697 Mon Sep 17 00:00:00 2001 From: salmonik Date: Mon, 26 Oct 2026 20:33:46 +0100 Subject: Bones: ignore uninitialized/zero bone matrices - validate each SetupBones matrix (finite and non-zero world translation); extra/modded and some vanilla bones (Father Grigori) can be uninitialized and were drawing lines to world (0,0,0) - invalid bones are skipped by the skeleton, hitbox centers and aim points; skeleton children connect to the nearest valid ancestor instead of a dead bone - log the per-model invalid bone count with the first bones diagnostics --- src/bones.cpp | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/bones.cpp b/src/bones.cpp index 1940449..787af1b 100644 --- a/src/bones.cpp +++ b/src/bones.cpp @@ -6,6 +6,7 @@ // Everything here is client-side; no server access is involved. #include "hl2h.h" +#include #include #include #include @@ -95,6 +96,7 @@ struct BoneEntry { const StudioHdr *hdr; int count; bool core[MAX_BONES]; + bool bone_valid[MAX_BONES]; Matrix3x4 mats[MAX_BONES]; }; @@ -138,6 +140,17 @@ static const char *bone_name_raw(const StudioHdr *h, int i) { return n[0] ? n : nullptr; } +// SetupBones can leave extra/modded bones uninitialized; they show up as a +// zero matrix and would draw a line to world (0,0,0). +static bool matrix_ok(const Matrix3x4 &m) { + for (int r = 0; r < 3; r++) + for (int c = 0; c < 4; c++) + if (!std::isfinite(m.m[r][c])) + return false; + float x = m.m[0][3], y = m.m[1][3], z = m.m[2][3]; + return (fabsf(x) + fabsf(y) + fabsf(z)) > 0.01f; +} + static const HitboxSet *hitbox_set0(const StudioHdr *h) { if (!h || h->numhitboxsets <= 0 || h->hitboxsetindex <= 0) return nullptr; @@ -181,7 +194,7 @@ static bool head_like_name(const char *n) { } static bool hitbox_world(const BoneEntry *e, const Hitbox &b, Vector &out) { - if (b.bone < 0 || b.bone >= e->count) + if (b.bone < 0 || b.bone >= e->count || !e->bone_valid[b.bone]) return false; Vector local{(b.bbmin.x + b.bbmax.x) * 0.5f, (b.bbmin.y + b.bbmax.y) * 0.5f, @@ -347,11 +360,18 @@ bool bones_update(int idx, void *ent) { e->hdr = hdr; e->count = hdr->numbones; build_core_mask(e); + int invalid_bones = 0; + for (int i = 0; i < e->count; i++) { + e->bone_valid[i] = matrix_ok(e->mats[i]); + if (!e->bone_valid[i]) + invalid_bones++; + } e->valid = true; e->stamp = now; if (g_logged < 4) { g_logged++; - logf("bones: idx=%d count=%d bone0=%s", idx, e->count, + logf("bones: idx=%d count=%d invalid=%d bone0=%s", idx, e->count, + invalid_bones, bone_name_raw(hdr, 0) ? bone_name_raw(hdr, 0) : "?"); } return true; @@ -385,6 +405,8 @@ bool bone_is_skeleton_core(int idx, int bone) { BoneEntry *e = find_entry(idx); if (!e || !e->valid || bone < 0 || bone >= e->count) return false; + if (!e->bone_valid[bone]) + return false; if (!e->humanoid) return true; // non-humanoid: draw its full bone tree return e->core[bone]; @@ -395,10 +417,13 @@ int bone_nearest_core_parent(int idx, int bone) { if (!e || !e->valid || bone < 0 || bone >= e->count) return -1; int p = bone_at(e->hdr, bone)->parent; - if (!e->humanoid) + if (!e->humanoid) { + while (p >= 0 && p < e->count && !e->bone_valid[p]) + p = bone_at(e->hdr, p)->parent; return (p >= 0 && p < e->count) ? p : -1; + } for (int guard = 0; p >= 0 && p < e->count && guard < 64; guard++) { - if (e->core[p]) + if (e->core[p] && e->bone_valid[p]) return p; p = bone_at(e->hdr, p)->parent; } @@ -412,7 +437,8 @@ bool bones_is_humanoid(int idx) { bool bone_pos(int idx, int bone, Vector &out) { BoneEntry *e = find_entry(idx); - if (!e || !e->valid || bone < 0 || bone >= e->count) + if (!e || !e->valid || bone < 0 || bone >= e->count || + !e->bone_valid[bone]) return false; const Matrix3x4 &m = e->mats[bone]; out = {m.m[0][3], m.m[1][3], m.m[2][3]}; -- cgit v1.2.3