Root-caused 'falls too fast, hard to predict' to fully elastic restitution (1.0), not gravity -- lowered to 0.1 to match a real reference-game clone
Context
User report: after release, a dropped piece 'travels to the ground much faster' than the real reference game and is 'difficult to predict,' with an explicit instruction to research real solutions rather than guess. Initial hypothesis was that gravity (-98.0 world-units/s², documented in game.gleam as 'chosen for a visually snappy demo... not physical accuracy' -- a leftover comment from before this project became a real merge-puzzle game) was simply too strong. Before changing it, captured a real, instrumented headless-Chromium trace of a single full-height drop (spawn y=85 onto an empty floor) using the established GPUBuffer-capture technique, sampling actual on-screen position over time rather than reasoning from the physics formulas alone. The trace showed the ball reaching the floor at a reasonable t=1.7s (in line with genre norms), but then elastically rebounding to y=19.8 -- a bounce of ~16 world units, 16% of the entire 100-unit play area -- on its very first bounce, then bouncing again, smaller, afterward. This directly matched `collision.gleam`'s and `bounds.gleam`'s existing `restitution`/reflection behavior: for any impact at or above genuine_impact_velocity (15.0, decisions 0027/0028's ramp reference), both used a full, energy-preserving elastic bounce (effectively restitution = 1.0) -- a real, fully-loaded drop from spawn height reaches ~54-80 units/s well before touching down, far above that 15.0 reference, so every real drop bounced at full elastic strength. This, not the raw fall speed, is almost certainly what reads as 'fast and hard to predict': a piece that visibly rebounds a fifth of the play area's height and bounces again is fundamentally unpredictable regardless of how long the initial descent took.
Options considered
Decision
glemy/pe/collision.gleam's `restitution` constant changed from 1.0 to 0.1 (now `pub`, so tests can reference it directly). glemy/pe/bounds.gleam, which previously had no separate restitution factor at all (its reflection ramp implicitly assumed 1.0), gained its own matching `pub const restitution = 0.1` and now multiplies it into `reflected_speed`'s scale factor alongside the existing genuine_impact_velocity ramp. Both modules' doc comments were rewritten to explain the real measured defect (the 16-unit rebound) and the reference-clone justification for 0.1, rather than leaving the old 'elastic at full strength' framing in place. test/glemy/pe/bounds_test.gleam and test/glemy/pe/collision_test.gleam were both comprehensively rewritten (not patched) so every 'genuine impact' test now computes its expected velocity via the same formula the source uses (restitution * min(1.0, v / genuine_impact_velocity)) rather than assuming a clean elastic swap/reflection identity, which no longer holds at any impact speed now that base restitution is 0.1, not 1.0. One test (`unequal_mass_collision_conserves_kinetic_energy_test`) was removed outright rather than patched: it specifically verified energy conservation under a fully elastic collision, a code path that no longer exists in this physics model by design -- momentum conservation (a universal invariant regardless of restitution) is still covered by the adjacent, unaffected `unequal_mass_collision_conserves_momentum_test`.
Verification
146 Erlang / 180 JavaScript gleam test cases passing on both targets (one fewer than before this change, from the deliberate removal described above). Re-ran the same real headless-Chromium full-height-drop trace used to find the original defect: the ball now touches the floor at t=1.70s (unchanged -- confirming the fix targeted the bounce, not the descent) and settles to a stable rest within ~150ms with only a barely-visible <0.2-world-unit wobble, instead of rebounding 16 units and bouncing again. A second real trace (dropping a piece onto an already-built stack near the danger line, exercising entity-entity collision restitution rather than just the wall) also completed without any visible large rebound.
Consequences
Dropped pieces now land close to how the reference game and its real clones feel: a near-dead 'plop' on contact rather than a visible bounce-back, which should directly address the reported 'hard to predict' complaint, since the piece's landing spot is no longer obscured by a large secondary rebound. The raw descent speed/gravity constant was deliberately left unchanged in this decision, since measurement showed it wasn't the demonstrated defect -- game.gleam's gravity doc comment still describes it as chosen for 'a visually snappy demo,' which is now stale framing (this is the shipped game, not a demo) worth revisiting in a future decision if the user's own playtesting after this fix still finds the descent itself too fast; entity.gleam's velocity_damping (0.98/frame) was also identified during this investigation as the dominant factor actually capping fall speed (creating an implicit ~80 units/s terminal velocity well below pure gravity kinematics), which is the more effective lever than gravity itself if further slowing is wanted later.