Skip to content

feat(world): 大规模世界架构重构 — WorldStore/WorldRuntime 统一持久化与流式加载#75

Merged
jayli merged 67 commits into
mainfrom
gen-big-map-first
May 7, 2026
Merged

feat(world): 大规模世界架构重构 — WorldStore/WorldRuntime 统一持久化与流式加载#75
jayli merged 67 commits into
mainfrom
gen-big-map-first

Conversation

@jayli

@jayli jayli commented May 7, 2026

Copy link
Copy Markdown
Owner

Summary

  • WorldStore 统一持久化架构:引入 WorldStore / RegionCache / WorldRuntime 三层架构,统一 chunk 读写路径,消除旧 PersistenceService 双写问题
  • MemoryWorldStore 运行期权威数据源:将运行期数据源从 IndexedDB 切换为纯内存存储,减少主线程 IO 阻塞
  • 跨区域溢出路由:实现 cross-region overflow block 的收集、分发与持久化,解决大型结构跨区域生成问题
  • 运行时实体统一持久化:重构特殊实体(炮塔、矿车、丧尸巢穴)的持久化流程,ShadowStore 架构确保实体生命周期正确
  • Chunk 级读取路径与 Worker 缓存:Worker 侧 region 缓存 + chunk-level read path,减少主线程与 Worker 间的数据搬运
  • 帧节流 Chunk 加载:可中断 chunk 装配能力 + 帧节流加载调度,消除加载尖峰
  • AO 热路径优化:消除 AO 计算中的大对象合并与冗余遍历,修复 consolidation 后 AO 丢失问题
  • 性能监控体系:Chunk 装载全链路性能打点(ChunkPerfMonitor),unload 路径异步写盘消除同步 IO 尖峰
  • 测试覆盖:新增 test-world-runtime.jstest-memory-world-store.jstest-world-generation-cross-region.js 等测试,总计 339 测试全部通过

Test Plan

  • 全量测试: 339/339 通过
  • 手动验证: 启动 npm run start,确认世界加载、实体持久化、跨区域移动正常
  • 回归验证: AO 阴影、面剔除、炮塔/矿车/丧尸巢穴功能正常

🤖 Generated with Claude Code

jayli and others added 30 commits April 26, 2026 17:15
实现基于 IndexedDB 的权威世界存储与运行时 chunk 流式装载架构:

- WorldStore: IndexedDB 权威数据源,WorldMeta/RegionRecord/ChunkRecord 三级存储
- RegionCache: 内存缓存层,按需从 IndexedDB 加载 region,降低主线程阻塞
- PersistenceWorker: 新增 clearWorld/WorldMeta/RegionRecord 操作,支持 DB 版本升级
- Chunk.loadFromRecord: 纯装载路径,跳过 BlockScatterManager.scatter,降低装载延迟
- WorldGenerationService: 预生成阶段跨 region overflow 方块回退机制,防止跨边界结构丢失
- Player.setWorld: runtime-streaming 阶段不再隐式调 gen(),由 World 显式控制装载
- index.html: 新世界创建时 forceReset: true,自动清除旧 IndexedDB 数据

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- _mergeOverflowBlocks 返回详细的诊断数据:resolved/unresolved 数量、目标/来源 chunk 热点、距离分布
- 新增跨 region overflow 诊断测试
- 测试 mock 支持 WorldStore postMessage 接口
- 修复 teardownEnvironment 中 undefined 判断

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…e large structure generation

Refactor pre-generation from chunk-level independent worker calls to
region-level unified worker calls. This eliminates duplicate large
structure generation caused by multiple chunk tasks executing the same
candidate (the original problem: spacing logic prevents structure A
and B from overlapping, but can't prevent the same structure A being
executed by chunk(1,1), chunk(1,2), chunk(2,1), chunk(2,2) separately).

Key changes:
- Extract generateChunkWithSharedState() in WorldWorker.js — reusable
  chunk generation function with injectable shared state (candidate
  index, large structure dedup set)
- Add handleRegionGeneration() — coordinates 8x8 chunk region generation
  with shared candidate index and cross-chunk dedup
