From 6da0a7822ca462fbdff0cc1962e088b13021c802 Mon Sep 17 00:00:00 2001 From: "JSD\\13999" <1399945104@qq.com> Date: Wed, 20 May 2026 12:08:11 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=B6=E5=AE=9A=20Game=20Core=20P1=20?= =?UTF-8?q?=E7=94=9F=E4=BA=A7=E5=8C=96=E5=AE=9E=E6=96=BD=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...2026-05-20-p1-production-hardening-plan.md | 1040 +++++++++++++++++ 1 file changed, 1040 insertions(+) create mode 100644 docs/superpowers/plans/2026-05-20-p1-production-hardening-plan.md diff --git a/docs/superpowers/plans/2026-05-20-p1-production-hardening-plan.md b/docs/superpowers/plans/2026-05-20-p1-production-hardening-plan.md new file mode 100644 index 0000000..c1b9d1a --- /dev/null +++ b/docs/superpowers/plans/2026-05-20-p1-production-hardening-plan.md @@ -0,0 +1,1040 @@ +# FlowScope Game Core P1 Production Hardening Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 在 P0 全绿和人工验收完成后,交付第一批 P1 生产化能力:资源后端适配层、配置/存档扩展点、轻量 UI 导航与预加载、MainMenu 样例验收升级,以及框架使用规范。 + +**Architecture:** P1 不重写 P0 核心,不引入全局 EventBus、大型 DI、完整包生态或编辑器工具链。所有新增能力优先通过小接口和适配层扩展,保持 `FlowScope.Runtime` 主接口稳定;涉及 Addressables 的强依赖从核心 Runtime 中拆出为独立适配程序集。每个任务独立提交,最终由集成任务跑全量 EditMode、PlayMode 和人工样例验收。 + +**Tech Stack:** Unity 2022.3.62f2c1, C#, Unity Test Framework, R3, Addressables, Task/CancellationToken, asmdef, JSON 本地配置/存档。 + +--- + +## P1 范围确认 + +### 本轮 P1 包含 + +1. 资源生产化第一步:引入 `IResourceBackend`,将 Addressables 反射适配从 `FlowScope.Runtime` 拆到 `FlowScope.Addressables`。 +2. 配置扩展点:引入 `IConfigSource` 与 `IConfigParser`,让 JSON provider 从“硬编码 JSON 文本映射”变成“source + parser”的组合。 +3. 存档迁移扩展点:引入 `ISaveMigration` 与带版本包裹的 JSON serializer,支持单 key 数据从旧版本迁移到当前版本。 +4. UI 生产化第一步:引入轻量 `IUIScreenNavigator` 与 panel 预加载服务,仍保持 `UIManager` 的层内 LIFO 规则。 +5. MainMenuP0 升级为 P1 验收样例:验证 UI 打开、Gold 点击递增、保存恢复、资源/订阅释放。 +6. 使用规范文档:Bootstrap、Feature 生命周期、资源释放、UI 路径、配置 DTO、存档迁移、资源后端接入。 + +### 本轮 P1 不包含 + +- Luban 完整接入。 +- YooAsset / AssetBundle 后端实现。 +- 云存档、加密、压缩、冲突解决。 +- 完整 UI 路由系统、URL 式导航、跨 Feature 页面恢复。 +- AudioMixer、3D 音频、动态音乐。 +- Unity Package 分发结构、Samples~、编辑器导入向导。 +- Source Generator 深度优化。 + +--- + +## 推荐工作流 + +1. 从当前 `main` 创建 `p1-contracts` worktree/branch。 +2. Task 0 完成并合并后,再并行执行 Task A、Task B、Task C。 +3. Task D 依赖 A/B/C 合并后执行。 +4. Task E 做最终集成、文档同步和验收。 +5. 所有 plan、汇报、commit message 使用中文。 + +推荐 worktrees: + +| Worktree | Purpose | +| --- | --- | +| `p1-contracts` | P1 小接口、asmdef 边界、文档边界 | +| `p1-resource-backend` | `IResourceBackend` 与 Addressables 独立适配 | +| `p1-config-save-extensions` | `IConfigSource`、`IConfigParser`、`ISaveMigration` | +| `p1-ui-navigation-preload` | `IUIScreenNavigator` 与 Panel 预加载 | +| `p1-sample-docs` | MainMenuP0 验收升级和使用规范 | +| `p1-integration` | 合并、修缝、全量验证 | + +--- + +## Task 0: P1 Contracts Worktree + +**Worktree:** `p1-contracts` + +**Goal:** 先稳定 P1 的接口边界,让后续并行任务不互相改核心契约。 + +**Owned files:** +- Create: `My project/Assets/FlowScope/Runtime/Resources/IResourceBackend.cs` +- Create: `My project/Assets/FlowScope/Runtime/Config/IConfigSource.cs` +- Create: `My project/Assets/FlowScope/Runtime/Config/IConfigParser.cs` +- Create: `My project/Assets/FlowScope/Runtime/Save/ISaveMigration.cs` +- Create: `My project/Assets/FlowScope/Runtime/UI/IUIScreenNavigator.cs` +- Create: `My project/Assets/FlowScope/Runtime/UI/IUIPreloadService.cs` +- Modify: `docs/requirements/p0-requirements-set.md` +- Create: `docs/requirements/p1-production-hardening.md` + +**Do not modify:** +- `My project/Assets/FlowScope/Runtime/Resources/AddressablesResourceService.cs` +- `My project/Assets/FlowScope/Runtime/Config/JsonConfigProvider.cs` +- `My project/Assets/FlowScope/Runtime/Save/SaveService.cs` +- `My project/Assets/FlowScope/Runtime/UI/UIManager.cs` +- Any sample scene or prefab files. + +- [ ] **Step 1: Create `IResourceBackend` contract** + +Create `My project/Assets/FlowScope/Runtime/Resources/IResourceBackend.cs`: + +```csharp +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace FlowScope.Resources +{ + public interface IResourceBackend + { + string Name { get; } + + Task LoadAsync( + string key, + Type assetType, + CancellationToken cancellationToken); + + void Release(string key, Type assetType, object asset); + } +} +``` + +- [ ] **Step 2: Create config extension contracts** + +Create `My project/Assets/FlowScope/Runtime/Config/IConfigSource.cs`: + +```csharp +using System.Threading; +using System.Threading.Tasks; + +namespace FlowScope.Config +{ + public interface IConfigSource + { + string Name { get; } + + Task LoadTextAsync( + string fileName, + CancellationToken cancellationToken); + } +} +``` + +Create `My project/Assets/FlowScope/Runtime/Config/IConfigParser.cs`: + +```csharp +using System; +using System.Collections.Generic; + +namespace FlowScope.Config +{ + public interface IConfigParser + { + string Format { get; } + + IReadOnlyList ParseRows( + string fileName, + string text) + where T : class, IConfigRow; + } +} +``` + +- [ ] **Step 3: Create save migration contract** + +Create `My project/Assets/FlowScope/Runtime/Save/ISaveMigration.cs`: + +```csharp +namespace FlowScope.Save +{ + public interface ISaveMigration + { + string Key { get; } + int FromVersion { get; } + int ToVersion { get; } + + string Migrate(string json); + } +} +``` + +- [ ] **Step 4: Create UI navigation and preload contracts** + +Create `My project/Assets/FlowScope/Runtime/UI/IUIScreenNavigator.cs`: + +```csharp +using System.Threading; +using System.Threading.Tasks; + +namespace FlowScope.UI +{ + public interface IUIScreenNavigator + { + Task PushAsync( + TViewModel viewModel, + CancellationToken cancellationToken) + where TPanel : UIPanelBase; + + bool CanGoBack { get; } + + void GoBack(); + + void Clear(); + } +} +``` + +Create `My project/Assets/FlowScope/Runtime/UI/IUIPreloadService.cs`: + +```csharp +using System.Threading; +using System.Threading.Tasks; + +namespace FlowScope.UI +{ + public interface IUIPreloadService + { + Task PreloadAsync(CancellationToken cancellationToken); + bool IsPreloaded(); + void Release(); + void ReleaseAll(); + } +} +``` + +- [ ] **Step 5: Write P1 requirement summary** + +Create `docs/requirements/p1-production-hardening.md` with these sections: + +```markdown +# FlowScope Game Core P1 生产化需求 + +## 目标 + +P1 在 P0 核心接口稳定后推进,目标是补齐第一批生产化扩展点,而不是扩张成完整框架生态。 + +## 本轮范围 + +- `IResourceBackend` 与 Addressables 独立适配。 +- `IConfigSource` / `IConfigParser` 配置扩展点。 +- `ISaveMigration` 存档迁移扩展点。 +- `IUIScreenNavigator` 与 Panel 预加载。 +- MainMenuP0 样例验收升级。 +- 使用规范文档。 + +## 不进入本轮 + +- Luban 完整接入。 +- YooAsset / AssetBundle 后端实现。 +- 云存档。 +- AudioMixer / 3D 音频。 +- Package 分发。 +``` + +- [ ] **Step 6: Compile contracts** + +Run: + +```powershell +dotnet build "My project\FlowScope.Runtime.csproj" --no-restore +``` + +Expected: `0 个错误`. Existing Unity package reference warnings are acceptable. + +- [ ] **Step 7: Commit contracts** + +```powershell +git add "My project/Assets/FlowScope/Runtime/Resources/IResourceBackend.cs" ` + "My project/Assets/FlowScope/Runtime/Config/IConfigSource.cs" ` + "My project/Assets/FlowScope/Runtime/Config/IConfigParser.cs" ` + "My project/Assets/FlowScope/Runtime/Save/ISaveMigration.cs" ` + "My project/Assets/FlowScope/Runtime/UI/IUIScreenNavigator.cs" ` + "My project/Assets/FlowScope/Runtime/UI/IUIPreloadService.cs" ` + "docs/requirements/p1-production-hardening.md" ` + "docs/requirements/p0-requirements-set.md" +git commit -m "定义 Game Core P1 生产化契约" +``` + +--- + +## Task A: Resource Backend Split + +**Worktree:** `p1-resource-backend` + +**Depends on:** Task 0 + +**Goal:** 让核心资源服务只依赖 `IResourceBackend`,将 Addressables 具体依赖放进独立 asmdef,降低 Runtime 核心升级风险。 + +**Owned files:** +- Modify: `My project/Assets/FlowScope/Runtime/Resources/AddressablesResourceService.cs` +- Create: `My project/Assets/FlowScope/Runtime/Resources/ResourceService.cs` +- Create: `My project/Assets/FlowScope/Addressables/FlowScope.Addressables.asmdef` +- Create: `My project/Assets/FlowScope/Addressables/AddressablesResourceBackend.cs` +- Create: `My project/Assets/FlowScope/Tests/EditMode/Resources/ResourceServiceBackendTests.cs` +- Create: `My project/Assets/FlowScope/Tests/PlayMode/Resources/AddressablesResourceBackendTests.cs` +- Modify: `My project/Assets/FlowScope/Tests/PlayMode/FlowScope.Tests.PlayMode.asmdef` + +**Do not modify:** +- Config, Save, UI, Audio, Flow runtime files. +- MainMenu sample files. + +- [ ] **Step 1: Write backend service edit mode tests** + +Create `My project/Assets/FlowScope/Tests/EditMode/Resources/ResourceServiceBackendTests.cs` with tests for: + +```csharp +[Test] +public void LoadAsync_WhenSameKeyLoadsConcurrently_UsesOneBackendLoad() + +[Test] +public void LoadAsync_WhenCanceled_DoesNotCreateHandle() + +[Test] +public void DisposeLastHandle_ReleasesBackendAsset() + +[Test] +public void CreateGroup_DisposesAllHandles() +``` + +Use a fake backend: + +```csharp +private sealed class FakeResourceBackend : IResourceBackend +{ + public string Name => "Fake"; + public int LoadCalls { get; private set; } + public int ReleaseCalls { get; private set; } + public TaskCompletionSource Gate { get; } = new(); + + public async Task LoadAsync(string key, Type assetType, CancellationToken cancellationToken) + { + LoadCalls++; + return await Gate.Task; + } + + public void Release(string key, Type assetType, object asset) + { + ReleaseCalls++; + } +} +``` + +- [ ] **Step 2: Implement backend-based `ResourceService`** + +Create `ResourceService` by moving current reference counting, shared load, group creation, and completed-load behavior from `AddressablesResourceService` into: + +```csharp +public sealed class ResourceService : IResourceService, ICompletedResourceService +{ + public ResourceService(IResourceBackend backend) + { + _backend = backend ?? throw new ArgumentNullException(nameof(backend)); + } +} +``` + +Keep P0 semantics: + +- Same key + type concurrent load shares backend load. +- Each caller receives an independent handle. +- Last handle disposal calls backend release. +- Canceled waiter does not prevent other waiters from completing. +- If all waiters cancel before backend completes, release completed asset when backend eventually resolves. + +- [ ] **Step 3: Convert `AddressablesResourceService` into compatibility wrapper** + +Modify `AddressablesResourceService` to preserve existing API: + +```csharp +public sealed class AddressablesResourceService : IResourceService, ICompletedResourceService +{ + private readonly ResourceService _inner; + + public AddressablesResourceService() + : this(new AddressablesResourceBackend()) + { + } + + public AddressablesResourceService(IResourceBackend backend) + { + _inner = new ResourceService(backend); + } +} +``` + +If `AddressablesResourceBackend` lives in a separate asmdef and cannot be referenced by Runtime, keep this wrapper in `FlowScope.Addressables` instead and update usages in sample/integration. Do not keep reflection backend in `FlowScope.Runtime`. + +- [ ] **Step 4: Create Addressables asmdef** + +Create `My project/Assets/FlowScope/Addressables/FlowScope.Addressables.asmdef`: + +```json +{ + "name": "FlowScope.Addressables", + "rootNamespace": "FlowScope.Addressables", + "references": [ + "FlowScope.Runtime", + "Unity.Addressables" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} +``` + +- [ ] **Step 5: Implement typed Addressables backend** + +Create `AddressablesResourceBackend` using direct API references: + +```csharp +using System; +using System.Threading; +using System.Threading.Tasks; +using FlowScope.Resources; +using UnityEngine.AddressableAssets; + +namespace FlowScope.Addressables +{ + public sealed class AddressablesResourceBackend : IResourceBackend + { + public string Name => "Addressables"; + + public async Task LoadAsync(string key, Type assetType, CancellationToken cancellationToken) + { + var method = typeof(AddressablesResourceBackend) + .GetMethod(nameof(LoadTypedAsync), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic) + .MakeGenericMethod(assetType); + return await (Task)method.Invoke(null, new object[] { key, cancellationToken }); + } + + public void Release(string key, Type assetType, object asset) + { + Addressables.Release(asset); + } + + private static async Task LoadTypedAsync(string key, CancellationToken cancellationToken) + { + var handle = Addressables.LoadAssetAsync(key); + await WaitWithCancellation(handle.Task, cancellationToken); + return handle.Result; + } + } +} +``` + +Add `WaitWithCancellation` helper matching P0 cancellation semantics. + +- [ ] **Step 6: Run resource tests** + +Run: + +```powershell +dotnet build "My project\FlowScope.Tests.EditMode.csproj" --no-restore +dotnet build "My project\FlowScope.Tests.PlayMode.csproj" --no-restore +``` + +Expected: both builds pass with `0 个错误`. + +- [ ] **Step 7: Commit** + +```powershell +git add "My project/Assets/FlowScope/Runtime/Resources" ` + "My project/Assets/FlowScope/Addressables" ` + "My project/Assets/FlowScope/Tests/EditMode/Resources" ` + "My project/Assets/FlowScope/Tests/PlayMode/Resources" ` + "My project/Assets/FlowScope/Tests/PlayMode/FlowScope.Tests.PlayMode.asmdef" +git commit -m "拆分 P1 资源后端适配层" +``` + +--- + +## Task B: Config Source, Parser, and Save Migration + +**Worktree:** `p1-config-save-extensions` + +**Depends on:** Task 0 + +**Goal:** 给配置和存档补扩展点,但不接入 Luban、云存档或复杂对象图。 + +**Owned files:** +- Modify: `My project/Assets/FlowScope/Runtime/Config/JsonConfigProvider.cs` +- Create: `My project/Assets/FlowScope/Runtime/Config/JsonConfigParser.cs` +- Create: `My project/Assets/FlowScope/Runtime/Config/InMemoryConfigSource.cs` +- Create: `My project/Assets/FlowScope/Runtime/Config/FileConfigSource.cs` +- Modify: `My project/Assets/FlowScope/Runtime/Save/JsonSaveSerializer.cs` +- Create: `My project/Assets/FlowScope/Runtime/Save/SaveEnvelope.cs` +- Create: `My project/Assets/FlowScope/Runtime/Save/SaveMigrationRegistry.cs` +- Test: `My project/Assets/FlowScope/Tests/EditMode/Config/ConfigExtensionTests.cs` +- Test: `My project/Assets/FlowScope/Tests/EditMode/Save/SaveMigrationTests.cs` + +**Do not modify:** +- Container, Resource, UI, Audio, Flow implementation files. +- Existing public `ISaveService` signature. + +- [ ] **Step 1: Write config extension tests** + +Create tests: + +```csharp +[Test] +public void LoadAllAsync_UsesConfiguredSourceAndParser() + +[Test] +public void LoadAllAsync_WhenSourceFails_IncludesSourceAndFileName() + +[Test] +public void LoadAllAsync_WhenParserFindsDuplicateId_IncludesFileName() +``` + +Expected behavior: + +- `JsonConfigProvider.Mapping("player.json", source, parser)` loads from source by file name. +- Parser returns rows and provider indexes by `Id`. +- Duplicate ids still throw. + +- [ ] **Step 2: Implement `JsonConfigParser`** + +Create parser that wraps existing `ReactivePropertyJsonConverter.DeserializeArray`: + +```csharp +public sealed class JsonConfigParser : IConfigParser +{ + public string Format => "json"; + + public IReadOnlyList ParseRows(string fileName, string text) + where T : class, IConfigRow + { + return ReactivePropertyJsonConverter.DeserializeArray(text); + } +} +``` + +- [ ] **Step 3: Implement config sources** + +Create `InMemoryConfigSource` for tests and simple samples: + +```csharp +public sealed class InMemoryConfigSource : IConfigSource +{ + private readonly Dictionary _files; + public string Name { get; } + + public Task LoadTextAsync(string fileName, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (!_files.TryGetValue(fileName, out var text)) + { + throw new InvalidOperationException($"Config file is missing: {fileName}"); + } + return Task.FromResult(text); + } +} +``` + +Create `FileConfigSource` using `File.ReadAllTextAsync` when available; otherwise use `Task.Run(() => File.ReadAllText(path), cancellationToken)`. + +- [ ] **Step 4: Extend `JsonConfigProvider.ConfigMapping` without breaking P0 API** + +Keep current API: + +```csharp +JsonConfigProvider.Mapping(string fileName, Func> loadTextAsync) +``` + +Add overload: + +```csharp +JsonConfigProvider.Mapping( + string fileName, + IConfigSource source, + IConfigParser parser) + where T : class, IConfigRow +``` + +The old overload should internally wrap `loadTextAsync` as an `IConfigSource` and use `JsonConfigParser`. + +- [ ] **Step 5: Write save migration tests** + +Create tests: + +```csharp +[Test] +public void Deserialize_WhenEnvelopeVersionIsOld_AppliesMigrationChain() + +[Test] +public void Deserialize_WhenMigrationIsMissing_ThrowsWithKeyAndVersion() + +[Test] +public void Serialize_WritesEnvelopeWithCurrentVersion() +``` + +Use a fake data class: + +```csharp +private sealed class PlayerSaveV2 +{ + public R3.ReactiveProperty Gold { get; set; } = new(); +} +``` + +- [ ] **Step 6: Implement save envelope and migration registry** + +Create `SaveEnvelope`: + +```csharp +internal sealed class SaveEnvelope +{ + public int Version { get; set; } + public string Payload { get; set; } +} +``` + +Create `SaveMigrationRegistry`: + +```csharp +public sealed class SaveMigrationRegistry +{ + public void Register(ISaveMigration migration); + + public string Migrate( + string key, + int fromVersion, + int currentVersion, + string json); +} +``` + +Migration chain must apply `fromVersion -> fromVersion + 1` until `currentVersion`. Missing step throws `InvalidOperationException` containing key and version. + +- [ ] **Step 7: Extend `JsonSaveSerializer`** + +Add constructor: + +```csharp +public JsonSaveSerializer( + int currentVersion = 1, + SaveMigrationRegistry migrations = null) +``` + +Behavior: + +- Existing raw JSON saves remain readable as version `1`. +- New saves are wrapped in envelope when `currentVersion > 1`. +- Migration applies before `PopulateObject`. + +- [ ] **Step 8: Run config/save tests** + +Run: + +```powershell +dotnet build "My project\FlowScope.Tests.EditMode.csproj" --no-restore +``` + +Expected: `0 个错误`. + +- [ ] **Step 9: Commit** + +```powershell +git add "My project/Assets/FlowScope/Runtime/Config" ` + "My project/Assets/FlowScope/Runtime/Save" ` + "My project/Assets/FlowScope/Tests/EditMode/Config" ` + "My project/Assets/FlowScope/Tests/EditMode/Save" +git commit -m "扩展 P1 配置来源和存档迁移" +``` + +--- + +## Task C: UI Navigation and Panel Preload + +**Worktree:** `p1-ui-navigation-preload` + +**Depends on:** Task 0 + +**Goal:** 在不破坏 P0 `UIManager` 层内 LIFO 的前提下,提供轻量页面返回策略和 Panel 预加载。 + +**Owned files:** +- Modify: `My project/Assets/FlowScope/Runtime/UI/UIManager.cs` +- Create: `My project/Assets/FlowScope/Runtime/UI/UIPreloadService.cs` +- Create: `My project/Assets/FlowScope/Runtime/UI/UIScreenNavigator.cs` +- Test: `My project/Assets/FlowScope/Tests/PlayMode/UI/UIPreloadServiceTests.cs` +- Test: `My project/Assets/FlowScope/Tests/PlayMode/UI/UIScreenNavigatorTests.cs` +- Modify: `My project/Assets/FlowScope/Tests/PlayMode/UI/UIManagerTests.cs` + +**Do not modify:** +- Resource service internals. +- MainMenu sample except in Task D. +- UI panel base contract unless Task 0 contract is insufficient and integration owner approves. + +- [ ] **Step 1: Write preload tests** + +Create tests: + +```csharp +[UnityTest] +public IEnumerator PreloadAsync_LoadsPanelResourceAndKeepsHandle() + +[UnityTest] +public IEnumerator OpenAsync_WhenPanelIsPreloaded_ReusesPreloadedHandle() + +[UnityTest] +public IEnumerator Release_DisposesPreloadedHandle() +``` + +Fake resource service must count load and dispose calls. + +- [ ] **Step 2: Implement `UIPreloadService`** + +Behavior: + +- Resolve panel path using same rules as `UIManager`. +- Load `GameObject` handle and store by panel type. +- Repeated preload for same panel is no-op. +- `Release` disposes that handle. +- `Dispose` releases all preloaded handles. + +- [ ] **Step 3: Add optional preload lookup to `UIManager`** + +Add constructor overload: + +```csharp +public UIManager(IResourceService resources, IUIPreloadService preloadService = null) +``` + +Open behavior: + +- If preload service has a handle for the requested panel, create an additional handle only if the resource service contract requires independent handle ownership. +- If preloaded handle can be safely reused, keep preload handle owned by preload service and let panel record use a lightweight retained handle. +- Tests must prove closing a preloaded Destroy panel does not dispose the preload cache. + +- [ ] **Step 4: Write navigator tests** + +Create tests: + +```csharp +[UnityTest] +public IEnumerator PushAsync_OpensPanelAndRecordsHistory() + +[UnityTest] +public IEnumerator GoBack_ClosesTopPanel() + +[UnityTest] +public IEnumerator Clear_ClosesAllNavigatorPanels() + +[UnityTest] +public IEnumerator GoBack_WhenOnlyOnePanelOpen_DoesNothing() +``` + +- [ ] **Step 5: Implement `UIScreenNavigator`** + +Create `UIScreenNavigator`: + +```csharp +public sealed class UIScreenNavigator : IUIScreenNavigator +{ + private readonly UIManager _uiManager; + private readonly Stack _closeStack = new(); +} +``` + +Rules: + +- `PushAsync` calls `UIManager.OpenAsync`. +- It pushes a close action that calls `UIManager.Close()`. +- `CanGoBack` is true when close stack count is greater than 1. +- `GoBack` closes only top screen if `CanGoBack` is true. +- `Clear` closes all panels opened by navigator. +- It does not close panels opened outside navigator. + +- [ ] **Step 6: Run UI play mode build** + +Run: + +```powershell +dotnet build "My project\FlowScope.Tests.PlayMode.csproj" --no-restore +``` + +Expected: `0 个错误`. + +- [ ] **Step 7: Commit** + +```powershell +git add "My project/Assets/FlowScope/Runtime/UI" ` + "My project/Assets/FlowScope/Tests/PlayMode/UI" +git commit -m "扩展 P1 UI 导航和预加载" +``` + +--- + +## Task D: MainMenuP0 Sample and Usage Docs + +**Worktree:** `p1-sample-docs` + +**Depends on:** Task A, Task B, Task C + +**Goal:** 让样例成为 P1 规则的可见验收样板,并补清楚框架使用约定。 + +**Owned files:** +- Modify: `My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/GameBootstrap.cs` +- Modify: `My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/MainMenuFeature.cs` +- Modify: `My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/MainMenuPanel.cs` +- Modify: `My project/Assets/FlowScope/Samples/MainMenuP0/Scenes/MainMenuP0.unity` +- Modify: `My project/Assets/FlowScope/Samples/MainMenuP0/Prefabs/MainMenuPanel.prefab` +- Modify: `My project/Assets/FlowScope/Tests/PlayMode/Samples/MainMenuP0Tests.cs` +- Create: `docs/guides/flowscope-runtime-usage.md` +- Create: `docs/guides/main-menu-p1-sample.md` + +**Do not modify:** +- Core Resource, Config, Save, UI implementations except through public APIs. +- Test assembly asmdefs unless a new adapter assembly reference is required. + +- [ ] **Step 1: Write upgraded sample play mode tests** + +Update `MainMenuP0Tests.cs` to include: + +```csharp +[UnityTest] +public IEnumerator Bootstrap_StartsAndOpensVisibleMainMenuPanel() + +[UnityTest] +public IEnumerator ClickIncrement_UpdatesGoldTextImmediately() + +[UnityTest] +public IEnumerator ShutdownAndRestart_RestoresSavedGold() + +[UnityTest] +public IEnumerator Shutdown_ReleasesFeatureResourcesAndSubscriptions() +``` + +Tests may instantiate `GameBootstrap` in an inactive root and call `StartupAsync` manually to avoid `Awake async void` races. + +- [ ] **Step 2: Update `GameBootstrap` registration to use P1 extension points** + +Use: + +- `IConfigSource` / `JsonConfigParser` for config. +- `JsonSaveSerializer(currentVersion: 1, migrations: registry)` for save. +- `ResourceService` with sample backend or Addressables backend depending on Task A outcome. +- `UIPreloadService` and `UIScreenNavigator` if sample flow uses navigation. + +Keep no-console-error behavior. + +- [ ] **Step 3: Make sample UI visibly meaningful** + +Ensure scene/prefab contains: + +- A Canvas. +- A configured `MainMenuPanel`. +- A visible Gold text. +- A visible increment button. +- Serialized references wired in prefab or scene where appropriate. + +Runtime fallback creation may remain, but manual sample validation should use real scene/prefab references. + +- [ ] **Step 4: Write runtime usage guide** + +Create `docs/guides/flowscope-runtime-usage.md` with sections: + +```markdown +# FlowScope Runtime 使用规范 + +## Bootstrap +## Container 注册规则 +## Feature 生命周期 +## 资源所有权 +## UI 路径和层级 +## 配置 DTO 约束 +## 存档版本迁移 +## Addressables 后端接入 +## 测试建议 +``` + +- [ ] **Step 5: Write sample guide** + +Create `docs/guides/main-menu-p1-sample.md` with: + +- How to open the scene. +- What the user should see. +- How Gold increment works. +- How save/restore is validated. +- What Console output is acceptable. + +- [ ] **Step 6: Run sample tests** + +Run: + +```powershell +dotnet build "My project\FlowScope.Tests.PlayMode.csproj" --no-restore +``` + +Then in Unity Test Runner run: + +- PlayMode: `FlowScope.Tests.PlayMode.Samples.MainMenuP0Tests` +- PlayMode full suite + +Expected: all green, no unexpected Console Error. + +- [ ] **Step 7: Manual validation** + +Open: + +```text +My project/Assets/FlowScope/Samples/MainMenuP0/Scenes/MainMenuP0.unity +``` + +Verify: + +- UI opens. +- Gold increments on click. +- UI updates immediately. +- Exit saves data. +- Restart restores data. +- Closing Feature releases resources and subscriptions. + +- [ ] **Step 8: Commit** + +```powershell +git add "My project/Assets/FlowScope/Samples/MainMenuP0" ` + "My project/Assets/FlowScope/Tests/PlayMode/Samples" ` + "docs/guides/flowscope-runtime-usage.md" ` + "docs/guides/main-menu-p1-sample.md" +git commit -m "升级 P1 主菜单样例和使用规范" +``` + +--- + +## Task E: Final P1 Integration + +**Worktree:** `p1-integration` + +**Depends on:** Task A, Task B, Task C, Task D + +**Goal:** 合并全部 P1 任务,修正模块边界缝隙,完成全量验证与最终文档。 + +**Owned files:** +- Modify only files needed to resolve integration seams. +- Modify: `docs/requirements/p1-production-hardening.md` +- Modify: `docs/reviews/flowscope-framework-review.md` +- Create: `docs/reviews/p1-completion-report.md` + +**Do not modify:** +- Add new P1 feature scope beyond this plan. +- Introduce P2 package/editor tooling. +- Revert P0 behavior unless tests prove a bug and doc is updated. + +- [ ] **Step 1: Merge P1 branches in order** + +Recommended order: + +```text +p1-contracts +p1-resource-backend +p1-config-save-extensions +p1-ui-navigation-preload +p1-sample-docs +``` + +- [ ] **Step 2: Run compile validation** + +Run: + +```powershell +dotnet build "My project\FlowScope.Runtime.csproj" --no-restore +dotnet build "My project\FlowScope.Tests.EditMode.csproj" --no-restore +dotnet build "My project\FlowScope.Tests.PlayMode.csproj" --no-restore +``` + +Expected: all pass with `0 个错误`. + +- [ ] **Step 3: Run Unity Test Runner** + +Run in Unity: + +- EditMode full suite. +- PlayMode full suite. + +Expected: + +- EditMode all green. +- PlayMode all green. +- No unexpected Error logs. + +- [ ] **Step 4: Manual sample validation** + +Repeat Task D manual validation after all branches are merged. + +- [ ] **Step 5: Update review and completion docs** + +Create `docs/reviews/p1-completion-report.md`: + +```markdown +# FlowScope Game Core P1 完成报告 + +## 范围 +## 已完成 +## 测试结果 +## 人工验收 +## 未进入本轮 +## P2 建议 +``` + +Update `docs/reviews/flowscope-framework-review.md` risk table: + +- Mark Addressables reflection risk as resolved or reduced. +- Mark Config/Save extension risk as reduced. +- Keep package/editor tooling as P2. + +- [ ] **Step 6: Final status check** + +Run: + +```powershell +git -c core.quotepath=false status --short +git diff --check +``` + +Expected: + +- Only intended files changed before final commit. +- `git diff --check` has no whitespace errors. + +- [ ] **Step 7: Commit integration** + +```powershell +git add "My project/Assets/FlowScope" docs +git commit -m "完成 Game Core P1 生产化集成" +``` + +--- + +## 验收标准总表 + +| Area | Acceptance | +| --- | --- | +| Resource | `FlowScope.Runtime` 不再包含 Addressables 反射 backend;核心资源服务通过 `IResourceBackend` 工作;Addressables 适配在独立 asmdef 中。 | +| Config | `JsonConfigProvider` 支持 source + parser;旧 mapping API 继续可用;重复 id、缺文件、格式错误仍有明确异常。 | +| Save | `JsonSaveSerializer` 支持版本 envelope 和 migration chain;旧 raw JSON 存档可读。 | +| UI | Navigator 可 push/back/clear;预加载可加载、复用、释放 panel 资源;P0 UIManager 行为不回退。 | +| Sample | MainMenuP0 场景可见、可点、可保存恢复;测试覆盖 startup/click/shutdown/restart。 | +| Docs | 有 P1 使用规范、样例说明、完成报告;旧评审风险状态同步更新。 | +| Tests | EditMode 全绿;PlayMode 全绿;人工样例验收通过;无未预期 Console Error。 | + +--- + +## 自检清单 + +- [ ] 每个任务都有明确 Owned files。 +- [ ] 每个任务都有 Do not modify 边界。 +- [ ] 每个任务都有测试和提交步骤。 +- [ ] P1 范围没有包含 P2/P3 能力。 +- [ ] P0 现有公开接口不被无故破坏。 +- [ ] 所有 commit message 使用中文。 +- [ ] 执行时使用新对话或新 worktree,避免 P0 调试上下文污染 P1 实现。