# 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`,并移除 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.Threading; using System.Threading.Tasks; namespace FlowScope.Resources { public interface IResourceBackend { string Name { get; } Task LoadAsync( string key, CancellationToken cancellationToken) where T : class; void Release(string key, T asset) where T : class; } } ``` - [ ] **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:** - Delete: `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/Addressables/AddressablesResourceService.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, CancellationToken cancellationToken) where T : class { LoadCalls++; return (T)await Gate.Task; } public void Release(string key, T asset) where T : class { 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. - `ResourceService` must call `IResourceBackend.LoadAsync` directly. Do not use reflection to bridge `Type` to generic calls. - [ ] **Step 3: Move `AddressablesResourceService` compatibility wrapper to adapter assembly** Delete or empty the old Runtime implementation file: ```text My project/Assets/FlowScope/Runtime/Resources/AddressablesResourceService.cs ``` Create a compatibility wrapper in the Addressables adapter assembly: ```text My project/Assets/FlowScope/Addressables/AddressablesResourceService.cs ``` Use namespace `FlowScope.Resources` to preserve existing call sites: ```csharp using System.Threading; using System.Threading.Tasks; namespace FlowScope.Resources { public sealed class AddressablesResourceService : IResourceService, ICompletedResourceService { private readonly ResourceService _inner; public AddressablesResourceService() : this(new AddressablesResourceBackend()) { } public AddressablesResourceService(IResourceBackend backend) { _inner = new ResourceService(backend); } public Task> LoadAsync(string key, CancellationToken cancellationToken) where T : class => _inner.LoadAsync(key, cancellationToken); public IResourceGroup CreateGroup() => _inner.CreateGroup(); public bool TryLoadCompleted(string key, out IResourceHandle handle) where T : class => _inner.TryLoadCompleted(key, out handle); } } ``` Do not keep an Addressables 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 typed Addressables API references. This backend must not use reflection: ```csharp 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, CancellationToken cancellationToken) where T : class { var handle = Addressables.LoadAssetAsync(key); await WaitWithCancellation(handle.Task, cancellationToken); return handle.Result; } public void Release(string key, T asset) where T : class { Addressables.Release(asset); } } } ``` 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 实现。