- Add resolveOverflowWithinRegion() with O(1) Set-based dedup — routes
  overflow blocks between chunks within the same region, replacing the
  main thread's _mergeOverflowBlocks
- Simplify WorldGenerationService._generateRegion() — from 100+ loop
  iterations with halo routing to a single worker.postMessage call
- Remove unnecessary post-processing in region path (scatteredBlocks,
  meshData, AO/visibility checks) — only blockDataBlocks + routing
  needed for region-level generation

Backward compatible: runtime chunk loading path unchanged. Old methods
(_generateChunkWithRouting, _mergeOverflowBlocks) preserved.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The mock returned flat chunk data but _generateRegion expects a
chunks map keyed by chunkKey, causing Object.entries to fail.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The previous mock only returned a single chunk, but
_generateRegion expects a chunks map for the entire region.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- sync seed in region-level worker path (WorldWorker.js:1392)
  handleRegionGeneration now calls setSeed(seed) before generation,
  ensuring terrainGen/noise use the same seed as the runtime chunk path.

- await structure preload before region generation (WorldWorker.js:1393)
  handleRegionGeneration now awaits structuresPreload, preventing
  silent omission of JSON-driven buildings on cold worker starts.

- filter per-chunk entities to prevent 64x data bloat (WorldWorker.js:1279)
  generateChunkWithSharedState now filters modGunMan/rovers to only
  entities within the current chunk bounds before returning.

- await saveRegionRecord with proper error propagation (WorldGenerationService.js)
  _generateRegion now rejects on persistence failure so the caller
  knows the region was not saved. generateInitialWorld and
  expandWorldIfNeeded wrap per-region calls in try/catch to skip
  failed regions without blocking the entire pre-generation flow.

Closes #68

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… blocks

- Upgrade IndexedDB version from 2 to 3
- Add world_overflow object store with keyPath regionKey
- Add saveOverflowBlocks/getOverflowBlocks/removeOverflowBlocks APIs
- Include world_overflow in clearWorld transaction
- Update old-connection detection to check for missing world_overflow store

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add saveOverflowBlocks/getOverflowBlocks/removeOverflowBlocks
wrapper methods to WorldStore, delegating to PersistenceService
via saveOverflowBlocks/getOverflowBlocks/removeOverflowBlocks
RPC messages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…om region generation

Modify resolveOverflowWithinRegion to collect and return unresolved
cross-region overflow block data, so the main thread can distribute
them later.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… overflow blocks

- Add _crossRegionOverflowMap in constructor to stage inter-region overflow blocks.
- Add _collectCrossRegionOverflow to gather unresolvedOverflowBlocks from worker results.
- Add _distributeCrossRegionOverflow to merge overflow into already-generated regions
  in the same batch, or persist leftovers to world_overflow store for future expansion.
- Add _consumeOverflowForRegion to merge previously persisted overflow blocks when
  a new region is generated during world expansion.
- Wire collection into _generateRegion callback after saving region record.
- Wire distribution into generateInitialWorld after all regions in the batch finish.
- Wire consumption + re-distribution into expandWorldIfNeeded after expansion regions
  are generated and before bounds are updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…, and consume tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…n thread

- Remove IndexedDB persistence for cross-region overflow blocks.
  Overflow is now kept in _crossRegionOverflowMap and merged
  automatically when the target region is generated during expansion.
- Add _yieldIfNeeded helper to yield the main thread every 5000
  iterations in dense loops, preventing FPS drops after game entry.
- Remove _consumeOverflowForRegion and background overflow
  distribution from generateInitialWorld, eliminating the long
  loading-modal hang after pre-generation.
- _generateRegion now merges pending overflow before saving the
  region record.
- Update tests to match pure-memory semantics (DB_VERSION 3,
  async _collectCrossRegionOverflow, removed persistence tests).
- Add design doc and implementation plan for cross-region overflow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add _regionLoadPromises Map in WorldRuntime to prevent N concurrent
  IndexedDB reads for the same region during chunk streaming
- Refactor ensureChunkData to reuse ensureRegion, eliminating duplicated
  cache-miss logic
