Phase 1 review: merge door/floor/juice, fold CCRs into contracts, integration fixes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
ca8fd75aac
commit
595371f684
@ -1,6 +1,15 @@
|
|||||||
{
|
{
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{ "name": "not-tonight", "runtimeExecutable": "npm", "runtimeArgs": ["run", "dev"], "port": 5199 }
|
{
|
||||||
|
"name": "not-tonight",
|
||||||
|
"runtimeExecutable": "npm",
|
||||||
|
"runtimeArgs": [
|
||||||
|
"run",
|
||||||
|
"dev"
|
||||||
|
],
|
||||||
|
"port": 5199,
|
||||||
|
"autoPort": true
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +1,2 @@
|
|||||||
node_modules/
|
node_modules
|
||||||
dist/
|
dist/
|
||||||
|
|||||||
@ -477,3 +477,65 @@ character), and does the sweep *feel* like a door rather than a filter.
|
|||||||
- Someone with speakers should ear-check the mix and tell me what to retune.
|
- Someone with speakers should ear-check the mix and tell me what to retune.
|
||||||
- Consider extracting `resyncBeat` into `scheduling.ts` so the throttled-tab
|
- Consider extracting `resyncBeat` into `scheduling.ts` so the throttled-tab
|
||||||
resync gets direct test coverage.
|
resync gets direct test coverage.
|
||||||
|
|
||||||
|
---
|
||||||
|
### REVIEW — Fable — 2026-07-19 (Phase 1 review & integration)
|
||||||
|
**Reviewed:** lane/door @ e0b5831 (231 tests), lane/floor @ 3eee843 (169),
|
||||||
|
lane/juice @ f5e1151 (111). All three gates verified green per-branch before merge.
|
||||||
|
**Merged to main:** yes — door, then floor, then juice; 413 tests green post-merge.
|
||||||
|
Verified in-browser after integration: Door night boots and plays (called a patron
|
||||||
|
up, stamped her, meters moved per the judge table), FloorDemo cone/crowd runs (F),
|
||||||
|
JuiceDemo runs with AUDIO LIVE @ 128 BPM after unlock (J).
|
||||||
|
|
||||||
|
**Quality note to all three lanes:** outstanding sessions. The play-found bug
|
||||||
|
lists (door's 10 + review pass, floor's meter-collapse and frozen-beat catches)
|
||||||
|
and juice's honest "verified by ear: NOTHING" are exactly what this file is for.
|
||||||
|
|
||||||
|
**Contract decisions (all folded into `src/data/types.ts` + CONTRACTS.md §4 by me):**
|
||||||
|
1. CCR-1 `PatronFlags.handHolding` — ACCEPTED, folded in; doorTypes.ts augmentation removed.
|
||||||
|
2. CCR-2 `hypeDelta` — ACCEPTED onto `VerdictOutcome` (not just DoorOutcome); judge.ts augmentation removed.
|
||||||
|
3. CCR-3 (per-slot logos, distinct shoe/outer silhouettes) — ACCEPTED for the
|
||||||
|
v0.4 art pass; logged. `LOGO_VISIBLE_SLOTS` stays the guard until then.
|
||||||
|
4. CCR-4 — resolved by adding optional `short?: string` to `DressCodeRule`.
|
||||||
|
Door's runtime guard may stay or simplify; either is fine.
|
||||||
|
5. JUICE-1 `door:phoneTheatre` + `audio:unlocked` + `beat:bar` — ACCEPTED, folded in;
|
||||||
|
`ui/juiceEvents.ts` deleted (its three importers updated).
|
||||||
|
6. FLOOR-1 `incident:log` — ACCEPTED; `core/meters.ts` is now the single incident
|
||||||
|
writer and all three direct `incidents.push` sites (floor demo, DoorScene,
|
||||||
|
NightScene deferred) now emit the event instead.
|
||||||
|
7. FLOOR-2 aggro decay — deferred to the Phase-2 tuning pass, alongside door's
|
||||||
|
economy findings (vibe pinning, hype ratchet, queue slack). One tuning pass,
|
||||||
|
one owner, against a human player.
|
||||||
|
|
||||||
|
**Rulings on questions:**
|
||||||
|
- `declare module` augmentation: it was the right unblocking move, and both lanes
|
||||||
|
executed it cleanly — but it does NOT become standing practice. Contract
|
||||||
|
extensions land in `types.ts` at each review, augmentations are deleted on
|
||||||
|
folding. One source of truth wins over lane autonomy here.
|
||||||
|
- `src/main.ts`: now integration-owned (reviewer/Phase-2). Lanes request routing
|
||||||
|
changes via handover. Current dev routes: N night · P parade · F floor · J juice,
|
||||||
|
same hashes.
|
||||||
|
- `data/strings/<lane>.ts`: ACCEPTED as the standing convention — new string
|
||||||
|
files there are lane-owned, no sign-off needed (floor's moved:
|
||||||
|
`src/data/strings/floor.ts`). `types.ts` stays frozen.
|
||||||
|
- Heat 3-strikes: keep summary-declared for v0.1; Phase 2's shell makes it an
|
||||||
|
immediate "licence pulled" cut — logged for integration.
|
||||||
|
- Vibe-pins-at-100 / economy retune: HELD for a human playtest (John). Door's
|
||||||
|
measured numbers are the starting point for that session, not auto-applied.
|
||||||
|
- Juice beat-0 anchor: `audio:unlocked.atMs` should carry the anchored beat-0
|
||||||
|
origin — implement next juice session.
|
||||||
|
- MeterHud heat count: read `NightState.heatStrikes.length` at construction
|
||||||
|
(single source of truth) — implement next juice session.
|
||||||
|
- Floor escalation balance ("aggression never feels rewarded"): CONFIRMED as
|
||||||
|
design intent; door should mirror the shape at the rope.
|
||||||
|
- `.gitignore` slash: fixed.
|
||||||
|
|
||||||
|
**Also applied at integration:** dazza fromMin retunes per door's measurements
|
||||||
|
(noSongRequesters 240→105, noHandHolders 280→135; two pinned-order tests updated),
|
||||||
|
floor strings moved to `data/strings/floor.ts`, vite port now respects `$PORT`
|
||||||
|
(preview autoPort), 413 tests + lint + build green on main.
|
||||||
|
|
||||||
|
**Next:** Phase 2 integration is OPEN — one lane, `lanes/` file to follow. Big
|
||||||
|
rocks: night-flow state machine (door⇄floor interleave + radio pulls), TechnoEngine
|
||||||
|
replacing StubBeatClock in the night, ui/Stamp + MeterHud replacing door's local
|
||||||
|
versions, save/progression wiring, then the tuning pass with a human.
|
||||||
|
|||||||
@ -120,9 +120,18 @@ export interface EventMap {
|
|||||||
'meters:delta': { vibe?: number; aggro?: number; hype?: number };
|
'meters:delta': { vibe?: number; aggro?: number; hype?: number };
|
||||||
'meters:changed': { vibe: number; aggro: number; hype: number };
|
'meters:changed': { vibe: number; aggro: number; hype: number };
|
||||||
'heat:strike': HeatStrike;
|
'heat:strike': HeatStrike;
|
||||||
|
// folded in at the Phase-1 review (2026-07-19):
|
||||||
|
'incident:log': IncidentRecord; // single writer: core/meters.ts appends to NightState.incidents
|
||||||
|
'door:phoneTheatre': { durationMs: number };
|
||||||
|
'audio:unlocked': { atMs: number };
|
||||||
|
'beat:bar': { barIndex: number; beatIndex: number; audioTimeMs: number };
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Phase-1 review also added: `PatronFlags.handHolding?` (CCR-1),
|
||||||
|
`VerdictOutcome.hypeDelta?` (CCR-2), `DressCodeRule.short?` (CCR-4).
|
||||||
|
`src/data/types.ts` remains the source of truth where this doc lags.
|
||||||
|
|
||||||
**Bus discipline:** scenes emit `meters:delta`; only `core/meters.ts` mutates state
|
**Bus discipline:** scenes emit `meters:delta`; only `core/meters.ts` mutates state
|
||||||
and emits `meters:changed`. HUD listens to `meters:changed` only. This keeps three
|
and emits `meters:changed`. HUD listens to `meters:changed` only. This keeps three
|
||||||
lanes from fighting over the same numbers.
|
lanes from fighting over the same numbers.
|
||||||
|
|||||||
@ -16,7 +16,6 @@ import {
|
|||||||
patternStep,
|
patternStep,
|
||||||
subdivisions,
|
subdivisions,
|
||||||
} from './scheduling';
|
} from './scheduling';
|
||||||
import '../ui/juiceEvents';
|
|
||||||
|
|
||||||
const DEFAULT_BPM = 128;
|
const DEFAULT_BPM = 128;
|
||||||
const DEFAULT_LOOKAHEAD_MS = 100;
|
const DEFAULT_LOOKAHEAD_MS = 100;
|
||||||
|
|||||||
@ -36,6 +36,9 @@ export class Meters {
|
|||||||
bus.on('night:phaseChange', ({ location }) => {
|
bus.on('night:phaseChange', ({ location }) => {
|
||||||
this.state.location = location;
|
this.state.location = location;
|
||||||
}),
|
}),
|
||||||
|
bus.on('incident:log', (incident) => {
|
||||||
|
this.state.incidents.push(incident);
|
||||||
|
}),
|
||||||
bus.on('door:clicker', ({ direction }) => {
|
bus.on('door:clicker', ({ direction }) => {
|
||||||
this.state.capacity.clickerShown = Math.max(
|
this.state.capacity.clickerShown = Math.max(
|
||||||
0,
|
0,
|
||||||
|
|||||||
@ -16,11 +16,11 @@ export const DAZZA_TEXTS: readonly DazzaLine[] = [
|
|||||||
{ id: 'rule-basics', text: 'mate its 930 no thongs no singlets we are not a beer garden', ruleId: 'noThongsSinglets', fromMin: 30 },
|
{ id: 'rule-basics', text: 'mate its 930 no thongs no singlets we are not a beer garden', ruleId: 'noThongsSinglets', fromMin: 30 },
|
||||||
{ id: 'rule-logos', text: 'seeing too many logos in there. no visible logos from NOW', ruleId: 'noLogos', fromMin: 90 },
|
{ id: 'rule-logos', text: 'seeing too many logos in there. no visible logos from NOW', ruleId: 'noLogos', fromMin: 90 },
|
||||||
{ id: 'rule-sneakers', text: 'white sneakers are OVER. unless vintage. u know the difference', ruleId: 'noWhiteSneakers', fromMin: 150 },
|
{ id: 'rule-sneakers', text: 'white sneakers are OVER. unless vintage. u know the difference', ruleId: 'noWhiteSneakers', fromMin: 150 },
|
||||||
{ id: 'rule-songRequest', text: 'if they look like theyd request a song they dont come in. u know the look', ruleId: 'noSongRequesters', fromMin: 240 },
|
{ id: 'rule-songRequest', text: 'if they look like theyd request a song they dont come in. u know the look', ruleId: 'noSongRequesters', fromMin: 105 },
|
||||||
{ id: 'rule-bucketHats', text: 'bucket hats r making the room feel like a festival toilet queue. gone', ruleId: 'noBucketHats', fromMin: 180 },
|
{ id: 'rule-bucketHats', text: 'bucket hats r making the room feel like a festival toilet queue. gone', ruleId: 'noBucketHats', fromMin: 180 },
|
||||||
{ id: 'rule-sunnies', text: 'anyone wearing sunnies inside at 11pm is a cop or a flog. neither gets in', ruleId: 'noSunnies', fromMin: 120 },
|
{ id: 'rule-sunnies', text: 'anyone wearing sunnies inside at 11pm is a cop or a flog. neither gets in', ruleId: 'noSunnies', fromMin: 120 },
|
||||||
{ id: 'rule-suits', text: 'too corporate in here tonight. no blazers til the suits ive already got leave', ruleId: 'noBlazers', fromMin: 200 },
|
{ id: 'rule-suits', text: 'too corporate in here tonight. no blazers til the suits ive already got leave', ruleId: 'noBlazers', fromMin: 200 },
|
||||||
{ id: 'rule-couples', text: 'no more couples holding hands in the queue its making the room feel married', ruleId: 'noHandHolders', fromMin: 280 },
|
{ id: 'rule-couples', text: 'no more couples holding hands in the queue its making the room feel married', ruleId: 'noHandHolders', fromMin: 135 },
|
||||||
|
|
||||||
// vibe commentary
|
// vibe commentary
|
||||||
{ id: 'vibe-mid', text: 'why is the floor MID rn. fix it', fromMin: 60 },
|
{ id: 'vibe-mid', text: 'why is the floor MID rn. fix it', fromMin: 60 },
|
||||||
|
|||||||
@ -40,6 +40,8 @@ export interface PatronFlags {
|
|||||||
uvStamped?: boolean;
|
uvStamped?: boolean;
|
||||||
timesDeniedBefore?: number;
|
timesDeniedBefore?: number;
|
||||||
disguised?: boolean;
|
disguised?: boolean;
|
||||||
|
/** Set on couples by door/QueueManager; predicate for noHandHolders (CCR-1). */
|
||||||
|
handHolding?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Patron {
|
export interface Patron {
|
||||||
@ -59,6 +61,8 @@ export interface Patron {
|
|||||||
export interface DressCodeRule {
|
export interface DressCodeRule {
|
||||||
id: string;
|
id: string;
|
||||||
text: string;
|
text: string;
|
||||||
|
/** Card label for the on-screen dress-code board (CCR-4). */
|
||||||
|
short?: string;
|
||||||
activeFrom: number; // clock minutes since 21:00
|
activeFrom: number; // clock minutes since 21:00
|
||||||
violates: (p: Patron) => boolean;
|
violates: (p: Patron) => boolean;
|
||||||
}
|
}
|
||||||
@ -73,6 +77,8 @@ export interface HeatStrike {
|
|||||||
export interface VerdictOutcome {
|
export interface VerdictOutcome {
|
||||||
vibeDelta: number;
|
vibeDelta: number;
|
||||||
aggroDelta: number;
|
aggroDelta: number;
|
||||||
|
/** Queue-theatre payoff channel — the 'wait' verdict's reward (CCR-2). */
|
||||||
|
hypeDelta?: number;
|
||||||
heatStrike?: HeatStrike;
|
heatStrike?: HeatStrike;
|
||||||
dazzaText?: string;
|
dazzaText?: string;
|
||||||
}
|
}
|
||||||
@ -137,4 +143,14 @@ export interface EventMap {
|
|||||||
'meters:delta': { vibe?: number; aggro?: number; hype?: number };
|
'meters:delta': { vibe?: number; aggro?: number; hype?: number };
|
||||||
'meters:changed': { vibe: number; aggro: number; hype: number };
|
'meters:changed': { vibe: number; aggro: number; hype: number };
|
||||||
'heat:strike': HeatStrike;
|
'heat:strike': HeatStrike;
|
||||||
|
|
||||||
|
// ---- folded in at Phase-1 review (see LANEHANDOVER.md REVIEW 2026-07-19) ----
|
||||||
|
/** Single-writer incident trail: core/meters.ts appends to NightState.incidents. */
|
||||||
|
'incident:log': IncidentRecord;
|
||||||
|
/** Player fiddled with their phone while a patron waits — pure theatre. */
|
||||||
|
'door:phoneTheatre': { durationMs: number };
|
||||||
|
/** AudioContext resumed after the first real user gesture. */
|
||||||
|
'audio:unlocked': { atMs: number };
|
||||||
|
/** Bar line boundary (every 4th beat) — for anything that wants downbeats. */
|
||||||
|
'beat:bar': { barIndex: number; beatIndex: number; audioTimeMs: number };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +1,9 @@
|
|||||||
// LANE-DOOR local contract extensions.
|
// LANE-DOOR type extensions. CCR-1 (PatronFlags.handHolding) and CCR-2
|
||||||
//
|
// (VerdictOutcome.hypeDelta) were folded into data/types.ts at the Phase-1
|
||||||
// Everything here is ADDITIVE (docs/CONTRACTS.md preamble: "Lanes may EXTEND
|
// review — this file now only holds door-domain types.
|
||||||
// (new event names in their namespace, new fields marked optional) but never
|
|
||||||
// repurpose existing fields"). Nothing in src/core/ or src/data/types.ts is
|
|
||||||
// edited — the PatronFlags addition below is a TypeScript interface
|
|
||||||
// augmentation, so the frozen file stays byte-identical.
|
|
||||||
//
|
|
||||||
// TODO(contract): CCR-1 in LANEHANDOVER.md asks the reviewer to fold
|
|
||||||
// `handHolding` into data/types.ts properly at Phase-2 integration; until then
|
|
||||||
// this augmentation is the whole implementation.
|
|
||||||
|
|
||||||
import type { HeatStrike, Patron, VerdictOutcome } from '../data/types';
|
import type { HeatStrike, Patron, VerdictOutcome } from '../data/types';
|
||||||
|
|
||||||
declare module '../data/types' {
|
|
||||||
interface PatronFlags {
|
|
||||||
/**
|
|
||||||
* Set by door/QueueManager when two patrons arrive as a couple. Rendered as
|
|
||||||
* a linked-hands pixel between them, so `noHandHolders` stays a rule the
|
|
||||||
* player can actually SEE — the whole point of design §4.1.
|
|
||||||
*/
|
|
||||||
handHolding?: boolean;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A consequence that lands LATER: Dazza does a lap and spots the bloke in
|
* A consequence that lands LATER: Dazza does a lap and spots the bloke in
|
||||||
* thongs you waved through, the inspector audits at 3 AM. Deferred hits are the
|
* thongs you waved through, the inspector audits at 3 AM. Deferred hits are the
|
||||||
|
|||||||
@ -13,15 +13,6 @@ import type { Patron, Verdict } from '../data/types';
|
|||||||
// pays vibe, it just costs far more aggro than denying a real violator does.
|
// pays vibe, it just costs far more aggro than denying a real violator does.
|
||||||
// Arbitrary power is rewarded and expensive at the same time. That is the game.
|
// Arbitrary power is rewarded and expensive at the same time. That is the game.
|
||||||
|
|
||||||
// TODO(contract): CCR-2 — VerdictOutcome has no hype channel, but design §3.1
|
|
||||||
// gives rope theatre its hype. Additive augmentation (same pattern doorTypes.ts
|
|
||||||
// uses for PatronFlags) until the reviewer folds hypeDelta in properly.
|
|
||||||
declare module './doorTypes' {
|
|
||||||
interface DoorOutcome {
|
|
||||||
hypeDelta?: number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const JUDGE_TUNING = {
|
export const JUDGE_TUNING = {
|
||||||
/** Deny someone who genuinely broke something. The job, done right. */
|
/** Deny someone who genuinely broke something. The job, done right. */
|
||||||
denyViolatorVibe: 6,
|
denyViolatorVibe: 6,
|
||||||
|
|||||||
@ -465,7 +465,7 @@ export class DoorScene extends Phaser.Scene {
|
|||||||
if (outcome.deferred?.length) this.night.scheduleDeferred(outcome.deferred);
|
if (outcome.deferred?.length) this.night.scheduleDeferred(outcome.deferred);
|
||||||
if (outcome.note) this.showToast(outcome.note);
|
if (outcome.note) this.showToast(outcome.note);
|
||||||
|
|
||||||
this.night.state.incidents.push({
|
this.night.bus.emit('incident:log', {
|
||||||
clockMin: this.night.state.clockMin,
|
clockMin: this.night.state.clockMin,
|
||||||
kind: verdict,
|
kind: verdict,
|
||||||
patronId: patron.id,
|
patronId: patron.id,
|
||||||
|
|||||||
@ -197,7 +197,7 @@ export class NightScene extends Phaser.Scene {
|
|||||||
if (hit.heatStrike && shouldRecordStrike(this.state, hit.heatStrike)) {
|
if (hit.heatStrike && shouldRecordStrike(this.state, hit.heatStrike)) {
|
||||||
this.bus.emit('heat:strike', hit.heatStrike);
|
this.bus.emit('heat:strike', hit.heatStrike);
|
||||||
}
|
}
|
||||||
this.state.incidents.push({
|
this.bus.emit('incident:log', {
|
||||||
clockMin,
|
clockMin,
|
||||||
kind: 'deferred',
|
kind: 'deferred',
|
||||||
patronId: hit.patronId,
|
patronId: hit.patronId,
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import { layoutPockets, type Pocket } from './patDown';
|
|||||||
import { CutOffOverlay } from './overlays/CutOffOverlay';
|
import { CutOffOverlay } from './overlays/CutOffOverlay';
|
||||||
import { StallOverlay } from './overlays/StallOverlay';
|
import { StallOverlay } from './overlays/StallOverlay';
|
||||||
import { PatDownOverlay } from './overlays/PatDownOverlay';
|
import { PatDownOverlay } from './overlays/PatDownOverlay';
|
||||||
import { HELP_TEXT, NO_STAMP_RADIO } from './strings';
|
import { HELP_TEXT, NO_STAMP_RADIO } from '../../data/strings/floor';
|
||||||
|
|
||||||
const VIEW_H = 360;
|
const VIEW_H = 360;
|
||||||
const INTERACT_RANGE = 46;
|
const INTERACT_RANGE = 46;
|
||||||
@ -398,9 +398,7 @@ export class FloorDemoScene extends Phaser.Scene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private logIncident(kind: string, patronId: string | undefined, detail: string): void {
|
private logIncident(kind: string, patronId: string | undefined, detail: string): void {
|
||||||
// TODO(contract): core has no incident API and EventMap is frozen, so the
|
this.bus.emit('incident:log', { clockMin: this.clock.clockMin, kind, patronId, detail });
|
||||||
// floor appends to its own NightState. Requested `incident:log` in LANEHANDOVER.
|
|
||||||
this.night.incidents.push({ clockMin: this.clock.clockMin, kind, patronId, detail });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private drawHud(dt: number): void {
|
private drawHud(dt: number): void {
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import {
|
|||||||
CUTOFF_REPLY_SOFT,
|
CUTOFF_REPLY_SOFT,
|
||||||
ESCALATION_LINES,
|
ESCALATION_LINES,
|
||||||
type EscalationLine,
|
type EscalationLine,
|
||||||
} from './strings';
|
} from '../../data/strings/floor';
|
||||||
|
|
||||||
export type EscalationOutcome = 'clean' | 'scene' | 'staggerFree';
|
export type EscalationOutcome = 'clean' | 'scene' | 'staggerFree';
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import Phaser from 'phaser';
|
|||||||
import { renderDoll } from '../../../patrons/doll';
|
import { renderDoll } from '../../../patrons/doll';
|
||||||
import type { DrunkStage, Patron } from '../../../data/types';
|
import type { DrunkStage, Patron } from '../../../data/types';
|
||||||
import { judgeEscalation, type EscalationResult } from '../escalation';
|
import { judgeEscalation, type EscalationResult } from '../escalation';
|
||||||
import { ESCALATION_LINES, type EscalationLine } from '../strings';
|
import { ESCALATION_LINES, type EscalationLine } from '../../../data/strings/floor';
|
||||||
|
|
||||||
const W = 640;
|
const W = 640;
|
||||||
const H = 360;
|
const H = 360;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import Phaser from 'phaser';
|
import Phaser from 'phaser';
|
||||||
import { CONTRABAND } from '../../../data/contraband';
|
import { CONTRABAND } from '../../../data/contraband';
|
||||||
import type { Patron } from '../../../data/types';
|
import type { Patron } from '../../../data/types';
|
||||||
import { PATDOWN_INTRO } from '../strings';
|
import { PATDOWN_INTRO } from '../../../data/strings/floor';
|
||||||
import { PATDOWN_MS, POCKET_COUNT, foundLine, scorePatDown } from '../patDown';
|
import { PATDOWN_MS, POCKET_COUNT, foundLine, scorePatDown } from '../patDown';
|
||||||
import type { PatDownResult, Pocket } from '../patDown';
|
import type { PatDownResult, Pocket } from '../patDown';
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import {
|
|||||||
type StallOutcome,
|
type StallOutcome,
|
||||||
type StallState,
|
type StallState,
|
||||||
} from '../bangJudge';
|
} from '../bangJudge';
|
||||||
import { STALL_BUSTED, STALL_MUFFLED, STALL_OFF_BEAT } from '../strings';
|
import { STALL_BUSTED, STALL_MUFFLED, STALL_OFF_BEAT } from '../../../data/strings/floor';
|
||||||
|
|
||||||
const W = 640;
|
const W = 640;
|
||||||
const H = 360;
|
const H = 360;
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
import type { RngStream } from '../../core/SeededRNG';
|
import type { RngStream } from '../../core/SeededRNG';
|
||||||
import { CONTRABAND } from '../../data/contraband';
|
import { CONTRABAND } from '../../data/contraband';
|
||||||
import type { Patron } from '../../data/types';
|
import type { Patron } from '../../data/types';
|
||||||
import { PATDOWN_CLEAR, PATDOWN_FOUND, PATDOWN_MISSED } from './strings';
|
import { PATDOWN_CLEAR, PATDOWN_FOUND, PATDOWN_MISSED } from '../../data/strings/floor';
|
||||||
|
|
||||||
export const PATDOWN_MS = 10_000;
|
export const PATDOWN_MS = 10_000;
|
||||||
export const POCKET_COUNT = 6;
|
export const POCKET_COUNT = 6;
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import { MeterHud } from './MeterHud';
|
|||||||
import { Phone } from './Phone';
|
import { Phone } from './Phone';
|
||||||
import { stamp, type StampHandle } from './Stamp';
|
import { stamp, type StampHandle } from './Stamp';
|
||||||
import { UI, chunkyButton, font } from './style';
|
import { UI, chunkyButton, font } from './style';
|
||||||
import './juiceEvents';
|
|
||||||
|
|
||||||
const W = 640;
|
const W = 640;
|
||||||
const H = 360;
|
const H = 360;
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import Phaser from 'phaser';
|
import Phaser from 'phaser';
|
||||||
import type { EventBus } from '../core/EventBus';
|
import type { EventBus } from '../core/EventBus';
|
||||||
import { UI, font, panel } from './style';
|
import { UI, font, panel } from './style';
|
||||||
import './juiceEvents';
|
|
||||||
|
|
||||||
// The player's only line to management. Dazza is a man who is frightened of the
|
// The player's only line to management. Dazza is a man who is frightened of the
|
||||||
// owner, so every buzz is a small emergency. The phone is deliberately fiddly:
|
// owner, so every buzz is a small emergency. The phone is deliberately fiddly:
|
||||||
|
|||||||
@ -1,23 +0,0 @@
|
|||||||
// LANE-JUICE event extensions.
|
|
||||||
//
|
|
||||||
// TODO(contract): `door:phoneTheatre` lives outside the audio domain, so it needs
|
|
||||||
// reviewer sign-off before it moves into data/types.ts. Until then we merge it
|
|
||||||
// into EventMap by declaration merging rather than editing the frozen contract
|
|
||||||
// file — same typing guarantees, zero edits to LANE-0 territory. See the CONTRACT
|
|
||||||
// CHANGE REQUEST in LANEHANDOVER.md.
|
|
||||||
//
|
|
||||||
// `audio:*` / `beat:*` additions are ours to extend freely (LANE_JUICE.md), but
|
|
||||||
// they are declared here too so the whole juice surface reads in one place.
|
|
||||||
|
|
||||||
declare module '../data/types' {
|
|
||||||
interface EventMap {
|
|
||||||
/** Player fiddled with their phone while a patron waits — pure theatre. */
|
|
||||||
'door:phoneTheatre': { durationMs: number };
|
|
||||||
/** AudioContext resumed after the first real user gesture. */
|
|
||||||
'audio:unlocked': { atMs: number };
|
|
||||||
/** Bar line boundary (every 4th beat) — for anything that wants downbeats. */
|
|
||||||
'beat:bar': { barIndex: number; beatIndex: number; audioTimeMs: number };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export {};
|
|
||||||
@ -51,12 +51,12 @@ describe('dazzaDue', () => {
|
|||||||
expect(ids(due.filter((l) => l.ruleId))).toEqual([
|
expect(ids(due.filter((l) => l.ruleId))).toEqual([
|
||||||
'rule-basics',
|
'rule-basics',
|
||||||
'rule-logos',
|
'rule-logos',
|
||||||
|
'rule-songRequest',
|
||||||
'rule-sunnies',
|
'rule-sunnies',
|
||||||
|
'rule-couples',
|
||||||
'rule-sneakers',
|
'rule-sneakers',
|
||||||
'rule-bucketHats',
|
'rule-bucketHats',
|
||||||
'rule-suits',
|
'rule-suits',
|
||||||
'rule-songRequest',
|
|
||||||
'rule-couples',
|
|
||||||
]);
|
]);
|
||||||
expect(ids(due.filter((l) => !l.ruleId))).toEqual([
|
expect(ids(due.filter((l) => !l.ruleId))).toEqual([
|
||||||
'vibe-empty',
|
'vibe-empty',
|
||||||
|
|||||||
@ -93,12 +93,12 @@ describe('activeRules', () => {
|
|||||||
expect(ids(activeRules(LATE))).toEqual([
|
expect(ids(activeRules(LATE))).toEqual([
|
||||||
'noThongsSinglets',
|
'noThongsSinglets',
|
||||||
'noLogos',
|
'noLogos',
|
||||||
|
'noSongRequesters',
|
||||||
'noSunnies',
|
'noSunnies',
|
||||||
|
'noHandHolders',
|
||||||
'noWhiteSneakers',
|
'noWhiteSneakers',
|
||||||
'noBucketHats',
|
'noBucketHats',
|
||||||
'noBlazers',
|
'noBlazers',
|
||||||
'noSongRequesters',
|
|
||||||
'noHandHolders',
|
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import {
|
|||||||
CUTOFF_REPLY_SOFT,
|
CUTOFF_REPLY_SOFT,
|
||||||
ESCALATION_LINES,
|
ESCALATION_LINES,
|
||||||
type EscalationLine,
|
type EscalationLine,
|
||||||
} from '../../src/scenes/floor/strings';
|
} from '../../src/data/strings/floor';
|
||||||
|
|
||||||
const STAGES: readonly DrunkStage[] = ['sober', 'tipsy', 'loose', 'messy', 'maggot'];
|
const STAGES: readonly DrunkStage[] = ['sober', 'tipsy', 'loose', 'messy', 'maggot'];
|
||||||
const LINES: readonly EscalationLine[] = ['water', 'done', 'escort'];
|
const LINES: readonly EscalationLine[] = ['water', 'done', 'escort'];
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { SeededRNG } from '../../src/core/SeededRNG';
|
import { SeededRNG } from '../../src/core/SeededRNG';
|
||||||
import { PATDOWN_CLEAR, PATDOWN_MISSED } from '../../src/scenes/floor/strings';
|
import { PATDOWN_CLEAR, PATDOWN_MISSED } from '../../src/data/strings/floor';
|
||||||
import {
|
import {
|
||||||
foundLine,
|
foundLine,
|
||||||
layoutPockets,
|
layoutPockets,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { defineConfig } from 'vite';
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
base: './',
|
base: './',
|
||||||
server: { port: 5199 },
|
server: { port: Number(process.env.PORT) || 5199 },
|
||||||
test: {
|
test: {
|
||||||
include: ['tests/**/*.test.ts'],
|
include: ['tests/**/*.test.ts'],
|
||||||
environment: 'node',
|
environment: 'node',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user