Files
FlowScope/docs/superpowers/plans/2026-05-20-p1-production-hardening-plan.md
2026-05-20 12:08:11 +08:00

1041 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<object> 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<string> 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<T> ParseRows<T>(
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<TPanel> PushAsync<TPanel, TViewModel>(
TViewModel viewModel,
CancellationToken cancellationToken)
where TPanel : UIPanelBase<TViewModel>;
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<TPanel>(CancellationToken cancellationToken);
bool IsPreloaded<TPanel>();
void Release<TPanel>();
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<object> Gate { get; } = new();
public async Task<object> 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<object> 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<object>)method.Invoke(null, new object[] { key, cancellationToken });
}
public void Release(string key, Type assetType, object asset)
{
Addressables.Release(asset);
}
private static async Task<object> LoadTypedAsync<T>(string key, CancellationToken cancellationToken)
{
var handle = Addressables.LoadAssetAsync<T>(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<T>("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<T>`:
```csharp
public sealed class JsonConfigParser : IConfigParser
{
public string Format => "json";
public IReadOnlyList<T> ParseRows<T>(string fileName, string text)
where T : class, IConfigRow
{
return ReactivePropertyJsonConverter.DeserializeArray<T>(text);
}
}
```
- [ ] **Step 3: Implement config sources**
Create `InMemoryConfigSource` for tests and simple samples:
```csharp
public sealed class InMemoryConfigSource : IConfigSource
{
private readonly Dictionary<string, string> _files;
public string Name { get; }
public Task<string> 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<T>(string fileName, Func<CancellationToken, Task<string>> loadTextAsync)
```
Add overload:
```csharp
JsonConfigProvider.Mapping<T>(
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<int> 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<TPanel>` 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<Action> _closeStack = new();
}
```
Rules:
- `PushAsync<TPanel, TViewModel>` calls `UIManager.OpenAsync`.
- It pushes a close action that calls `UIManager.Close<TPanel>()`.
- `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 实现。