- Add prefetchRegions() to warm up adjacent regions ahead of player movement
- Add 500ms prefetch timer in World (runtime-streaming phase only) with
  dispose cleanup

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…e iteration

Replace full blockData scan in _registerLightSources/_unregisterLightSources
with a dedicated Set index that tracks light-emitting block coordinates.

The index is maintained synchronously across all blockData write paths:
- _injectBlockData (bulk load / record restore)
- _updateBlockState (single block mutation)
- removeBlocksBatch (batch deletion)
- acceptScatteredBlocks / appendScatteredBlocks (worker result / overflow)

This eliminates iterating 3000-4000 blockData entries per chunk during
finalizeNonDeferredPhase, reducing light registration from ~8-15ms to <1ms.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…es on chunk load

pendingRuntimeEntities was initialized as null and never assigned a non-null
value, making the entity restore logic in finalizeNonDeferredPhase dead code.
This caused zombie nests, turrets, and minecarts to be lost when chunks were
loaded at runtime (player exploring new areas).

Fixes both loading paths:
- loadFromRecord: now merges persistence cache entities and calls
  finalizeNonDeferredPhase to trigger restore.
- assembleEntityPhase: now includes minecarts in the entity merge and
  populates pendingRuntimeEntities from the merged snapshot.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…egions

Prevents TypeError in test environments where worldRuntime is replaced
with a partial mock that lacks the prefetchRegions method.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Design for migrating turret/minecart/zombieNest persistence from
persistenceService.cache to WorldStore RegionRecord.runtimeEntities.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Detailed task-by-task plan for migrating turret/minecart/zombieNest
persistence to WorldStore RegionRecord.runtimeEntities.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…in design and plan

- Add `id` field to minecart data format in design doc
- Replace position-based dedup with UUID-based dedup for cross-chunk moves
- Add Step 5 in plan: global UUID dedup in restoreMinecartsForChunk

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ysis

- 设计文档:从简单字段迁移方案重写为带根因分析的修订版,新增三层真相来源、统一生命周期、渲染与 consolidation 边界、仓储层设计
- 实现计划:从按 Manager 逐个修改改为按基础设施和生命周期重新组织,新增 RuntimeEntityRepository、激活/停用语义、旧路径兼容桥、回归验证矩阵
- 明确 blockData 与 runtimeEntities 职责边界,禁止 Manager 直接操作 WorldStore
…s on chunk unload/reload

This commit fixes the root cause where blockData was lost during chunk
unloading due to an async flush race condition, and restores the
session-level cache overlay mechanism to work correctly with the new
WorldStore architecture.

Changes:

- PersistenceService:
  - Auto-create session snapshots when cache is missing in recordChangeForChunk
  - Add ensureChunkSnapshot, snapshotChunkBlocks, hydrateChunkBlocks,
    replaceChunkBlocks for unified session overlay management
  - Fix saveChunkData to merge data without overwriting existing entities/blocks

- Chunk.loadFromRecord:
  - Ensure persistenceService cache snapshot exists on entry
  - Use cache.blocks as authority over chunkRecord.blockData (overlay semantics)
  - Keep runtime entities recovery from cache.entities unchanged

- World (chunk unload):
  - Fix unload order: snapshot blocks -> stop minecarts -> async flush ->
    dispose -> delete from active set
  - snapshotChunkBlocks is now synchronous and happens before any async work

- WorldRuntime:
  - flushChunk/flushBeforeUnload now accept an optional blockDataSnapshot
    so they no longer depend on the active chunk still being present

- TurretManager:
  - Add id to turret snapshots
  - Use id for deduplication, with position as compatibility fallback
  - restoreTurretsForChunk reuses snapshot id via restoredId option

- ZombieNestManager:
  - Add id and lastSpawnTime to nest snapshots
  - Use id for deduplication
  - restoreNestsForChunk reuses snapshot id and lastSpawnTime

- MinecartManager:
  - Use id for deduplication in saveMinecartToSnapshot
  - restoreMinecartsForChunk skips existing ids to avoid duplicates

- Tests:
  - Add test-runtime-session-persistence.js covering unload/reload scenarios
  - Update test-persistence.js to match auto-snapshot behavior

