cadence census item 2: gyro spring-damper stepped on the pod's 28Hz clock (gotcha 32) -- the cockpit bounce runs the machine's timescale again. The binary steps IntegrateEyeJoint @004b2ec0 / IntegrateBody @004b30ec once per pod frame with PER-TICK math (no-dt position step, damping overwrite carrying last-tick state); at ~60fps the springs stepped 2.14x too often. Fix: 28Hz per-instance accumulator (heat.cpp #119 map pattern), 1/28 slices, remainder carried, catch-up capped 28; integrator BODIES byte-untouched; BT_GYRO_SPRING_HZ override (=0 restores render cadence, proven behaviorally identical to the old code: control run reproduces the BEFORE curve exactly, 0.0471 @ +0.240s / period 1.435s). Impulse bench (new BT_GYRO_KICK one-shot deterministic hit through the authentic GyroApplyDamage fan-out; [gtrace] gains wall-ms): BEFORE eye trough -0.0452 @ +0.24s, overshoot 23%, period ~1.43s, settle +3.44s; AFTER trough -0.0537 @ +0.29s, overshoot 7%, period ~2.4s, settle +3.49s; 97 integrator steps / 3.49s ~ 28Hz -- the clock provably ticks. Shape verdict: both single-overshoot damped responses; every delta is the per-tick semantics at dt=1/28 (per-SECOND damping ~e^c is cadence-invariant, hence equal settle). Nothing beyond the timescale.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bw71WVsccjwW7uKRg4aWD
This commit is contained in:
co-authored by
Claude Fable 5
parent
2c6dc001e3
commit
f811c64590
@@ -53,6 +53,8 @@
|
||||
#endif
|
||||
#include <JOINT.hpp> // Joint, JointSubsystem (fwd shim)
|
||||
#include <ROTATION.hpp> // EulerAngles, Radian (fwd shim)
|
||||
#include <time.h> // clock() -- [gtrace] wall-ms stamps (cadence bench)
|
||||
#include <map> // the 28Hz integrator clock (heat.cpp #119 pattern)
|
||||
#if !defined(APP_HPP)
|
||||
# include <app.hpp>
|
||||
#endif
|
||||
@@ -478,8 +480,51 @@ void
|
||||
if (swayAngle > maxAnimationNoise) swayAngle = maxAnimationNoise;
|
||||
if (swayAngle < minAnimationNoise) swayAngle = minAnimationNoise;
|
||||
|
||||
IntegrateEyeJoint(time_slice); // FUN_004b2ec0
|
||||
IntegrateBody(time_slice); // FUN_004b30ec
|
||||
// (cadence census item 2, gotcha 32) THE INTEGRATOR CLOCK: the binary
|
||||
// steps these two spring-damper integrators once per Perform = once per
|
||||
// pod frame (28 Hz), and the math is PER-TICK (no-dt position step,
|
||||
// damping overwrite carrying last-tick state) -- at the port's ~60 fps
|
||||
// the springs stepped 2.14x as often and the cockpit bounce ran a
|
||||
// foreign timescale. Step them on a 28 Hz accumulator (heat.cpp #119
|
||||
// pattern: per-instance static map -- the factory size-locks the layout,
|
||||
// no new members), passing the pod tick as the time_slice exactly as
|
||||
// the machine's frame did; remainder carries. The integrator BODIES
|
||||
// stay byte-exact (including the no-dt position step and the damping
|
||||
// carry -- per-tick semantics, untouched). Impulses landing between
|
||||
// ticks sit in the force accumulators until the next tick, same as a
|
||||
// between-frame hit on the pod. Catch-up is capped at 28 ticks (1 s);
|
||||
// a longer hitch drops the excess like the heat clock does.
|
||||
// BT_GYRO_SPRING_HZ=<hz> overrides for bracketing (=0 or negative
|
||||
// restores raw render-cadence stepping).
|
||||
{
|
||||
static Scalar s_springHz = -2.0f;
|
||||
if (s_springHz < -1.0f)
|
||||
{
|
||||
const char *hz = getenv("BT_GYRO_SPRING_HZ");
|
||||
s_springHz = (hz != 0 && *hz != '\0') ? (Scalar)atof(hz) : 28.0f;
|
||||
}
|
||||
if (s_springHz > 0.0f)
|
||||
{
|
||||
static std::map<const void *, Scalar> s_springClock;
|
||||
Scalar &clock = s_springClock[this];
|
||||
clock += time_slice;
|
||||
const Scalar kTick = 1.0f / s_springHz;
|
||||
int catchUp = 0;
|
||||
while (clock >= kTick && ++catchUp <= 28)
|
||||
{
|
||||
clock -= kTick;
|
||||
IntegrateEyeJoint(kTick); // FUN_004b2ec0, pod-tick slice
|
||||
IntegrateBody(kTick); // FUN_004b30ec
|
||||
}
|
||||
if (catchUp > 28)
|
||||
clock = 0.0f; // hitch: drop the excess
|
||||
}
|
||||
else
|
||||
{
|
||||
IntegrateEyeJoint(time_slice); // FUN_004b2ec0 (render cadence)
|
||||
IntegrateBody(time_slice); // FUN_004b30ec
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE (task #56, byte-verified): the binary Performance @004b275c ENDS here.
|
||||
// WriteEyeJoint/WriteMechJoint are NOT called from the gyro -- they are called
|
||||
@@ -659,6 +704,8 @@ void
|
||||
+ bodyOrientation.z*bodyOrientation.z;
|
||||
if (m2 > 1e-9f)
|
||||
DEBUG_STREAM << "[gtrace] g=" << (void *)this << " f=" << s_traceFrame
|
||||
<< " t=" << (long)clock() // wall ms (MSVC CLOCKS_PER_SEC=1000) -- the
|
||||
// bounce-timescale bench needs real time, not frames
|
||||
<< " eye=" << (float)eyePosition.x << " " << (float)eyePosition.y
|
||||
<< " " << (float)eyePosition.z
|
||||
<< " body=" << (float)bodyOrientation.x << " " << (float)bodyOrientation.y
|
||||
|
||||
@@ -7886,6 +7886,47 @@ void
|
||||
}
|
||||
}
|
||||
|
||||
// GYRO IMPULSE BENCH (cadence census item 2, night17):
|
||||
// BT_GYRO_KICK=<frame>[,<amt>] injects EXACTLY ONE deterministic hit
|
||||
// into the gyro's authentic damage fan-out (GyroApplyDamage ->
|
||||
// ApplyDamageResponse @004b2980) at the given master-perf frame
|
||||
// (default 900) -- a FIXED damageForce so the direction never rides
|
||||
// the random fallback, ballistic type, amt default 25. Pure gyro
|
||||
// impulse (no zone damage, no armor change): the [gtrace] receipts
|
||||
// then record the eye/body bounce trajectory for the 28-vs-render
|
||||
// spring-timescale comparison. Env-gated, off by default.
|
||||
if ((Entity *)this == application->GetViewpointEntity()
|
||||
&& getenv("BT_GYRO_KICK"))
|
||||
{
|
||||
static int s_gkFrame = 0;
|
||||
static int s_gkDone = 0;
|
||||
++s_gkFrame;
|
||||
int gkAt = 900;
|
||||
float gkAmt = 25.0f;
|
||||
{
|
||||
char spec[64];
|
||||
strncpy(spec, getenv("BT_GYRO_KICK"), sizeof(spec) - 1);
|
||||
spec[sizeof(spec) - 1] = 0;
|
||||
char *comma = strchr(spec, ',');
|
||||
if (comma != 0) { *comma = 0; gkAmt = (float)atof(comma + 1); }
|
||||
if (spec[0] && atoi(spec) > 0) gkAt = atoi(spec);
|
||||
}
|
||||
if (!s_gkDone && s_gkFrame >= gkAt)
|
||||
{
|
||||
s_gkDone = 1;
|
||||
Damage dmg;
|
||||
dmg.damageType = Damage::BallisticDamageType;
|
||||
dmg.damageAmount = gkAmt;
|
||||
dmg.burstCount = 1;
|
||||
dmg.damageForce = Vector3D(1.0f, 0.0f, 0.3f); // fixed => deterministic dir
|
||||
dmg.impactPoint = localOrigin.linearPosition;
|
||||
extern void GyroApplyDamage(Subsystem *, const Damage &);
|
||||
GyroApplyDamage(gyroSubsystem, dmg);
|
||||
DEBUG_STREAM << "[gyro-kick] frame=" << s_gkFrame
|
||||
<< " amt=" << gkAmt << " (one-shot)\n" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
// Gitea #6 scripted verify (BT_VIEWCYCLE_TEST=<frame>): pulse one
|
||||
// secondary-schematic cycle (Damage -> Critical -> Heat -> Damage) at
|
||||
// the given frame and every 300 frames after -- with
|
||||
|
||||
Reference in New Issue
Block a user