feat: integrate room framework and isolated subgame bundles
Complete room UI and protocol integration, move game definitions and resources behind bundle entries, publish authoritative version XML, and document single-game builds. Include all current resource changes and experiment artifacts.
This commit is contained in:
@@ -82,14 +82,15 @@ test('assertGameEntry rejects invalid identity, routes, resolver presence, and m
|
||||
assert.throws(() => assertGameEntry({ ...entry, route }), /reserved.*route/);
|
||||
}
|
||||
assert.throws(() => assertGameEntry({ ...entry, resolveSeatCount: undefined as never }), /resolveSeatCount/);
|
||||
assert.throws(() => assertGameEntry({ ...entry, createModule: () => undefined as never }), /module/);
|
||||
assert.throws(() => assertGameEntry({ ...entry, createModule: undefined as never }), /createModule/);
|
||||
});
|
||||
|
||||
test('GameEntry preflight does not call a resolver before its game-owned roomtype exists', () => {
|
||||
const calls: Array<readonly unknown[]> = [];
|
||||
const entry = {
|
||||
...makeEntry(() => makeModule([])),
|
||||
resolveSeatCount(roomtype: readonly unknown[]) {
|
||||
resolveSeatCount(roomtype: import('../../YouleNexus/assets/framework/sdk/contracts/roomtype.ts').Roomtype) {
|
||||
assert.ok(Array.isArray(roomtype));
|
||||
calls.push(roomtype);
|
||||
if (roomtype[1] !== 4) throw new Error('Game roomtype requires four seats at index 1');
|
||||
return 4;
|
||||
@@ -99,16 +100,26 @@ test('GameEntry preflight does not call a resolver before its game-owned roomtyp
|
||||
assert.deepEqual(calls, []);
|
||||
});
|
||||
|
||||
test('assertGameEntry disposes its validation module exactly once', () => {
|
||||
let disposals = 0;
|
||||
const entry = makeEntry(() => ({
|
||||
...makeModule([]),
|
||||
dispose: () => { disposals++; },
|
||||
}));
|
||||
test('declaration checks and constructor have zero factory or disposer side effects', () => {
|
||||
let created = 0, disposals = 0;
|
||||
const entry = makeEntry(() => {
|
||||
created++;
|
||||
return {...makeModule([]),dispose: () => { disposals++; }};
|
||||
});
|
||||
|
||||
assert.doesNotThrow(() => assertGameEntry(entry));
|
||||
|
||||
const session = new GameSessionHost(entry);
|
||||
assert.equal(created, 0);
|
||||
assert.equal(disposals, 0);
|
||||
session.open(makeHost());
|
||||
assert.equal(created, 1);
|
||||
session.close();
|
||||
assert.equal(disposals, 1);
|
||||
session.open(makeHost());
|
||||
session.dispose();
|
||||
assert.equal(created, 2);
|
||||
assert.equal(disposals, 2);
|
||||
});
|
||||
|
||||
test('GameSessionHost delivers room events and deskinfo to one active module in order', () => {
|
||||
@@ -186,18 +197,17 @@ test('GameSessionHost preserves create and contract failures when open cleanup s
|
||||
let createCalls = 0;
|
||||
const createSessions = new GameSessionHost(makeEntry(() => {
|
||||
createCalls += 1;
|
||||
if (createCalls === 1) return makeModule([]);
|
||||
throw createError;
|
||||
}));
|
||||
|
||||
assert.equal(caughtError(() => createSessions.open(makeHost())), createError);
|
||||
assert.equal(createSessions.state, 'idle');
|
||||
assert.equal(createCalls, 1);
|
||||
|
||||
let contractCalls = 0;
|
||||
let contractDisposals = 0;
|
||||
const contractSessions = new GameSessionHost(makeEntry(() => {
|
||||
contractCalls += 1;
|
||||
if (contractCalls === 1) return makeModule([]);
|
||||
return {
|
||||
handlePlatformEvent() {},
|
||||
handleGameMessage() {},
|
||||
@@ -208,6 +218,7 @@ test('GameSessionHost preserves create and contract failures when open cleanup s
|
||||
|
||||
assert.match(caughtError(() => contractSessions.open(makeHost())).message, /implement attach/);
|
||||
assert.equal(contractDisposals, 1);
|
||||
assert.equal(contractCalls, 1);
|
||||
assert.equal(contractSessions.state, 'idle');
|
||||
});
|
||||
|
||||
@@ -218,7 +229,6 @@ test('GameSessionHost preserves attach then dispose failures by identity and occ
|
||||
let disposals = 0;
|
||||
const sessions = new GameSessionHost(makeEntry(() => {
|
||||
createCalls += 1;
|
||||
if (createCalls === 1) return makeModule([]);
|
||||
return {
|
||||
...makeModule([]),
|
||||
attach() { throw attachError; },
|
||||
@@ -514,3 +524,19 @@ test('terminal disposal during module creation cleans the returned module withou
|
||||
assert.equal(attaches, 0);
|
||||
assert.equal(sessions.state, 'disposed');
|
||||
});
|
||||
|
||||
test('active game projection is exposed read-only and disappears with its owning module',()=>{
|
||||
const projection={getSnapshot(){return {revision:0,roundPhase:'waiting',selfParticipation:'waiting' as const,scoresBySeat:{},restrictions:{}};},subscribe(){return ()=>{};}};
|
||||
const session=new GameSessionHost(makeEntry(()=>({...makeModule([]),roomProjection:projection})));
|
||||
assert.equal(session.roomProjection,null);session.open(makeHost());assert.equal(session.roomProjection,projection);session.close();assert.equal(session.roomProjection,null);
|
||||
const unsupported=new GameSessionHost(makeEntry(()=>makeModule([])));unsupported.open(makeHost());assert.equal(unsupported.roomProjection,null);unsupported.dispose();
|
||||
});
|
||||
|
||||
test('an undefined module is rejected only when the actual room opens',()=>{
|
||||
let creations=0;
|
||||
const entry=makeEntry(()=>{creations++;return undefined as never;});
|
||||
const sessions=new GameSessionHost(entry);
|
||||
assert.equal(creations,0);assert.equal(sessions.state,'idle');
|
||||
assert.throws(()=>sessions.open(makeHost()),/module/);
|
||||
assert.equal(creations,1);assert.equal(sessions.state,'idle');sessions.close();sessions.dispose();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user