- Docs:
  - Mark Phase 1 as complete in design and plan documents

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ation

Second phase of the special entity compatibility migration. Unifies the
persistence path for runtime entities (turrets, zombie nests, minecarts)
from persistenceService.cache.entities into worldStore ChunkRecord.runtimeEntities.

Key changes:
- Extend ChunkRecord format with runtimeEntities field
- flushBeforeUnload now writes entities to worldStore (world_regions IndexedDB)
- loadFromRecord prefers runtimeEntities (new format), falls back to cache.entities
  (old format) with progressive migration from world_deltas
- collectSnapshot reads from worldStore instead of cache (manual save path unified)
- Three entity managers gain getEntitiesForChunk(cx, cz) methods
- finalizeNonDeferredPhase becomes async to support progressive migration

NOTE: This is a near-complete draft that needs extensive review, testing,
and manual regression verification before shipping.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…r runtimeEntities

- ensureChunkData/getChunkRecord/getChunkRecordsInRegion now project runtimeEntities
- flushBeforeUnload serializes blockDataSnapshot (Map → plain object)
- flushChunk and flushAllDirty carry runtimeEntities to prevent split-brain

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
jayli and others added 28 commits April 29, 2026 13:19
核心问题:
- flushBeforeUnload 写回 worldStore 后,region cache 未同步更新,导致同会话 reload 读到旧 chunkRecord
- loadFromRecord 在无 runtimeEntities 时会无脑清空 ShadowStore,把同会话内仍存活的炮塔/巢穴误删

变更内容:
- WorldRuntime.flushBeforeUnload: 写回后调用 _updateRegionCacheChunkRecord 同步更新缓存中的 chunkRecord
- WorldRuntime.migrateLegacyEntities: 异步回填成功后也同步更新 region cache
- WorldRuntime._updateRegionCacheChunkRecord: 新增方法,将最新 chunkRecord 写回 region cache
- Chunk.loadFromRecord: 仅当 ShadowStore 也无存活数据时才清空,避免同会话内 live 数据被误清
- 新增两项测试覆盖 flushBeforeUnload 缓存同步和 ShadowStore 回退路径

关联 #69

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
统一三条 flush 路径的 blockData 取数策略为"显式快照 > dirty snapshot > region cache > live chunk",
避免仅实体脏化时回退到主线程将 live Map 整块序列化。

变更内容:
- WorldRuntime._resolveSerializedBlockData: 新增,按优先级链解析序列化 blockData
- WorldRuntime._resolveStaticEntities / _resolveRuntimeSeedData: 新增,统一从 chunk 或 cache 回退取数
- WorldRuntime._getSerializedBlockMetrics: 新增,计算 blockCount 和 serializedBytes
- WorldRuntime._recordFlushPerf: 新增,统一 perf 埋点入口
- flushChunk / flushAllDirty / flushBeforeUnload: 重构为调用上述新方法
- flushAllDirty: 累计总 blockCount 和 serializedBytes,在最终 perf 事件中输出
- flushBeforeUnload: 增加 perf 记录
- 测试覆盖: flushChunk/flushAllDirty 复用 region cache 不触发 live Map 序列化,
  以及 flush-all-dirty 的 perf 事件验证

关联 #69

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
新增 command/run-tests.js,使用 Playwright Chromium headless 模式
在 CLI 中运行浏览器测试套件,替代手动点击页面的方式。
支持 --verbose 和 --port 参数,测试失败时退出码为 1。

更新 CLAUDE.md,添加 TDD Red-Green-Refactor 工作流说明,
明确各阶段运行 run-tests.js 的时机。
…ldStore

WorldRuntime 中不再直接依赖 PersistenceService,实体迁移改为通过
WorldStore.getLegacyChunkDelta 读取旧档。Chunk.loadFromRecord 不再
回退 cache.entities,仅从 chunkRecord.runtimeEntities 或同会话 ShadowStore
恢复运行时实体。

WorldStore 新增 loadChunkRecord/commitChunkRecord/commitRuntimeEntities/
getLegacyChunkDelta 等专用方法,WorldRuntime 通过 _commitChunkRecord 统一
提交,兼容新旧 WorldStore API。

