# Changelog

本文件按时间倒序记录 Infinitown / lite_infinitown 的所有版本变更。
版本号遵循 `主版本.次版本.修订` 语义化命名 + 短语后缀（`v4.0.0-immersive` 等），与 git tag 对齐。

> **lite_infinitown 起源**：从 `lovert_infinitown@v4.1.1-animals` 完整 fork 出来的精简版本，唯一差异是网格更小（4×4 chunk）+ 每 chunk 最多 1 个 master building 的规则。下方 v0.x 段为 lite 版历史，再往下是上游父项目的历史。

---

## [Unreleased]

### Fixed
- **路灯位置：路口 → 路段中点**（GAM-11） — `TownLifeSystem._addStreetLamps` 原本在每个内部路口的 4 个角各放一根黑色灯柱，白天与路口的红绿灯杆视觉重叠、拥挤。改为沿东西/南北每段路（相邻路口之间）的中点两侧人行道各放 1 根，路口本身不再放灯柱。灯柱总数 100 → 80，路口区域更干净，人行道照明覆盖到位。
- **路灯向路边靠齐**（GAM-11 跟进） — 上一轮把 `sidewalkOffset` 取成 4m，实际 `Road_Lane_01_fixed` 路面边缘（curb）在 ±10m 处（用顶点 Z 直方图核对），4m 落在了行车道内侧。改为 11m，灯柱紧贴路缘外 1m 的人行道，不再挡车也不再立在行人路中间。
- **删除/放置公园时长椅 sitter 联动**（GAM-12 Bug 2） — `_addParkSitters` 只在 `TownLifeSystem.build()` 末尾跑一次；玩家用 `GridManager.removeBuildingAt` 删除一个 `park_*_merged` 后，sitter 因为 parent 是 `townLife.root`（不是 `chunk.block`）会原地保留、悬浮在空格上方；反向新放公园则永远不长 sitter。新增 `TownLifeSystem.{clearSittersInChunk, addSittersForChunk}` 两个公开方法，`GridManager` 在 `setTownLife()` 注入后于 place/remove 时按 footprint 联动。漫游 NPC（`crowdAgents` / `NPCBatchVAT`）按设计不联动撤离。

## [v0.4.0-polish-realism] — 2026-05-16

主题：**小镇真实感打磨**（与 lovert v4.4.0 同步）。完整移植 4 项变更：移除交警 mesh + UI 折叠 + 车辆停止线前移 + NPC 进出建筑 + 远山自然化。OpenSpec change `polish-town-realism-and-interactions` 在 `openspec/changes/`。

### Added / Changed / Removed
与 lovert v4.4.0 完全等价。源码两侧 `diff` 仅 `Config.CHUNK_COUNT/TABLE_SIZE`、`WORLD_SIZE`、`FOG_NEAR/FAR` 等环境常量差异 + 个别历史注释顺序差异，无逻辑分支差异。

### lite 特别说明
- TrafficPolice 路口数：lite 25 个（6×6 → 5×5）vs lovert 361 个
- BuildingDoorRegistry 门点：两侧 BuildingCatalog 独立维护，elbo_b00/01/02 均添加默认 door（dx=0, dz=5, yaw=π）
- 远山带 perpStart=-560 与 lovert 一致（lite WORLD_SIZE=360 时大部分山被 FOG_FAR=320 隐去，符合预期）

## [v0.3.0-life-quality] — 2026-05-13

主题：**小镇生活补完包**（与 lovert v4.3.0 同步上线）。一次性处理 8 项交通 / 行人 / 椅子 / 信号视觉问题。北极星方向写入 [project_north_star_slow_life.md](../../root/.claude/projects/-data-lovert-infinitown/memory/project_north_star_slow_life.md)：长期向"都市天际线场景密度 + 慢生活节奏"演进。

### Added
- **路口红绿灯 emissive marker** — 在原版烘焙柱顶叠加发光球（lite 25 × 4 = 100），按 phase 切换红 / 绿；phase 切换前 1 秒 2Hz 闪烁
- **路口左 / 右 / 掉头转弯** — Car.js 实装含 U-turn；用 `this.direction` 数学旋转避免历史"读 rotation.y 数值"踩坑
- **必要时倒车** — 持续停滞 > 4 秒触发 6m 倒车
- **stuck 车诊断接口** `__trafficPolice.getStuckCars()`

