feat(paint): ORANGE = BOUNCE — plug in the buff that was wired but dead
`jumpMul` has been plumbed end-to-end since Lane A — the controller multiplies take-off speed by it, BuffSystem copies it every step — but computeModifiers hardcoded `jumpMul: 1`, so a finished mechanic sat unplugged and 2 of the 7 palette colours (orange, yellow) had neither an effect nor a single source in any course. Orange now drives it: jumpMul 1.0 -> 1.5 at full strength (BOUNCE_JUMP, GDD §6 "rubberized"), same buffStrength curve as every other buff. Paint source: orange bubbles on Breakfast Rush's high shelf, right before the gap-jump lip — the one place in the game where jump height IS the problem, so the buff has an obvious purpose instead of being decoration. Deliberately opt-in and deliberately costly: they sit inside the GREEN grip zone, so grabbing orange dilutes your green. Route for the jump or route for the grip. Tuned during verification: the field uses impulse 1, not the default 3 — at full strength the pops stacked and launched the blob 3.3u above the shelf right at the lip, throwing off a jump this course is finely tuned around. Verified end-to-end: orange 61% -> jumpMul 1.424 (matches buffStrength exactly), take-off 7.10 -> 10.30, jump height 1.95 -> 3.87 (nearly double); HUD reads "BOUNCE ×1.41" in orange; rolling the shelf pops 4 bubbles per pass. New test assertions cover the 19%/45%/70% curve AND pin it to BOUNCE_JUMP, so a regression to a constant 1 fails loudly instead of silently removing the buff (53 assertions, 10 suites green). Palette now 6/7 usable. Yellow ZAP is the remaining gap and is real design work (triggering machinery at range), not a one-liner. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ed1c3af17a
commit
2777a580c9
16
src/game.ts
16
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,
|
||||
|
||||
@ -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 }))
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -103,6 +103,7 @@ export class PaintHUD {
|
||||
private buffText(cov: CoverageReport, mods: BlobModifiers): string {
|
||||
const parts: string[] = []
|
||||
if (cov.byColor.red >= ACTIVATE) parts.push(`<b style="color:${PALETTE.red}">BURN</b> ×${mods.speedMul.toFixed(2)}`)
|
||||
if (cov.byColor.orange >= ACTIVATE) parts.push(`<b style="color:${PALETTE.orange}">BOUNCE</b> ×${mods.jumpMul.toFixed(2)}`)
|
||||
if (cov.byColor.green >= ACTIVATE) parts.push(`<b style="color:${PALETTE.green}">GRIP</b> ${mods.grip.toFixed(2)}`)
|
||||
if (cov.byColor.blue >= ACTIVATE) parts.push(`<b style="color:${PALETTE.blue}">SLICK</b> waterproof`)
|
||||
if (cov.byColor.purple >= ACTIVATE) parts.push(`<b style="color:${PALETTE.purple}">MEGA</b> ×${mods.size.toFixed(2)}`)
|
||||
|
||||
@ -23,6 +23,7 @@ export {
|
||||
dominantColor,
|
||||
ACTIVATE,
|
||||
SUPER,
|
||||
BOUNCE_JUMP,
|
||||
} from './coverage-math'
|
||||
|
||||
export interface PaintEventTarget {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user