Pick geometry completeness + the audio crackle root cause
Device raycast/scene parser (vpxlog.cpp): - Connection lists: accumulate the WHOLE batch before slicing (records straddling burst boundaries were dropped + misaligned the rest), do not wipe polys per batch header (floor ships tri batch + quad batch; only the last survived), keep every loop index (closing-dup drop halved each quad). Together these were the "holey ground" the range-finder fell through. - Own-mech exclusion by minimum pick range (15) instead of the dcs_parent ancestry walk: sibling link edges made the FLOOR and SKY read as the player's subtree, so ground/sky aims never resolved. Measured: own barrels/flash live within ~8 of the eye; sky/ground are real pickable entities (sky.mod, afloor) -- firing into them fires and misses, per the operator's arcade experience. EMU8000 core (emu8k.cpp): clamp the oscillator output after cubic interpolation. Crest overshoot on UNFILTERED voices overflowed int32 in the volume multiply and wrapped the crest negative for 1-3 samples -- the speech/effects crackle (recorded signature: +0.7 flipping to -0.7 at crests, random phase vs the chunk grid). Filter paths already clamped; the bypass path now matches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1796,6 +1796,17 @@ emu8k_update(emu8k_t *emu8k)
|
||||
dat = EMU8K_READ_INTERP_CUBIC(emu8k, emu_voice->addr.int_address,
|
||||
emu_voice->addr.fract_address);
|
||||
#endif
|
||||
/* Cubic interpolation overshoots past int16 on wave crests;
|
||||
* UNFILTERED voices then overflow int32 in the volume
|
||||
* multiply below (~39000 * 65535 > INT32_MAX) and the crest
|
||||
* wraps NEGATIVE for 1-3 samples -- the audible "crackle"
|
||||
* on loud speech/effects (recorded signature: +0.7 flipping
|
||||
* to -0.7 exactly at crests). The filter paths clamp their
|
||||
* output already; clamp the oscillator the same way. */
|
||||
if (dat > 32767)
|
||||
dat = 32767;
|
||||
else if (dat < -32768)
|
||||
dat = -32768;
|
||||
|
||||
/* Filter section */
|
||||
if (emu_voice->filterq_idx || emu_voice->cvcf_curr_filt_ctoff != 0xFFFF) {
|
||||
|
||||
@@ -808,6 +808,7 @@ static struct VScene {
|
||||
unsigned geom_node; size_t geom_need, geom_stride; bool geom_active;
|
||||
std::vector<float> geom_acc;
|
||||
unsigned conn_node, conn_npolys, conn_loop; bool conn_active;
|
||||
std::vector<int> conn_acc; /* whole-batch index staging */
|
||||
/* action-26 texel upload assembly */
|
||||
unsigned tex_node; size_t tex_need; bool tex_active;
|
||||
std::vector<unsigned char> tex_acc;
|
||||
@@ -1914,15 +1915,29 @@ static void scene_burst(const unsigned char *p, size_t n) {
|
||||
return;
|
||||
}
|
||||
if (S.conn_active && action == 25) {
|
||||
std::vector<std::vector<int> > &pl = S.polys[S.conn_node];
|
||||
/* Accumulate the whole batch FIRST, then slice (mirrors the vertex
|
||||
* path and Dave's proven parser). The old per-message slicing broke
|
||||
* two ways on large geometries: a record straddling a burst boundary
|
||||
* lost its tail AND misaligned every record after it, and a SECOND
|
||||
* conn batch on the same geometry (floor = mixed tri+quad batches)
|
||||
* terminated instantly against the total-poly count and dropped its
|
||||
* indices -- the "holey ground" the reticle fell through. Also keep
|
||||
* ALL loop indices (the old closing-dup drop halved every quad);
|
||||
* genuinely closed loops just add a zero-area fan triangle. */
|
||||
size_t nw = nb / 4;
|
||||
for (size_t o = 0; o + S.conn_loop <= nw; o += S.conn_loop) {
|
||||
std::vector<int> loop;
|
||||
for (unsigned j = 0; j + 1 < S.conn_loop; j++) /* drop closing dup */
|
||||
loop.push_back((int)rd_u32(d + (o + j) * 4));
|
||||
pl.push_back(loop);
|
||||
for (size_t o = 0; o < nw; o++)
|
||||
S.conn_acc.push_back((int)rd_u32(d + o * 4));
|
||||
if (S.conn_acc.size() >= (size_t)S.conn_npolys * S.conn_loop) {
|
||||
std::vector<std::vector<int> > &pl = S.polys[S.conn_node];
|
||||
for (size_t o = 0; o + S.conn_loop <= S.conn_acc.size();
|
||||
o += S.conn_loop) {
|
||||
pl.push_back(std::vector<int>(
|
||||
S.conn_acc.begin() + o,
|
||||
S.conn_acc.begin() + o + S.conn_loop));
|
||||
}
|
||||
S.conn_acc.clear();
|
||||
S.conn_active = false;
|
||||
}
|
||||
if (pl.size() >= S.conn_npolys) S.conn_active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2134,7 +2149,13 @@ static void scene_burst(const unsigned char *p, size_t n) {
|
||||
S.conn_loop = rd_u32(d + 8);
|
||||
S.conn_active = (S.conn_npolys > 0 && S.conn_loop >= 2 &&
|
||||
S.conn_loop <= 16);
|
||||
S.polys[S.conn_node].clear();
|
||||
S.conn_acc.clear();
|
||||
/* do NOT clear S.polys here: geometries ship MULTIPLE conn
|
||||
* batches (floor/walls = a triangle batch + a quad batch);
|
||||
* clearing per header kept only the LAST batch. Fresh
|
||||
* geometry arrives under fresh handles, so appending is
|
||||
* safe; a re-flushed batch at worst duplicates triangles,
|
||||
* which the raycast tolerates. */
|
||||
}
|
||||
break;
|
||||
case 26: /* texel upload header [node][nbytes][w][h]... (4/8/16bpp) */
|
||||
@@ -2278,24 +2299,15 @@ static bool raycast_pick(unsigned *inst_out, unsigned *dcs_out, unsigned *gg_out
|
||||
S.inst_w3.find(inst);
|
||||
if (wi != S.inst_w3.end() && wi->second != 2 && wi->second != 3)
|
||||
continue;
|
||||
/* Never pick the player's OWN articulation subtree (arms, torso,
|
||||
* muzzle flash, searchlight): the ray grazes own geometry from
|
||||
* the over-the-shoulder eye and a mid-volley pick of the muzzle
|
||||
* flash retargets the salvo ("missiles hare off sometimes").
|
||||
* Root DCS comes from the bridge's CAM line, 7th field. */
|
||||
unsigned own_root = bridge_cam_root();
|
||||
if (own_root) {
|
||||
unsigned a = di->first;
|
||||
bool own = false;
|
||||
for (int hop = 0; hop < 64; hop++) {
|
||||
if (a == own_root) { own = true; break; }
|
||||
std::map<unsigned, unsigned>::const_iterator ppi =
|
||||
S.dcs_parent.find(a);
|
||||
if (ppi == S.dcs_parent.end() || ppi->second == a) break;
|
||||
a = ppi->second;
|
||||
}
|
||||
if (own) continue;
|
||||
}
|
||||
/* Own-mech exclusion is by MINIMUM RANGE, not ancestry: walking
|
||||
* S.dcs_parent (dcs_link edges) falsely flagged the FLOOR and
|
||||
* SKY as the player's subtree (links also chain sibling scene
|
||||
* DCSs in this dialect) -- ground/sky aims went no-hit and every
|
||||
* such trigger boo-beeped. The player's arms and muzzle flash
|
||||
* all live within ~12 of the eye; everything legitimately
|
||||
* targetable is farther. Sky/ground are REAL pickable entities
|
||||
* (sky.mod, afloor): firing into them fires -- and misses --
|
||||
* exactly like the period pods. */
|
||||
std::map<unsigned, unsigned>::const_iterator oi =
|
||||
S.inst_object.find(inst);
|
||||
if (oi == S.inst_object.end()) continue;
|
||||
@@ -2339,7 +2351,7 @@ static bool raycast_pick(unsigned *inst_out, unsigned *dcs_out, unsigned *gg_out
|
||||
m16_xform(world, &vv[o1], w1);
|
||||
m16_xform(world, &vv[o2], w2);
|
||||
float t = ray_tri(O, D, w0, w1, w2);
|
||||
if (t > 0.0f && t < best_t) {
|
||||
if (t > 15.0f && t < best_t) { /* min pick range */
|
||||
best_t = t; best_inst = inst;
|
||||
best_dcs = di->first; best_gg = gg; best_geo = geo;
|
||||
}
|
||||
@@ -2378,7 +2390,7 @@ static void scene_reset(void) {
|
||||
S.dcs_parent.clear(); S.lights.clear();
|
||||
S.view = VFrame();
|
||||
S.geom_active = false; S.conn_active = false;
|
||||
S.geom_acc.clear();
|
||||
S.geom_acc.clear(); S.conn_acc.clear();
|
||||
}
|
||||
|
||||
static void vpx_render_start(void) {
|
||||
|
||||
Reference in New Issue
Block a user