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,
|
dat = EMU8K_READ_INTERP_CUBIC(emu8k, emu_voice->addr.int_address,
|
||||||
emu_voice->addr.fract_address);
|
emu_voice->addr.fract_address);
|
||||||
#endif
|
#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 */
|
/* Filter section */
|
||||||
if (emu_voice->filterq_idx || emu_voice->cvcf_curr_filt_ctoff != 0xFFFF) {
|
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;
|
unsigned geom_node; size_t geom_need, geom_stride; bool geom_active;
|
||||||
std::vector<float> geom_acc;
|
std::vector<float> geom_acc;
|
||||||
unsigned conn_node, conn_npolys, conn_loop; bool conn_active;
|
unsigned conn_node, conn_npolys, conn_loop; bool conn_active;
|
||||||
|
std::vector<int> conn_acc; /* whole-batch index staging */
|
||||||
/* action-26 texel upload assembly */
|
/* action-26 texel upload assembly */
|
||||||
unsigned tex_node; size_t tex_need; bool tex_active;
|
unsigned tex_node; size_t tex_need; bool tex_active;
|
||||||
std::vector<unsigned char> tex_acc;
|
std::vector<unsigned char> tex_acc;
|
||||||
@@ -1914,15 +1915,29 @@ static void scene_burst(const unsigned char *p, size_t n) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (S.conn_active && action == 25) {
|
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;
|
size_t nw = nb / 4;
|
||||||
for (size_t o = 0; o + S.conn_loop <= nw; o += S.conn_loop) {
|
for (size_t o = 0; o < nw; o++)
|
||||||
std::vector<int> loop;
|
S.conn_acc.push_back((int)rd_u32(d + o * 4));
|
||||||
for (unsigned j = 0; j + 1 < S.conn_loop; j++) /* drop closing dup */
|
if (S.conn_acc.size() >= (size_t)S.conn_npolys * S.conn_loop) {
|
||||||
loop.push_back((int)rd_u32(d + (o + j) * 4));
|
std::vector<std::vector<int> > &pl = S.polys[S.conn_node];
|
||||||
pl.push_back(loop);
|
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;
|
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_loop = rd_u32(d + 8);
|
||||||
S.conn_active = (S.conn_npolys > 0 && S.conn_loop >= 2 &&
|
S.conn_active = (S.conn_npolys > 0 && S.conn_loop >= 2 &&
|
||||||
S.conn_loop <= 16);
|
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;
|
break;
|
||||||
case 26: /* texel upload header [node][nbytes][w][h]... (4/8/16bpp) */
|
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);
|
S.inst_w3.find(inst);
|
||||||
if (wi != S.inst_w3.end() && wi->second != 2 && wi->second != 3)
|
if (wi != S.inst_w3.end() && wi->second != 2 && wi->second != 3)
|
||||||
continue;
|
continue;
|
||||||
/* Never pick the player's OWN articulation subtree (arms, torso,
|
/* Own-mech exclusion is by MINIMUM RANGE, not ancestry: walking
|
||||||
* muzzle flash, searchlight): the ray grazes own geometry from
|
* S.dcs_parent (dcs_link edges) falsely flagged the FLOOR and
|
||||||
* the over-the-shoulder eye and a mid-volley pick of the muzzle
|
* SKY as the player's subtree (links also chain sibling scene
|
||||||
* flash retargets the salvo ("missiles hare off sometimes").
|
* DCSs in this dialect) -- ground/sky aims went no-hit and every
|
||||||
* Root DCS comes from the bridge's CAM line, 7th field. */
|
* such trigger boo-beeped. The player's arms and muzzle flash
|
||||||
unsigned own_root = bridge_cam_root();
|
* all live within ~12 of the eye; everything legitimately
|
||||||
if (own_root) {
|
* targetable is farther. Sky/ground are REAL pickable entities
|
||||||
unsigned a = di->first;
|
* (sky.mod, afloor): firing into them fires -- and misses --
|
||||||
bool own = false;
|
* exactly like the period pods. */
|
||||||
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;
|
|
||||||
}
|
|
||||||
std::map<unsigned, unsigned>::const_iterator oi =
|
std::map<unsigned, unsigned>::const_iterator oi =
|
||||||
S.inst_object.find(inst);
|
S.inst_object.find(inst);
|
||||||
if (oi == S.inst_object.end()) continue;
|
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[o1], w1);
|
||||||
m16_xform(world, &vv[o2], w2);
|
m16_xform(world, &vv[o2], w2);
|
||||||
float t = ray_tri(O, D, w0, w1, 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_t = t; best_inst = inst;
|
||||||
best_dcs = di->first; best_gg = gg; best_geo = geo;
|
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.dcs_parent.clear(); S.lights.clear();
|
||||||
S.view = VFrame();
|
S.view = VFrame();
|
||||||
S.geom_active = false; S.conn_active = false;
|
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) {
|
static void vpx_render_start(void) {
|
||||||
|
|||||||
Reference in New Issue
Block a user