feat(sdk): define single-game public contracts

This commit is contained in:
2026-09-05 00:41:39 +08:00
parent bdd8ddb2f6
commit e1556d50f4
13 changed files with 385 additions and 2 deletions
@@ -0,0 +1,10 @@
import type { GameModule } from './game-module.ts';
/** The one compile-time game identity and module factory for an app. */
export interface GameEntry {
readonly key: string;
readonly gameId: string | number;
readonly route: string;
resolveSeatCount(roomtype: readonly unknown[]): number;
createModule(): GameModule;
}
@@ -0,0 +1,15 @@
import type { GameSeatMapper } from './game-seat-mapper.ts';
import type { PlatformGameSnapshot } from './snapshots.ts';
export type GameHostCommand =
| { readonly type: 'room.prepare' }
| { readonly type: 'room.exit' };
/** The only platform capability surface available to a game module. */
export interface GameHost {
readonly seat: GameSeatMapper;
getSnapshot(): PlatformGameSnapshot;
subscribe(listener: (snapshot: PlatformGameSnapshot) => void): () => void;
sendGameMessage(rpc: string, data: unknown): void;
execute(command: GameHostCommand): void;
}
@@ -0,0 +1,11 @@
import type { GameHost } from './game-host.ts';
import type { GameServerMessage, PlatformToGameEvent } from './platform-events.ts';
/** Per-room game implementation with an explicit lifecycle. */
export interface GameModule {
attach(host: GameHost): void;
handlePlatformEvent(event: PlatformToGameEvent): void;
handleGameMessage(message: GameServerMessage): void;
restore(deskinfo: unknown): void;
dispose(): void;
}
@@ -0,0 +1,5 @@
/** Maps server seat numbers to the current game's view-seat numbers. */
export interface GameSeatMapper {
toView(serverSeat: number): number;
toServer(viewSeat: number): number;
}
@@ -0,0 +1,6 @@
export * from './game-entry.ts';
export * from './game-host.ts';
export * from './game-module.ts';
export * from './game-seat-mapper.ts';
export * from './platform-events.ts';
export * from './snapshots.ts';
@@ -0,0 +1,15 @@
/** Platform lifecycle events delivered to the current game module. */
export type PlatformToGameEvent =
| { readonly type: 'room.entered'; readonly roomtype: readonly unknown[] }
| { readonly type: 'room.player-joined'; readonly seat: number }
| { readonly type: 'room.player-left'; readonly seat: number }
| { readonly type: 'room.player-ready'; readonly seat: number }
| { readonly type: 'room.player-offline'; readonly seat: number }
| { readonly type: 'room.player-online'; readonly seat: number }
| { readonly type: 'room.dissolved' };
/** Opaque game-route server message; only the game interprets its data. */
export interface GameServerMessage {
readonly rpc: string;
readonly data: unknown;
}
@@ -0,0 +1,45 @@
/** Public connection phases available to a game. */
export type PlatformGameConnectionPhase =
| 'connected'
| 'logged-in'
| 'reconnecting'
| 'slow'
| 'kicked';
/** Plain public connection projection. */
export interface PlatformGameAppSnapshot {
readonly phase: PlatformGameConnectionPhase;
}
/** Plain public identity projection for the local player. */
export interface PlatformGameSelfSnapshot {
readonly playerId: number;
readonly seat: number;
}
/** Plain public room projection. `roomtype` remains opaque server data. */
export interface PlatformGameRoomSnapshot {
readonly roomcode: string;
readonly roomtype: readonly unknown[];
readonly stage: number;
readonly needprepare: number;
readonly infinite: number;
}
/** Plain public player projection for one occupied game seat. */
export interface GameSeatSnapshot {
readonly seat: number;
readonly playerId: number;
readonly nickname: string;
readonly avatar: string;
readonly online: boolean;
readonly ready: boolean;
}
/** Immutable platform data exposed to a game module. */
export interface PlatformGameSnapshot {
readonly connection: PlatformGameAppSnapshot;
readonly self: PlatformGameSelfSnapshot;
readonly room: PlatformGameRoomSnapshot;
readonly seats: readonly GameSeatSnapshot[];
}
@@ -1,3 +1,5 @@
export * from './contracts/index.ts';
import type { EventBus } from '../core/events.ts';
import type { ReadonlyReactive } from '../core/reactive.ts';
import type { ReadonlyPlayerStore } from '../platform/readonly.ts';
@@ -26,6 +28,8 @@ export interface GameSeat {
}
/**
* @deprecated migration-only. Use GameHost from sdk/contracts instead.
*
* GameContext:子游戏调用框架能力的唯一入口。
*
* 只暴露只读 Store + 受限 net.send + 座位工具 + 事件总线。
@@ -47,6 +51,8 @@ export interface GameContext {
}
/**
* @deprecated migration-only. Use GameModule from sdk/contracts instead.
*
* IGameModule:子游戏实现,被框架调用(框架 spec §4)。
*
* 子游戏实现这个接口并注册自己;框架 Router 据 route 分发对局包。
@@ -92,4 +98,4 @@ export interface IGameModule {
onDissolve?(): void;
/** 玩家离线/上线(在线由 onPlayerJoin 重复触发,离线由本钩子)。 */
onOffline?(seat: number): void;
}
}