fix(03): revise structural correction plans

This commit is contained in:
unanmed 2026-09-11 10:41:27 +08:00
parent 39d5e366a4
commit 1077d5f65d
6 changed files with 47 additions and 36 deletions

View File

@ -3,12 +3,14 @@ phase: 03-data-completion
plan: 10
type: execute
wave: 6
depends_on: [03-07]
depends_on: [03-07, 03-14]
files_modified:
- packages-user/data-state/src/core.ts
- packages-user/data-state/test/fixtures/closed-loop.ts
- script/test-data-node.ts
files_deleted:
- packages-user/data-state/src/legacy/dependencies.ts
- packages-user/data-state/src/legacy/events.ts
- packages-user/data-state/test/fixtures/closed-loop.ts
- packages-user/data-state/test/coreSerializedEvents.test.ts
autonomous: true
requirements: [DATA-01]
@ -22,16 +24,20 @@ estimate:
must_haves:
truths:
- "CORR-03-01: Phase 3 keeps legacy code as the existing compatibility path and adds no new legacy system or loading boundary (user-authoritative requirement 1)."
- "CoreState uses the pre-Phase-3 compatibility loading path rather than an internally injected legacy architecture, while current replay/event assembly remains intact (D-09, D-10, D-12)."
- "CoreState uses the pre-Phase-3 compatibility loading callbacks, legacy tile/item/enemy converter or bridge attachments, and SaveSystem construction while current replay/event assembly remains intact (D-09, D-10, D-12)."
- "The fixed replay fixture is data-side setup and reaches the existing event store/map APIs without depending on a Phase-3 legacy loader."
- "The fixed Node route has an explicit event-mutation completion boundary before its final snapshot; no event mutation can land after the snapshot (D-13, D-14, D-26)."
artifacts:
- path: "packages-user/data-state/src/core.ts"
provides: "CoreState construction with the existing compatibility loading path and no new legacy dependency seam"
provides: "CoreState construction with the existing compatibility loading callbacks, legacy attachments, SaveSystem, and no new legacy dependency seam"
- path: "packages-user/data-state/test/fixtures/closed-loop.ts"
provides: "Direct explicit data fixture for the closed replay path"
provides: "Direct explicit data fixture and deterministic completion signal for the closed replay path"
- path: "script/test-data-node.ts"
provides: "Node replay verifier boundary that waits for the fixture event mutation before snapshot"
key_links:
- "CoreState -> existing legacy loading callbacks and converters"
- "CoreState -> existing loading callbacks -> legacy tile/item/enemy attachments and SaveSystem"
- "closed-loop fixture -> existing GameEventStore and MapState data APIs"
- "closed-loop event mutation hook -> verifier finish -> final snapshot"
- "CoreState replay import -> @user/data-common barrel, preserving the user's current import cleanup"
---
@ -39,7 +45,7 @@ must_haves:
Close CORR-03-01 by removing the Phase-3 legacy dependency and serialized-loading additions, restoring CoreState to the existing compatibility-only loading path, and changing the replay fixture back to explicit data-side setup.
Purpose: The structural review rejects new legacy systems and loading boundaries. This correction removes only the Phase-3 architecture that caused that drift; it does not begin the Phase-5 legacy migration.
Output: CoreState without the new legacy boundary, removal of the two Phase-3 legacy adapter files, a direct closed-loop fixture, and removal of the serialized-loading regression that only exercised the rejected boundary.
Output: CoreState without the new legacy boundary, removal of the two Phase-3 legacy adapter files, a direct closed-loop fixture with a deterministic completion signal, an explicit verifier barrier, and removal of the serialized-loading regression that only exercised the rejected boundary.
</objective>
<execution_context>
@ -67,11 +73,11 @@ Output: CoreState without the new legacy boundary, removal of the two Phase-3 le
<name>Task 1: CORR-03-01 restore the existing compatibility loading path</name>
<gap_id>CORR-03-01</gap_id>
<files>packages-user/data-state/src/core.ts, packages-user/data-state/src/legacy/dependencies.ts, packages-user/data-state/src/legacy/events.ts</files>
<action>Remove the Phase-3-only dependency factory, serialized-event adapter, symbol-keyed load entry, serialized branch, and raw-map loader added for that boundary. Restore CoreState's existing compatibility construction and loading callbacks using the repository's pre-Phase-3 legacy converters, `loading`, and browser-host data source; keep the existing `SaveSystem` assignment in this restoration, with the save-specific deletion and regression assertions completed by Plan 03-11. Preserve the current event built-in and replay assembly, preserve the no-argument `createCoreState()` shape, and retain the user's current `ReplaySystem` import from `@user/data-common` rather than reintroducing a relative package-internal import. Do not add another adapter, public registration API, or new legacy abstraction; legacy remains compatibility-only under user-authoritative requirements 1 and 2 (D-09, D-10, D-12, D-19, D-23, D-28).</action>
<action>Remove the Phase-3-only dependency factory, serialized-event adapter, symbol-keyed load entry, serialized branch, and raw-map loader added for that boundary, deleting the two legacy adapter files listed in frontmatter. Restore the pre-Phase-3 CoreState compatibility construction exactly at the existing boundaries: construct `SaveSystem`, attach the legacy tile converter, attach the legacy item converter, construct `EnemyManager` with its legacy bridge, and register the existing `loading.once('coreInit', ...)` and `loading.once('loaded', ...)` callbacks that initialize save storage and legacy tile/item/enemy/map data. Preserve the current event built-in and replay assembly, preserve the no-argument `createCoreState()` shape, and retain the user's current `ReplaySystem` import from `@user/data-common` rather than reintroducing a relative package-internal import. Do not add another adapter, public registration API, or new legacy abstraction; legacy remains compatibility-only under user-authoritative requirement 1 (D-09, D-10, D-12, D-19, D-23, D-28).</action>
<verify>
<automated>$paths = @('packages-user/data-state/src/legacy/dependencies.ts','packages-user/data-state/src/legacy/events.ts'); foreach ($path in $paths) { if (Test-Path -LiteralPath $path) { throw "Rejected Phase-3 legacy artifact still exists: $path" } }; $core = Get-Content -Raw 'packages-user/data-state/src/core.ts'; if ($core -match 'createLegacyDependencies|LOAD_SERIALIZED_DATA|registerSerializedEvents') { throw 'CoreState still references the rejected loading boundary' }; if ($core -notmatch "from '@user/data-common'") { throw 'CoreState data-common barrel import is missing' }; if ($core -match "from '../../data-common/src/replay/system'") { throw 'CoreState reverted the user import cleanup' }; pnpm exec tsx -e "import { createCoreState } from './packages-user/data-state/src/core.ts'; const state = createCoreState(); if (state.saveSystem.constructor.name !== 'SaveSystem') throw new Error('existing SaveSystem path is not active');"
<automated>$paths = @('packages-user/data-state/src/legacy/dependencies.ts','packages-user/data-state/src/legacy/events.ts'); foreach ($path in $paths) { if (Test-Path -LiteralPath $path) { throw "Rejected Phase-3 legacy artifact still exists: $path" } }; $core = Get-Content -Raw 'packages-user/data-state/src/core.ts'; $required = @('new SaveSystem\(\)', 'loading\.once\(\x27coreInit\x27', 'loading\.once\(\x27loaded\x27', 'attachLegacyConverter\(new TileLegacyBridge\(\)\)', 'attachLegacyConverter\(new ItemLegacyBridge\(this\)\)', 'new EnemyManager\(new EnemyLegacyBridge\(\)\)', 'saveSystem\.init\('); foreach ($pattern in $required) { if ($core -notmatch $pattern) { throw "Existing compatibility behavior is missing: $pattern" } }; if ($core -match 'createLegacyDependencies|LOAD_SERIALIZED_DATA|registerSerializedEvents|MemorySaveSystem') { throw 'CoreState still references the rejected Phase-3 loading/save boundary' }; if ($core -notmatch "from '@user/data-common'") { throw 'CoreState data-common barrel import is missing' }; if ($core -match "from '../../data-common/src/replay/system'") { throw 'CoreState reverted the user import cleanup' }; pnpm exec vitest run packages-user/data-state/test/tileLegacy.test.ts; if ($?) { pnpm exec tsx -e "import { createCoreState } from './packages-user/data-state/src/core.ts'; const state = createCoreState(); if (state.saveSystem.constructor.name !== 'SaveSystem') throw new Error('existing SaveSystem path is not active'); if (!state.tileStore || !state.itemStore || !state.enemyManager) throw new Error('existing compatibility stores are not constructed');" }
</automated>
<fails_when>Either rejected legacy artifact remains, CoreState still reaches the Phase-3 loading boundary, the current data-common ReplaySystem import cleanup is reversed, CoreState cannot be constructed, or the existing SaveSystem is not selected.</fails_when>
<fails_when>Either deleted legacy artifact remains, any positive compatibility callback/attachment/SaveSystem check fails, CoreState still reaches the Phase-3 loading boundary, the current data-common ReplaySystem import cleanup is reversed, the tile compatibility regression fails, or CoreState cannot be constructed with SaveSystem.</fails_when>
</verify>
<done>CoreState no longer depends on the Phase-3 legacy architecture and again uses the existing compatibility loading and save construction path without changing the current replay import cleanup.</done>
</task>
@ -79,12 +85,12 @@ Output: CoreState without the new legacy boundary, removal of the two Phase-3 le
<task type="auto">
<name>Task 2: CORR-03-01 restore direct replay fixture coverage</name>
<gap_id>CORR-03-01</gap_id>
<files>packages-user/data-state/test/fixtures/closed-loop.ts, packages-user/data-state/test/coreSerializedEvents.test.ts</files>
<action>Replace the fixture's symbol-keyed serialized load payload with the established explicit data-side fixture shape: create the maps through the existing raw/map APIs, register the fixture event directly in the existing `GameEventStore`, and keep the map/tile event reference and awaited event mutation assertions intact. Delete the regression file whose only purpose was to validate the removed serialized legacy loading boundary. Keep the route, final snapshot, and Node replay entry behavior deterministic, and do not move event bodies into map/save data or introduce a replacement loader (D-03, D-04, D-11, D-13, D-22, D-24, D-27; user-authoritative requirement 1).</action>
<files>packages-user/data-state/test/fixtures/closed-loop.ts, packages-user/data-state/test/coreSerializedEvents.test.ts, script/test-data-node.ts</files>
<action>Replace the fixture's symbol-keyed serialized load payload with the established explicit data-side fixture shape: create the maps through the existing raw/map APIs, register the fixture event directly in the existing `GameEventStore`, and keep the map/tile event reference and event mutation assertions intact. Delete `coreSerializedEvents.test.ts`, whose only purpose was to validate the removed serialized legacy loading boundary. Keep the fixed route and final snapshot deterministic without reintroducing async replay command handling: use the existing `IMapLayerHooks.onUpdateBlock` callback as a synchronous mutation marker, expose a fixture completion promise resolved by that callback, and make the Node verifier's existing `finish()` boundary await that exact signal before taking the final snapshot. Set the fixture's `eventSetBlock` statement to the non-async call form so the built-in mutation itself has no artificial statement-level async boundary; the completion promise still covers the already-asynchronous movement/event pipeline. Do not move event bodies into map/save data, add a replacement loader, await a replay controller in a command, or change any `@shouldReplay()` placement (D-03, D-04, D-11, D-13, D-14, D-22, D-24, D-26, D-27; user-authoritative requirements 1 and 5).</action>
<verify>
<automated>if (Test-Path -LiteralPath 'packages-user/data-state/test/coreSerializedEvents.test.ts') { throw 'Rejected serialized-loading regression still exists' }; $fixture = Get-Content -Raw 'packages-user/data-state/test/fixtures/closed-loop.ts'; if ($fixture -match 'LOAD_SERIALIZED_DATA|ILegacySerializedLoadData|serialized') { throw 'Closed-loop fixture still uses the rejected loader' }; if ($fixture -notmatch 'eventStore\.addEvent') { throw 'Closed-loop fixture no longer registers its explicit event' }; pnpm exec vitest run packages-user/data-state/test/nodeReplay.test.ts; if ($?) { pnpm test:data-node }
<automated>if (Test-Path -LiteralPath 'packages-user/data-state/test/coreSerializedEvents.test.ts') { throw 'Rejected serialized-loading regression still exists' }; $fixture = Get-Content -Raw 'packages-user/data-state/test/fixtures/closed-loop.ts'; $runner = Get-Content -Raw 'script/test-data-node.ts'; if ($fixture -match 'LOAD_SERIALIZED_DATA|ILegacySerializedLoadData|serialized') { throw 'Closed-loop fixture still uses the rejected loader' }; if ($fixture -notmatch 'eventStore\.addEvent') { throw 'Closed-loop fixture no longer registers its explicit event' }; if ($fixture -notmatch 'onUpdateBlock') { throw 'Closed-loop fixture has no synchronous mutation marker' }; if ($fixture -notmatch 'Promise\.withResolvers') { throw 'Closed-loop fixture has no deterministic completion signal' }; if ($fixture -notmatch 'async:\s*false') { throw 'Closed-loop event call still has an artificial async statement boundary' }; if ($runner -notmatch 'waitForEventCompletion|eventCompletion') { throw 'Node verifier snapshots without the fixture completion boundary' }; if ($runner -notmatch 'await .*Completion') { throw 'Node verifier does not await event completion before snapshot' }; pnpm exec vitest run packages-user/data-state/test/nodeReplay.test.ts packages-user/data-state/test/nodeTracer.test.ts; if ($?) { pnpm test:data-node }
</automated>
<fails_when>The fixture still imports or invokes the removed loader, the obsolete regression file remains, the direct event fixture is absent, the replay regression fails, or the dedicated Node replay command exits non-zero.</fails_when>
<fails_when>The fixture still imports or invokes the removed loader, the obsolete regression file remains, the direct event fixture or synchronous completion marker is absent, the verifier can snapshot before the completion signal, the replay regressions fail, or the dedicated Node replay command exits non-zero.</fails_when>
</verify>
<done>The closed-loop replay proves the existing data-side event path directly, with no Phase-3 legacy loading system or boundary.</done>
</task>
@ -110,12 +116,14 @@ Output: CoreState without the new legacy boundary, removal of the two Phase-3 le
<verification>
- `pnpm exec vitest run packages-user/data-state/test/nodeReplay.test.ts`
- `pnpm exec vitest run packages-user/data-state/test/nodeReplay.test.ts packages-user/data-state/test/nodeTracer.test.ts`
- `pnpm test:data-node`
- Source checks confirm the Phase-3 legacy boundary files and references are absent while the compatibility callback path remains.
- Source and focused compatibility checks positively confirm the existing loading callbacks, tile/item/enemy attachments, SaveSystem construction/path, and data-common replay import remain while the Phase-3 legacy boundary files/references are absent.
- Source and runtime checks confirm the fixed route's synchronous map mutation marker is awaited by the verifier before the final snapshot.
</verification>
<success_criteria>
CORR-03-01 is closed when legacy additions from Phase 3 are removed, CoreState uses the existing compatibility-only loading path, the current data-common import cleanup remains, and the fixed replay still passes without a replacement legacy loader.
CORR-03-01 is closed when legacy additions from Phase 3 are removed, CoreState positively retains the existing compatibility callbacks, legacy attachments, and SaveSystem path, the current data-common import cleanup remains, and the fixed replay reaches a deterministic mutation completion boundary before its exact final snapshot without a replacement legacy loader.
</success_criteria>
<output>

