回滚误入 master 的游戏分发 WIP(夹带在 OSS v1 签名提交里)
Project CI / AI game creator shell Rust shard 2/4 (push) Failing after 19s
Project CI / AI game creator shell Rust shard 3/4 (push) Failing after 20s
Project CI / AI game creator shell Rust shard 1/4 (push) Failing after 20s
Project CI / AI game creator shell Rust shard 4/4 (push) Failing after 20s
Project CI / Backend tests (push) Failing after 17s
Project CI / AI game creator shell Rust crates (push) Failing after 17s
Project CI / AI game creator shell Rust smoke (push) Failing after 19s
Project CI / Native shell tests (push) Failing after 18s
Project CI / Frontend tests (push) Failing after 8s
Project CI / AI game creator shell web tests (push) Failing after 15s
Project CI / Repository checks (push) Failing after 17s

- 撤销 39aed2e48 夹带的游戏分发前后端、SpacetimeDB 绑定、主站路由、后台审核页、文档与 nginx 改动,master 恢复可编译状态
- 保留该提交自身的 AGC 发号改动:agc-global-version.mjs 与其参数构造单测
- 恢复被误删的 output 产物,避免把无关清理混进该提交
- 补回两篇技术文档在 docs/README.md 的索引(它们属于后续两个补提交文档,不属于游戏分发)
- 游戏分发完整实现保留在 codex/game-distribution 分支继续开发
This commit is contained in:
2026-09-20 20:50:23 +08:00
parent a2acfa9a6f
commit 429f991bde
236 changed files with 2016 additions and 2666 deletions
@@ -1,6 +1,5 @@
import {
FolderKanban,
Gamepad2,
Home,
Monitor,
Palette,
@@ -67,31 +66,6 @@ const ProjectGalleryView = lazy(async () => {
return { default: module.ProjectGalleryView };
});
const GameGalleryPage = lazy(async () => {
const module = await import('../game-distribution/GameGalleryPage');
return { default: module.GameGalleryPage };
});
const GameDetailPage = lazy(async () => {
const module = await import('../game-distribution/GameDetailPage');
return { default: module.GameDetailPage };
});
const GamePublishPage = lazy(async () => {
const module = await import('../game-distribution/GamePublishPage');
return { default: module.GamePublishPage };
});
const MyGamesPage = lazy(async () => {
const module = await import('../game-distribution/MyGamesPage');
return { default: module.MyGamesPage };
});
const GamePlayPage = lazy(async () => {
const module = await import('../game-distribution/GamePlayPage');
return { default: module.GamePlayPage };
});
type ActiveRailButtonProps = {
active: boolean;
emphasized?: boolean;
@@ -171,26 +145,16 @@ function ActiveBottomNavButton({
function MobileProfileDock({
active,
onOpenProfile,
gameActive,
onOpenGames,
}: {
active: boolean;
onOpenProfile: () => void;
gameActive: boolean;
onOpenGames: () => void;
}) {
return (
<div className="platform-mobile-bottom-dock min-w-0 shrink-0 lg:hidden">
<nav
className="platform-bottom-nav grid grid-cols-2"
className="platform-bottom-nav grid grid-cols-1"
aria-label="移动平台导航"
>
<ActiveBottomNavButton
active={gameActive}
icon={Gamepad2}
label="游戏"
onClick={onOpenGames}
/>
<ActiveBottomNavButton
active={active}
icon={UserRound}
@@ -394,34 +358,6 @@ export function PlatformEntryFlowShellImpl({
setSelectionStage('profile', { path: '/profile' });
}, [setSelectionStage]);
const openGames = useCallback(() => {
setSelectionStage('games', { path: '/games' });
}, [setSelectionStage]);
const openGamePublish = useCallback(() => {
setSelectionStage('game-publish', { path: '/games/publish' });
}, [setSelectionStage]);
const openMyGames = useCallback(() => {
setSelectionStage('game-mine', { path: '/games/mine' });
}, [setSelectionStage]);
const openGameDetail = useCallback(
(gameId: string) => {
const path = `/games/detail?id=${encodeURIComponent(gameId)}`;
setSelectionStage('game-detail', { path });
},
[setSelectionStage],
);
const openGamePlay = useCallback(
(gameId: string) => {
const path = `/games/play?id=${encodeURIComponent(gameId)}`;
setSelectionStage('game-play', { path });
},
[setSelectionStage],
);
const openEditorProject = useCallback(
(projectId: string, options?: { guide?: boolean; tool?: string }) => {
if (!isDesktopLayout) {
@@ -466,12 +402,7 @@ export function PlatformEntryFlowShellImpl({
setSelectionStage('platform', { path: '/' });
}}
/>
<MobileProfileDock
active={false}
gameActive={false}
onOpenProfile={openProfile}
onOpenGames={openGames}
/>
<MobileProfileDock active={false} onOpenProfile={openProfile} />
<PlatformActiveMobileWelcomeDialog
open={shouldOpenMobileHomeWelcome}
platformThemeClass={platformThemeClass}
@@ -499,15 +430,6 @@ export function PlatformEntryFlowShellImpl({
const isCreationStage =
!isProfileStage &&
(selectionStage === 'platform' || selectionStage === 'creation-home');
const isGamesStage =
selectionStage === 'games' ||
selectionStage === 'game-detail' ||
selectionStage === 'game-play' ||
selectionStage === 'game-mine' ||
selectionStage === 'game-publish';
const gameId = new URLSearchParams(
typeof window === 'undefined' ? '' : window.location.search,
).get('id');
const avatarUrl = authUi?.user?.avatarUrl?.trim() || null;
const avatarLabel = resolveActiveUserAvatarLabel(authUi?.user);
const publicUserCode = resolveActivePublicUserCode(authUi?.user);
@@ -562,14 +484,6 @@ export function PlatformEntryFlowShellImpl({
label="项目"
onClick={openProjects}
/>
<ActiveRailButton
active={isGamesStage}
emphasized={isGamesStage}
icon={Gamepad2}
iconSrc="/creation-home/nav-projects.png"
label="游戏"
onClick={openGames}
/>
<ActiveRailButton
active={isProfileStage}
icon={UserRound}
@@ -601,12 +515,8 @@ export function PlatformEntryFlowShellImpl({
<input
type="search"
value={searchInput}
placeholder={
isGamesStage
? '搜索游戏、作者或标签'
: '搜索项目、素材、作者或描述'
}
aria-label={isGamesStage ? '搜索游戏' : '搜索项目和素材'}
placeholder="搜索项目、素材、作者或描述"
aria-label="搜索项目和素材"
className="min-w-0 flex-1 bg-transparent text-sm text-[var(--platform-text-strong)] outline-none placeholder:text-[var(--platform-text-soft)]"
onChange={(event) => {
const value = event.target.value;
@@ -712,42 +622,6 @@ export function PlatformEntryFlowShellImpl({
searchKeyword={activeSearchKeyword}
/>
</Suspense>
) : isGamesStage ? (
<Suspense
fallback={<LoadingPanel label="正在加载游戏广场..." />}
>
{selectionStage === 'games' ? (
<GameGalleryPage
searchKeyword={activeSearchKeyword}
onOpenDetail={openGameDetail}
onOpenMyGames={openMyGames}
onOpenPublish={openGamePublish}
/>
) : selectionStage === 'game-publish' ? (
<GamePublishPage
onBack={openGames}
onOpenMyGames={openMyGames}
/>
) : selectionStage === 'game-mine' ? (
<MyGamesPage
onBack={openGames}
onOpenDetail={openGameDetail}
/>
) : selectionStage === 'game-detail' ? (
<GameDetailPage
gameId={gameId}
onBack={openGames}
onPlay={openGamePlay}
/>
) : (
<GamePlayPage
gameId={gameId}
onBack={() =>
gameId ? openGameDetail(gameId) : openGames()
}
/>
)}
</Suspense>
) : (
<Suspense fallback={<LoadingPanel label="正在加载项目..." />}>
<ProjectGalleryView
@@ -761,9 +635,7 @@ export function PlatformEntryFlowShellImpl({
</div>
<MobileProfileDock
active={isProfileStage}
gameActive={isGamesStage}
onOpenProfile={openProfile}
onOpenGames={openGames}
/>
</div>
</div>
@@ -3,12 +3,7 @@ export type SelectionStage =
| 'creation-home'
| 'project'
| 'profile'
| 'image-editor'
| 'games'
| 'game-detail'
| 'game-play'
| 'game-mine'
| 'game-publish';
| 'image-editor';
export type PlatformEntryFlowShellProps = {
selectionStage: SelectionStage;