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:
2026-09-09 03:13:18 +08:00
parent a7ec8aa6a7
commit 35216f75e7
1028 changed files with 835556 additions and 21415 deletions
@@ -7,6 +7,8 @@ const FRAMEWORK_INTERNAL = /framework[\\/](?:net|protocol|platform|application|d
const LEGACY_RUNTIME = /framework[\\/](?:net[\\/]net-client|platform[\\/](?:session|startup|room-rpc-bus|readonly|stores[\\/](?:app-store|player-store|room-store|types))|protocol[\\/]room-handlers)\.ts$/;
const CONTRACTS_PATH = /framework[\\/]sdk[\\/]contracts(?:[\\/]|$)/;
const MIGRATION_ONLY_NAMES = new Set(['GameContext', 'IGameModule']);
// Pre-existing, quarantined prototype entry points; modern code must not import them.
const LEGACY_SCENE_SCRIPT = /scripts[\\/](?:LoginFlow|RoomEventProbe|RoomSceneStart)\.ts$/;
/**
* Recursively scans TypeScript imports for framework/game layering violations.
@@ -15,6 +17,8 @@ const MIGRATION_ONLY_NAMES = new Set(['GameContext', 'IGameModule']);
export function scanImportBoundaries(options) {
const frameworkDir = resolve(options.frameworkDir);
const gamesDir = resolve(options.gamesDir);
const publicDirs = (options.publicDirs ?? []).map(path=>resolve(path));
const appDir = options.appDir ? resolve(options.appDir) : null;
const compilerOptions = readCompilerOptions(options.tsconfigPath);
const resolutionBase = options.tsconfigPath
? dirname(resolve(options.tsconfigPath))
@@ -26,7 +30,7 @@ export function scanImportBoundaries(options) {
);
const violations = [];
for (const file of [...typescriptFiles(frameworkDir), ...typescriptFiles(gamesDir)]) {
for (const file of [...typescriptFiles(frameworkDir), ...typescriptFiles(gamesDir), ...publicDirs.flatMap(dir=>[...typescriptFiles(dir)]), ...(appDir?[...typescriptFiles(appDir)]:[])]) {
const source = readFileSync(file, 'utf8');
for (const imported of extractImports(source, file)) {
const resolved = resolveDependency(
@@ -44,6 +48,8 @@ export function scanImportBoundaries(options) {
resolved,
frameworkDir,
gamesDir,
publicDirs,
appDir,
});
if (violation) violations.push(violation);
}
@@ -273,13 +279,19 @@ function literalCallSpecifier(node) {
}
function findViolation(context) {
const { file, specifier, bindings, resolved, frameworkDir, gamesDir } = context;
const { file, specifier, bindings, resolved, frameworkDir, gamesDir, publicDirs, appDir } = context;
if(appDir && isInside(file,appDir) && resolved && isInside(resolved,gamesDir))return violation(file.replaceAll('\\','/'),specifier,'app composition cannot import concrete games');
const filePath = displayPath(file);
const resolvedPath = resolved ? displayPath(resolved) : specifier;
const isContract = CONTRACTS_PATH.test(filePath);
const isSdk = SDK_ALLOWED.test(filePath);
const isGame = isInside(file, gamesDir);
const isFramework = isInside(file, frameworkDir);
const isPublic = publicDirs.some(dir=>isInside(file,dir));
if ((isFramework || isPublic) && resolved && (isInside(resolved,gamesDir) || (appDir && isInside(resolved,appDir)))) {
return violation(filePath,specifier,'public framework/platform code cannot import games or app composition');
}
if (isContract && !resolved) {
return violation(filePath, specifier, `sdk/contracts may import only sibling contracts; non-relative specifier ${specifier} is forbidden`);
@@ -304,6 +316,11 @@ function findViolation(context) {
return violation(filePath, specifier, `game code may import only framework/sdk; game import resolved to ${resolvedPath}`);
}
if (isGame && resolved && isInside(resolved,gamesDir)
&& relative(gamesDir,file).split(/[\\/]/)[0] !== relative(gamesDir,resolved).split(/[\\/]/)[0]) {
return violation(filePath,specifier,'game code cannot import another game');
}
if (importsMigrationOnlyName(bindings)) {
const importer = isGame ? 'game code' : 'production code';
return violation(filePath, specifier, `${importer} cannot import ${migrationOnlyName(bindings)}; it is migration-only`);
@@ -313,7 +330,7 @@ function findViolation(context) {
return violation(filePath, specifier, `framework code cannot import games; framework import resolved to ${resolvedPath}`);
}
if (resolved && LEGACY_RUNTIME.test(resolvedPath) && !LEGACY_RUNTIME.test(filePath)) {
if (resolved && (LEGACY_RUNTIME.test(resolvedPath)||LEGACY_SCENE_SCRIPT.test(resolvedPath)) && !LEGACY_RUNTIME.test(filePath)&&!LEGACY_SCENE_SCRIPT.test(filePath)) {
return violation(filePath, specifier, `new production code cannot import quarantined legacy runtime ${resolvedPath}`);
}