diff --git a/src/game.ts b/src/game.ts index 495a5a7..a0ea3d0 100644 --- a/src/game.ts +++ b/src/game.ts @@ -92,6 +92,22 @@ function buildBreakfastRush(world: World, blob: BlobHandle, skin: PaintSkin): Fi splats: 2, count: 6, area: [5, 1.0, 2.5], drift: [0, 0.3, 0], bubbleRadius: 0.6, }) + // ---- ORANGE bubbles on the high shelf, just before the gap-jump lip. + // BOUNCE (jumpMul) is the buff, and this is the one spot in the game where + // jump height IS the problem — pop a couple and you clear the gap comfortably + // instead of relying on the recovery trampoline. Deliberately opt-in and + // deliberately costly: they're sitting in the GREEN grip zone, so grabbing + // orange dilutes your green. Route for the jump or route for the grip. ---- + // impulse 1, not the default 3: at full strength the pops stacked and launched + // the blob 3.3u above the shelf right at the gap lip, which throws off a jump + // this course is finely tuned around. Here the bubbles are a paint pickup with + // a nudge — the BOUNCE buff is the payoff, not the boop. + createPaintBubbles(world, { + id: 'bubbles-gap', position: [0, 4.4, 5], color: 'orange', radius: 0.6, + splats: 2, count: 5, area: [3.5, 0.7, 2], drift: [0, 0.25, 0], + bubbleRadius: 0.55, impulse: 1, + }) + // ---- paint-mines: first-through floor panels on the centre line ---- // REWARD: roll over it and it launches you forward + coats you GREEN (GRIP) — // a shortcut booster for the runner who takes the middle. HAZARD: further down, diff --git a/src/paint/coverage-math.test.ts b/src/paint/coverage-math.test.ts index ac89e12..3fc0b26 100644 --- a/src/paint/coverage-math.test.ts +++ b/src/paint/coverage-math.test.ts @@ -9,6 +9,7 @@ import type { CoverageReport, PaintColor } from '../contracts' import { ACTIVATE, SUPER, + BOUNCE_JUMP, MEGA_SIZE, MINI_SIZE, MEGA_MASS, @@ -88,6 +89,7 @@ near(buffStrength(0.45), 0.15 + 0.85 * 0.5, 'strength ramps linearly at midpoint near(clean.massMul, 1, 'clean massMul 1') near(clean.glow, 0, 'clean no glow') near(clean.size, 1, 'clean size 1 (no grow/shrink in MVP)') + near(clean.jumpMul, 1, 'clean jumpMul 1') } { // RED just under activation: no buff yet @@ -113,6 +115,21 @@ near(buffStrength(0.45), 0.15 + 0.85 * 0.5, 'strength ramps linearly at midpoint const m = computeModifiers(cov({ blue: 0.25 })) ok(m.waterproof === true, 'blue 25% waterproof') } +{ + // ORANGE = BOUNCE. Guards the wire that was dead for a whole release: jumpMul + // is plumbed controller<-BuffSystem<-here, so a regression to a constant 1 + // silently removes the buff rather than failing anywhere visible. + near(computeModifiers(cov({ orange: 0.19 })).jumpMul, 1, 'orange 19% → no bounce yet') + const s = 0.15 + 0.85 * 0.5 // buffStrength(0.45) + near( + computeModifiers(cov({ orange: 0.45 })).jumpMul, + 1 + BOUNCE_JUMP * s, + 'orange 45% jumpMul tracks BOUNCE_JUMP', + ) + const sup = computeModifiers(cov({ orange: SUPER })) + near(sup.jumpMul, 1 + BOUNCE_JUMP, 'orange 70% jumpMul maxed') + near(sup.glow, 1, 'orange 70% is super (glow)') +} { // massMul clamps at 1.8 when fully painted (no purple/pink → base only) const m = computeModifiers(cov({ red: 0.5, blue: 0.5 })) diff --git a/src/paint/coverage-math.ts b/src/paint/coverage-math.ts index 21ad760..30a3b75 100644 --- a/src/paint/coverage-math.ts +++ b/src/paint/coverage-math.ts @@ -54,6 +54,9 @@ export const SUPER = 0.70 // ≥70% one colour = super state /** Strength floor at the activation threshold so the buff visibly "kicks in". */ const ACTIVATE_FLOOR = 0.15 +/** Extra take-off speed at full ORANGE strength: jumpMul 1.0 → 1.5 (BOUNCE). */ +export const BOUNCE_JUMP = 0.5 + // ---- Scale buffs (GDD §5.3 / §6): purple MEGA + pink MINI (Lane G) --------- // Both drive `modifiers.size` (the controller scales collider + visuals) and // bias `massMul` on top of the paint=weight base. They oppose each other: a blob @@ -112,6 +115,7 @@ export function dominantColor(cov: CoverageReport): PaintColor | null { /** * The core mapping: coverage -> modifiers (GDD §6). * RED -> speedMul up to 1.6 (BURN) + * ORANGE -> jumpMul up to 1.5 (BOUNCE — rubberized, §6) * GREEN -> grip up to 1.0 (GRIP) * BLUE -> waterproof (SLICK) * PURPLE -> size up to 1.45 + extra mass (MEGA — grow big & heavy, §5.3) @@ -131,6 +135,10 @@ export function computeModifiers(cov: CoverageReport): BlobModifiers { const speedMul = 1 + 0.6 * buffStrength(red) const grip = buffStrength(green) // 0..1 const waterproof = blue >= ACTIVATE + // ORANGE = BOUNCE (GDD §6): rubberized, jumps higher. `jumpMul` was already + // plumbed end-to-end (controller multiplies take-off speed by it, BuffSystem + // copies it) but nothing ever drove it — orange is the colour that does. + const jumpMul = 1 + BOUNCE_JUMP * buffStrength(cov.byColor.orange) // ---- scale (MEGA vs MINI) ---- // net grow/shrink strength; purple and pink oppose so a mixed blob nets out. @@ -147,7 +155,7 @@ export function computeModifiers(cov: CoverageReport): BlobModifiers { return { speedMul, - jumpMul: 1, + jumpMul, massMul, grip, size, diff --git a/src/paint/hud.ts b/src/paint/hud.ts index ba04fa6..883f428 100644 --- a/src/paint/hud.ts +++ b/src/paint/hud.ts @@ -103,6 +103,7 @@ export class PaintHUD { private buffText(cov: CoverageReport, mods: BlobModifiers): string { const parts: string[] = [] if (cov.byColor.red >= ACTIVATE) parts.push(`BURN ×${mods.speedMul.toFixed(2)}`) + if (cov.byColor.orange >= ACTIVATE) parts.push(`BOUNCE ×${mods.jumpMul.toFixed(2)}`) if (cov.byColor.green >= ACTIVATE) parts.push(`GRIP ${mods.grip.toFixed(2)}`) if (cov.byColor.blue >= ACTIVATE) parts.push(`SLICK waterproof`) if (cov.byColor.purple >= ACTIVATE) parts.push(`MEGA ×${mods.size.toFixed(2)}`) diff --git a/src/paint/index.ts b/src/paint/index.ts index 51a27c8..bc98fc3 100644 --- a/src/paint/index.ts +++ b/src/paint/index.ts @@ -23,6 +23,7 @@ export { dominantColor, ACTIVATE, SUPER, + BOUNCE_JUMP, } from './coverage-math' export interface PaintEventTarget {