View File

@ -5,10 +5,11 @@ type: execute
wave: 7
depends_on: [03-10]
files_modified:
- packages-user/data-common/src/save/memory.ts
- packages-user/data-common/src/save/index.ts
- packages-user/data-state/test/coreNode.test.ts
- packages-user/data-state/test/nodeTracer.test.ts
files_deleted:
- packages-user/data-common/src/save/memory.ts
autonomous: true
requirements: [DATA-01]
gap_closure: true
@ -68,7 +69,7 @@ Output: No Phase-3 save adapter source/export and focused CoreState/tracer asser
<name>Task 1: CORR-03-02 remove the Phase-3 save adapter</name>
<gap_id>CORR-03-02</gap_id>
<files>packages-user/data-common/src/save/memory.ts, packages-user/data-common/src/save/index.ts</files>
<action>Delete the Phase-3 in-memory save implementation and remove only its save-barrel export. Do not add a replacement adapter, alter `ISaveSystem`, alter `SaveSystem`, move save into rendering, or change the existing compatibility initialization callback restored by Plan 03-10. Keep the ordinary save package exports and all existing save compression/registration behavior unchanged (D-18, D-23, D-28 are superseded here by user-authoritative requirement 2 for the save architecture).</action>
<action>Delete the Phase-3 in-memory save implementation listed in frontmatter and remove only its save-barrel export. Do not add a replacement adapter, alter `ISaveSystem`, alter `SaveSystem`, move save into rendering, or change the existing compatibility initialization callbacks restored by Plan 03-10. Keep the ordinary save package exports and all existing save compression/registration behavior unchanged; this correction follows user-authoritative requirement 2 for the save architecture and preserves the existing SaveSystem path (D-09, D-10, D-12, D-23, D-28).</action>
<verify>
<automated>if (Test-Path -LiteralPath 'packages-user/data-common/src/save/memory.ts') { throw 'Phase-3 save adapter still exists' }; $barrel = Get-Content -Raw 'packages-user/data-common/src/save/index.ts'; if ($barrel -match 'memory') { throw 'Save barrel still exports the Phase-3 adapter' }; if ((Get-Content -Raw 'packages-user/data-common/src/save/system.ts') -notmatch 'class SaveSystem') { throw 'Existing SaveSystem is missing' }; pnpm exec tsx -e "import { createCoreState } from './packages-user/data-state/src/core.ts'; const state = createCoreState(); if (state.saveSystem.constructor.name !== 'SaveSystem') throw new Error('CoreState is not using SaveSystem');"
</automated>
@ -81,11 +82,11 @@ Output: No Phase-3 save adapter source/export and focused CoreState/tracer asser
<name>Task 2: CORR-03-02 align CoreState and tracer regressions with existing save behavior</name>
<gap_id>CORR-03-02</gap_id>
<files>packages-user/data-state/test/coreNode.test.ts, packages-user/data-state/test/nodeTracer.test.ts</files>
<action>Replace assertions that identify the removed adapter with assertions that two factory-created states have distinct save-system instances of the existing SaveSystem implementation, while retaining the independent hero/map/event-state checks and the no-browser-global construction check. Keep tests focused on construction and replay behavior; do not call save methods before the existing compatibility initialization path has configured the browser database, and do not add a test-only save substitute. Place the required Chinese coverage comment immediately before every `it` call (D-03, D-04, D-09, D-12, D-23; user-authoritative requirement 2).</action>
<action>Replace assertions that identify the removed adapter with assertions that two factory-created states have distinct save-system instances of the existing SaveSystem implementation, while retaining the independent hero/map/event-state checks and the no-browser-global construction check. Keep tests focused on construction and replay behavior; do not call save methods before the existing compatibility initialization callbacks have configured the browser database, and do not add a test-only save substitute. Place the required Chinese coverage comment immediately before every `it` call (D-03, D-04, D-09, D-12, D-23; user-authoritative requirement 2).</action>
<verify>
<automated>$files = @('packages-user/data-state/test/coreNode.test.ts','packages-user/data-state/test/nodeTracer.test.ts'); foreach ($file in $files) { $text = Get-Content -Raw $file; if ($text -match 'MemorySaveSystem|save/memory') { throw "Removed save adapter referenced by $file" } }; pnpm exec vitest run packages-user/data-state/test/coreNode.test.ts packages-user/data-state/test/nodeTracer.test.ts; if ($?) { pnpm test:data-node }
</automated>
<fails_when>Either test references the removed adapter, a required Chinese test comment is missing, CoreState independence/tracer behavior fails, or the independent Node command exits non-zero.</fails_when>
<fails_when>Either test references the removed adapter, a required Chinese test comment is missing, either factory state is not an independent existing SaveSystem instance, CoreState independence/tracer behavior fails, or the independent Node command exits non-zero.</fails_when>
</verify>
<done>Focused construction and replay tests prove independent states while preserving the existing SaveSystem and compatibility save architecture.</done>
</task>

