64 lines
2.2 KiB
TypeScript
64 lines
2.2 KiB
TypeScript
// The glassie run, pure half (docs/SCENARIOS.md): carry a wire rack of glasses
|
|
// through a nightclub crowd without redecorating the floor. Wobble physics and
|
|
// breakage rules live here so the feel is testable; the scene only reports
|
|
// bumps and steering.
|
|
|
|
export const RACK_GLASSES = 6;
|
|
/** Wobble at or past this breaks a glass (and the wobble sheds with it). */
|
|
export const BREAK_AT = 1;
|
|
/** A break dumps you back here — shaken, not reset. */
|
|
const AFTER_BREAK = 0.45;
|
|
/** Passive settle per second when you walk smoothly. */
|
|
const SETTLE_PER_S = 0.35;
|
|
/** One crowd bump is most of the way to trouble on a settled rack. */
|
|
const BUMP_WOBBLE = 0.55;
|
|
/** Full-lock steering reversal adds this much per second. */
|
|
const TURN_WOBBLE_PER_S = 1.4;
|
|
/** In-game minutes between racks — the bar never stops producing empties. */
|
|
export const RACK_RESPAWN_MIN = 22;
|
|
|
|
export interface RackState {
|
|
glasses: number;
|
|
wobble: number;
|
|
}
|
|
|
|
export const freshRack = (): RackState => ({ glasses: RACK_GLASSES, wobble: 0 });
|
|
|
|
/**
|
|
* Advance the rack one frame. `turnSharpness` is 0..1 (how hard the carry
|
|
* velocity swung this frame); `bumped` is a de-bounced crowd contact.
|
|
* Returns the new state plus how many glasses broke THIS frame (0 or 1 — a
|
|
* frame only ever breaks one; the wobble shed makes chains possible but earned).
|
|
*/
|
|
export function stepRack(
|
|
rack: RackState,
|
|
dtMs: number,
|
|
bumped: boolean,
|
|
turnSharpness: number,
|
|
): { rack: RackState; broke: number } {
|
|
const dt = Math.max(0, dtMs) / 1000;
|
|
let wobble = rack.wobble - SETTLE_PER_S * dt;
|
|
wobble += Math.max(0, Math.min(1, turnSharpness)) * TURN_WOBBLE_PER_S * dt;
|
|
if (bumped) wobble += BUMP_WOBBLE;
|
|
wobble = Math.max(0, wobble);
|
|
|
|
if (wobble >= BREAK_AT && rack.glasses > 0) {
|
|
return { rack: { glasses: rack.glasses - 1, wobble: AFTER_BREAK }, broke: 1 };
|
|
}
|
|
return { rack: { glasses: rack.glasses, wobble }, broke: 0 };
|
|
}
|
|
|
|
/** 0..1 steering harshness from consecutive frame velocities. */
|
|
export function turnSharpness(
|
|
prevVx: number,
|
|
prevVy: number,
|
|
vx: number,
|
|
vy: number,
|
|
maxSpeed: number,
|
|
): number {
|
|
if (maxSpeed <= 0) return 0;
|
|
const dx = vx - prevVx;
|
|
const dy = vy - prevVy;
|
|
return Math.min(1, Math.hypot(dx, dy) / (maxSpeed * 2));
|
|
}
|