### Changed
- **交通警察形象升级** — `worker.glb` → `business_man.glb`（深蓝制服 + 反光条），POLICE_SCALE 0.40 → 1.0
- **公园 sitter Y 校正** — `_scanBenchTops` 用 maxY 替代 mean，并应用 Quaternius -0.42m 偏移
- **凉亭 sitter Y 校正** — Mixamo 偏移 0.15 → 0.05

### OpenSpec
- 详见 [openspec/changes/improve-town-traffic-and-life/](openspec/changes/improve-town-traffic-and-life/)

---

## [v0.2.0-traffic-police] — 2026-05-13

主题：**交通警察 NPC 系统**（与 lovert_infinitown 同步上线）。在每个十字路口部署一个可见的交警，弥补此前红绿灯"凭空切换"的视觉断层。

### Added
- **交通警察 NPC 系统** — [src/scene/TrafficPoliceSystem.js](src/scene/TrafficPoliceSystem.js) + [src/objects/TrafficPolice.js](src/objects/TrafficPolice.js)。每个 chunk 边界交汇点（lite 网格 6×6 → 25 个）部署一个 instance。
- **路口独立 phase 状态机** — 4 相位 `green-x → all-red → green-z → all-red`，基础 6.5s 绿 + 1.5s 全红。起始偏移 0–6.5s 随机。
- **拥堵感知动态时长** — 排队 ≥ 6 → 绿灯 9.75s；排队 ≤ 2 且对向行人 ≥ 3 → 缩短 4s。
- **InstancedMesh 渲染** — 25 instance 共用一个 mesh，仅在 yaw 过渡 0.3s 内更新 matrix。
- **头部 yaw 过渡** — phase 进入时 0.3s 线性插值 `rotation.y`，模拟转身指挥。
- **反光背心** — 复用 `quaternius_people/worker.glb` + emissive 橙色 (0xff8800)。
- **NPCCounterWidget 交警计数** — "交警: 25" 只读行。

### Changed
- **信号灯：全局同步周期 → 路口独立** — [src/scene/ChunkGenerator.js](src/scene/ChunkGenerator.js) 删除 `_trafficSignalState` 18s 周期、`isTrafficAxisGreen`，三个对外 API 改为代理给 `TrafficPoliceSystem`。Car/NPCPathfinder 接口签名不变。
- **失去"绿波"特性** — 之前所有路口同步绿，现在按各自偏移走。

### OpenSpec
- capability `traffic-police`（[openspec/changes/add-traffic-police-system/](openspec/changes/add-traffic-police-system/)）。

---

## [v0.1.0-lite-init] — 2026-05-13

主题：**从 lovert_infinitown 完整复制为 lite 子项目，缩小网格 + 引入多格 master 地标占位**。

### Changed
- [src/core/Config.js](src/core/Config.js)：`TABLE_SIZE` / `CHUNK_COUNT` 20 → 4，`WORLD_SIZE` 1200 → 240，`FOG_NEAR/FAR` 400/700 → 120/240（按 1/5 比例同步拉近，否则 240m 世界整张图泡雾里看不清）
- [src/core/Config.js](src/core/Config.js)：新增 `MAX_MASTERS_PER_CHUNK = 1`（规则常量已就位，校验逻辑待落地）
- [src/data/SaveManager.js](src/data/SaveManager.js)：`SAVE_KEY` `infinitown_save` → `lite_infinitown_save`（避免和兄弟项目 lovert_infinitown 的 localStorage 串档）
- [vite.config.js](vite.config.js) + [server.sh](server.sh)：开发端口 8080 → 8100，PID/log 文件名加 `lite-` 前缀（两个项目可同时跑）
- [package.json](package.json)：name → `lite_infinitown`，version → `0.1.0`，versionTag → `v0.1.0-lite-init`
- [index.html](index.html)：title / 加载页 → `Lite Infinitown`

### Added
- [src/data/BuildingCatalog.js](src/data/BuildingCatalog.js)：新增 `civic`（公共）分类
- `lite_school`（小学，3×2 footprint）和 `lite_lake`（湖，2×2 footprint）两条多格 master 占位条目 — 复用 `poly_pizza/apartment_building.glb` 和 `kenney_nature/ground_riverStraight.glb` 作占位模型，groundStyle 分别为 `urban` / `water`