View File

@ -75,11 +75,11 @@ Output: Module-owned registration builders, a thin event barrel, and focused tes
<name>Task 1: CORR-03-03 move registration construction into event modules</name>
<gap_id>CORR-03-03</gap_id>
<files>packages-user/data-state/src/event/map.ts, packages-user/data-state/src/event/hero.ts, packages-user/data-state/src/event/event.ts, packages-user/data-state/src/event/index.ts</files>
<action>Move each event group's `BuiltInFunction` construction and parameter adaptation beside its implementation: map registration entries in `map.ts`, hero entries in `hero.ts`, and event-control entries in `event.ts`. Preserve the existing eight names, parameter interfaces, `(param, env)` contract, and approved handler behavior. Make `index.ts` import the three module-owned registration collections, concatenate them in the existing stable order, and re-export the modules/types; it must not own parser helpers, environment-shape helpers, or implementation bodies. Remove the generic invocation-time object/property descriptor validation pipeline; rely on the typed AnonTokyo built-in contract and the handlers' existing target-existence/no-op safeguards, without adding another per-call validation layer (D-10, D-11, D-24, D-27; user-authoritative requirement 3).</action>
<action>Move each event group's `BuiltInFunction` construction and parameter adaptation beside its implementation: map registration entries in `map.ts`, hero entries in `hero.ts`, and event-control entries in `event.ts`. Preserve the existing eight names, parameter interfaces, `(param, env)` contract, and approved handler behavior. Export distinct module-owned registration builders from the three implementation modules (use unambiguous names such as `createMapEventBuiltinRegistrations`, `createHeroEventBuiltinRegistrations`, and `createControlEventBuiltinRegistrations`), and make `index.ts` call all three builders and concatenate them in the existing stable order while re-exporting modules/types. `index.ts` must not own parser helpers, environment-shape helpers, or implementation bodies. Remove the generic invocation-time object/property descriptor validation pipeline; rely on the typed AnonTokyo built-in contract and the handlers' existing target-existence/no-op safeguards, without adding another per-call validation layer (D-10, D-11, D-24, D-27; user-authoritative requirement 3).</action>
<verify>
<automated>$index = Get-Content -Raw 'packages-user/data-state/src/event/index.ts'; if ($index -match 'Object\.getOwnPropertyDescriptor|function parse|function read|isBuiltinParameter|isBlockEventEnv') { throw 'Event barrel still owns runtime parsing/validation helpers' }; if ($index -notmatch 'create.*Builtin') { throw 'Event barrel does not assemble module registrations' }; pnpm exec tsx -e "import { createEventBuiltinRegistrations } from './packages-user/data-state/src/event/index.ts'; const names = createEventBuiltinRegistrations().map(item => item.name); if (names.length !== 8) throw new Error('registration count drift');"
<automated>$index = Get-Content -Raw 'packages-user/data-state/src/event/index.ts'; $map = Get-Content -Raw 'packages-user/data-state/src/event/map.ts'; $hero = Get-Content -Raw 'packages-user/data-state/src/event/hero.ts'; $event = Get-Content -Raw 'packages-user/data-state/src/event/event.ts'; foreach ($text in @($index,$map,$hero,$event)) { if ($text -match 'Object\.getOwnPropertyDescriptor|isBuiltinParameter|isBlockEventEnv|function parse[A-Z]|function read[A-Z]|typeof\s+param|Array\.isArray\(param') { throw 'Event implementation modules still contain per-call runtime shape validation' } }; $indexBody = $index -replace '(?m)^\s*import .*$', '' -replace '(?m)^\s*export \*.*$', ''; if ($indexBody -match 'function\s+(?!createEventBuiltinRegistrations\b)[A-Za-z_]') { throw 'Event barrel owns a non-assembly function' }; if ($index -match 'function\s+(read|parse|is)[A-Z]|const\s+(read|parse|is)[A-Z]|BuiltInFunction\s*=|func:\s*|event(SetBlock|MoveBlock|DeleteBlock|MoveHero|MoveHeroStep|TouchFront|InsertEvents|InsertEvent)\(') { throw 'Event barrel still owns parser/handler construction' }; foreach ($pattern in @('createMapEventBuiltinRegistrations\(', 'createHeroEventBuiltinRegistrations\(', 'createControlEventBuiltinRegistrations\(')) { if ($index -notmatch $pattern) { throw "Event barrel is not calling module registration builder: $pattern" } }; foreach ($pattern in @('createMapEventBuiltinRegistrations', 'createHeroEventBuiltinRegistrations', 'createControlEventBuiltinRegistrations')) { if (($map + $hero + $event) -notmatch $pattern) { throw "Module-owned registration builder is missing: $pattern" } }; foreach ($pattern in @('eventSetBlock', 'eventMoveBlock', 'eventDeleteBlock')) { if ($map -notmatch $pattern) { throw "Map implementation ownership is missing: $pattern" } }; foreach ($pattern in @('eventMoveHero', 'eventMoveHeroStep')) { if ($hero -notmatch $pattern) { throw "Hero implementation ownership is missing: $pattern" } }; foreach ($pattern in @('eventTouchFront', 'eventInsertEvents', 'eventInsertEvent')) { if ($event -notmatch $pattern) { throw "Event-control implementation ownership is missing: $pattern" } }; pnpm exec tsx -e "import { createEventBuiltinRegistrations } from './packages-user/data-state/src/event/index.ts'; const names = createEventBuiltinRegistrations().map(item => item.name); if (names.length !== 8) throw new Error('registration count drift'); if (new Set(names).size !== 8) throw new Error('registration name drift');"
</automated>
<fails_when>The event barrel still contains parser/environment/runtime-validation helpers, module registration assembly is missing, or the real registration factory no longer returns exactly eight entries.</fails_when>
<fails_when>Any of the four event modules retains a generic per-call shape-validation helper, index.ts owns behavior/parsing instead of only calling the three named module builders and exporting, an implementation group is not owned by its module, or the real registration factory does not return eight unique entries.</fails_when>
</verify>
<done>All event behavior and parameter construction are owned by event modules while the barrel only assembles the approved registration list and exports.</done>
</task>

