clone

an endless marble-race generator in the browser; the url is the seed, so every track can be shared

M11: Marble Blast feel package live (gravity 20, authority, restitution)

Dami committed 8cecd29
On top of the smooth ribbon: gravity -20 (MBHaxe value at our scale),
authority acceleration (6) toward target 3.2, max 9 with curvature-scaled
safe speeds and stronger braking, climb carry +2.6, marble restitution
0.35 / friction 0.7 in the shim. Survivability harness now stops at the
goal (a fast marble outruns the finite 5-segment test world and 'fell'
off the unbuilt horizon - false negative). All gates green; live: 507
units in 3 sim-minutes (~2x the kinematic era), real bounces and falls.
commit
8cecd2949251208b08f594f83811a8da161abd3d
parent
aa81e760df15fdb4c0641e36fc7ff59c24cef5eb
author
Dami <dami.etoile@gmail.com>
3 files changed, +31 -13
Msrc/client/physics/engine.ts20+++++++++++---------
Msrc/client/physics/shim.c4++++
Mtests/physics_test.ts20++++++++++++++++----
M src/client/physics/engine.ts +11 -9
@@ -63,9 +63,9 @@ export interface MarbleState {
6363 }6464 6565 export const MARBLE_RADIUS = 0.2;66-/** Proven M10 value. Marble Blast runs g=20 at our scale — adopt WITH the67- * smooth ribbon + restitution thresholds, not before (docs/physics.md). */68-export const GRAVITY_Y = -10;66+/** Marble Blast's gravity at our scale (MBHaxe: _gravity = 20) — adopted67+ * together with the smooth ribbon, per docs/physics.md. */68+export const GRAVITY_Y = -20;6969 /** Fixed physics clock: 60 Hz × 4 substeps (the spike's validated rate). */7070 export const FIXED_DT = 1 / 60;7171 export const SUBSTEPS = 4;
@@ -100,22 +100,24 @@ export function assistForce(
100100 // global speed raise (4.2/10) launched marbles off curve seams and101101 // crest lips — dynamic speed needs CURVATURE-AWARE braking (the speed102102 // envelope) and banked/taller walls, not a bigger constant.103- const TARGET = 2.6;104- const MAX = 7.5;105- const safe = Math.max(TARGET, MAX - curveAhead * 4.5);103+ const TARGET = 3.2;104+ const MAX = 9;105+ const safe = Math.max(TARGET, MAX - curveAhead * 5.5);106106 if (vAlong > safe) {107- const f = mass * Math.min(6, 2 + (vAlong - safe) * 1.5);107+ const f = mass * Math.min(10, 3 + (vAlong - safe) * 2);108108 return [-tangent[0] * f, -tangent[1] * f, -tangent[2] * f];109109 }110110 const climbing = tangent[1] > 0.12 || climbTy > 0.12;111111 if (climbing && vAlong < TARGET) {112112 // Cancel the along-slope gravity component and push a little extra.113- const a = -GRAVITY_Y * Math.max(tangent[1], climbTy) + 2.2;113+ const a = -GRAVITY_Y * Math.max(tangent[1], climbTy) + 2.6;114114 const f = mass * a;115115 return [tangent[0] * f, tangent[1] * f, tangent[2] * f];116116 }117117 if (vAlong < TARGET) {118- const f = mass * 3.2;118+ // Marble Blast responsiveness = authority (their angular accel ≈ 15119+ // linear); scaled to our slalom widths.120+ const f = mass * 6;119121 return [tangent[0] * f, tangent[1] * f, tangent[2] * f];120122 }121123 return null;
M src/client/physics/shim.c +4 -0
M tests/physics_test.ts +16 -4
@@ -76,8 +76,13 @@ interface RunResult {
7676 hash: bigint;7777 }7878 79-function drive(seed: string, segments: number, simSeconds: number): RunResult {80- return driveWith(seed, segments, simSeconds);79+function drive(80+ seed: string,81+ segments: number,82+ simSeconds: number,83+ stopAtS = Infinity,84+): RunResult {85+ return driveWith(seed, segments, simSeconds, stopAtS);8186 }8287 8388 let module_: PhysicsModule | null = null;
@@ -86,6 +91,7 @@ function driveWith(
8691 seed: string,8792 segments: number,8893 simSeconds: number,94+ stopAtS = Infinity,8995 ): RunResult {9096 const w = new EndlessWorld(seed, catalog);9197 const engine = new PhysicsEngine(module_!);
@@ -123,7 +129,11 @@ function driveWith(
123129 124130 const totalSteps = Math.round(simSeconds * 60);125131 let st: MarbleState = engine.state();126- for (let step = 0; step < totalSteps && !fell; step++) {132+ for (133+ let step = 0;134+ step < totalSteps && !fell && reachedS < stopAtS;135+ step++136+ ) {127137 engine.step(1 / 60, () => {128138 // Assist toward the local tangent when slow (per fixed step).129139 const p = st.pos;
@@ -177,7 +187,9 @@ Deno.test("physics survivability: assisted marble clears early segments", async
177187 for (const seed of SEEDS) {178188 const w = new EndlessWorld(seed, catalog);179189 const goal = w.get(3).startS; // past the end of segment 2180- const r = drive(seed, 5, 90);190+ // Stop at the goal — a fast marble outruns the finite test world and191+ // "falls" off the unbuilt horizon otherwise.192+ const r = drive(seed, 5, 90, goal);181193 results.push({ seed, ok: !r.fell && r.reachedS >= goal, r, goal });182194 }183195 const failures = results.filter((x) => !x.ok);