### 已知遗留
- **CHUNK_COUNT 取值不一致**：用户描述为「横轴 5 个 chunk（CHUNK_COUNT=4）」，两值矛盾，当前按括号里的代码值 `4` 落地（4×4=16 chunks）；如需 5×5 把 `TABLE_SIZE/CHUNK_COUNT` 改 5、`WORLD_SIZE` 改 `5*60`、雾相应再调（约 150 / 300）
- **每 chunk 最多 1 master 校验未实施**：`Config.MAX_MASTERS_PER_CHUNK` 常量已就位，但 `GridManager.isPlacementValid` 还没读它 — 当前规则只是文档约定，玩家仍能在同一 chunk 内放多个普通 1×1 master
- **school / lake 模型为占位**：`poly_pizza/apartment_building.glb` 不是学校、`kenney_nature/ground_riverStraight.glb` 是河块不是湖；视觉对不齐语义，等专门资源到位再替换 url
- npm install 未执行：源项目 `node_modules` 没拷过来，第一次 `./server.sh start` 前需要 `npm install`

---

## [Unreleased]

### Fixed (v4.4.0 后 round 4 —— 远山 "牛粪" 美化)
- **远山看起来像牛粪 (uniform olive mounds)** — 单色无层次 + 无大气透视。本轮把每座山变成独立着色 + 远近 band 明显分级：
  - `MountainGeometryFactory` 新增 `tintShift`（0=纯色山近、1=完全 fade 到大气色）与 `hueShift`（每座山 ±0.7 的色相微抖动，绿/橄榄/蓝绿区分）
  - `_addDistantMountains`：远 band 传 `tintShift=0.65`（明显雾蓝化）、近 band 传 `tintShift=0.20`（保留绿色），每座山 `hueShift=(rng()-0.5)*1.4`
  - 颜色 palette 加 `FOREST_PATCH = 0x35522c` 暗绿；低-中坡用 chaotic patchNoise 随机点缀（concentration > 0.55 区域 lerp 0.55 到 forest 暗绿）→ 山体出现"森林斑块"质感，不再纯色平涂
- **雾气看不见** — round 3 的横躺面在抬高视角下完全失效。本轮整改：
  - **新增 12 张立式 mist bank**（4 方向 × 3 层，y=25m，宽 1600×100m），位于近远 band 之间不同距离，opacity 0.30/0.22/0.16，color 渐变 0xd5e2ee → 0xb8c8d8 → 0x9eb3c4 → 视觉上"近浅远深"的雾层
  - 保留 4 张低位地表雾面（横躺，y=6m，opacity 0.14，color 0xe2ecf2）作为山谷薄霭
  - renderOrder 4→7 让多层 alpha-blend 正确叠加


### Fixed (v4.4.0 后 round 3)
- **远山仍然太高/突兀（"像冰激凌"）** — 参考用户实景图（缓圆山丘 + 大气透视 + 谷雾）。本轮：
  - 山高度大幅下调：近圈 22-50m（原 45-100）/ 远圈 45-85m（原 95-190），baseR 加宽 → "rolling hill"质感
  - `MountainGeometryFactory` 重写：levels 6→7、top ring rNorm 0.20→0.32（broader cap）、sub-peak bump 0.07→0.04、subPeakCount 2-3→1-2、segments 18→20、移除 cy lift（顶部平）、颜色从"绿-灰-白"改为"绿-雾绿-雾蓝灰"（大气透视，无积雪）
  - **新增 `_addMistLayers(edge)`**：四方向各 3 层水平半透明面（y=3/9/17，opacity 0.22/0.16/0.10，颜色 0xc8d8e8 接近天/雾色）→ 谷间薄霭叠加效果
- **行人原地踏步** — 是 round 2 的 `_isBlockedByCar` 误伤：人行道上 NPC 沿路走，旁边车道经过的车被误判为"前方有车"导致原地等。修复 = `_isBlockedByCar` 调小到 2.0m × ±1.2m（原 3.0 × 1.6），**且只在 NPC 当前 step 跨 chunk 边界（即在过马路）时才检查**，沿人行道走完全不触发；另加 stuck 计数器 `_carBlockedFrames`，连续 60 帧（~1s）卡住则强制走（覆盖"车永远停那里"的边界情形）


### Fixed (v4.4.0 后再补丁 round 2)
- **远山仍像"冰激凌"** — `MountainGeometryFactory` 重新调参：sub-peak bump 0.18→0.07（不再尖刺）；segments 8→18（圆滑轮廓）；vertical levels 4→6（更多腰线）；snow zone threshold 0.75→0.85（雪盖更小）；颜色加入 per-vertex micro variation（绿/棕/灰随机微抖）让山体不再纯色平涂
- **行人仍穿车** — v4.4.1 round 1 只让 *车* 看到 *人*，反过来 *人* 不看 *车*。本轮：`NPCBatchVAT` 新增 `_isBlockedByCar(ins, dirX, dirZ)`，每帧 t 推进前查"前方 3m × ±1.6m 内是否有车"，命中则不推进 t（NPC 原地等）；`NPCSystem` 新增 `setCarHost(carHost)` 并在 `addBatch` 时自动转发，InfinitownApp 调用 `npcSystem.setCarHost(this.table)`