同步更新测试用例,验证新路径下的迁移行为与空结构补齐逻辑,
修复 ShadowStore 未清理导致的测试间污染问题。
…istenceService 依赖

- ChunkGenerator.gen 改为从 WorldStore.loadChunkRecord 读取权威数据,并构建兼容 Worker 的 snapshot
- ChunkPersistence.saveDebounced 统一通过 worldRuntime.flushChunk 写回,移除旧 saveChunkData 路径
- World unload 时统一通过 WorldRuntime.flushBeforeUnload 写回,消除 persistenceService 回退
- PlaygroundService 移除 persistenceService.cache 回退查找路径
- 新增 bootstrapping 和 WorldStore 读取路径的测试覆盖

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…O 尖峰

- flushBeforeUnload 改为将稳定快照入队而非立即 commit,chunk 卸载不再阻塞等待写盘
- 新增 flushPendingUnloadQueueWithinBudget 方法,runtime idle 时按 region 合批消费队列
- WorldStore 新增 applyRegionPatch 增量更新接口,PersistenceWorker 支持对应消息
- World.dispose 接入 flushAllPendingWork 确保退出时尽力写回剩余数据
- getRuntimeIdleStats 暴露 unload flush 队列指标用于遥测监控
- 新增大量测试覆盖入队/去重/region合批/退出flush等场景

关联 #69
- loadFromRecord 不再同步完成 build + finalize,改为只入装配队列交由调度器切片执行
- finalizeNonDeferredPhase 在 runtime-streaming 下延迟光源注册与 AO 刷新到 deferred finalize
- ChunkAssemblyScheduler 限制同轮只处理初始队列任务,防止新入队后续阶段连击
- onChunkFinalized 支持 deferAORefresh 选项,避免 finalized 当帧立即刷新 AO
- 新增测试覆盖装配队列切片行为与 deferred finalize 延迟逻辑

关联 #69
- Replace sourceHash with O(1) contentRevision for cache invalidation
- Add data-layer fidelity tasks (WorldStore/WorldRuntime/PersistenceWorker)
before runtime cache consumption
- Define renderCache serialization spec for TypedArray persistence
- Add contentRevision monotonicity constraints across flush/unload/patch
- Mark worker fallback as P1 optional, P0 uses main-thread stable fallback
- Handle cross-region overflow pollution (dirty-by-overflow -> miss)
- Require Chunk instance to explicitly hold contentRevision/renderCache

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
新增基于兴趣集调度的 chunk 加载节流方案 v2,替代 renderCache 方向:
- 设计文档:ChunkLoadScheduler 调度器 + 双层 consolidation 限流架构
- 实施计划:9 个 Task,从 feature flags 到 consolidation 队列的完整步骤
- 视觉对比:renderCache vs renderDelay 方案 CPU 尖峰分布对比图

关键改进:
- 新增 pending 过时任务淘汰机制(玩家快速移动时丢弃旧区域 key)
- consolidation 改为两层限流,保留现有 idle grace 语义不被破坏
- 首帧立即创建最近 chunk + 冷却帧控制,统一测试语义

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
设计文档升级:
- 将总体策略从两条链路扩展为三条,新增 ChunkAssemblyScheduler runtime 保守准入
- 统一 onExpansionFinished retry 入口至 ChunkLoadScheduler,防止旁路洪峰
- 新增 runtime-build 单任务耗时观测指标(runtimeBuildLastMs / MaxMs / LongTaskCount)
- consolidation 双层限流增加 in-flight 上限,降低 worker 回包同帧聚集概率
- 修复 _processDeferredConsolidationQueue 可能循环调用 scheduleConsolidation 的问题

计划文档同步:
- 拆分 Task 6(runtime assembly 收紧)与 Task 7(观测指标)
- 扩充 Task 9 测试覆盖范围,新增 in-flight 与循环调用验证
- 完成标准增加 onExpansionFinished 不绕过调度器、runtime assembly 不超标

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 新增设计约束章节:明确优化范围、非目标、正确性要求
- 精化 Worker Cache 语义:命中时刷新 LRU 顺序、clearWorld 清空缓存
- 新增 WorldRuntime 运行时缓存语义:_regionCache 允许保存部分 region
- Task 1 补充:所有写路径(saveRegionRecord / applyRegionPatch /
  saveRegionRecordsBatch / clearWorld)同步更新 Worker cache
