The venue ladder has always listed four venues in data/venues.ts, but they shared ONE floor map and differed only by difficulty knobs — so a promotion you survived a whole week for looked exactly like the room you had just left. - venueMap.ts grows a FloorLayout contract (map + props + lights + posts + probes + palette) and the newGrid() primitives every room is painted with. Voltage stays IN venueMap rather than moving to layouts/, so the new rooms can import primitives from it without closing an import cycle. - Three new rooms in src/scenes/floor/layouts/: The Royal (horseshoe public bar, pool room, pokies corner, trough + two cubicles, a beer garden that is plainly the nicest room in the pub, and a DJ "corner" that is a folding table because this pub never built a booth), Elevate (open-air rooftop — most of the grid is sky, the DJ is on an unwalled plinth, you arrive by lift), ROOM (concrete warehouse, pillars you path around, central booth, loading-dock smoking area, twelve lights in the whole venue). - Nothing about a room is a module singleton any more. FloorView, sweep and FloorDemoScene take the layout; the door picks its street plate by venue id. - FloorDemoScene's six hardcoded station spots (TAPS_SPOT, DECKS_SPOT, ...) were Voltage's tile coordinates. At four venues a hardcoded (47,3) puts the bar shift inside The Royal's pool room, so each layout now names its own stations and the scene reads them from there. - tests/floor/layoutInvariants.ts is an executable rulebook: perimeter holds, every walkable tile reachable from the entry, anchors on walkable ground, posts not sealed (a post on a sealed tile freezes the player for the night — that has shipped twice), probes reachable, props on-grid. Proved against Voltage BEFORE the new rooms were authored against it. Dev route #floor:<venueId> boots the floor straight into one room, because otherwise seeing ROOM means surviving three weeks of the ladder. Gate: lint clean, build clean, 821 tests passing (was 784). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
59 lines
2.6 KiB
TypeScript
59 lines
2.6 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { VOLTAGE } from '../../src/scenes/floor/venueMap';
|
|
import { DEFAULT_LAYOUT, LAYOUTS, layoutFor } from '../../src/scenes/floor/layouts';
|
|
import { VENUES } from '../../src/data/venues';
|
|
import { assertLayout } from './layoutInvariants';
|
|
|
|
// Voltage is the reference room — it shipped, it is tuned, and its
|
|
// reachability was already asserted the long way round in venueMap.test.ts.
|
|
// Running it through the shared rulebook proves the rulebook, not the room: if
|
|
// assertLayout is wrong, it is wrong HERE first, before three new layouts are
|
|
// authored against it.
|
|
describe('VOLTAGE layout', () => {
|
|
it('satisfies the shared venue invariants', () => {
|
|
assertLayout(VOLTAGE, { bar: 6, dance: 8, booth: 4, toilet: 4, smoke: 1, entry: 1, exit: 1 });
|
|
});
|
|
});
|
|
|
|
describe('the layout registry', () => {
|
|
// layoutFor() falls back to Voltage on an unknown id so the dev routes can
|
|
// boot the floor with no night at all. That kindness also means a venue whose
|
|
// room was never built fails SILENTLY — you would play a week at "Elevate"
|
|
// in Voltage's room and the only symptom is a nagging sense of déjà vu.
|
|
it('has a room for every venue in the ladder', () => {
|
|
for (const venue of VENUES) {
|
|
const layout = layoutFor(venue.id);
|
|
expect(layout.id, `venue '${venue.id}' has no layout of its own`).toBe(venue.id);
|
|
}
|
|
});
|
|
|
|
it('has no layout without a venue, and no duplicate ids', () => {
|
|
const venueIds = new Set(VENUES.map((v) => v.id));
|
|
const seen = new Set<string>();
|
|
for (const layout of LAYOUTS) {
|
|
expect(venueIds.has(layout.id), `layout '${layout.id}' belongs to no venue`).toBe(true);
|
|
expect(seen.has(layout.id), `duplicate layout id '${layout.id}'`).toBe(false);
|
|
seen.add(layout.id);
|
|
}
|
|
});
|
|
|
|
it('falls back to the tuned reference room, not to nothing', () => {
|
|
expect(layoutFor(undefined)).toBe(DEFAULT_LAYOUT);
|
|
expect(layoutFor('a venue that does not exist')).toBe(DEFAULT_LAYOUT);
|
|
expect(DEFAULT_LAYOUT).toBe(VOLTAGE);
|
|
});
|
|
|
|
it('gives every room the stations the floor scene needs', () => {
|
|
// FloorDemoScene resolves the playable shifts from these names. A layout
|
|
// missing one silently parks that station in the middle of the map.
|
|
for (const layout of LAYOUTS) {
|
|
for (const post of ['taps', 'decks']) {
|
|
expect(layout.posts[post], `${layout.id} has no '${post}' post`).toBeDefined();
|
|
}
|
|
for (const probe of ['tapsFront', 'rack', 'dishwasher', 'water']) {
|
|
expect(layout.probes[probe], `${layout.id} has no '${probe}' probe`).toBeDefined();
|
|
}
|
|
}
|
|
});
|
|
});
|