View File

@ -74,7 +74,7 @@ Output: Updated event parameter contract, direct interpreter execution, correcte
<verify>
<automated>$event = Get-Content -Raw 'packages-user/data-state/src/event/event.ts'; if ($event -match 'eventInsertEvent[\s\S]*eventInsertEvents\(') { throw 'Single-body event path delegates to id sequence' }; if ($event -match 'eventInsertEvent[\s\S]*store\.getEvent') { throw 'Single-body event path resolves an event id' }; if ((Get-Content -Raw 'packages-user/data-state/src/event/types.ts') -notmatch "from 'anon-tokyo'") { throw 'Statement type contract is missing' }; pnpm exec prettier --check packages-user/data-state/src/event/event.ts packages-user/data-state/src/event/types.ts
</automated>
<fails_when>The single-event implementation still delegates to the id helper or event store, the Statement[] contract is absent, or the data-state type check fails.</fails_when>
<fails_when>The single-event implementation still delegates to the id helper or event store, the Statement[] contract is absent, or the Prettier command run by this task fails.</fails_when>
</verify>
<done>eventInsertEvent receives and directly awaits a Statement[] body through the existing interpreter without event-id resolution.</done>
</task>
@ -85,9 +85,9 @@ Output: Updated event parameter contract, direct interpreter execution, correcte
<files>packages-user/data-state/src/event/event.test.ts, .planning/phases/03-data-completion/03-EVENT-CONTRACT.md</files>
<action>Update the contract record so `eventInsertEvent` documents a Statement[] body, direct interpreter execution, awaited completion, and the absence of id lookup. Replace the old single-id test call with a statement-body fixture that invokes an approved built-in and mutates the event layer; use a body whose execution would fail or remain unobserved if the event store were consulted, and assert the mutation occurs without registering that body under an id. Retain the existing `eventInsertEvents` ordered-id assertions and required Chinese coverage comments (D-01, D-03, D-04, D-11, D-24, D-27; user-authoritative requirement 4).</action>
<verify>
<automated>$contract = Get-Content -Raw '.planning/phases/03-data-completion/03-EVENT-CONTRACT.md'; if ($contract -notmatch 'Statement\[\]') { throw 'Event contract does not document Statement[]' }; if ($contract -match 'eventInsertEvent[\s\S]*id 对应|single id|单 id') { throw 'Event contract still describes eventInsertEvent as id-based' }; pnpm exec vitest run packages-user/data-state/src/event/event.test.ts; if ($?) { pnpm exec vitest run packages-user/data-state/src/event/event.test.ts packages-user/data-system/src/event/eventDispatch.test.ts }
<automated>$contract = Get-Content -Raw '.planning/phases/03-data-completion/03-EVENT-CONTRACT.md'; if ($contract -notmatch 'Statement\[\]') { throw 'Event contract does not document Statement[]' }; if ($contract -match 'eventInsertEvent[\s\S]*id 对应|single id|单 id') { throw 'Event contract still describes eventInsertEvent as id-based' }; pnpm exec vitest run packages-user/data-state/src/event/event.test.ts; if ($?) { pnpm exec vitest run packages-user/data-state/src/event/event.test.ts packages-user/data-system/src/event/eventDispatch.test.ts }; if ($?) { pnpm exec tsx script/check-data-type.ts }
</automated>
<fails_when>The contract remains id-based, direct Statement[] execution does not mutate through the registered built-in, the body still requires an event-store id, or focused event tests fail.</fails_when>
<fails_when>The contract remains id-based, direct Statement[] execution does not mutate through the registered built-in, the body still requires an event-store id, focused event tests fail, or the existing scoped data type gate exits non-zero.</fails_when>
</verify>
<done>Tests and contract prove that eventInsertEvent executes a Statement[] body directly while eventInsertEvents continues to resolve its explicit id list.</done>
</task>
@ -113,7 +113,7 @@ Output: Updated event parameter contract, direct interpreter execution, correcte
<verification>
- `pnpm exec vitest run packages-user/data-state/src/event/event.test.ts packages-user/data-system/src/event/eventDispatch.test.ts`
- `pnpm exec tsc --noEmit --pretty false --project packages-user/data-state/tsconfig.json`
- `pnpm exec tsx script/check-data-type.ts`
</verification>
<success_criteria>