- Task 3 补充:_upsertRegionCacheChunkRecord 注入机制,维持 flush/unload 基线
- Task 4 补充:测试需断言 _regionCache 注入、mock 改为 getChunkRecord
- Task 5 新增:回归检查与性能验收步骤

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
将 ensureChunkData 的读取路径从整包 region(9MB)改为 chunk 级裁剪(~100KB),
postMessage 传输量降低约 90%。

核心变更:
- PersistenceWorker 新增 LRU region 缓存(最多 6 个 region),新增 getChunkRecord
  方法,命中缓存时直接裁剪目标 chunk 返回,未命中时读取 DB 并缓存
- WorldStore.getChunkRecord 改为直接调用 Worker 的 getChunkRecord action,
  不再在主线程裁剪整包 region
- WorldRuntime.ensureChunkData 走 chunk 级读取,不再调用 ensureRegion
- 新增 _upsertRegionCacheChunkRecord 维持运行时最小 region 基线(__partial 标记)
- flushAllDirty 对 partial region 走 applyRegionPatch 增量写,避免覆盖未加载 chunk
- 引入 __runtimeEntitiesWasDefault 标记解决 Worker 侧默认值填充导致的旧档迁移跳过问题
- 写路径深 clone(structuredClone)避免 applyRegionPatch 原地修改污染缓存
- 更新测试 mocks 从 getRegionRecord 改为 getChunkRecord,新增 partial region 测试

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
为 chunk 装载路径添加细粒度性能监控,覆盖主线程与 Worker 端各阶段耗时,
支持按 chunk 聚合的性能报告输出,便于定位装载瓶颈。

核心变更:
- ChunkPerfMonitor 新增 aggregateChunkLoadPerf / printChunkLoadPerfReport /
  帧级耗时监控(startFrameChunkPerf / endFrameChunkPerf)
- Chunk.loadFromRecord 注入各子阶段打点(injectBlockData、staticEntities、
  entityRestore、enqueueAssembly),buildMeshesFromRecord 拆分 4 个子阶段
- Chunk._injectBlockData 添加 clear / iterate / write 细分计时
- ChunkAssemblyScheduler 记录排队延迟(queueWaitMs)与 stage 执行前后状态对比
- WorldWorker 端新增 _workerPerfPhases 返回子阶段耗时(faceCulling、
  buildBlockDataBlocks、buildScatteredBlocks、buildMeshData、buildRouting)
- World._onChunkWorkerResult 接收并透传 Worker 端子阶段打点
- GlobalInstancedMeshManager.replaceChunkVisibleBlocks / patchChunkVisibleBlocks
  添加性能记录
- World 暴露 printChunkLoadPerf / getChunkLoadPerf 供浏览器控制台调试

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 新增 MemoryWorldStore 作为运行期世界级权威内存存储
- World 启动时导入旧存档到内存,新世界生成直写内存
- chunk record 读路径优先从内存获取,回退 IndexedDB
- 方块修改立即同步到 MemoryWorldStore.applyBlockMutation
- 旁路 unload/background flush 运行期主链路
- 拆分 chunk 装配为 runtime-hydrate → runtime-build-mesh → runtime-finalize 三阶段
- GlobalInstancedMeshManager 新增 applyChunkDelta 增量 patch
- 补充性能打点:world.runtime-chunk-record-memory、global-instanced-mesh.delta-patch 等
- 新增 MemoryWorldStore 单元测试

当前状态:读路径切换和 flush 旁路已完成,但 stage 拆分仅完成
「拆分」本身,尚未实现「可中断」,接下来需要实现可中断装配
将 runtime-hydrate 和 runtime-build-mesh 两个 stage 改为可中断的分批执行模式,
确保单个 stage 耗时不超过 scheduler 预算(~3ms),避免帧卡顿。