### Fixed (v4.4.0 实施后补丁，~2026-05-16 末)
- **车辆停在斑马线上** — 用户截图反馈 v4.4.0 的 `CROSSWALK_HALF_WIDTH=4` 仍让车头压在斑马线纹路上。bump 到 7，且 `shouldVehicleStopForSignal` 的 stopDist 加上车身半长（large 4.5m / small 2.5m）→ 小车 stopDist=15m / 大车 stopDist=19.5m，车头明显落在斑马线外
- **车之间互相穿模** — `detectCar()` 的 45° 锥和 `_checkFollowDistance()` 的 dot>0.9 同向过滤都漏了"两车从不同方向几乎同位置"的情况。新增 `Car._checkVehicleOverlap(neighbors)`：与方向无关地查"中心距 ≤ MIN_DIST(小5/大7) 且不在严格后方"，命中立即 speed=0；新 stopReason='overlap'
- **行人穿车** — 车的避让用 `npcSystem.eachPedestrianNear`，原本只查 VAT 街道行人，TownLife 的"学校/工厂/湖边密集人群"完全没被纳入。NPCSystem 新增 `setTownLife(townLife)`；`eachPedestrianNear` 现在同时迭代 `townLife.people` 与 `townLife.crowdAgents`，把每个 `actor.mesh.position` 暴露给车的前向检测盒


### Fixed (lite-only)
- **车辆死锁 / 小地图继承大城车辆默认值**：lite 当前是 6×6 chunk，小镇内部可生成车辆的有效路网远小于 lovert 20×20，但 UI 仍继承 `max=420 / initial=280`。280 辆车会在 16 个内部 chunk 上反复生成，路口和车道入口被货车/公交挤满，随后 `followStopDistance`、行人避让、右侧让行互相叠加，形成永久死锁。修法：`ChunkGenerator.getVehicleCapacity()` 按 lite 路网容量计算上限，`InfinitownApp._initUI()` 使用该上限，lite 默认车辆数改为容量内的 64；`setVehicleCount()` 也二次 clamp，避免外部输入继续灌入 280+。
- **车辆出生点重叠导致开局堵死**：`_spawnOneVehicle()` 以前随机 chunk/lane 后直接 add，车辆多时会贴着已有车辆出生。修法：新增 `_isVehicleSpawnClear()`，生成时检查附近 9m 内已有车辆，过近则丢弃本次 spawn 并重试。
- **路口混合死锁不会自动解开**：旧逻辑只给“右侧让行”做了 pinwheel 超时突破，但真实卡死还会由 radar 停车、行人避让和横向让行叠加造成。修法：`Car` 增加 `_updateDeadlockEscape()`，当车辆在冲突点连续停止且前方没有同车道 leader 时，给予短暂 crawl-through token；跟车队尾不会拿 token，避免整排车同时穿模冲出。
- **死锁脱困不能穿过行人**：车辆拿到 crawl-through token 后仍必须让行人。修法：行人避让检测不再受 deadlock bypass 影响，检测到过街行人会立即取消 token 并停车；`_updateDeadlockEscape()` 也不再允许 `pedestrian` stopReason 触发脱困。
- **红绿灯没有实际约束交通**：场景里有红绿灯视觉元素，但车辆和行人此前只靠雷达/避让自由通行，路口容易互抢。修法：`ChunkGenerator` 新增 18 秒信号周期（南北向 / 全红 / 东西向 / 全红），`Car.shouldVehicleStopForSignal()` 让车辆红灯停在线前且不能被 deadlock bypass 穿过；`NPCPathfinder.shouldWaitForSignal()` 让跨 chunk 过街的行人只在对应方向绿灯时进入路口。