View File

@ -72,7 +72,7 @@ Output: Synchronous command implementations, synchronous replay-safety restorati
<name>Task 1: CORR-03-05 restore synchronous replay command and decorator behavior</name>
<gap_id>CORR-03-05</gap_id>
<files>packages-user/data-common/src/replay/func.ts, packages-user/data-state/src/replay/commands.ts, .planning/phases/03-data-completion/03-REPLAY-CONTRACT.md</files>
<action>Remove the Phase-3 Promise-settlement branch from the replay-safety decorator so the existing collection context is restored immediately after the wrapped method returns. Remove `async`/`await` command bodies and controller waiting from the new data-state command implementations; invoke the existing state APIs synchronously and adapt their immediate results only as required by the already-existing `IReplayCommand.execute(): Promise<boolean>` boundary, without changing that interface or `ReplaySandbox`. Keep stable command codes/order, parameter validation, failure booleans, and production registry ownership. Update the replay contract to state the synchronous correction. Explicitly do not touch decorator placement on `HeroAttribute.set`, `HeroAttribute.mul`, or any other user-owned state method; this plan implements user-authoritative requirement 5 only (D-05, D-07, D-08, D-25).</action>
<action>Remove the Phase-3 Promise-settlement branch from the replay-safety decorator so the existing collection context is restored immediately after the wrapped method returns. Remove `async`/`await` command bodies and controller waiting from the new data-state command implementations; invoke the existing state APIs synchronously and adapt their immediate results only as required by the already-existing `IReplayCommand.execute(): Promise<boolean>` boundary, without changing that interface or `ReplaySandbox`. Keep stable command codes/order, parameter validation, failure booleans, and production registry ownership. The fixed Node fixture's asynchronous movement/event pipeline is synchronized separately by its existing verifier completion boundary in Plan 03-10; do not add a command-level wait or make replay command handling asynchronous again. Update the replay contract to state the synchronous correction. Explicitly do not touch decorator placement on `HeroAttribute.set`, `HeroAttribute.mul`, or any other user-owned state method; this plan implements user-authoritative requirement 5 only (D-05, D-07, D-08, D-25).</action>
<verify>
<automated>if ((Get-Content -Raw 'packages-user/data-common/src/replay/func.ts') -match 'isPromiseLike|Promise\.resolve\(result\)') { throw 'Replay safety still has asynchronous restoration logic' }; $commands = Get-Content -Raw 'packages-user/data-state/src/replay/commands.ts'; if ($commands -match 'async (moveHero|moveToPoint|useItem|equip|unequip)|await controller\.onEnd') { throw 'Replay command bodies still add asynchronous execution' }; if ((Get-Content -Raw 'packages-user/data-base/src/hero/attribute.ts') -match 'shouldReplay') { throw 'User-owned HeroAttribute decorator placement was modified' }; pnpm exec eslint packages-user/data-common/src/replay/func.ts packages-user/data-state/src/replay/commands.ts; if ($?) { pnpm exec prettier --check packages-user/data-common/src/replay/func.ts packages-user/data-state/src/replay/commands.ts }
</automated>

