Fix three arcade physics defects the unit tests could not see
tests/physics_sim.c runs the arcade backend the way a game does -- a Mario-esque jump and fall, Zelda-style top-down walking, and a run reversed at full speed -- and prints what the actor actually did. tests/physics.c already checked that akgl_physics_simulate does the arithmetic it claims. Every one of these got past it. The first step was however long the level took to load. gravity_time was never initialized and dt was unbounded, so a 250 ms load produced a 250 ms step: 100 px of fall where a 60 Hz frame under the same gravity is 0.44 px, straight through whatever was underneath. Both initializers seed the clock, and simulate bounds dt to max_timestep -- a new physics.max_timestep property, default 0.05 s, read like gravity and drag. Zero disables the bound. The field replaces the dead timer_gravity, so the struct is the same size. Releasing a vertical key cancelled gravity. The _off handlers zeroed ay, ey, ty and vy together, and ey is where the arcade backend accumulates gravity -- tapping down mid-jump stopped the character in the air. They clear ax/tx (or ay/ty) and nothing else now. Velocity was never theirs to clear either: simulate recomputes v as e + t every step. Diagonal movement was 41% too fast. Thrust was capped per axis, so an actor holding two directions got both caps at once and travelled their diagonal. It is capped as a vector against the sx/sy/sz ellipse, which also keeps a character whose horizontal and vertical top speeds differ moving at the ratio it asked for. An axis with a zero max speed stays out of the magnitude and is forced to zero, as the old clamp did. Each fix was checked by reverting it and confirming the simulation goes red: 100.1 px, vy 0.0 after a down tap, and 141% diagonal respectively. tests/actor.c and tests/physics.c pinned the old behaviour in both places and now assert the new contract. Bumped to 0.6.0: akgl_PhysicsBackend changed. TODO.md records the four things the simulations found and this does not fix -- no terminal velocity, no deceleration on release, Euler's frame-rate dependence, and a drag coefficient large enough to invert velocity. 26/26 ctest, memcheck clean, warning-clean at -Wall -Werror. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B8T5FAYXE8HEJqFLCYwNNc
This commit is contained in:
@@ -531,20 +531,69 @@ akerr_ErrorContext *test_physics_simulate_thrust_and_clamp(void)
|
||||
CATCH(e, akgl_physics_init_null(&backend));
|
||||
CATCH(e, make_sim_actor(&actor, &basechar, "thruster"));
|
||||
|
||||
// Thrust beyond the actor's max speed clamps to +sx, preserving sign.
|
||||
// Over-thrust on one axis alone clamps to that axis's max speed,
|
||||
// preserving sign. This is the case the old per-axis clamp got right,
|
||||
// and it has to keep working.
|
||||
actor->sx = 50.0f; actor->sy = 40.0f; actor->sz = 30.0f;
|
||||
actor->tx = 500.0f; actor->ty = 400.0f; actor->tz = 300.0f;
|
||||
TEST_EXPECT_OK(e, akgl_physics_simulate(&backend, NULL), "simulate with positive over-thrust");
|
||||
actor->tx = 500.0f; actor->ty = 0.0f; actor->tz = 0.0f;
|
||||
TEST_EXPECT_OK(e, akgl_physics_simulate(&backend, NULL), "simulate with single-axis over-thrust");
|
||||
TEST_ASSERT_FEQ(e, actor->tx, 50.0f, "tx clamped to %f, expected 50", actor->tx);
|
||||
TEST_ASSERT_FEQ(e, actor->ty, 40.0f, "ty clamped to %f, expected 40", actor->ty);
|
||||
|
||||
actor->tx = -500.0f; actor->ty = 0.0f; actor->tz = 0.0f;
|
||||
TEST_EXPECT_OK(e, akgl_physics_simulate(&backend, NULL), "simulate with negative single-axis over-thrust");
|
||||
TEST_ASSERT_FEQ(e, actor->tx, -50.0f, "tx clamped to %f, expected -50", actor->tx);
|
||||
|
||||
actor->tx = 0.0f; actor->ty = -400.0f; actor->tz = 0.0f;
|
||||
TEST_EXPECT_OK(e, akgl_physics_simulate(&backend, NULL), "simulate with negative y over-thrust");
|
||||
TEST_ASSERT_FEQ(e, actor->ty, -40.0f, "ty clamped to %f, expected -40", actor->ty);
|
||||
|
||||
actor->tx = 0.0f; actor->ty = 0.0f; actor->tz = 300.0f;
|
||||
TEST_EXPECT_OK(e, akgl_physics_simulate(&backend, NULL), "simulate with z over-thrust");
|
||||
TEST_ASSERT_FEQ(e, actor->tz, 30.0f, "tz clamped to %f, expected 30", actor->tz);
|
||||
|
||||
// Negative over-thrust clamps to -sx.
|
||||
// Over-thrust on several axes at once scales the *vector* back to the
|
||||
// ellipse the three max speeds describe, rather than clamping each axis
|
||||
// against its own. Clamping the axes independently let the corner of the
|
||||
// box through: an actor holding two directions travelled their diagonal,
|
||||
// 41% faster than either alone, which is the classic diagonal-run bug.
|
||||
// tests/physics_sim.c measures it (`top-down diagonal speed`).
|
||||
//
|
||||
// The two things that makes true are asserted here rather than the three
|
||||
// numbers that fall out of them: the direction asked for is preserved,
|
||||
// so every axis is scaled by the same factor, and the result lands
|
||||
// exactly on the ellipse.
|
||||
actor->tx = 500.0f; actor->ty = 400.0f; actor->tz = 300.0f;
|
||||
TEST_EXPECT_OK(e, akgl_physics_simulate(&backend, NULL), "simulate with over-thrust on every axis");
|
||||
TEST_ASSERT_FEQ(e, actor->tx / actor->sx, actor->ty / actor->sy,
|
||||
"x and y were scaled differently (%f vs %f), so the direction moved",
|
||||
actor->tx / actor->sx, actor->ty / actor->sy);
|
||||
TEST_ASSERT_FEQ(e, actor->ty / actor->sy, actor->tz / actor->sz,
|
||||
"y and z were scaled differently (%f vs %f), so the direction moved",
|
||||
actor->ty / actor->sy, actor->tz / actor->sz);
|
||||
TEST_ASSERT_FEQ(e,
|
||||
((actor->tx / actor->sx) * (actor->tx / actor->sx))
|
||||
+ ((actor->ty / actor->sy) * (actor->ty / actor->sy))
|
||||
+ ((actor->tz / actor->sz) * (actor->tz / actor->sz)),
|
||||
1.0f,
|
||||
"capped thrust (%f, %f, %f) is not on the max-speed ellipse",
|
||||
actor->tx, actor->ty, actor->tz);
|
||||
|
||||
// Signs survive the scaling.
|
||||
actor->tx = -500.0f; actor->ty = -400.0f; actor->tz = -300.0f;
|
||||
TEST_EXPECT_OK(e, akgl_physics_simulate(&backend, NULL), "simulate with negative over-thrust");
|
||||
TEST_ASSERT_FEQ(e, actor->tx, -50.0f, "tx clamped to %f, expected -50", actor->tx);
|
||||
TEST_ASSERT_FEQ(e, actor->ty, -40.0f, "ty clamped to %f, expected -40", actor->ty);
|
||||
TEST_ASSERT_FEQ(e, actor->tz, -30.0f, "tz clamped to %f, expected -30", actor->tz);
|
||||
TEST_EXPECT_OK(e, akgl_physics_simulate(&backend, NULL), "simulate with negative over-thrust on every axis");
|
||||
TEST_ASSERT(e, (actor->tx < 0.0f) && (actor->ty < 0.0f) && (actor->tz < 0.0f),
|
||||
"capping negative thrust changed a sign: (%f, %f, %f)",
|
||||
actor->tx, actor->ty, actor->tz);
|
||||
|
||||
// An axis whose max speed is zero cannot be thrust along at all, and it
|
||||
// stays out of the magnitude -- dividing by it would not end well.
|
||||
actor->sz = 0.0f;
|
||||
actor->tx = 10.0f; actor->ty = 0.0f; actor->tz = 300.0f;
|
||||
TEST_EXPECT_OK(e, akgl_physics_simulate(&backend, NULL), "simulate with a zero max speed");
|
||||
TEST_ASSERT_FEQ(e, actor->tz, 0.0f, "thrust survived a zero max speed as %f", actor->tz);
|
||||
TEST_ASSERT_FEQ(e, actor->tx, 10.0f,
|
||||
"a zero max speed on z disturbed in-range x thrust (%f)", actor->tx);
|
||||
actor->sz = 30.0f;
|
||||
|
||||
// Thrust inside the limit is left alone.
|
||||
actor->tx = 10.0f; actor->ty = -10.0f; actor->tz = 5.0f;
|
||||
|
||||
Reference in New Issue
Block a user