核心改动:
- Chunk 新增 _assemblyProgress 游标状态,支持跨 scheduler 调用恢复执行
- 新增 _clearForBlockInjection / _injectBlockDataBatch 分批注入 blockData
- 新增 _buildMeshFromExistingBlockDataIncremental 状态机(iterate → convert-group → visible → build-mesh)
- 静态 _computeTransformMatrix 内联计算变换矩阵,避免 per-block 的 THREE.Object3D 开销
- Scheduler 处理 'continue'/'done' 返回值,实现 stage 重新入队或推进到下一阶段
- 新增 chunk.inject-block-data.partial / chunk.build-mesh-increment.partial 性能打点

旧版同步方法(_loadFromCachedRecord / _buildMeshFromExistingBlockData)保留不变,
供非 scheduler 场景(测试/首屏生成)使用。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
在所有测试执行完毕后补充一次最终进度回调,确保进度条显示 100% 后再隐藏,避免因最后几个快速测试导致视觉上进度丢失。
复用预分配的缓存数组/Set 替代 Array.from、[...set] 展开和每帧 new,
减少主循环 GC 压力。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
WorldWorker 在 bootstrap 阶段生成的 AO 数据不会被存入 MemoryWorldStore 权威数据源,
且在 finalize 阶段会被 _refreshAOFromStableSource → AOWorker 全量重算并覆写。
将 Worker 侧 AO 计算替换为常量 aoLow/aoHigh=1,减少 bootstrap 阶段的每方块 6 方向遮挡查询。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
健康检查技能重大升级:
- 新增 iOS (ObjC/Swift) 项目体检能力,含头文件注释、内存管理、命名规范检查
- 新增 9 种语言专属 checker:cpp, flutter, go, ios, java, js, php, python, ruby, rust
- 新增 5 个检查维度:架构、错误处理、可观测性、性能、技术栈合规
- run-check.sh 支持多语言自动路由分发
- 新增 generate-detailed-report.sh 生成详细体检报告
- 重新调整评分权重体系,降低对遗留代码结构问题的惩罚
- 附带 AO 热路径实现计划文档更新

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- AOWorker: 删除 createOcclusionCheckerFromCache 中每次合并 blockData 大对象的逻辑,改为按世界坐标直接定位 chunkCache 查询
- Chunk: _markAllBlocksDirtyAO 优先遍历可见实例(instanceIndexMap/visibleKeys),只标记 face culling 后的可见方块
- Chunk: _markBoundaryDirtyAO 改为按邻居方向直接生成边界影响带,不再扫描整个 blockDataArray
- Chunk: 新增 _markCornerDirtyAO 处理对角邻居方向的角点影响带补刷新
- World: onChunkAOStable 新增对角邻居(3x3x3 邻域)的角点补刷新逻辑
- Chunk: _refreshAOFromStableSource 非 fullRefresh 路径跳过 fullSync 调用,减少冗余通信
- 全链路添加 chunk.ao-refresh 系列性能打点
- 新增两条 AO 热路径回归测试
consolidation 重建整个 chunk 的 InstancedMesh 时,WorldWorker 写入的
中性 AO 值错误(aoLow=1, aoHigh=1 导致几乎所有顶点 AO=0,即全黑),
且重建后非脏方块的原有正确 AO 值被丢弃,仅 dirtyAOPositions(3x3x3
范围)被 AOWorker 修正,其余方块保持深色。

修复方案:
1. ChunkConsolidation 新增旧 AO 提取/恢复机制 — 清理旧 mesh 前提取
   所有可见方块的 AO,新 mesh 构建后对非脏位置恢复旧 AO 值。同时支持
   本地路径(group.children)和全局路径(GlobalInstancedMeshManager)。
2. WorldWorker 中性 AO 值修正为 0x00ffffff(全亮),作为安全网处理
   面剔除变化后新出现方块的 AO 初值。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
之前跳过 dirty 位置不恢复 AO,依赖异步 AO 刷新填补,导致合并完成后
有短暂窗口 AO 掉回中性值。现在 dirty 位置仍优先写回旧 AO,只有
新出现且无旧值的 dirty 实例才同步补算当前 AO。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@jayli jayli merged commit 8c39a36 into main May 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant