Piece families flow gen->client: EndlessSegment.pieceFamilies (aligned
with path.segments) -> rail stations carry {speed, grav} mods -> the
assist law applies them (speed scales target/safe speeds and force,
grav adds extra pull while on the tile). First rules: helix rides 1.35x
(banked spiral), slant/ramp chutes pull 1.2x/1.15x gravity - the momentum
that carries the next climb. This is the one data channel that later
carries icy/boost/bouncer surfaces, planner-placed powerup pads, and
island gravity rotation. All gates green; live behavior verified.- commit
- 42fdd2f367e25e65595c0457158aa60ef328d142
- parent
- 452fc491dea2936aac856a03738f7377b7849a64
- author
- Dami <dami.etoile@gmail.com>
| M | src/ | 44 | +++++++++++++++++++++++++++--- |
| M | src/ | 5 | +++ |
@@ -35,6 +35,20 @@ import type { DecorCatalog } from "../gen/decor.ts";
3535 import { genVersion } from "../gen/version.ts";3636 import { installCua } from "./cua.ts";3737 38+/**39+ * Per-tile physics modifiers (M11, Marble Blast-inspired): piece families40+ * carry movement character. `speed` scales the assist's target/safe41+ * speeds (helix = banked spiral, ride it faster); `grav` scales gravity42+ * while on the tile (chute families pull harder — the momentum that43+ * carries the next climb). The same channel will carry icy/boost/bouncer44+ * surfaces and island gravity rotation later.45+ */46+const TILE_MODS: Record<string, { speed?: number; grav?: number }> = {47+ helix: { speed: 1.35 },48+ slant: { grav: 1.2 },49+ ramp: { grav: 1.15 },50+};51+3852 /** Segments built ahead of / kept behind the marble's segment. */3953 const AHEAD = 3;4054 const BEHIND = 2;@@ -208,7 +222,12 @@ class Session {
208222 private engine: PhysicsEngine | null = null;209223 private readonly rails = new Map<210224 number,211- { pts: number[][]; tans: number[][]; s: number[] }225+ {226+ pts: number[][];227+ tans: number[][];228+ s: number[];229+ mods: ({ speed?: number; grav?: number } | null)[];230+ }212231 >();213232 private nearest: { seg: number; idx: number } = { seg: 0, idx: 0 };214233 deaths = 0;@@ -372,9 +391,11 @@ class Session {
372391 pts: [] as number[][],373392 tans: [] as number[][],374393 s: [] as number[],394+ mods: [] as ({ speed?: number; grav?: number } | null)[],375395 };376396 let acc = 0;377- for (const ls of data.path.segments) {397+ for (const [lsIdx, ls] of data.path.segments.entries()) {398+ const mod = TILE_MODS[data.pieceFamilies[lsIdx]] ?? null;378399 for (const part of ls.curve) {379400 const plen = segLength(part);380401 const steps = Math.max(1, Math.ceil(plen / 0.5));@@ -386,6 +407,7 @@ class Session {
386407 rail.pts.push([a[0], a[1], a[2]]);387408 rail.tans.push([d[0] / l, d[1] / l, d[2] / l]);388409 rail.s.push(acc);410+ rail.mods.push(mod);389411 acc += l;390412 }391413 }@@ -563,8 +585,22 @@ class Session {
563585 );564586 curveAhead += Math.acos(Math.max(-1, dot));565587 }566- const f = assistForce(st.vel, t, engine.mass, climbTy, curveAhead);567- if (f) engine.force(f[0], f[1], f[2]);588+ const mod = rail.mods[this.nearest.idx];589+ const f = assistForce(590+ st.vel,591+ t,592+ engine.mass,593+ climbTy,594+ curveAhead / (mod?.speed ?? 1),595+ );596+ if (f) {597+ const sc = mod?.speed ?? 1;598+ engine.force(f[0] * sc, f[1] * sc, f[2] * sc);599+ }600+ if (mod?.grav && mod.grav !== 1) {601+ // Extra gravity while on the tile (chutes pull harder).602+ engine.force(0, (mod.grav - 1) * -20 * engine.mass * -1, 0);603+ }568604 });569605 const st = engine.state();570606 this.marbleObj.position.set(st.pos[0], st.pos[1], st.pos[2]);@@ -41,6 +41,10 @@ export interface EndlessSegment {
4141 index: number;4242 /** Planning data in TRACK CELLS (the generator's abstract grid). */4343 plan: SegmentPlan;44+ /** Piece family per path segment (aligned with path.segments) — the45+ * per-tile physics modifier channel (M11): the client maps families to46+ * speed/gravity behavior at the rail-station level. */47+ pieceFamilies: string[];4448 /** Track placements in WORLD coordinates (×TRACK_SCALE, meshes scaled —4549 * the slalom rule; islands/decor stay 1× on the world grid). */4650 placements: Placement[];@@ -307,6 +311,7 @@ export class EndlessWorld {
307311 this.segments.push({308312 index: i,309313 plan,314+ pieceFamilies: windows.map((w) => w.def.family),310315 placements,311316 path,312317 islands,