### Changed (lite-only)
- **网格从 4×4 扩到 6×6**（[src/core/Config.js](src/core/Config.js)）：`TABLE_SIZE` / `CHUNK_COUNT` 4 → **6**，`WORLD_SIZE` 240 → **360m**。雾 NEAR/FAR 120/240 → **180/320**（按比例放大，否则 360m 世界仍按 240m 的雾参数会让远景早早消失在白雾里）
- **暂时撤下 lite_school**（[src/data/BuildingCatalog.js](src/data/BuildingCatalog.js)）：3×2 公寓楼占位模型语义不对（不像学校），先从 catalog 移除等找到合适的 school 模型再加回。`civic` 分类保留，仅 `lite_lake` 一条
- **TownLifeSystem 自动地标也撤下**（[src/scene/TownLifeSystem.js:90-101](src/scene/TownLifeSystem.js#L90)）：build() 里写死 `_addElementarySchool([2, 2], ...)` 在 lite 6×6 里照样会自动生成一个占四分之一地图的大学校（用户截图证实）。整块（lake / school / middle / university / transit / factory）按 `Config.WORLD_SIZE >= 600` 门控，lite 跳过。修完截图里那个大建筑应该消失
- ⚠️ **存档兼容性**：`SAVE_KEY = lite_infinitown_save` 里如果有旧版 4×4 网格的放置数据，加载到 6×6 时坐标偏移会让建筑落到错位 chunk；建议在浏览器 console 执行 `localStorage.removeItem('lite_infinitown_save')` 后刷新，或在 UI 用清空按钮（如果有）

### Fixed / Changed (从 lovert 同步)
- **车辆 radar bug / 跟车硬停 / 行人避让 / followStopDistance 加大 / 路口右侧让行** — 详见 lovert CHANGELOG [Unreleased]。改动文件：[src/objects/Car.js](src/objects/Car.js) + [src/objects/NPCBatchVAT.js](src/objects/NPCBatchVAT.js) + [src/scene/NPCSystem.js](src/scene/NPCSystem.js) + [src/scene/InfinitownApp.js](src/scene/InfinitownApp.js)
- ~~deterministic 右行 spawn + 路口转弯~~ 已回滚，详见 lovert CHANGELOG `Reverted` 段
- **BoundaryBandSystem 浅蓝水域错位** — 用户截图里地图边界外漂浮的浅蓝色矩形，根因是 `_addSouthParkWater` 等主题分区使用绝对坐标（-275, -420）写死给 lovert 1200m 世界，在 lite 240m 里位置完全错乱。修法：theme districts 按 `Config.WORLD_SIZE >= 600` 门控，lite 跳过；band 宽度也按 `min(150, edge × 0.75)` 缩。改动文件：[src/scene/BoundaryBandSystem.js](src/scene/BoundaryBandSystem.js)
- **人口滑块接管 sitter + 上限拉到 3000**（上一轮）— 同样详见 lovert CHANGELOG。改动文件：[src/scene/TownLifeSystem.js](src/scene/TownLifeSystem.js) + [src/scene/InfinitownApp.js](src/scene/InfinitownApp.js)

### Chore
- [vite.config.js](vite.config.js) 加 `server.watch.ignored`，排除 `public/assets/**`、`asset_input/**`、`codex_sources/**`、`KayKit_*/**`、`assets/main/Maps/**` — lovert + lite 同时跑时，两个 vite 的 inotify watcher 会超出系统配额 28379 导致后启的 ENOSPC 起不来

### 已知遗留（lite 端）
- `server.sh stop` PID file 只杀 `npx vite` wrapper 不杀 `node vite` 子进程，孤儿继续占端口 → 下次 start 自动跳到非 8100。修复方法：把 `kill "$PID"` 换成 `pkill -P "$PID"` 或 `kill -- -"$PID"`（待用户授权再改）

---

## 以下为父项目 lovert_infinitown 在 fork 前的历史（docs-only backfill）

## [v4.1.1-animals] — 2026-05-12

主题：**Quaternius 农场动物 GLB 接入 + .gltf→.glb 修复**。

### Added
- `TownLifeSystem._addHorse` — 牛/马/羊三类按 48/42/10 分布
- `_loadStaticModel` — fetch + arrayBuffer + parse(buffer) 渐进可见性加载
- `_replaceAnimalWithModel` — box fallback 立刻可见 → 异步 cloneSkeleton + 替换 + Eating/Idle/Idle_Headlow 动画播放
- `_fitModelToGround` — 用 Box3 算 bbox 缩放到 targetSize 米
- `_pickAnimationClip` — 按 preferredNames 优先级选 clip
- `animalMixers` 数组在 update() 内 mixer.update(delta)
- 新 GLB 文件：`public/assets/models/quaternius_animals/{cow,horse,horse_white}.glb`

### Fixed
- **r183 GLTFLoader 静默 bug**：`parse(arrayBuffer, '', onLoad, onError)` 对 `.gltf` 含 inline base64 buffer 的 arrayBuffer **既不触发 onLoad 也不触发 onError**（headless 验证）。同样 `loader.load(url)` 也卡死无回调。规避方法：把 `.gltf` 重打包成 `.glb` 二进制走熟悉的 .glb 路径
- 转换脚本：`/tmp/gltf2glb.mjs`（解 inline base64 buffer，重打包成 12B header + JSON chunk + BIN chunk 的 GLB binary format，体积 ~40% 缩减）
- 加 try/catch + console.error 到 `_replaceAnimalWithModel`，避免后续 SkeletonUtils.clone / clipAction 异常被 promise 静默吞掉

### 已知遗留
- `_addSheep` 还是 sphere+box 拼装，未接 GLB（Quaternius Asia Pack 还没下，没有 sheep GLB 候选）

---

## [v4.1.0-townlife] backfilled note

CHANGELOG 段在 commit `3674c4e` 时已写，本段仅为 v4.1.1 backfill 留 anchor。

### 待规划阶段
- 阶段 7：NPC 类型扩展（小孩 running 缩放、推婴儿车、坐姿）
- 阶段 8：用户自放建筑触发关联 NPC（公园椅 → 扇扇人；自放学校 → 学生群；工厂 → 工人）
- 阶段 9：性能调优 + LOD + 1500+ NPC 升 VAT

---

## [v4.1.0-townlife] — 2026-05-12

主题：**沉浸式小镇生活系统扩展与问题修正**。codex 主导，本轮新增 `TownLifeSystem` 引入 6 大城镇地标、时间/车辆/人口三个横切控制、1450 InstancedMesh 轻量人群、拥堵动力学；并修复了一连串风格/比例/碰撞/朝向问题。详见下方 Added / Fixed / Changed 段。

### Added
- `TownLifeSystem` 增加固定城镇生活区：湖、小学、中学、大学、公交/地铁站、工厂、远山、路灯、校门、校园围栏、操场、早晚高峰人流与车辆事件。
- 增加时间控制：右上角可调时间，驱动昼夜光照、夜间路灯、楼房窗户灯光、学校早高峰/下午放学、工厂下班事件。
- 增加车辆数量控制：右上角可调车辆数量，并新增拥堵系数；车辆数量越高，路口附近越慢，出现排队和停停走走效果。
- 增加大规模人口支持：人物数量上限提升到 `1500`，默认 `760`；保留少量真实 GLB 路人，其余使用轻量 `InstancedMesh` 人群承载。
- 增加轻量人群类型：街道人群、校园人群、湖边游客、工厂工人、亲子并排行走、建筑门口进出人群。
- 湖区增加浅水层、岸线、涟漪、荷叶、石桥、凉亭、船、码头和湖边游客，减少“平面色块”感。
- 草地增加少量低模牛羊装饰。
- 远景山体改为前后两层山脊，并增加山脚树丛，弱化单个尖锥假山感。
- 小学和中学增加侧翼、入口雨棚、屋顶层次、门厅和更多窗户，减少“大盒子建筑”感。

### Fixed
- 问题：人物数量只有几百时大城镇显得空。
  解决：引入分层人口系统，街道路人 GLB 只保留安全上限，主体人流改用轻量实例化人群，支持 `1500` 总人口。
- 问题：轻量人群最初比例过大，像“大头积木人”。
  解决：按角色压缩比例，小学生、中学生、成人、工人分别使用不同 scale，并持续下调到接近原街道路人的视觉比例。
- 问题：大街上看不到人。
  解决：调整人群激活顺序，优先激活街道/人行道人群，再填充校园、工厂、湖区等区域。
- 问题：部分行人出现在车行道上。
  解决：街道、亲子、门口进出人群统一使用 `NPCPathfinder` 同款人行道 inset=`22` 坐标，避免落到车道中线。
- 问题：学校和工厂人物速度过快、比例不稳。
  解决：统一降低校内/厂区人群移动速度，区分小学生、中学生、成人、工人尺寸。
- 问题：校园/工厂人物一度是圆柱体。
  解决：改为低模人形 fallback，含头、身体、腿、手臂，和街道路人的比例接近。
- 问题：车辆穿过学校、工厂、建筑区。
  解决：`TownLifeSystem.getBlockedVehicleCells()` 标记多格功能区，`ChunkGenerator.setBlockedCells()` 接收阻挡格；普通车辆进入阻挡格前会反向避让。
- 问题：学校操场上出现汽车。
  解决：学校/工厂/湖等多格功能区加入车辆阻挡，生成车辆时跳过阻挡格，并移除已落入阻挡格的车辆。
- 问题：早高峰接送车看起来像黄色发光方块。
  解决：事件车改用独立 `vehicleGlass` 材质，不再复用楼房夜间发光窗户材质。
- 问题：早高峰接送车比例偏大。
  解决：缩小送学车和工厂下班车尺寸，车窗同步按比例缩小。
- 问题：早高峰事件车横着开。
  解决：修正事件车朝向公式，让车辆长边按行驶方向对齐。
- 问题：早高峰学校门口车路径穿过建筑/地块。
  解决：送学车改为校门外道路上的短距离排队挪动，不再做穿越地块的长直线轨迹。
- 问题：牛羊像临时调试方块，观感粗糙。
  解决：减少数量，重做为更小的分组低模动物，并降低它们在画面中的存在感。
- 问题：右上角控制轴布局混乱。
  解决：统一收纳为“小镇控制”，包含时间、车辆数量、人物数量三行控制。

### Changed
- 人物数量滑条语义从“单一路人 batch 数量”改为“全城人物总量”，由 `NPCCounterWidget` 分配给 `TownLifeSystem` 与 `NPCSystem`。
- 车辆数量滑条上限提升到 `420`，默认车辆数至少 `280`，用于营造更明显的高峰车流。
- 街道路人 GLB 上限保持较低，避免 800-1500 人时大量骨骼动画拖垮性能。
- `TownLifeSystem` 的事件人群不再依赖旧的详细人物激活状态，避免人口分层后早晚高峰事件不显示。

### Verification
- 多轮执行 `npm run build` 均通过。
- 仍存在 Vite 原有警告：`jquery.min.js` 非 module 无法被打包，以及主 chunk 超过 500KB；这些不是本轮改动引入。
- Headless 验证（2026-05-12）：无 JS 错误；townLife.people=288 / animals=14 / eventPeople=144 / eventVehicles=24 / blockedCells=43 / crowdCapacity=1450 / ripples=6 / npc_batches=7；`_peak` 钟形曲线 hour=7.5→schoolMorning=1.0、hour=17.5→factoryEvening=1.0 触发正确。

---

## [v4.0.0-immersive] — 2026-05-11

主题：**让边界消失，让人群有群体感**。codex 主导本轮，反思详见 `feedback_visual_engineering.md`。

### Added
- `src/scene/BoundaryBandSystem.js`：在 20×20 网格外铺连续景观（草地、人行道、绿篱、行道树、远景建筑群、路口对接 6 slot），用 `BoxGeometry+Cylinder+Dodecahedron` 程序化生成 + seeded RNG，零素材依赖。
- NPC 群体感 5 维 jitter：`scaleVariance` / `speedVariance` / `lateralSpread` / `headingOffset` / `t=Math.random()`（初始 phase 随机）。
- NPC `spawnRadius` + `stayInSpawnArea`：可限定生成区域。
- Quaternius CC0 角色 7 个：`woman_casual` / `woman_tank_top` / `woman_dress` / `man_suit` / `man_long_sleeves` / `business_man` / `casual_character`（接受纯色风混搭）。
- `NPCSystem.setTotalCount`：按 baseCount 比例分配总人数到各 batch。
- `NPCCounterWidget` 支持 `batchIdx=null` → 走全局 setTotalCount。

### Changed
- `NPCBatch.load`：改 `fetch → arrayBuffer → loader.parse(buffer, '')`，绕过 Vite dev server 资源池阻塞。
- 撤掉单格边界要素（Kenney 河/山 7 条），改由 BoundaryBandSystem 一次性铺设。

### Removed
- `kenney_river_*` / `kenney_cliff_*` 共 7 条 catalog 条目（纯色风与城市内 Boss/Elbo 贴图风格不兼容）。

---

## [v3.3.0-npc] — 2026-05-11

主题：**让小镇有活人**。

### Added
- `src/scene/NPCSystem.js`：顶层管理，注入 InfinitownApp.update 链路。
- `src/objects/NPCBatch.js`：单 batch 管 N 个同类型 NPC，`SkeletonUtils.clone()` + 独立 `AnimationMixer`（Plan B，~50 上限）。
- `src/scene/NPCPathfinder.js`：4 corners/chunk 节点图，`inset=22` 人行道带（避开车道）。
- `src/ui/NPCCounterWidget.js`：右上角滑块 + 数字框（0-100）。
- Mixamo Boss 角色：`walking.glb` / `running.glb` / `sitting.glb`（暖色贴图，与 Elbo 砖楼匹配）。
- Root motion filter：剥 `mixamorig:Hips.position` track，避免动画循环闪回 1-2 秒前位置。

### Fixed
- 段间过渡闪现 bug：t≥1 时切节点后必须重算 `_tmpFrom/_tmpTo` 再 lerp，剩余距离换算成新段 t。

### Tuned
- NPC 速度从 8 → 3 → 1.5（接近真人步行）。
- NPC scale 从 4 → 1.0 → 2.0 → 3.0 → 1.5（人比 1 辆车稍高）。

---

## [v3.2.0-boundary] — 2026-05-11

主题：**边界系统基础设施**（阶段 1-4，本是 codex 反思前的尝试，后被 v4.0 替代为 BoundaryBandSystem）。

### Added
- `GridManager.isBorderCell(gx, gy)`：识别最外圈 76 格。
- BuildingCatalog `placement` 字段：`border-only` / `interior-only` / `any`。
- BuildingCatalog `groundStyle` 字段：5 套样式（urban / industrial / rural / water / mountain）。
- `GridManager._addGroundPatch(chunk, style)`：扩展支持不同 style。
- `placeMultiCell` + master/slave cells：支持 footprint 多格放置。
- BuildingPlacer ghost：违规格涂红 + 多格高亮。
- 单格 GLB 边界要素：Kenney 河/山（4 河 + 3 山，纯色）+ Poly Pizza 公寓/烟囱/仓库（贴图）。
- `src/debug/Inspector.js`：按 I 切换，点击场景输出 mesh 信息。
- 加载动画 overlay：进度条 + 旋转图标 + 渐隐过渡。

### Fixed
- 蓝底 bug：Three.js r183 不再尊重 `material.visible = false`，必须 `mesh.visible = false`。移除 ChunkScene 和 BuildingPlacer 的 raycast plane，改用 `Raycaster.ray.intersectPlane()` 数学求交。

### Architecture decisions
- `CLAUDE.md` 新建：合并 karpathy 行为准则 + Infinitown 项目背景。

---

## [v3.1-pre-kaykit] — 2026-04-09

KayKit GLTF 集成后的回滚标记点。

### Reverted
- KayKit City Builder Bits 全量撤掉（纯色低多边形风与 Elbo 砖楼根本不兼容）。

参考：[project_asset_direction.md](/root/.claude/projects/-data-lovert-infinitown/memory/project_asset_direction.md)

---

## [v3.0-city-builder] — 2026-04-08

主题：**从无限拼接 → 20×20 有界城市建造器**。

### Added
- 20×20 有界网格（CHUNK_COUNT=20，CHUNK_SIZE=60）。
- 建筑放置 / 删除 / 拖放 / R 旋转 / Esc 取消。
- 点击/拖拽区分（5px 阈值）。
- 6 个分类 Tab：商业 / 住宅 / 服务 / 休闲 / Elbo / 边界。
- 罗盘 widget（E/W 旋转 ±15° / N/S 俯仰）。
- 滚轮缩放 + pinch 手势。
- 自动存档（localStorage + JSON 导入导出）。

### Fixed
- 边界外幽灵汽车：越界对象设为不可见。
- 建筑放置失败：世界坐标 → 场景本地坐标转换。

---

## [v2.0-r183-stable] — 2026-04-07

主题：**Three.js r84 → r183 升级**。

### Changed
- Three.js 从 r84（19425 行混淆 main.nice.js）→ r183（npm 包）。
- 60 个独立 ES Module 替代 browserify 打包。
- 自定义 36KB GLSL PBR shader → MeshStandardMaterial。
- Vite 构建替代 jQuery CDN + http-server。
- 移除 Bluebird / lodash 依赖。

### Architecture
- AmbientLight + HemisphereLight + DirectionalLight 光照体系。
- PCFSoftShadowMap 阴影 + Scene.fog 雾效。

详见 `RELEASE.md`。

---

## [v1.0-working] — 2026-04-06

原始 Little Workshop 版本修复运行：CDN 修复、补充 CSS、开发服务器。

Three.js r84 时代的原版，单文件 browserify 打包。

---

## 发版流程

每次发版三件事（按顺序）：
1. 改 `package.json` 的 `version` 字段。
2. 在本文件 `## [Unreleased]` 下方新增对应 `## [vX.Y.Z-tag]` 段落（用 `Added/Changed/Fixed/Removed` 子标题）。
3. 用户验收后打 git tag：`git tag vX.Y.Z-tag -m "<主题>"`。

UI 右下角的版本角标由 `vite.config.js` 注入 `__APP_VERSION__`（从 `package.json` 读取），无需手动同步。