View File

@ -2,8 +2,8 @@
phase: 03-data-completion
plan: 15
type: execute
wave: 6
depends_on: [03-14]
wave: 8
depends_on: [03-10, 03-11, 03-14]
files_modified:
- packages-user/data-state/src/replay/commands.ts
- packages-user/data-state/src/replay/commands.test.ts
@ -68,9 +68,9 @@ Output: Independent command classes, stable registration assembly, corrected con
<files>packages-user/data-state/src/replay/commands.ts, .planning/phases/03-data-completion/03-REPLAY-CONTRACT.md</files>
<action>Replace the shared command-entrance object and closure factory with one independent class per approved instruction in `commands.ts`: up, right, down, left, auto-pathfind-to-point, use-item, equip, and unequip. Each class must implement the existing `IReplayCommand` behavior, own only its needed state reference, perform its own parameter validation/action call, and never call another instruction class or rely on shared mutable entrance state. Keep pure validation helpers only when they do not create inter-command coupling. Make `createReplayCommandItems()` instantiate one independent class per stable enum entry in the existing order, keep `registerReplayCommandItems()` and the `IReplayCommandItem` extension point unchanged, and document the ownership correction. Do not alter the synchronous policy from Plan 03-14 or any user-owned `@shouldReplay()` placement (D-08, D-09, D-10, D-25; user-authoritative requirements 5 and 6).</action>
<verify>
<automated>$commands = Get-Content -Raw 'packages-user/data-state/src/replay/commands.ts'; if ($commands -match 'ReplayCommandEntrances|createMoveCommand') { throw 'Shared replay entrance/factory remains' }; pnpm exec tsx -e "import { createCoreState } from './packages-user/data-state/src/core.ts'; import { createReplayCommandItems } from './packages-user/data-state/src/replay/commands.ts'; const items = createReplayCommandItems(createCoreState()); const names = items.map(item => item.command.constructor.name); if (new Set(names).size !== 8) throw new Error('replay instructions are not independent classes');"
<automated>$commands = Get-Content -Raw 'packages-user/data-state/src/replay/commands.ts'; if ($commands -match 'ReplayCommandEntrances|createMoveCommand|entries\.') { throw 'Shared replay entrance/helper remains' }; $classes = @('ReplayUpCommand','ReplayRightCommand','ReplayDownCommand','ReplayLeftCommand','ReplayAutoPathfindCommand','ReplayUseItemCommand','ReplayEquipCommand','ReplayUnequipCommand'); foreach ($class in $classes) { $match = [regex]::Match($commands, "class\s+$class\b[\s\S]*?(?=\nclass\s|\nfunction\s|\nexport function\s|\n/\*\*)"); if (!$match.Success -or $match.Value -notmatch 'execute\s*\(') { throw "Command implementation is not owned by $class" } }; foreach ($class in $classes) { if ($commands -match "class\s+$class[\s\S]*?(?:Replay(?:Up|Right|Down|Left|AutoPathfind|UseItem|Equip|Unequip)Command)\s*\(") { throw "Cross-command construction/delegation remains in $class" } }; pnpm exec tsx -e "import { createCoreState } from './packages-user/data-state/src/core.ts'; import { createReplayCommandItems } from './packages-user/data-state/src/replay/commands.ts'; const first = createReplayCommandItems(createCoreState()); const second = createReplayCommandItems(createCoreState()); const names = first.map(item => item.command.constructor.name); if (new Set(names).size !== 8) throw new Error('replay instructions are not eight distinct classes'); if (new Set(first.map(item => item.command)).size !== 8) throw new Error('registry reuses command objects'); for (let index = 0; index < first.length; index++) { if (first[index].command === second[index].command) throw new Error('command instance is shared across states'); }"
</automated>
<fails_when>The shared entrance object/factory remains, fewer than eight distinct command classes are registered, command classes depend on one another, or the structural probe exits non-zero.</fails_when>
<fails_when>The shared entrance object/helper remains, any class lacks its own execute implementation, source shows cross-command construction/delegation, command instances are reused within or across registries, fewer than eight distinct classes are registered, or the structural probe exits non-zero.</fails_when>
</verify>
<done>All eight replay instructions are independent classes in one command file and the stable registry still creates one class instance per ordered code.</done>
</task>
@ -79,11 +79,11 @@ Output: Independent command classes, stable registration assembly, corrected con
<name>Task 2: CORR-03-06 verify isolation, order, and user extensibility</name>
<gap_id>CORR-03-06</gap_id>
<files>packages-user/data-state/src/replay/commands.test.ts, .planning/phases/03-data-completion/03-REPLAY-CONTRACT.md</files>
<action>Add regression coverage that checks eight distinct command constructors, stable enum order, independent CoreState-bound command instances, preserved invalid-parameter false results, and registration of an existing custom `IReplayCommandItem` through the current registry interface. Retain synchronous behavior assertions from Plan 03-14, required Chinese comments, and the explicit non-scope assertion that no user-owned state-method decorator placement is changed. Do not reintroduce a shared entrance object, cross-command delegation, a second code owner, or a new public registry interface (D-01, D-03, D-04, D-08, D-09, D-25; user-authoritative requirements 5 and 6).</action>
<action>Add regression coverage that checks eight distinct command constructors, stable enum order, one fresh command object per registry entry and per CoreState-bound registry, each instruction's own valid/invalid execution behavior, preserved invalid-parameter false results, and registration of an existing custom `IReplayCommandItem` through the current registry interface. Include a practical source-ownership assertion for the eight class bodies: every class contains its own `execute` implementation, no class constructs or invokes another replay command class, and no mutable shared entrance/helper is used. Retain synchronous behavior assertions from Plan 03-14, required Chinese comments, and the explicit non-scope assertion that no user-owned state-method decorator placement is changed. Do not reintroduce a shared entrance object, cross-command delegation, a second code owner, or a new public registry interface (D-01, D-03, D-04, D-08, D-09, D-25; user-authoritative requirements 5 and 6).</action>
<verify>
<automated>$lines = Get-Content -LiteralPath 'packages-user/data-state/src/replay/commands.test.ts'; for ($i = 0; $i -lt $lines.Count; $i++) { if ($lines[$i] -match '^\s*it(?:\.[A-Za-z]+)*\s*\(') { if ($i -eq 0 -or $lines[$i - 1] -notmatch '^\s*//\s*[\u4e00-\u9fff]') { throw "Missing Chinese coverage comment before it at line $($i + 1)" } } }; pnpm exec vitest run packages-user/data-state/src/replay/commands.test.ts; if ($?) { pnpm test:ci packages-user/data-common packages-user/data-base packages-user/data-system packages-user/data-state }; if ($?) { pnpm test:data-node }
<automated>$lines = Get-Content -LiteralPath 'packages-user/data-state/src/replay/commands.test.ts'; for ($i = 0; $i -lt $lines.Count; $i++) { if ($lines[$i] -match '^\s*it(?:\.[A-Za-z]+)*\s*\(') { if ($i -eq 0 -or $lines[$i - 1] -notmatch '^\s*//\s*[\u4e00-\u9fff]') { throw "Missing Chinese coverage comment before it at line $($i + 1)" } } }; $commands = Get-Content -Raw 'packages-user/data-state/src/replay/commands.ts'; if ($commands -match 'ReplayCommandEntrances|createMoveCommand|entries\.') { throw 'Regression source check found shared entrance/helper coupling' }; foreach ($class in @('ReplayUpCommand','ReplayRightCommand','ReplayDownCommand','ReplayLeftCommand','ReplayAutoPathfindCommand','ReplayUseItemCommand','ReplayEquipCommand','ReplayUnequipCommand')) { $match = [regex]::Match($commands, "class\s+$class\b[\s\S]*?(?=\nclass\s|\nfunction\s|\nexport function\s|\n/\*\*)"); if (!$match.Success -or $match.Value -notmatch 'execute\s*\(') { throw "Missing owned execute implementation: $class" } }; pnpm exec vitest run packages-user/data-state/src/replay/commands.test.ts; if ($?) { pnpm test:ci packages-user/data-common packages-user/data-base packages-user/data-system packages-user/data-state }; if ($?) { pnpm test:data-node }; if ($?) { pnpm exec tsx script/check-data-type.ts }; if ($?) { pnpm exec tsx script/check-data-circular.ts }
</automated>
<fails_when>Class-isolation/order/extensibility tests or existing synchronous replay tests fail, a required Chinese comment is missing, the full data suite or Node replay gate fails, or user-owned decorator placement changes.</fails_when>
<fails_when>Class-isolation/ownership/order/extensibility tests or existing synchronous replay tests fail, a required Chinese comment is missing, the source ownership probe finds shared mutable entrances or cross-command calls, the full data suite, Node replay gate, scoped type gate, or scoped circular gate fails, or user-owned decorator placement changes.</fails_when>
</verify>
<done>Independent command classes, stable registration order, existing extension interfaces, synchronous behavior, and user-owned decorator boundaries are all regression-tested.</done>
</task>
@ -111,7 +111,9 @@ Output: Independent command classes, stable registration assembly, corrected con
- `pnpm exec vitest run packages-user/data-state/src/replay/commands.test.ts`
- `pnpm test:ci packages-user/data-common packages-user/data-base packages-user/data-system packages-user/data-state`
- `pnpm test:data-node`
- Structural probe confirms eight distinct command constructors and no shared entrance object.
- `pnpm exec tsx script/check-data-type.ts`
- `pnpm exec tsx script/check-data-circular.ts`
- Structural and runtime probes confirm eight distinct command classes, per-class `execute` ownership, fresh command instances, no shared mutable entrance/helper, no cross-command calls, and stable registry order.
</verification>
<success_criteria>