SegmentPlan.mods (planner-placed, cell-indexed zones) -> per-window windowMods -> rail stations: big descents (>=1.0 drop) get a boost zone from two cells before the lip through the chute (assist x1.9 target + extra pull). The spectacle reads as the track hurling the marble, and the exit momentum carries what follows. Zone kinds extend to shock/ propeller/jump next; all gates green.
- commit
- 7f8c43e3f83b01fcf52363801273a832c4c8360e
- parent
- dfc86a3d7ea98d9df2c662cd91362c1bd0ef6046
- author
- Dami <dami.etoile@gmail.com>
| M | src/ | 5 | ++++- |
| M | src/ | 23 | +++++++++++++++++++++++ |
| M | src/ | 10 | ++++++++++ |
@@ -395,7 +395,10 @@ class Session {
395395 };396396 let acc = 0;397397 for (const [lsIdx, ls] of data.path.segments.entries()) {398- const mod = TILE_MODS[data.pieceFamilies[lsIdx]] ?? null;398+ const zone = data.windowMods[lsIdx];399+ const base = TILE_MODS[data.pieceFamilies[lsIdx]] ?? null;400+ // Planner zones outrank family character: a boost-chute hurls.401+ const mod = zone === "boost" ? { speed: 1.9, grav: 1.2 } : base;399402 for (const part of ls.curve) {400403 const plen = segLength(part);401404 const steps = Math.max(1, Math.ceil(plen / 0.5));@@ -105,9 +105,18 @@ export interface IslandCrossing {
105105 margin: number;106106 }107107 108+/** Planner-placed physics zone (cell-indexed): the set-piece channel. */109+export interface PlanMod {110+ start: number;111+ len: number;112+ kind: "boost";113+}114+108115 export interface SegmentPlan {109116 index: number;110117 cells: OpenCell[];118+ /** Movement set-piece zones (M11): e.g. boost runs into big chutes. */119+ mods: PlanMod[];111120 /** Slope/set-piece windows (local indices) — realize commits them as-is. */112121 reservations: Reservation[];113122 crossings: IslandCrossing[];@@ -173,6 +182,7 @@ export function planSegment(
173182 class SegmentBuilder {174183 private cells: OpenCell[] = [];175184 private reservations: Reservation[] = [];185+ private mods: PlanMod[] = [];176186 private crossings: IslandCrossing[] = [];177187 private pose: HeadPose;178188 private sinceAnchor: number;@@ -235,6 +245,7 @@ class SegmentBuilder {
235245 const plan: SegmentPlan = {236246 index: this.index,237247 cells: this.cells,248+ mods: this.mods,238249 reservations: this.reservations,239250 crossings: this.crossings,240251 entry: this.entry,@@ -436,6 +447,18 @@ class SegmentBuilder {
436447 }437448 const start = this.cells.length;438449 if (!this.commitSteps(steps)) return false;450+ // BOOST-CHUTE set-piece: big descents get a boost zone from two cells451+ // ahead of the lip through the chute — the spectacle reads as the452+ // track hurling the marble (and the exit momentum carries what453+ // follows).454+ const totalDrop = parts.reduce((a2, p2) => a2 + p2.delta, 0);455+ if (!climb && totalDrop >= 1.0) {456+ this.mods.push({457+ start: Math.max(0, start - 2),458+ len: start - Math.max(0, start - 2) + option.totalLen,459+ kind: "boost",460+ });461+ }439462 let at = start;440463 for (const part of parts) {441464 this.reservations.push({@@ -45,6 +45,8 @@ export interface EndlessSegment {
4545 * per-tile physics modifier channel (M11): the client maps families to4646 * speed/gravity behavior at the rail-station level. */4747 pieceFamilies: string[];48+ /** Planner set-piece zone kind per path segment (or null). */49+ windowMods: (string | null)[];4850 /** Track placements in WORLD coordinates (×TRACK_SCALE, meshes scaled —4951 * the slalom rule; islands/decor stay 1× on the world grid). */5052 placements: Placement[];@@ -312,6 +314,14 @@ export class EndlessWorld {
312314 index: i,313315 plan,314316 pieceFamilies: windows.map((w) => w.def.family),317+ windowMods: windows.map((w) => {318+ for (const m of plan.mods) {319+ if (w.start < m.start + m.len && m.start < w.start + w.len) {320+ return m.kind;321+ }322+ }323+ return null;324+ }),315325 placements,316326 path,317327 islands,