فهرست منبع

将项目改造为"荷锄"种田游戏,替换文档、图标及游戏源码

- 替换 README.md 为荷锄项目文档
- 完善 .gitignore(Android/OS/编辑器/构建产物)
- 更新应用图标资源
- 移除原 normal 变体的 MainActivity 等文件
- 添加 embed/assets 完整游戏源码
- 更新 Gradle 构建配置与 Manifest

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
nh99sysrj6-blip 6 ماه پیش
والد
کامیت
0584ca5fb8
100فایلهای تغییر یافته به همراه2939 افزوده شده و 207 حذف شده
  1. 25 2
      .gitignore
  2. 238 179
      README.md
  3. 53 26
      app/build.gradle
  4. 8 0
      app/src/embed/assets/.busted
  5. 198 0
      app/src/embed/assets/.kiro/settings/lsp.json
  6. 1 0
      app/src/embed/assets/.kiro/specs/layered-architecture-refactor/.config.kiro
  7. 884 0
      app/src/embed/assets/.kiro/specs/layered-architecture-refactor/design.md
  8. 166 0
      app/src/embed/assets/.kiro/specs/layered-architecture-refactor/requirements.md
  9. 186 0
      app/src/embed/assets/.kiro/specs/layered-architecture-refactor/tasks.md
  10. 403 0
      app/src/embed/assets/DATA_GUIDE.md
  11. 490 0
      app/src/embed/assets/MOD_GUIDE.md
  12. 287 0
      app/src/embed/assets/README.md
  13. BIN
      app/src/embed/assets/assets/VonwaonBitmap-16px.ttf
  14. BIN
      app/src/embed/assets/assets/characters/human/axe/base_axe_strip10.png
  15. BIN
      app/src/embed/assets/assets/characters/human/axe/bowlhair_axe_strip10.png
  16. BIN
      app/src/embed/assets/assets/characters/human/axe/curlyhair_axe_strip10.png
  17. BIN
      app/src/embed/assets/assets/characters/human/axe/longhair_axe_strip10.png
  18. BIN
      app/src/embed/assets/assets/characters/human/axe/mophair_axe_strip10.png
  19. BIN
      app/src/embed/assets/assets/characters/human/axe/shorthair_axe_strip10.png
  20. BIN
      app/src/embed/assets/assets/characters/human/axe/spikeyhair_axe_strip10.png
  21. BIN
      app/src/embed/assets/assets/characters/human/axe/tools_axe_strip10.png
  22. BIN
      app/src/embed/assets/assets/characters/human/carry/base_carry_strip8.png
  23. BIN
      app/src/embed/assets/assets/characters/human/carry/bowlhair_carry_strip8.png
  24. BIN
      app/src/embed/assets/assets/characters/human/carry/curlyhair_carry_strip8.png
  25. BIN
      app/src/embed/assets/assets/characters/human/carry/longhair_carry_strip8.png
  26. BIN
      app/src/embed/assets/assets/characters/human/carry/mophair_carry_strip8.png
  27. BIN
      app/src/embed/assets/assets/characters/human/carry/shorthair_carry_strip8.png
  28. BIN
      app/src/embed/assets/assets/characters/human/carry/spikeyhair_carry_strip8.png
  29. BIN
      app/src/embed/assets/assets/characters/human/carry/tools_carry_strip8.png
  30. BIN
      app/src/embed/assets/assets/characters/human/dig/base_dig_strip13.png
  31. BIN
      app/src/embed/assets/assets/characters/human/dig/bowlhair_dig_strip13.png
  32. BIN
      app/src/embed/assets/assets/characters/human/dig/curlyhair_dig_strip13.png
  33. BIN
      app/src/embed/assets/assets/characters/human/dig/longhair_dig_strip13.png
  34. BIN
      app/src/embed/assets/assets/characters/human/dig/mophair_dig_strip13.png
  35. BIN
      app/src/embed/assets/assets/characters/human/dig/shorthair_dig_strip13.png
  36. BIN
      app/src/embed/assets/assets/characters/human/dig/spikeyhair_dig_strip13.png
  37. BIN
      app/src/embed/assets/assets/characters/human/dig/tools_dig_strip13.png
  38. BIN
      app/src/embed/assets/assets/characters/human/doing/base_doing_strip8.png
  39. BIN
      app/src/embed/assets/assets/characters/human/doing/bowlhair_doing_strip8.png
  40. BIN
      app/src/embed/assets/assets/characters/human/doing/curlyhair_doing_strip8.png
  41. BIN
      app/src/embed/assets/assets/characters/human/doing/longhair_doing_strip8.png
  42. BIN
      app/src/embed/assets/assets/characters/human/doing/mophair_doing_strip8.png
  43. BIN
      app/src/embed/assets/assets/characters/human/doing/shorthair_doing_strip8.png
  44. BIN
      app/src/embed/assets/assets/characters/human/doing/spikeyhair_doing_strip8.png
  45. BIN
      app/src/embed/assets/assets/characters/human/doing/tools_doing_strip8.png
  46. BIN
      app/src/embed/assets/assets/characters/human/idle/base_idle_strip9.png
  47. BIN
      app/src/embed/assets/assets/characters/human/idle/bowlhair_idle_strip9.png
  48. BIN
      app/src/embed/assets/assets/characters/human/idle/curlyhair_idle_strip9.png
  49. BIN
      app/src/embed/assets/assets/characters/human/idle/longhair_idle_strip9.png
  50. BIN
      app/src/embed/assets/assets/characters/human/idle/mophair_idle_strip9.png
  51. BIN
      app/src/embed/assets/assets/characters/human/idle/shorthair_idle_strip9.png
  52. BIN
      app/src/embed/assets/assets/characters/human/idle/spikeyhair_idle_strip9.png
  53. BIN
      app/src/embed/assets/assets/characters/human/idle/tools_idle_strip9.png
  54. BIN
      app/src/embed/assets/assets/characters/human/mining/base_mining_strip10.png
  55. BIN
      app/src/embed/assets/assets/characters/human/mining/bowlhair_mining_strip10.png
  56. BIN
      app/src/embed/assets/assets/characters/human/mining/curlyhair_mining_strip10.png
  57. BIN
      app/src/embed/assets/assets/characters/human/mining/longhair_mining_strip10.png
  58. BIN
      app/src/embed/assets/assets/characters/human/mining/mophair_mining_strip10.png
  59. BIN
      app/src/embed/assets/assets/characters/human/mining/shorthair_mining_strip10.png
  60. BIN
      app/src/embed/assets/assets/characters/human/mining/spikeyhair_mining_strip10.png
  61. BIN
      app/src/embed/assets/assets/characters/human/mining/tools_mining_strip10.png
  62. BIN
      app/src/embed/assets/assets/characters/human/walking/base_walk_strip8.png
  63. BIN
      app/src/embed/assets/assets/characters/human/walking/bowlhair_walk_strip8.png
  64. BIN
      app/src/embed/assets/assets/characters/human/walking/curlyhair_walk_strip8.png
  65. BIN
      app/src/embed/assets/assets/characters/human/walking/longhair_walk_strip8.png
  66. BIN
      app/src/embed/assets/assets/characters/human/walking/mophair_walk_strip8.png
  67. BIN
      app/src/embed/assets/assets/characters/human/walking/shorthair_walk_strip8.png
  68. BIN
      app/src/embed/assets/assets/characters/human/walking/spikeyhair_walk_strip8.png
  69. BIN
      app/src/embed/assets/assets/characters/human/walking/tools_walk_strip8.png
  70. BIN
      app/src/embed/assets/assets/characters/human/watering/base_watering_strip5.png
  71. BIN
      app/src/embed/assets/assets/characters/human/watering/bowlhair_watering_strip5.png
  72. BIN
      app/src/embed/assets/assets/characters/human/watering/curlyhair_watering_strip5.png
  73. BIN
      app/src/embed/assets/assets/characters/human/watering/longhair_watering_strip5.png
  74. BIN
      app/src/embed/assets/assets/characters/human/watering/mophair_watering_strip5.png
  75. BIN
      app/src/embed/assets/assets/characters/human/watering/shorthair_watering_strip5.png
  76. BIN
      app/src/embed/assets/assets/characters/human/watering/spikeyhair_watering_strip5.png
  77. BIN
      app/src/embed/assets/assets/characters/human/watering/tools_watering_strip5.png
  78. BIN
      app/src/embed/assets/assets/crops/beet_juice_05.png
  79. BIN
      app/src/embed/assets/assets/crops/beet_sugar_05.png
  80. BIN
      app/src/embed/assets/assets/crops/beetroot_00.png
  81. BIN
      app/src/embed/assets/assets/crops/beetroot_01.png
  82. BIN
      app/src/embed/assets/assets/crops/beetroot_02.png
  83. BIN
      app/src/embed/assets/assets/crops/beetroot_03.png
  84. BIN
      app/src/embed/assets/assets/crops/beetroot_04.png
  85. BIN
      app/src/embed/assets/assets/crops/beetroot_05.png
  86. BIN
      app/src/embed/assets/assets/crops/cabbage_00.png
  87. BIN
      app/src/embed/assets/assets/crops/cabbage_01.png
  88. BIN
      app/src/embed/assets/assets/crops/cabbage_02.png
  89. BIN
      app/src/embed/assets/assets/crops/cabbage_03.png
  90. BIN
      app/src/embed/assets/assets/crops/cabbage_04.png
  91. BIN
      app/src/embed/assets/assets/crops/cabbage_05.png
  92. BIN
      app/src/embed/assets/assets/crops/carrot_00.png
  93. BIN
      app/src/embed/assets/assets/crops/carrot_01.png
  94. BIN
      app/src/embed/assets/assets/crops/carrot_02.png
  95. BIN
      app/src/embed/assets/assets/crops/carrot_03.png
  96. BIN
      app/src/embed/assets/assets/crops/carrot_04.png
  97. BIN
      app/src/embed/assets/assets/crops/carrot_05.png
  98. BIN
      app/src/embed/assets/assets/crops/carrot_juice_05.png
  99. BIN
      app/src/embed/assets/assets/crops/carrot_vinegar_05.png
  100. BIN
      app/src/embed/assets/assets/crops/cauliflower_00.png

+ 25 - 2
.gitignore

@@ -13,12 +13,23 @@ captures/
 .externalNativeBuild/
 .cxx/
 *.apk
+*.ap_
+*.aab
 output.json
 
+# Signed release output
+app/release/
+
+# Build variant output directories
+app/embed*/
+
 # IntelliJ
 *.iml
 .idea/
 
+# VS Code
+.vscode/
+
 # Keystore files
 *.jks
 *.keystore
@@ -29,6 +40,18 @@ google-services.json
 # Android Profiling
 *.hprof
 
+# OS generated files
+.DS_Store
+**/.DS_Store
+Thumbs.db
+Desktop.ini
+
+# Editor temp files
+*.swp
+*.swo
+*~
+\#*\#
+.\#*
+
 # love-android specific
-app/src/main/assets/
-app/src/embed/assets/
+app/src/main/assets/

+ 238 - 179
README.md

@@ -1,228 +1,287 @@
-Android Port of LÖVE, an awesome 2D game engine for Lua (http://love2d.org)  
-Copyright (c) 2006-2024 LOVE Development Team
+# 荷锄 — 种田游戏
 
-![Build Status](https://github.com/love2d/love-android/workflows/build/badge.svg)
+基于 LÖVE2D (LuaJIT) 的竖屏触控农场游戏,支持 Android 打包与 Mod 系统。
 
-Download:
----------
+---
 
-You can download pre-built Android packages from https://github.com/love2d/love/releases/latest that allow
-you to run .love files by opening them using a file manager of your choice.
+## 快速上手
 
-Important:
-----------
-
-If you want to build from source, make sure to clone the submodules too. Often errors include missing
-liblove.so and "Missing LÖVE" error when building. A proper way to clone this repository is:
-
-```
-git clone --recurse-submodules https://github.com/love2d/love-android
-```
-
-Add `-b <branch/tag name>` and `--depth 1` if needed.
-
-If you already cloned the repository but forgot to initialize the submodules, execute:
+```bash
+# 运行游戏(桌面)
+& 'C:\Program Files\LOVE\lovec.exe' --console .
 
+# 运行测试
+busted .
 ```
-git submodule sync --recursive
-git submodule update --init --force --recursive
-```
-
-In the repository directory. For the last command, add `--depth 1` if needed.
 
-Instructions:
--------------
+存档位置:
+- Windows: `%APPDATA%\LOVE\farm\`
+- Linux:   `~/.local/share/love/farm/`
+- Android: `/sdcard/Android/data/<包名>/files/`
 
-Detailed instructions can be viewed at https://github.com/love2d/love-android/wiki
+日志文件:`%APPDATA%\LOVE\farm\game.log`(运行时错误会写入此处)
 
-Quick Start:
-------------
+---
 
-Before you start, install JDK 17 (not later not earlier). If you intend to build from Android Studio, skip this step as
-Android Studio bundles its own JDK 17.
-
-Install CMake version 3.21 or later in your system. Install Android SDK with SDK API 34 (34.x.y) and Android NDK
-26.1.10909125, set the environment variable `ANDROID_HOME` to your Android SDK as necessary then run.
+## 分层架构
 
 ```
-$ ./gradlew assembleNormalRecord
+main.lua          ← LÖVE2D 回调入口,只做转发,不含逻辑
+    │
+engine/           基础设施层:引擎主循环、存档、事件总线、Mod加载
+    │
+love_adapter/     LÖVE2D 适配层:将 love API 包装为接口,隔离平台依赖
+    │
+game.lua          协调层:组装所有子系统,处理输入分发与绘制调度
+    │
+systems/          游戏逻辑层:农场、玩家、世界、背包等纯逻辑
+    │
+render/           渲染层:所有绘制代码,依赖 systems/ 提供的接口
+    │
+data/             纯数据层:Lua table 定义,无副作用,无依赖
 ```
 
-in the root folder of this project. This should give you an .apk file in the `app/build/outputs/apk/normalRecord`
-subdirectory that you can then sign and install on your phone. The `normalRecord` .apk variant is what you normally have
-when downloading APK from https://love2d.org.
-
-If you want to put your game inside the APK, you can either:
-
-1. Put all your games in `app/src/embed/assets` such that your `main.lua` path is `app/src/embed/assets/main.lua`; or
-2. Put your zipped \*.love in `app/src/embed/assets` with name `game.love`
-
-And change the application id, application version string and codes, application display name, and the icons (see
-[Game Packaging Wiki](https://github.com/love2d/love-android/wiki/Game-Packaging). Afterwards,
-run either `gradlew assembleEmbedNoRecordRelease` (or `gradlew assembleEmbedRecordRelease` if your game uses microphone) to
-generate APK which you can install or `gradlew bundleEmbedNoRecordRelease` (or `gradlew bundleEmbedRecordRelease`) to generate
-AAB which you can upload to Play Store.
-
-Alternatively, you can install Android Studio **2022.3.1** or later. After opening it for the first time, open its SDK Manager
-and on the tab "SDK Tools", tick "Show Package Details" then select NDK (Side By Side) version 26.1.10909125. After that, open
-the repository root.
-
-Notice: Previously, the embed + APKTool method is preferred, but recent announcements by Google and recent changes in Android render that method obsolete.
-
-Bugs:
------
+**依赖规则:** 上层可依赖下层,同层通过 `engine/eventbus` 解耦,禁止下层依赖上层。
 
-Bugs and/or feature requests should be reported to the issue tracker at:
-* https://github.com/love2d/love-android/issues - LÖVE-Android-specific issues
-* https://github.com/love2d/love/issues - for LÖVE in general
+---
 
-Note: If in doubt, fill an issue report to https://github.com/love2d/love/issues. We'll transfer the issue to this repository as needed.
+## 关键设计决策
 
-Changelog:
-----------
+### 接口隔离(engine/interface.lua)
 
-11.5:
+所有跨层传递的对象都通过接口定义,用 `classic.lua` 的 `Object:extend()` 实现:
 
-* Contains all relevant changes for desktop LÖVE [11.5](https://love2d.org/wiki/11.5).
-* Fixed audio in Android still playing in the background in certain cases. ([love2d/love#1828](https://github.com/love2d/love/issues/1828))
+- `IWorldQuery` — systems 层查询世界地图(WorldSystem 实现)
+- `IFarmQuery` — render 层查询农场状态(FarmSystem 实现)
+- `IGameStateQuery` — render 层查询玩家状态(game.lua 内 GameStateAdapter 实现)
+- `IGraphics` — 绘图接口(love_adapter/graphics.lua 实现,测试用 MockGraphics)
+- `IFilesystem` — 文件系统接口(love_adapter/filesystem.lua 实现)
+- `IAudio` / `IWindow` — 音频/窗口接口
 
+这样 systems/ 和 render/ 完全不直接调用 `love.*`,可以在无 LÖVE 环境下单元测试。
 
-11.4:
+### 对象实例化(classic.lua)
 
-* Contains all relevant changes for desktop LÖVE [11.4](https://love2d.org/wiki/11.4).
-* Changed how LOVE looks for game due to Android restrictions.
-* Fixed "pointer too large" and "bad lightuserdata" issue when compiling LOVE with recent Android SDK and running LOVE on Android 11 and later.
-* For Developers: Added experimental support for 3rd-party Lua modules. Please refer to `love/src/jni/lua-modules` for more information.
-* For Packaging: Added experimental fusing method. [See the FAQ for details](https://github.com/love2d/love-android/wiki/FAQ---Frequently-Asked-Questions#whats-experimental-fusing-method).
+项目使用 [classic](https://github.com/rxi/classic) 作为 OOP 基础。**正确的实例化方式是调用类本身**(触发 `__call`),而不是 `:new()`:
 
-11.3:
+```lua
+-- 正确
+local obj = MyClass(arg1, arg2)
 
-* Contains all relevant changes for desktop LÖVE [11.3](https://love2d.org/wiki/11.3).
-* Added support for microphone recording on Android. **This is disabled in Play Store builds**.
-* Added t.audio.mic (`false` by default). On Android, setting it to true requests microphone recording permission from the user.
-* Fixed performance regression on Android devices with Adreno GPU.
-* Fixed video playback support on Android devices with Adreno GPU.
-
-11.2:
-
-* Contains all relevant changes for desktop LÖVE [11.2](https://love2d.org/wiki/11.2).
-* Added support for ARM64 devices to comply with Play Store requirements.
-* Fixed `love.system.openURL` crashing in some cases.
-* Changed target SDK to 28 so it comply with Play Store requirements.
-
-0.10.2:
-
-* Contains all relevant changes for desktop LÖVE [0.10.2](https://love2d.org/wiki/0.10.2).
-* Upgrade of SDL2 to 2.0.5 (fixes an issue with the accelerometer)
-
-0.10.1:
-
-* Contains all relevant changes for desktop LÖVE [0.10.1](https://love2d.org/wiki/0.10.1).
-* Added a new love.conf flag t.externalstorage, which determines whether files are saved in internal or external storage on Android devices.
-* Fixed audio on Android to pause when the app is inactive, and resume when the app becomes active again.
-* Fixed a driver bug on some Android devices which caused all objects to show up as black.
-* Fixed love.graphics.clear(colortable) causing crashes on OpenGL ES 2 systems when a Canvas is active.
-* New icons
-
-0.10.0:
+-- 错误(:new() 直接调用方法,返回 nil)
+local obj = MyClass:new(arg1, arg2)
+```
 
-* first official release!
-* Disabled JIT by default as it can cause performance problems. To enable JIT call jit.on()
+`new()` 方法里直接给 `self` 赋字段,不需要 `return self`(`__call` 负责返回实例)。
 
-0.10.0-alpha2:
+### 适配层注入顺序(game.lua)
 
-* Update to the next love API 0.10.0 (not yet officially released)
-* Added building of libtheora
-* Updated LuaJIT from 2.0.1 to 2.1
-* Fixed a compatibility issue with Android 2.3 devices
-* Updated libogg from 1.3.2. to 1.3.5
-* Updated OpenAL to 1.17.0
-* Updated SDL2 to a dev version of 2.0.4
+`Game.new()` 按固定顺序初始化,顺序不能乱:
 
-0.9.2a:
+1. 创建 love_adapter 实例(`LoveGraphics()` 等)
+2. 将适配层注入 engine 模块(`Save.setFS`、`ModLoader.setFS`、`Anim.setGraphics` 等)
+3. 创建 systems 层对象(`FarmSystem()`、`WorldSystem()` 等)
+4. 创建 GameStateAdapter(包装 gameState 为 IGameStateQuery)
+5. 创建 render 层对象,注入接口
+6. 创建依赖接口的 systems 对象(`PlayerSystem`)
+7. 注入跨系统引用(`TickSystem.setWorld`、`Save.setWorld` 等)
 
-* Added bugfix for ParticleSystem:clone
+### Save 模块的调用方式
 
-0.9.2:
+`engine/save.lua` 里的 `_fs` 是 `IFilesystem` 实例,所有调用必须用冒号语法:
 
-* updated API to match that of LÖVE 0.9.2
-* love.window.setFullscreen can be used to switch between regular and immersive mode without status and navbar
-* added loading of games by opening a main.lua file
-* quitting LÖVE now conforms to the Android application lifecycle
-* stop vibrator when app is paused
-* fixed battery drain by properly pausing OpenAL device
-* fixed printing of non-number and non-string values
-* fixed compilation of Android NDK r10
-* fixed compilation warnings concerning ```APP_PLATFORM```
-* old instance is shut down when opening a new game (note: it may crash when opening games at a high frequency, e.g. more than 2 per second)
-* updated OpenAL-Soft to version 1.16.0
-* updated to newer SDL version (f9244b2a151)
+```lua
+_fs:exists(path)   -- 正确
+_fs.exists(path)   -- 错误(self 会变成 path,导致 getInfo(nil))
+```
 
-0.9.1b:
+---
 
-* added love.system.vibrate(seconds)
-* print statements are now redirected to logcat. Output is prefixed with "[LOVE] "
-* removed DevIL, libpng, libjpeg, libmng, and libtiff
-* pngs are loaded using lodepng and jpegs using libturbo-jpeg
-* repeatedly fixed a bug which caused Release builds to crash
-* update to latest mobile-common branch
+## 目录结构
 
-0.9.1a:
+```
+main.lua              LÖVE2D 回调入口
+conf.lua              窗口配置
+game.lua              游戏主控制器(协调层,~3000行)
+classic.lua           轻量 OOP 库
+assets/sphere.glsl        球面畸变 shader(世界层后处理)
+
+engine/
+  engine.lua          主循环、错误捕获、自动存档(每5分钟)
+  eventbus.lua        发布/订阅事件总线
+  registry.lua        统一数据访问(getCrop/getItem/getBlock/getTool)
+  save.lua            多存档读写(save_1~3.lua),版本17
+  camera.lua          摄像机(环形世界坐标、zoom、tileToScreen)
+  mod_loader.lua      Mod 加载、API 工厂、钩子分发
+  interface.lua       跨层接口定义 + MockGraphics(测试用)
+  audio.lua           程序化音频合成
+  sfx.lua             音效预合成与事件绑定
+  anim.lua            动画帧计时
+  pathfinding.lua     A* 寻路(环形地图)
+  particles.lua       粒子效果
+
+love_adapter/
+  graphics.lua        IGraphics → love.graphics
+  filesystem.lua      IFilesystem → love.filesystem
+  audio.lua           IAudio → love.audio
+  window.lua          IWindow → love.graphics.getWidth/Height
+
+systems/
+  state.lua           gameState 创建与操作(金币/经验/精力/天赋)
+  farm.lua            农场格子逻辑(耕/种/浇/收)
+  world_system.lua    100×100 环形地图(实现 IWorldQuery)
+  player.lua          玩家移动 + 工具操作(整合 Character + A*)
+  character.lua       角色像素坐标、动画状态机
+  inventory.lua       背包(30格 + 快捷栏6格)
+  block_system.lua    可放置方块(箱子/工作台/栅栏等)
+  tick_system.lua     物流方块定时驱动(每0.5秒)
+  time.lua            游戏时间、昼夜、节气
+  weather.lua         天气系统(晴/阴/雨/暴雨)
+  genetics.lua        作物基因系统(杂交、表现型、高产)
+  talent_stats.lua    天赋+种族+职业+装备 → stats 表
+  market.lua          动态市场价格
+  drops.lua           掉落物(吸引+自动拾取)
+  dungeon.lua         副本地图数据
+  pet.lua             宠物(跟随、离线收益)
+  achievement_system.lua  成就系统
+  consumable_system.lua   消耗品使用逻辑
+  equipment_system.lua    装备穿脱与套装效果
+  shop_system.lua         商店购买逻辑
+  workbench_system.lua    工作台合成
+  forge_system.lua        扑克工厂锻造
+  quiz_npc_system.lua     问答NPC
+  lighting_system.lua     夜晚光照贴图
+
+render/
+  world_renderer.lua      地图底层/覆盖层精灵(y-sort)
+  farm_renderer.lua       农场格子与作物生长动画
+  character_renderer.lua  玩家行走/动作动画(strip 精灵表)
+  ui_renderer.lua         HUD、商店、消息、飘字、Mod管理面板
+  inventory_renderer.lua  背包/快捷栏/装备槽/拖拽
+  block_renderer.lua      方块精灵
+  crop_renderer.lua       作物图标工具
+  pet_renderer.lua        宠物渲染与升级面板
+  talent_renderer.lua     天赋选择/查看面板
+  workbench_renderer.lua  工作台合成面板
+  forge_renderer.lua      扑克工厂锻造面板
+  dungeon_renderer.lua    副本地图渲染
+  drops_renderer.lua      掉落物渲染
+  effect_renderer.lua     粒子特效封装
+  render_pipeline.lua     渲染调度框架
+
+data/                     纯数据(return { ... },无副作用)
+  config.lua              全局配置(窗口/玩家初始值)
+  crops.lua               作物定义
+  items.lua               消耗品/可放置物品
+  blocks.lua              方块定义
+  tools.lua               工具定义
+  talents.lua             天赋定义
+  equipment.lua           装备(扑克牌+骰子)
+  equipment_sets.lua      套装效果
+  races.lua               种族定义
+  classes.lua             职业定义
+  achievements.lua        成就定义
+  recipes.lua             工作台配方
+  forge.lua               锻造链
+  levels.lua              升级经验表
+  quiz.lua                问答题库
+  solar_terms.lua         24节气
+  seasons.lua             季节定义
+  sfx.lua                 音效参数
+  music.lua               背景音乐
+  pets.lua                宠物定义
+  redeem_codes.lua        兑换码
+
+mods/
+  seasonal_trader/        内置示例 Mod(季节商人)
+
+spec/                     busted 单元测试
+```
 
-* using latest SDL\_androidgl.c (fixes some random performance issues)
-* using latest love-android @ changeset 8659be0e75a3 (adds support for
-	compressed textures)
+---
 
-0.9.1:
+## 核心常量
 
-* uses 0.9.1 API
-* fixed crash on Moto G (and possibly other devices). This was a nasty bug that would just show a blue screen without an error message. The bug was resolved using the help of headchant
-* fixed loading of jpegs (it probably hasn't worked up to now)
-* fixed issues with looping over active touches. This fix was sponsored by slime!
+```lua
+-- engine/camera.lua
+Camera.TILE_PX  = 32    -- 每格像素
+Camera.MAP_COLS = 100   -- 地图宽(格)
+Camera.MAP_ROWS = 100   -- 地图高(格)
 
-beta2:
+-- engine/save.lua
+version = 17            -- 存档版本,不兼容时提示删除旧存档
+```
 
-* fixed bug with canvases
-* fixed writing of files when no identity in conf.lua was set
-* added file association (somewhat experimental)
+---
 
-beta1:
+## EventBus 主要事件
 
-* fixed nasty crash on startup bug
-* fixed love.filesystem.getDirectoryItems()
+模块间通信通过 `Bus.emit(event, data)` / `Bus.on(event, fn)` 解耦。
 
-alpha9:
+| 事件 | 触发时机 | data 字段 |
+|------|----------|-----------|
+| `tile_harvested` | 收获作物 | `cropId, row, col, count, gene, dropSeed` |
+| `tile_tilled` | 耕地 | `row, col` |
+| `tile_watered` | 浇水 | `row, col` |
+| `tree_chopped` | 砍树 | `row, col, wood` |
+| `gravel_mined` | 采碎石堆 | `row, col, count` |
+| `block_placed/mined` | 放置/挖掉方块 | `row, col, blockId` |
+| `block_interact` | 交互方块 | `row, col, action, blockData` |
+| `gain_exp` | 获得经验 | `amount` |
+| `player_levelup` | 玩家升级 | `level` |
+| `sold` | 出售作物 | `amount` |
+| `day_changed` | 游戏日切换 | — |
+| `weather_changed` | 天气/节气变化 | `solarTerm` |
+| `achievement_unlocked` | 解锁成就 | `id` |
+| `show_msg` | 显示底部消息 | `text` |
+| `item_drop` | 背包满掉落 | `item, pixX, pixY` |
 
-* Packaged games do not get duplicated for loading, instead are loaded from memory (!!!)
-* Using inofficial physfs 2.1
-* Removed love.android.getDisplayMetrics(), instead use love.window.getPixelScale() 
-* Properly link LGPL libraries dynamically. Everything else is linked statically
-* Added an icon (design by @josefnpat)
-* Fixed crash on startup on OUYA (and possibly other devices)
+---
 
-alpha8:
+## 测试
 
-* Exposing DisplayMetrics in love.android.getDisplayMetrics())
-* Accelerometer is now available as a joystick
-* enabled armv6 compilation (larger files, better compatibility with Tegra2 devices)
-* updated to latest mobile-common branch (including (very) basic multi-touch gesture tracking)
-* updated OpenAL from 1.13 to 1.15.1
-* updated jpeg library from 8c to 9a
-* updated lcms from 2.2 to 2.5
-* updated libogg from 1.3.0 to 1.3.1
-* updated libvorbis from 1.3.2 to 1.3.4
-* updated mpg123 from 1.13.4 to 1.17.0
+```bash
+busted .
+```
 
-alpha7:
+| 文件 | 覆盖内容 |
+|------|---------|
+| `spec/regression_core_spec.lua` | Genetics/Inventory/EventBus/Market/State/Save |
+| `spec/phase1~6_*_spec.lua` | 各层路径与接口验证 |
+| `spec/layered_arch/` | 属性测试(层依赖、接口契约) |
+| `spec/helpers/love_mock.lua` | love API stub,无需 LÖVE 运行时 |
+
+---
+
+## Mod 系统
+
+详见 `MOD_GUIDE.md`。
+
+Mod 放在存档目录的 `user_mods/<mod_name>/mod.lua`,内置 Mod 在 `mods/` 下。主菜单有 Mod 管理面板,可按存档槽独立启用/禁用。
+
+```lua
+-- mod.lua 最小结构
+return {
+    priority = 50,  -- 加载顺序,越小越先
+    init = function(api)
+        -- api.registerCrop/Item/Block/Tool/Talent/Race/Class/Achievement/Recipe
+        -- api.overrideCrop/Item/Block/Tool/Talent
+        -- api.wrapFunction(modulePath, fnName, wrapper)
+        -- api.addConsumable / addToolAction / addDraw / addTick / addKey
+        -- api.on(event, fn) / api.emit(event, data)
+        -- api.replaceAsset(virtualPath, modRelativePath)
+        -- 运行时(只在回调里用):getGame/getFarm/getPlayer/getGameState
+        -- api.addMoney/addExp/addStamina/addItem/showMsg
+    end,
+}
+```
 
-* love.system.getOS() now returns "Android"
-* hardware search key is reported as "search"
-* switched to mobile-common branch
-* using new love.touch module (love.touchpressed(id,x,y,p), love.touchmoved(id,x,y,p), love.touchmoved(id,x,y,p))
-* added LOVE_ANDROID define
+---
 
-License:
---------
+## 存档版本历史
 
-This project contains code from multiple projects using various licenses. Please look at LOVE
-[license.txt](https://github.com/love2d/love/blob/master/license.txt) for the respective licenses.
+| 版本 | 变更 |
+|------|------|
+| 15 | 基础版本 |
+| 16 | 新增 quizDoneDay |
+| 17 | 新增 achievements、enabledMods、多存档(save_1~3.lua) |

+ 53 - 26
app/build.gradle

@@ -5,7 +5,7 @@ plugins {
 }
 
 android {
-    namespace "org.love2d.android"
+    namespace "com.nonvioce.farm"
     ndkVersion '27.1.12297006'
 
     defaultConfig {
@@ -28,9 +28,9 @@ android {
             }
         }
 
+        // 移除全局abiFilters,使用productFlavors为每个架构创建单独的构建
         ndk {
             //noinspection ChromeOsAbiSupport
-            abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64'
             debugSymbolLevel 'full'
         }
 
@@ -62,30 +62,35 @@ android {
     def retrieveAll3pModules = { ->
         def modules = []
 
-        fileTree("src/main/cpp/lua-modules/").visit { FileVisitDetails details ->
-            if (details.isDirectory()) {
-                if (file(details.file.path + "/Android.mk").exists() ||
-                        file(details.file.path + "/CMakeLists.mk").exists()) {
-                    def logger = project.getLogger()
-                    logger.lifecycle("3rd-party module: " + details.file.path)
-
-                    def javainfo = file(details.file.path + "/java.txt")
-                    if (javainfo.exists()) {
-                        def fstream = new FileInputStream(javainfo)
-                        def infile = new BufferedReader(new InputStreamReader(fstream))
-                        def javapath = infile.readLine().replace("\\", "/")
-                        def mpath = null
-
-                        if (javapath[0] != '/') {
-                            mpath = details.file.path + "/" + javapath
-                        } else {
-                            mpath = details.file.path + javapath
+        // 遍历lua-modules目录,寻找包含Android.mk或CMakeLists.mk的目录
+        def luaModulesDir = file("src/main/cpp/lua-modules/")
+        if (luaModulesDir.exists() && luaModulesDir.isDirectory()) {
+            luaModulesDir.eachFileRecurse { file ->
+                if (file.isDirectory()) {
+                    def androidMk = file("${file.path}/Android.mk")
+                    def cmakeLists = file("${file.path}/CMakeLists.mk")
+                    if (androidMk.exists() || cmakeLists.exists()) {
+                        def logger = project.getLogger()
+                        logger.lifecycle("3rd-party module: ${file.path}")
+
+                        def javainfo = file("${file.path}/java.txt")
+                        if (javainfo.exists()) {
+                            def fstream = new FileInputStream(javainfo)
+                            def infile = new BufferedReader(new InputStreamReader(fstream))
+                            def javapath = infile.readLine().replace("\\", "/")
+                            def mpath = null
+
+                            if (javapath[0] != '/') {
+                                mpath = "${file.path}/${javapath}"
+                            } else {
+                                mpath = "${file.path}${javapath}"
+                            }
+
+                            modules << mpath
+
+                            logger.lifecycle("Registered path ${mpath}")
+                            infile.close()
                         }
-
-                        modules << mpath
-
-                        logger.lifecycle("Registered path " + mpath)
-                        infile.close()
                     }
                 }
             }
@@ -105,7 +110,7 @@ android {
         prefab true
     }
 
-    flavorDimensions = ['mode', 'recording']
+    flavorDimensions = ['mode', 'recording', 'architecture']
     productFlavors {
         normal {
             dimension 'mode'
@@ -119,6 +124,28 @@ android {
         noRecord {
             dimension 'recording'
         }
+        // 为每个架构创建单独的构建变体
+        armeabiV7a {
+            dimension 'architecture'
+            ndk {
+                abiFilters 'armeabi-v7a'
+            }
+            versionCode 101
+        }
+        arm64V8a {
+            dimension 'architecture'
+            ndk {
+                abiFilters 'arm64-v8a'
+            }
+            versionCode 102
+        }
+        x86_64 {
+            dimension 'architecture'
+            ndk {
+                abiFilters 'x86_64'
+            }
+            versionCode 103
+        }
     }
 
     sourceSets {

+ 8 - 0
app/src/embed/assets/.busted

@@ -0,0 +1,8 @@
+return {
+  default = {
+    lua        = "lua",
+    pattern    = "_spec",
+    ROOT       = {"spec"},
+    ["lua-path"] = "./?.lua",
+  },
+}

+ 198 - 0
app/src/embed/assets/.kiro/settings/lsp.json

@@ -0,0 +1,198 @@
+{
+  "languages": {
+    "python": {
+      "name": "pyright",
+      "command": "pyright-langserver",
+      "args": [
+        "--stdio"
+      ],
+      "file_extensions": [
+        "py"
+      ],
+      "project_patterns": [
+        "pyproject.toml",
+        "setup.py",
+        "requirements.txt",
+        "pyrightconfig.json"
+      ],
+      "exclude_patterns": [
+        "**/__pycache__/**",
+        "**/venv/**",
+        "**/.venv/**",
+        "**/.pytest_cache/**"
+      ],
+      "multi_workspace": false,
+      "initialization_options": {},
+      "request_timeout_secs": 60
+    },
+    "java": {
+      "name": "jdtls",
+      "command": "jdtls",
+      "args": [],
+      "file_extensions": [
+        "java"
+      ],
+      "project_patterns": [
+        "pom.xml",
+        "build.gradle",
+        "build.gradle.kts",
+        ".project"
+      ],
+      "exclude_patterns": [
+        "**/target/**",
+        "**/build/**",
+        "**/.gradle/**"
+      ],
+      "multi_workspace": false,
+      "initialization_options": {
+        "settings": {
+          "java": {
+            "compile": {
+              "nullAnalysis": {
+                "mode": "automatic"
+              }
+            },
+            "configuration": {
+              "annotationProcessing": {
+                "enabled": true
+              }
+            }
+          }
+        }
+      },
+      "request_timeout_secs": 60
+    },
+    "go": {
+      "name": "gopls",
+      "command": "gopls",
+      "args": [],
+      "file_extensions": [
+        "go"
+      ],
+      "project_patterns": [
+        "go.mod",
+        "go.sum"
+      ],
+      "exclude_patterns": [
+        "**/vendor/**"
+      ],
+      "multi_workspace": false,
+      "initialization_options": {
+        "usePlaceholders": true,
+        "completeUnimported": true
+      },
+      "request_timeout_secs": 60
+    },
+    "cpp": {
+      "name": "clangd",
+      "command": "clangd",
+      "args": [
+        "--background-index"
+      ],
+      "file_extensions": [
+        "cpp",
+        "cc",
+        "cxx",
+        "c",
+        "h",
+        "hpp",
+        "hxx"
+      ],
+      "project_patterns": [
+        "CMakeLists.txt",
+        "compile_commands.json",
+        "Makefile"
+      ],
+      "exclude_patterns": [
+        "**/build/**",
+        "**/cmake-build-**/**"
+      ],
+      "multi_workspace": false,
+      "initialization_options": {},
+      "request_timeout_secs": 60
+    },
+    "typescript": {
+      "name": "typescript-language-server",
+      "command": "typescript-language-server",
+      "args": [
+        "--stdio"
+      ],
+      "file_extensions": [
+        "ts",
+        "js",
+        "tsx",
+        "jsx"
+      ],
+      "project_patterns": [
+        "package.json",
+        "tsconfig.json"
+      ],
+      "exclude_patterns": [
+        "**/node_modules/**",
+        "**/dist/**"
+      ],
+      "multi_workspace": false,
+      "initialization_options": {
+        "preferences": {
+          "disableSuggestions": false
+        }
+      },
+      "request_timeout_secs": 60
+    },
+    "rust": {
+      "name": "rust-analyzer",
+      "command": "rust-analyzer",
+      "args": [],
+      "file_extensions": [
+        "rs"
+      ],
+      "project_patterns": [
+        "Cargo.toml"
+      ],
+      "exclude_patterns": [
+        "**/target/**"
+      ],
+      "multi_workspace": false,
+      "initialization_options": {
+        "cargo": {
+          "buildScripts": {
+            "enable": true
+          }
+        },
+        "diagnostics": {
+          "enable": true,
+          "enableExperimental": true
+        },
+        "workspace": {
+          "symbol": {
+            "search": {
+              "scope": "workspace"
+            }
+          }
+        }
+      },
+      "request_timeout_secs": 60
+    },
+    "ruby": {
+      "name": "solargraph",
+      "command": "solargraph",
+      "args": [
+        "stdio"
+      ],
+      "file_extensions": [
+        "rb"
+      ],
+      "project_patterns": [
+        "Gemfile",
+        "Rakefile"
+      ],
+      "exclude_patterns": [
+        "**/vendor/**",
+        "**/tmp/**"
+      ],
+      "multi_workspace": false,
+      "initialization_options": {},
+      "request_timeout_secs": 60
+    }
+  }
+}

+ 1 - 0
app/src/embed/assets/.kiro/specs/layered-architecture-refactor/.config.kiro

@@ -0,0 +1 @@
+{"specId": "35f356fd-aba0-4b40-a8d8-502ef1cc78c2", "workflowType": "requirements-first", "specType": "feature"}

+ 884 - 0
app/src/embed/assets/.kiro/specs/layered-architecture-refactor/design.md

@@ -0,0 +1,884 @@
+# 技术设计文档:分层架构重构
+
+## 概述
+
+本文档描述对 LÖVE2D Lua 农场游戏进行分层架构重构的技术方案。
+
+### 当前问题
+
+| 问题 | 位置 | 影响 |
+|------|------|------|
+| 上帝对象 | `game.lua` | 同时持有 systems 层和 render 层引用,承担过多职责 |
+| 反向依赖 | `render/ui_renderer.lua` | 顶层 `require("systems/farm")`、`require("systems/state")` |
+| 跨层调用 | `systems/player.lua` | 通过注入的 `_world` 变量调用 `WorldRenderer` 方法 |
+| love 直接调用 | `render/` 所有文件 | 直接调用 `love.graphics.*`,无法在无 LÖVE 环境下测试渲染逻辑 |
+| love 直接调用 | `engine/particles.lua`、`engine/engine.lua` | 部分 Engine 层模块直接调用 `love.*`,未通过适配器隔离 |
+| 缺少层注释 | 全部文件 | 无法快速定位模块所属层次 |
+| OOP 不统一 | 全部文件 | 部分用 `Class.__index = Class`,部分用 classic,不一致 |
+
+### 重构目标
+
+引入清晰的五层架构,使用 `classic` 库统一接口定义,通过接口隔离各层,消除跨层调用和反向依赖。**只有最底层的 Love 适配层(`love_adapter/`)允许直接调用 `love.*` API;Engine 层通过适配层接口访问框架能力;Render 层通过 `IGraphics` 接口调用绘图;Systems 层完全不接触 love。更换游戏引擎时,只需重写 `love_adapter/` 目录下的实现,其余所有层无需改动。**
+
+---
+
+## 架构
+
+### 五层架构总览
+
+```
+┌─────────────────────────────────────────────────────────┐
+│                    协调层 (game.lua)                      │
+│  负责组装各层对象、注入接口实现、转发框架事件              │
+└──────────────────────┬──────────────────────────────────┘
+                       │ 创建 + 注入
+          ┌────────────┴────────────┐
+          ▼                         ▼
+┌─────────────────┐       ┌─────────────────────┐
+│   Systems 层     │       │     Render 层         │
+│  业务逻辑        │◄──────│  通过接口读取状态      │
+│  farm / player  │  接口  │  通过 IGraphics 绘图  │
+│  world / pet    │       │  不调用任何 love.*    │
+└────────┬────────┘       └──────────┬────────────┘
+         │ 依赖                       │ 依赖 IGraphics
+         ▼                           ▼
+┌─────────────────────────────────────────────────────────┐
+│                     Engine 层                             │
+│  EventBus / Camera / Save / Audio / Registry / Anim      │
+│  通过 Love 适配层接口访问框架能力,不直接调用 love.*       │
+└──────────────────────────────────────────────────────────┘
+         │ 依赖(通过接口)
+         ▼
+┌─────────────────────────────────────────────────────────┐
+│               Love 适配层 (love_adapter/)                 │
+│  IGraphics / IFilesystem / IAudio / IWindow 的实现        │
+│  ★ 唯一允许直接调用 love.* 的层                           │
+│  更换引擎时只需重写此层                                    │
+└──────────────────────────────────────────────────────────┘
+         ▲
+         │ 只读数据
+┌─────────────────────────────────────────────────────────┐
+│                      Data 层                              │
+│  crops / tools / items / blocks / config(纯静态表)      │
+└──────────────────────────────────────────────────────────┘
+```
+
+### love 隔离原则
+
+**只有 Love 适配层(`love_adapter/`)允许直接调用 `love.*` API。** 各层隔离方式:
+
+| 层 | love 隔离方式 | 现状 |
+|----|--------------|------|
+| Love 适配层 | 唯一调用 `love.*` 的层,实现各框架接口 | 待创建 |
+| Engine 层 | 通过注入的适配层接口访问框架能力,不直接调用 `love.*` | 部分满足(save/audio 已注入隔离,particles/engine.lua 待整理) |
+| Systems 层 | 完全不使用 love | 已满足(world_system、lighting_system 等均无 love 调用) |
+| Render 层 | 通过 `IGraphics` 接口调用绘图,不直接调用 `love.*` | 待重构 |
+| Data 层 | 纯静态表,无任何 love 调用 | 已满足 |
+
+### 依赖方向规则
+
+```
+Love 适配层  ←  Engine 层(Engine 通过接口依赖适配层)
+Engine 层    ←  Systems 层(Systems 依赖 Engine + Data,不调用 love)
+Engine 层    ←  Render 层(Render 依赖 Engine,通过 IGraphics 绘图)
+Systems 层   ✗  Render 层(禁止)
+Render 层    ✗  Systems 层(禁止直接 require)
+Engine 层    ✗  love.*(禁止,通过适配层接口访问)
+Systems 层   ✗  love.*(禁止)
+Render 层    ✗  love.*(禁止)
+```
+
+### 接口隔离机制
+
+```
+Systems 层                    Render 层
+┌──────────────┐              ┌──────────────────┐
+│ WorldSystem  │              │ WorldRenderer    │
+│ implements   │◄─────────────│ 构造时注入        │
+│ IWorldQuery  │   接口引用    │ IWorldQuery      │
+└──────────────┘              └──────────────────┘
+
+┌──────────────┐              ┌──────────────────┐
+│ FarmSystem   │◄─────────────│ UIRenderer       │
+│ implements   │   接口引用    │ FarmRenderer     │
+│ IFarmQuery   │              │ 构造时注入        │
+└──────────────┘              │ IFarmQuery       │
+                              └──────────────────┘
+
+Love 适配层                   Engine 层 / Render 层
+┌──────────────────────┐      ┌──────────────────────┐
+│ LoveGraphics         │      │ Engine 模块(如 Anim)│
+│ implements IGraphics │◄─────│ Render 层所有渲染器   │
+│ 唯一调用 love.graphics│      │ 构造时注入 IGraphics  │
+└──────────────────────┘      └──────────────────────┘
+
+┌──────────────────────┐      ┌──────────────────────┐
+│ LoveFilesystem       │      │ engine/save.lua       │
+│ implements           │◄─────│ engine/mod_loader.lua │
+│ IFilesystem          │      │ 通过注入接口访问文件系统│
+└──────────────────────┘      └──────────────────────┘
+
+┌──────────────────────┐      ┌──────────────────────┐
+│ LoveAudio            │      │ engine/audio.lua      │
+│ implements IAudio    │◄─────│ engine/sfx.lua        │
+└──────────────────────┘      └──────────────────────┘
+
+┌──────────────────────┐      ┌──────────────────────┐
+│ LoveWindow           │      │ engine/engine.lua     │
+│ implements IWindow   │◄─────│ engine/camera.lua     │
+└──────────────────────┘      └──────────────────────┘
+```
+
+---
+
+## 组件与接口
+
+### Love 适配层(love_adapter/)
+
+**这是整个项目唯一允许调用 `love.*` 的目录。** 包含四个适配器文件,每个文件实现一个框架接口。更换引擎时,只需重写此目录下的文件。
+
+#### love_adapter/graphics.lua — IGraphics 实现
+
+```lua
+-- [Love适配层] IGraphics 的 LÖVE2D 实现,封装所有 love.graphics 调用
+local IGraphics = require("engine/interface").IGraphics
+
+local LoveGraphics = IGraphics:extend()
+
+function LoveGraphics:setColor(r, g, b, a)              love.graphics.setColor(r, g, b, a) end
+function LoveGraphics:getWidth()                        return love.graphics.getWidth() end
+function LoveGraphics:getHeight()                       return love.graphics.getHeight() end
+function LoveGraphics:setScissor(x, y, w, h)            love.graphics.setScissor(x, y, w, h) end
+function LoveGraphics:rectangle(mode, x, y, w, h, rx)   love.graphics.rectangle(mode, x, y, w, h, rx) end
+function LoveGraphics:circle(mode, x, y, r)             love.graphics.circle(mode, x, y, r) end
+function LoveGraphics:print(text, x, y, r, sx, sy)      love.graphics.print(text, x, y, r, sx, sy) end
+function LoveGraphics:printf(text, x, y, lim, align)    love.graphics.printf(text, x, y, lim, align) end
+function LoveGraphics:newImage(path)                    return love.graphics.newImage(path) end
+function LoveGraphics:newQuad(x, y, w, h, sw, sh)       return love.graphics.newQuad(x, y, w, h, sw, sh) end
+function LoveGraphics:draw(drawable, x, y, r, sx, sy, ox, oy)
+    love.graphics.draw(drawable, x, y, r, sx, sy, ox, oy)
+end
+function LoveGraphics:push()                            love.graphics.push() end
+function LoveGraphics:pop()                             love.graphics.pop() end
+function LoveGraphics:translate(x, y)                   love.graphics.translate(x, y) end
+function LoveGraphics:scale(sx, sy)                     love.graphics.scale(sx, sy) end
+function LoveGraphics:newCanvas(w, h)                   return love.graphics.newCanvas(w, h) end
+function LoveGraphics:setCanvas(canvas)                 love.graphics.setCanvas(canvas) end
+function LoveGraphics:clear(r, g, b, a)                 love.graphics.clear(r, g, b, a) end
+function LoveGraphics:newShader(code)                   return love.graphics.newShader(code) end
+function LoveGraphics:setShader(shader)                 love.graphics.setShader(shader) end
+function LoveGraphics:newFont(path, size)               return love.graphics.newFont(path, size) end
+function LoveGraphics:setFont(font)                     love.graphics.setFont(font) end
+function LoveGraphics:getFont()                         return love.graphics.getFont() end
+
+return LoveGraphics
+```
+
+#### love_adapter/filesystem.lua — IFilesystem 实现
+
+```lua
+-- [Love适配层] IFilesystem 的 LÖVE2D 实现,封装所有 love.filesystem 调用
+local IFilesystem = require("engine/interface").IFilesystem
+
+local LoveFilesystem = IFilesystem:extend()
+
+function LoveFilesystem:read(path)           return love.filesystem.read(path) end
+function LoveFilesystem:write(path, data)    return love.filesystem.write(path, data) end
+function LoveFilesystem:exists(path)         return love.filesystem.getInfo(path) ~= nil end
+function LoveFilesystem:remove(path)         return love.filesystem.remove(path) end
+function LoveFilesystem:getInfo(path)        return love.filesystem.getInfo(path) end
+function LoveFilesystem:getDirectoryItems(p) return love.filesystem.getDirectoryItems(p) end
+function LoveFilesystem:createDirectory(p)   return love.filesystem.createDirectory(p) end
+function LoveFilesystem:load(path)           return love.filesystem.load(path) end
+
+return LoveFilesystem
+```
+
+#### love_adapter/audio.lua — IAudio 实现
+
+```lua
+-- [Love适配层] IAudio 的 LÖVE2D 实现,封装所有 love.audio / love.sound 调用
+local IAudio = require("engine/interface").IAudio
+
+local LoveAudio = IAudio:extend()
+
+function LoveAudio:newSoundData(samples, rate, bits, channels)
+    return love.sound.newSoundData(samples, rate, bits, channels)
+end
+function LoveAudio:newSource(soundData)
+    return love.audio.newSource(soundData)
+end
+
+return LoveAudio
+```
+
+#### love_adapter/window.lua — IWindow 实现
+
+```lua
+-- [Love适配层] IWindow 的 LÖVE2D 实现,封装 love.graphics 屏幕尺寸查询
+local IWindow = require("engine/interface").IWindow
+
+local LoveWindow = IWindow:extend()
+
+function LoveWindow:getWidth()  return love.graphics.getWidth() end
+function LoveWindow:getHeight() return love.graphics.getHeight() end
+
+return LoveWindow
+```
+
+### engine/interface.lua — 接口基础设施
+
+所有跨层接口的基类,依赖 `classic.lua`,不依赖任何其他项目模块。同时定义所有框架接口(`IGraphics`、`IFilesystem`、`IAudio`、`IWindow`)和业务接口(`IWorldQuery`、`IFarmQuery`、`IGameStateQuery`)。
+
+```lua
+-- [Engine层] 接口基类定义,所有跨层接口继承自此
+local Object = require("classic")
+
+local Interface_Base = Object:extend()
+
+local function makeUnimplemented(interfaceName, methodName)
+    return function()
+        error(interfaceName .. "." .. methodName .. " 未实现")
+    end
+end
+```
+
+**框架接口**(供 Engine 层模块使用,由 Love 适配层实现):
+
+**IGraphics 接口**(覆盖 Render 层和 Engine 层所需的绘图方法):
+
+```lua
+local IGraphics = Interface_Base:extend()
+function IGraphics:setColor(r, g, b, a)                  end
+function IGraphics:getWidth()                            end
+function IGraphics:getHeight()                           end
+function IGraphics:setScissor(x, y, w, h)                end
+function IGraphics:rectangle(mode, x, y, w, h, rx, ry)   end
+function IGraphics:circle(mode, x, y, r)                 end
+function IGraphics:line(...)                             end
+function IGraphics:print(text, x, y, r, sx, sy)          end
+function IGraphics:printf(text, x, y, limit, align)      end
+function IGraphics:newImage(path)                        end
+function IGraphics:newQuad(x, y, w, h, sw, sh)           end
+function IGraphics:draw(drawable, x, y, r, sx, sy, ox, oy) end
+function IGraphics:push()                                end
+function IGraphics:pop()                                 end
+function IGraphics:translate(x, y)                       end
+function IGraphics:scale(sx, sy)                         end
+function IGraphics:newCanvas(w, h)                       end
+function IGraphics:setCanvas(canvas)                     end
+function IGraphics:clear(r, g, b, a)                     end
+function IGraphics:newShader(code)                       end
+function IGraphics:setShader(shader)                     end
+function IGraphics:newFont(path, size)                   end
+function IGraphics:setFont(font)                         end
+function IGraphics:getFont()                             end
+```
+
+**IFilesystem 接口**(供 `engine/save.lua`、`engine/mod_loader.lua` 使用):
+
+```lua
+local IFilesystem = Interface_Base:extend()
+function IFilesystem:read(path)                end
+function IFilesystem:write(path, data)         end
+function IFilesystem:exists(path)              end
+function IFilesystem:remove(path)              end
+function IFilesystem:getInfo(path)             end
+function IFilesystem:getDirectoryItems(path)   end
+function IFilesystem:createDirectory(path)     end
+function IFilesystem:load(path)                end
+```
+
+**IAudio 接口**(供 `engine/audio.lua`、`engine/sfx.lua` 使用):
+
+```lua
+local IAudio = Interface_Base:extend()
+function IAudio:newSoundData(samples, rate, bits, channels) end
+function IAudio:newSource(soundData)                        end
+```
+
+**IWindow 接口**(供 `engine/camera.lua`、`engine/engine.lua` 使用):
+
+```lua
+local IWindow = Interface_Base:extend()
+function IWindow:getWidth()  end
+function IWindow:getHeight() end
+```
+
+**MockGraphics**(测试用,不依赖 love,定义于 `engine/interface.lua` 或独立测试辅助文件):
+
+```lua
+-- [Engine层] IGraphics 的测试 mock 实现,不调用任何 love API
+local MockGraphics = IGraphics:extend()
+function MockGraphics:new()
+    self.calls = {}
+    self._w, self._h = 480, 854
+end
+function MockGraphics:setColor(r, g, b, a)
+    table.insert(self.calls, {"setColor", r, g, b, a})
+end
+function MockGraphics:getWidth()  return self._w end
+function MockGraphics:getHeight() return self._h end
+-- ... 其余方法均记录调用
+```
+
+### classic.lua — OOP 基础库
+
+来自 rxi/classic,放置于项目根目录。提供 `Object:extend()`、`Object:new()` 等方法。所有 Systems 层和 Render 层的主要类通过 `classic` 定义。
+
+### Engine 层模块说明
+
+Engine 层模块通过构造时注入的适配层接口访问框架能力,不直接调用 `love.*`:
+
+- `engine/save.lua`:已通过 `Save.setFS(IFilesystem)` 注入文件系统接口,无需修改
+- `engine/audio.lua`:已通过 `Audio.setLove(IAudio)` 注入音频接口,无需修改
+- `engine/camera.lua`:已通过 `Camera.setScreenSize(w, h)` 注入屏幕尺寸,无需修改
+- `engine/particles.lua`:当前直接调用 `love.graphics.*`,需重构为接收 `IGraphics` 注入
+- `engine/engine.lua`:当前直接调用 `love.*`,需重构为通过 `IGraphics`、`IFilesystem`、`IWindow` 接口访问
+- `engine/mod_loader.lua`:当前直接调用 `love.filesystem.*`,需重构为通过 `IFilesystem` 接口访问
+
+### Systems 层类定义
+
+**WorldSystem**(实现 IWorldQuery):
+
+```lua
+-- [Systems层] 世界地图数据与逻辑
+local Object = require("classic")
+local IWorldQuery = require("engine/interface").IWorldQuery
+
+local WorldSystem = IWorldQuery:extend()
+
+function WorldSystem:new(seed)
+    -- 初始化地图数据
+    self._baseData    = nil
+    self._overlayData = nil
+    -- ... 生成地图
+    self:init(seed)
+end
+
+-- 实现 IWorldQuery 的所有方法
+function WorldSystem:isPath(row, col) ... end
+function WorldSystem:isCollision(row, col) ... end
+-- ... 其余 12 个方法
+```
+
+**FarmSystem**(实现 IFarmQuery):
+
+```lua
+-- [Systems层] 农场业务逻辑
+local Object = require("classic")
+local IFarmQuery = require("engine/interface").IFarmQuery
+
+local FarmSystem = IFarmQuery:extend()
+
+function FarmSystem:new()
+    self.farmData = {}
+end
+
+-- 实现 IFarmQuery
+function FarmSystem:getTile(row, col) ... end
+function FarmSystem:getProgress(row, col, speedMult) ... end
+function FarmSystem:getRemaining(row, col, speedMult) ... end
+function FarmSystem:countNeighborCrops(row, col) ... end
+
+-- FarmSystem 自有方法(不在接口中)
+function FarmSystem:till(row, col) ... end
+function FarmSystem:plant(row, col, cropId, gene) ... end
+function FarmSystem:water(row, col) ... end
+function FarmSystem:harvest(row, col, stats) ... end
+function FarmSystem:setWorldPave(fn) ... end
+```
+
+**PlayerSystem**(接收 IWorldQuery):
+
+```lua
+-- [Systems层] 玩家控制与工具操作
+local Object = require("classic")
+
+local PlayerSystem = Object:extend()
+
+-- 构造函数接收 IWorldQuery 接口实例
+function PlayerSystem:new(farmSystem, gameState, blockSystem, worldQuery)
+    self.farm      = farmSystem
+    self.state     = gameState
+    self.blocks    = blockSystem
+    self._world    = worldQuery  -- IWorldQuery 实例,替代模块级 _world 变量
+    self.character = Character:new(50, 51)
+    -- ...
+end
+```
+
+**Character**(接收 IWorldQuery):
+
+```lua
+-- [Systems层] 角色状态机与寻路移动
+local Object = require("classic")
+
+local Character = Object:extend()
+
+function Character:new(startRow, startCol, worldQuery)
+    -- worldQuery 用于 isPath 查询(替代注入函数 _isPath)
+    self._world = worldQuery
+    -- ...
+end
+```
+
+**Pet**(接收 IWorldQuery):
+
+```lua
+-- [Systems层] 宠物跟随与离线收益
+local Object = require("classic")
+
+local Pet = Object:extend()
+
+function Pet:new(worldQuery)
+    self._world = worldQuery  -- 替代模块级 _isCollision 函数
+    -- ...
+end
+```
+
+### Render 层类定义
+
+Render 层所有类通过构造函数接收 `IGraphics` 实例,**不直接调用 `love.graphics.*`**。
+
+**WorldRenderer**(接收 IWorldQuery + IGraphics):
+
+```lua
+-- [Render层] 世界地图渲染,通过 IGraphics 绘图,不直接调用 love
+local Object = require("classic")
+
+local WorldRenderer = Object:extend()
+
+function WorldRenderer:new(worldQuery, graphics)
+    self._world    = worldQuery  -- IWorldQuery
+    self._gfx      = graphics   -- IGraphics,替代直接调用 love.graphics
+    self._animTime = 0
+    self._tileImgs = {}
+end
+
+function WorldRenderer:update(dt) ... end
+function WorldRenderer:drawBackground(brightnessMap, baseBrightness)
+    -- 使用 self._gfx:setColor(...)、self._gfx:draw(...) 等
+    -- 不出现任何 love.graphics.* 调用
+end
+```
+
+**FarmRenderer**(接收 IFarmQuery + IGraphics):
+
+```lua
+-- [Render层] 农场格子渲染
+local Object = require("classic")
+
+local FarmRenderer = Object:extend()
+
+function FarmRenderer:new(farmQuery, graphics)
+    self._farm = farmQuery  -- IFarmQuery
+    self._gfx  = graphics  -- IGraphics
+end
+
+function FarmRenderer:draw(camera) ... end
+function FarmRenderer.getTileCenter(row, col) ... end
+```
+
+**UIRenderer**(接收 IFarmQuery + IGameStateQuery + IGraphics):
+
+```lua
+-- [Render层] HUD、商店、消息等 UI 渲染
+local Object = require("classic")
+
+local UIRenderer = Object:extend()
+
+function UIRenderer:new(farmQuery, gameStateQuery, graphics)
+    self._farm      = farmQuery       -- IFarmQuery
+    self._gameState = gameStateQuery  -- IGameStateQuery
+    self._gfx       = graphics        -- IGraphics
+end
+```
+
+所有其他 Render 层类(`BlockRenderer`、`CharacterRenderer`、`CropRenderer`、`DropsRenderer`、`EffectRenderer`、`InventoryRenderer`、`PetRenderer`、`TalentRenderer`、`WorkbenchRenderer`、`DungeonRenderer`、`ForgeRenderer`)均同样接收 `IGraphics` 实例,不直接调用 `love.graphics.*`。
+
+### 协调层 game.lua
+
+`game.lua` 重构为薄协调层,职责:
+1. 创建 Love 适配层实例(`LoveGraphics`、`LoveFilesystem`、`LoveAudio`、`LoveWindow`)
+2. 将适配层实例注入到 Engine 层模块
+3. 创建所有 Systems 层对象
+4. 将 Systems 对象包装为接口实现
+5. 将接口(含 `IGraphics`)注入到 Render 层模块
+6. 转发框架事件给对应系统
+
+```lua
+-- [协调层] 游戏主控制器,负责组装各层对象并传递接口
+function Game:new()
+    -- 1. 创建 Love 适配层实例(唯一 require love_adapter/ 的地方)
+    local LoveGraphics    = require("love_adapter/graphics")
+    local LoveFilesystem  = require("love_adapter/filesystem")
+    local LoveAudio       = require("love_adapter/audio")
+    local LoveWindow      = require("love_adapter/window")
+
+    self.graphics   = LoveGraphics:new()
+    self.filesystem = LoveFilesystem:new()
+    self.audio      = LoveAudio:new()
+    self.window     = LoveWindow:new()
+
+    -- 2. 将适配层注入 Engine 层模块
+    local Save      = require("engine/save")
+    local Audio     = require("engine/audio")
+    local ModLoader = require("engine/mod_loader")
+    Save.setFS(self.filesystem)
+    Audio.setLove(self.audio)
+    ModLoader.setFS(self.filesystem)
+
+    -- 3. 创建 Systems 层对象
+    self.worldSystem = WorldSystem:new(os.time())
+    self.farmSystem  = FarmSystem:new()
+    -- ...
+
+    -- 4. 创建 Render 层对象,注入接口(含 IGraphics)
+    self.worldRenderer = WorldRenderer:new(self.worldSystem, self.graphics)
+    self.farmRenderer  = FarmRenderer:new(self.farmSystem, self.graphics)
+    self.uiRenderer    = UIRenderer:new(
+        self.farmSystem,
+        GameStateAdapter:new(self.gameState),
+        self.graphics
+    )
+
+    -- 5. 创建需要接口的 Systems 对象
+    self.playerSystem = PlayerSystem:new(
+        self.farmSystem, self.gameState, self.blockSystem, self.worldSystem
+    )
+    -- ...
+end
+```
+
+**GameStateAdapter**(IGameStateQuery 适配器):
+
+由于 `systems/state.lua` 是纯数据表(非 classic 类),需要一个适配器将其包装为 `IGameStateQuery`:
+
+```lua
+-- [协调层] GameState 到 IGameStateQuery 的适配器
+local IGameStateQuery = require("engine/interface").IGameStateQuery
+
+local GameStateAdapter = IGameStateQuery:extend()
+
+function GameStateAdapter:new(gs)
+    self._gs = gs
+end
+
+function GameStateAdapter:getMoney()    return self._gs.money end
+function GameStateAdapter:getLevel()    return self._gs.level end
+-- ... 其余方法
+```
+
+---
+
+## 数据模型
+
+### 接口继承层次
+
+```
+classic.Object
+  └── Interface_Base
+        ├── IGraphics
+        │     ├── LoveGraphics(Love适配层实现,唯一调用 love.graphics)
+        │     └── MockGraphics(测试用,不依赖 love)
+        ├── IFilesystem
+        │     ├── LoveFilesystem(Love适配层实现)
+        │     └── MockFilesystem(测试用)
+        ├── IAudio
+        │     └── LoveAudio(Love适配层实现)
+        ├── IWindow
+        │     └── LoveWindow(Love适配层实现)
+        ├── IWorldQuery
+        │     └── WorldSystem(实现)
+        ├── IFarmQuery
+        │     └── FarmSystem(实现)
+        └── IGameStateQuery
+              └── GameStateAdapter(适配器实现)
+
+classic.Object
+  ├── PlayerSystem(持有 IWorldQuery)
+  ├── Character(持有 IWorldQuery)
+  ├── Pet(持有 IWorldQuery)
+  ├── WorldRenderer(持有 IWorldQuery + IGraphics)
+  ├── FarmRenderer(持有 IFarmQuery + IGraphics)
+  ├── UIRenderer(持有 IFarmQuery + IGameStateQuery + IGraphics)
+  └── 其他 Renderer(持有 IGraphics)
+```
+
+### 目录结构与层归属
+
+```
+love_adapter/          ← Love 适配层(唯一调用 love.* 的目录)
+  graphics.lua         -- [Love适配层] IGraphics 的 LÖVE2D 实现
+  filesystem.lua       -- [Love适配层] IFilesystem 的 LÖVE2D 实现
+  audio.lua            -- [Love适配层] IAudio 的 LÖVE2D 实现
+  window.lua           -- [Love适配层] IWindow 的 LÖVE2D 实现
+engine/                ← Engine 层(不调用 love.*,通过接口访问适配层)
+  interface.lua        -- [Engine层] 所有接口定义(含框架接口和业务接口)
+  camera.lua           -- [Engine层] 摄像机(通过 setScreenSize 注入)
+  save.lua             -- [Engine层] 存档(通过 setFS 注入 IFilesystem)
+  audio.lua            -- [Engine层] 音频合成(通过 setLove 注入 IAudio)
+  ...
+data/                  ← Data 层(纯静态表)
+systems/               ← Systems 层(不调用 love.*)
+render/                ← Render 层(通过 IGraphics 绘图,不调用 love.*)
+game.lua               ← 协调层(创建适配层实例并注入)
+main.lua               ← 协调层(LÖVE2D 入口,转发事件给 engine/engine.lua)
+```
+
+### 层归属注释格式
+
+每个文件第一行必须包含:
+
+| 目录 | 注释格式 | 示例 |
+|------|----------|------|
+| `love_adapter/` | `-- [Love适配层] 说明` | `-- [Love适配层] IGraphics 的 LÖVE2D 实现` |
+| `engine/` | `-- [Engine层] 说明` | `-- [Engine层] 事件总线,解耦模块间通信` |
+| `data/` | `-- [Data层] 说明` | `-- [Data层] 作物静态数据表` |
+| `systems/` | `-- [Systems层] 说明` | `-- [Systems层] 农场业务逻辑` |
+| `render/` | `-- [Render层] 说明` | `-- [Render层] 世界地图渲染` |
+| 根目录协调 | `-- [协调层] 说明` | `-- [协调层] 游戏主控制器,组装各层对象` |
+
+### 依赖检查规则(静态分析)
+
+```
+love_adapter/ 下文件 → 允许调用 love.*,不得 require 其他层(除 engine/interface)
+engine/       下文件 → 不得包含 love. 字符串(通过适配层接口访问)
+engine/       下文件 → 不得包含 require("systems/ 或 require("render/
+systems/      下文件 → 不得包含 love. 字符串
+systems/      下文件 → 不得包含 require("render/
+render/       下文件 → 不得包含 love. 字符串(通过 IGraphics 接口调用)
+render/       下文件 → 不得包含顶层 require("systems/
+data/         下文件 → 不得包含任何 require(除 data/ 内部)
+```
+
+---
+
+## 正确性属性
+
+*属性是在系统所有有效执行中都应成立的特征或行为——本质上是关于系统应该做什么的形式化陈述。属性是人类可读规范与机器可验证正确性保证之间的桥梁。*
+
+### 属性 1:接口未实现方法抛出格式化错误
+
+*对于任意* 接口名称和方法名称,当通过未实现该方法的子类调用时,系统应抛出包含 `"接口名.方法名 未实现"` 格式的错误信息。
+
+**验证:需求 1.3**
+
+### 属性 2:IWorldQuery 接口方法完整性
+
+*对于* IWorldQuery 接口声明的每一个方法,当通过未实现的子类调用时,系统应抛出包含该方法名的错误(证明方法已被声明且有默认未实现行为)。
+
+**验证:需求 1.4**
+
+### 属性 3:IFarmQuery 接口方法完整性
+
+*对于* IFarmQuery 接口声明的每一个方法,当通过未实现的子类调用时,系统应抛出包含该方法名的错误。
+
+**验证:需求 1.5**
+
+### 属性 4:IGameStateQuery 接口方法完整性
+
+*对于* IGameStateQuery 接口声明的每一个方法,当通过未实现的子类调用时,系统应抛出包含该方法名的错误。
+
+**验证:需求 1.6**
+
+### 属性 5:所有 Lua 源文件包含层归属注释
+
+*对于* 项目中 `engine/`、`data/`、`systems/`、`render/` 目录下的任意 Lua 源文件,其第一行应匹配 `-- [*层] ` 格式的层归属注释。
+
+**验证:需求 2.1、2.2、2.3、2.4、2.5**
+
+### 属性 6:Systems 层不依赖 Render 层
+
+*对于* `systems/` 目录下的任意 Lua 源文件,其文件内容不应包含 `require("render/` 字符串。
+
+**验证:需求 3.1、3.6、9.3**
+
+### 属性 7:Render 层不直接 require Systems 层
+
+*对于* `render/` 目录下的任意 Lua 源文件,其文件内容不应包含顶层(文件加载时执行的)`require("systems/` 字符串。
+
+**验证:需求 4.1、4.5、9.4**
+
+### 属性 11:Render 层不直接调用 love API
+
+*对于* `render/` 目录下的任意 Lua 源文件,其文件内容不应包含 `love.` 字符串(所有绘图调用均通过 `IGraphics` 接口)。
+
+**验证:需求 9.4(扩展)**
+
+### 属性 12:Systems 层不直接调用 love API
+
+*对于* `systems/` 目录下的任意 Lua 源文件,其文件内容不应包含 `love.` 字符串。
+
+**验证:需求 9.3(扩展)**
+
+### 属性 8:通过接口访问与直接调用等价
+
+*对于任意* 有效的地图坐标 `(row, col)`,通过 `IWorldQuery` 接口调用 `WorldSystem` 的查询方法,应与直接调用 `WorldSystem` 对应方法返回相同的结果。
+
+**验证:需求 8.4**
+
+### 属性 9:农场操作状态机转换正确性
+
+*对于任意* 有效的农场格子初始状态和操作序列(耕地→播种→浇水→收获),FarmSystem 的状态转换应满足:`EMPTY →(till)→ TILLED →(plant)→ SEEDED →(harvest when ready)→ EMPTY`,且每步操作返回值与状态变更一致。
+
+**验证:需求 8.3**
+
+### 属性 10:Engine 层不依赖上层模块且不直接调用 love
+
+*对于* `engine/` 目录下的任意 Lua 源文件,其文件内容不应包含 `require("systems/`、`require("render/` 或 `require("data/` 字符串;同时不应包含 `love.` 字符串(Engine 层通过适配层接口访问框架能力)。
+
+**验证:需求 9.1**
+
+### 属性 13:Love 适配层是唯一调用 love 的层
+
+*对于* `engine/`、`systems/`、`render/`、`data/` 目录下的任意 Lua 源文件,其文件内容不应包含 `love.` 字符串。只有 `love_adapter/` 目录下的文件允许包含 `love.` 字符串。
+
+**验证:love 隔离原则**
+
+---
+
+## 错误处理
+
+### 接口未实现错误
+
+当调用未被子类覆盖的接口方法时,`Interface_Base` 的默认实现抛出 Lua error:
+
+```lua
+error("IWorldQuery.isPath 未实现")
+```
+
+调用方(Systems 层或 Render 层)不需要捕获此错误——它代表编程错误(忘记实现接口),应在开发阶段暴露。
+
+### 接口注入缺失
+
+若 `game.lua` 忘记注入接口实例,相关模块在首次调用接口方法时会因 `nil` 调用而报错。为提前发现此类问题,各模块构造函数应做断言:
+
+```lua
+function PlayerSystem:new(farmSystem, gameState, blockSystem, worldQuery)
+    assert(worldQuery, "PlayerSystem:new 需要 IWorldQuery 实例")
+    -- ...
+end
+```
+
+### 依赖方向违规检测
+
+在 CI/测试阶段,通过静态扫描脚本检测违规 `require`。违规时输出文件名和行号,阻止合并。
+
+---
+
+## 测试策略
+
+### 双轨测试方法
+
+- **单元测试**:验证具体示例、边界条件、错误情况
+- **属性测试**:验证跨所有输入的通用属性
+
+使用 [busted](https://lunarmodules.github.io/busted/) 框架(已配置于 `.busted`),属性测试使用 [lua-quickcheck](https://github.com/mcmtroffaes/lua-quickcheck) 或手动生成随机输入。
+
+### 属性测试配置
+
+每个属性测试最少运行 **100 次迭代**,标签格式:
+
+```lua
+-- Feature: layered-architecture-refactor, Property N: 属性描述
+```
+
+### 测试文件组织
+
+```
+spec/
+  helpers/
+    love_mock.lua          -- 已有 LÖVE2D mock(仅用于 love_adapter 层测试)
+    interface_helpers.lua  -- 接口测试辅助函数(新增)
+    mock_graphics.lua      -- MockGraphics 实现(新增,供 Engine/Render 层测试使用)
+    mock_filesystem.lua    -- MockFilesystem 实现(新增,供 Engine 层测试使用)
+  layered_arch/
+    property_interface_spec.lua   -- 属性 1-4:接口正确性(含框架接口)
+    property_layer_deps_spec.lua  -- 属性 5-7、10-13:层依赖约束(含 love 隔离检查)
+    property_equivalence_spec.lua -- 属性 8:接口等价性
+    property_farm_spec.lua        -- 属性 9:农场状态机
+```
+
+### 属性测试示例
+
+**属性 1 — 接口未实现方法抛出格式化错误**:
+
+```lua
+-- Feature: layered-architecture-refactor, Property 1: 接口未实现方法抛出格式化错误
+it("对任意方法名,未实现时调用应抛出包含方法名的错误", function()
+    local Interface_Base = require("engine/interface").Interface_Base
+    -- 生成随机方法名列表
+    local methodNames = { "foo", "bar", "doSomething", "query_x", "get123" }
+    for _ = 1, 100 do
+        local name = methodNames[math.random(#methodNames)] .. tostring(math.random(1000))
+        local TestIface = Interface_Base:extend()
+        TestIface[name] = function() error("TestIface." .. name .. " 未实现") end
+        local impl = TestIface:extend()
+        local obj = impl:new()
+        local ok, err = pcall(function() obj[name](obj) end)
+        assert.is_false(ok)
+        assert.truthy(err:find(name))
+    end
+end)
+```
+
+**属性 8 — 接口访问与直接调用等价**:
+
+```lua
+-- Feature: layered-architecture-refactor, Property 8: 通过接口访问与直接调用等价
+it("对任意坐标,IWorldQuery 接口调用与 WorldSystem 直接调用结果相同", function()
+    local WorldSystem = require("systems/world_system")
+    WorldSystem.init(42)  -- 固定 seed
+    for _ = 1, 100 do
+        local row = math.random(1, 100)
+        local col = math.random(1, 100)
+        assert.equal(WorldSystem:isPath(row, col),     WorldSystem.isPath(row, col))
+        assert.equal(WorldSystem:isCollision(row, col), WorldSystem.isCollision(row, col))
+        assert.equal(WorldSystem:isObstacle(row, col),  WorldSystem.isObstacle(row, col))
+    end
+end)
+```
+
+**属性 9 — 农场状态机转换**:
+
+```lua
+-- Feature: layered-architecture-refactor, Property 9: 农场操作状态机转换正确性
+it("对任意坐标,农场状态机转换 EMPTY→TILLED→SEEDED→READY 正确", function()
+    local FarmSystem = require("systems/farm")
+    for _ = 1, 100 do
+        local farm = FarmSystem:new()
+        local row, col = math.random(1, 100), math.random(1, 100)
+        -- 先开垦
+        farm:clearGrass(row, col)
+        assert.equal("empty", farm:getTile(row, col).state)
+        -- 耕地
+        local ok = farm:till(row, col)
+        assert.is_true(ok)
+        assert.equal("tilled", farm:getTile(row, col).state)
+        -- 播种
+        ok = farm:plant(row, col, "carrot", nil)
+        assert.is_true(ok)
+        assert.equal("seeded", farm:getTile(row, col).state)
+    end
+end)
+```
+
+### 单元测试覆盖
+
+- `engine/interface.lua`:接口定义、错误格式(含 IGraphics、IFilesystem、IAudio、IWindow)
+- `love_adapter/graphics.lua`:LoveGraphics 委托行为(需 love mock)
+- `systems/world_system.lua`:地图查询方法
+- `systems/farm.lua`:农场操作、状态转换
+- `systems/player.lua`:通过 MockGraphics + mock IWorldQuery 验证注入
+- `render/ui_renderer.lua`:通过 MockGraphics + mock IFarmQuery/IGameStateQuery 验证注入,不依赖 love
+- 层依赖静态检查:扫描所有 `.lua` 文件的 `require` 调用和 `love.` 字符串,验证只有 `love_adapter/` 下文件包含 `love.`
+
+### 回归测试
+
+重构完成后,运行现有 `spec/` 下所有测试套件,确保全部通过:
+
+```bash
+busted --run
+```

+ 166 - 0
app/src/embed/assets/.kiro/specs/layered-architecture-refactor/requirements.md

@@ -0,0 +1,166 @@
+# 需求文档
+
+## 简介
+
+本项目是一款基于 LÖVE2D 框架的 Lua 农场游戏。当前代码库存在以下架构问题:
+
+- `game.lua` 同时持有 systems 层和 render 层的直接引用,充当了"上帝对象"
+- `render/ui_renderer.lua` 直接 `require` systems 层模块(如 `FarmSystem`、`State`),造成渲染层反向依赖业务层
+- `systems/player.lua` 通过注入的 `_world` 变量直接调用 `WorldRenderer` 的方法,造成 systems 层依赖 render 层
+- 各文件缺少层归属注释,难以快速定位模块所属层次
+- 缺乏统一的面向对象接口规范,模块间耦合方式不一致
+
+本次重构目标是引入清晰的四层架构(Engine → Data → Systems → Render),使用 `classic` 库统一接口定义,通过接口隔离各层,消除跨层调用和反向依赖。
+
+---
+
+## 词汇表
+
+- **Engine 层**:最底层,提供与 LÖVE2D 框架交互的基础设施,包括事件总线、摄像机、存档、音频、注册中心等。不依赖任何上层模块。
+- **Data 层**:纯数据定义层,包含作物、工具、物品、方块、配置等静态数据表。不含任何逻辑或副作用。
+- **Systems 层**:业务逻辑层,包含农场、玩家、世界、背包、天气等系统。只依赖 Engine 层和 Data 层,不依赖 Render 层。
+- **Render 层**:渲染层,负责将 Systems 层的状态绘制到屏幕。只依赖 Engine 层,通过接口(Interface)读取 Systems 层数据,不直接 `require` Systems 层模块。
+- **classic 库**:一个轻量级 Lua OOP 库,提供 `Class:extend()`、`Class:new()` 等方法,用于定义类和接口。
+- **Interface(接口)**:用 classic 定义的抽象基类,声明方法签名但不提供实现,用于隔离层间依赖。
+- **IWorldQuery**:世界地图查询接口,供 Systems 层(player、pet 等)使用,隔离对 WorldSystem 的直接依赖。
+- **IFarmQuery**:农场状态查询接口,供 Render 层使用,隔离对 FarmSystem 的直接依赖。
+- **IGameStateQuery**:游戏状态查询接口,供 Render 层使用,隔离对 State 的直接依赖。
+- **EventBus**:事件总线,已存在于 `engine/eventbus.lua`,用于跨层异步通信。
+- **跨层调用**:某层直接 `require` 并调用了不相邻层的模块(如 render 层直接调用 systems 层)。
+- **反向依赖**:低层模块依赖高层模块(如 systems 层依赖 render 层)。
+- **层归属注释**:每个文件开头的注释,格式为 `-- [层名称层] 文件说明`,明确标注该文件所属架构层次。
+
+---
+
+## 需求
+
+### 需求 1:引入 classic 库并建立接口基础设施
+
+**用户故事:** 作为开发者,我希望项目中有统一的 OOP 基础设施,以便用接口(抽象类)来隔离各层之间的依赖。
+
+#### 验收标准
+
+1. THE 项目 SHALL 在根目录包含 `classic.lua` 文件(来自 rxi/classic 库)。
+2. THE 项目 SHALL 在 `engine/interface.lua` 中定义所有跨层接口的基类,使用 classic 的 `Class:extend()` 机制。
+3. WHEN 某接口方法被调用但未被子类实现时,THE Interface_Base SHALL 抛出包含方法名的错误信息,格式为 `"[接口名].[方法名] 未实现"`。
+4. THE 项目 SHALL 定义 `IWorldQuery` 接口,包含以下方法:`isPath(row, col)`、`isCollision(row, col)`、`isObstacle(row, col)`、`isTree(row, col)`、`isWell(row, col)`、`isGrass(row, col)`、`removeGrass(row, col)`、`isMushroom(row, col)`、`pickMushroom(row, col)`、`isBush(row, col)`、`chopBush(row, col)`、`chopTree(row, col)`、`isGravelPile(row, col)`、`mineGravelPile(row, col)`。
+5. THE 项目 SHALL 定义 `IFarmQuery` 接口,包含以下方法:`getTile(row, col)`、`getProgress(row, col, speedMult)`、`getRemaining(row, col, speedMult)`、`countNeighborCrops(row, col)`。
+6. THE 项目 SHALL 定义 `IGameStateQuery` 接口,包含以下方法:`getMoney()`、`getLevel()`、`getExp()`、`getStamina()`、`getMaxStamina()`、`getPlayerRow()`、`getPlayerCol()`、`getInventory()`、`getEquippedSlots()`、`getWateringCanWater()`。
+
+---
+
+### 需求 2:为所有文件添加层归属注释
+
+**用户故事:** 作为开发者,我希望每个文件开头都有明确的层归属注释,以便快速理解模块所属层次和职责。
+
+#### 验收标准
+
+1. THE 项目 SHALL 在每个 Lua 源文件的第一行添加层归属注释,格式为 `-- [层名称层] 文件简要说明`。
+2. THE Engine_Layer 注释 SHALL 使用格式 `-- [Engine层] 说明`,适用于 `engine/` 目录下所有文件。
+3. THE Data_Layer 注释 SHALL 使用格式 `-- [Data层] 说明`,适用于 `data/` 目录下所有文件。
+4. THE Systems_Layer 注释 SHALL 使用格式 `-- [Systems层] 说明`,适用于 `systems/` 目录下所有文件。
+5. THE Render_Layer 注释 SHALL 使用格式 `-- [Render层] 说明`,适用于 `render/` 目录下所有文件。
+6. WHERE 文件属于入口或跨层协调模块(如 `game.lua`、`main.lua`),THE 注释 SHALL 使用格式 `-- [协调层] 说明` 并说明其协调职责。
+
+---
+
+### 需求 3:消除 Systems 层对 Render 层的依赖
+
+**用户故事:** 作为开发者,我希望 systems 层的模块不直接依赖 render 层,以便在无渲染环境(如单元测试)中独立运行业务逻辑。
+
+#### 验收标准
+
+1. THE Systems_Layer SHALL 不包含任何对 `render/` 目录下模块的 `require` 调用。
+2. WHEN `systems/player.lua` 需要查询世界地图状态时,THE PlayerSystem SHALL 通过 `IWorldQuery` 接口访问,而非直接调用 `WorldRenderer` 或 `WorldSystem` 的具体方法。
+3. WHEN `systems/pet.lua` 需要查询碰撞信息时,THE PetSystem SHALL 通过 `IWorldQuery` 接口访问。
+4. WHEN `systems/character.lua` 需要查询路径信息时,THE Character SHALL 通过 `IWorldQuery` 接口访问。
+5. THE `systems/farm.lua` SHALL 通过注入的 `IWorldQuery` 接口调用 `pave` 相关方法,不直接依赖 `WorldRenderer`。
+6. IF `systems/` 目录下任意文件包含 `require("render/` 字符串,THEN THE 构建检查 SHALL 报告违规错误。
+
+---
+
+### 需求 4:消除 Render 层对 Systems 层的直接 require 依赖
+
+**用户故事:** 作为开发者,我希望 render 层不直接 require systems 层模块,以便渲染逻辑与业务逻辑解耦,支持独立替换渲染实现。
+
+#### 验收标准
+
+1. THE Render_Layer SHALL 不包含任何对 `systems/` 目录下模块的顶层 `require` 调用(文件加载时执行的 require)。
+2. WHEN `render/ui_renderer.lua` 需要显示农场格子信息时,THE UIRenderer SHALL 通过 `IFarmQuery` 接口获取数据,而非直接 `require("systems/farm")`。
+3. WHEN `render/ui_renderer.lua` 需要显示玩家状态(金币、等级、精力等)时,THE UIRenderer SHALL 通过 `IGameStateQuery` 接口获取数据,而非直接 `require("systems/state")`。
+4. WHEN `render/farm_renderer.lua` 需要读取农场瓦片数据时,THE FarmRenderer SHALL 通过 `IFarmQuery` 接口获取,而非直接持有 FarmSystem 引用。
+5. IF `render/` 目录下任意文件包含顶层 `require("systems/` 字符串,THEN THE 构建检查 SHALL 报告违规错误。
+
+---
+
+### 需求 5:重构 game.lua 为薄协调层
+
+**用户故事:** 作为开发者,我希望 `game.lua` 只负责组装各层对象并传递接口,不包含业务逻辑,以便降低核心文件的复杂度。
+
+#### 验收标准
+
+1. THE `game.lua` SHALL 负责创建所有 Systems 层对象、Render 层对象,并将接口实现注入到需要的模块中。
+2. THE `game.lua` SHALL 将 `WorldSystem` 的实例包装为 `IWorldQuery` 的实现,并注入到 `PlayerSystem`、`PetSystem`、`Character` 等需要世界查询的模块。
+3. THE `game.lua` SHALL 将 `FarmSystem` 的实例包装为 `IFarmQuery` 的实现,并注入到 `UIRenderer`、`FarmRenderer` 等需要农场数据的渲染模块。
+4. THE `game.lua` SHALL 将 `GameState` 包装为 `IGameStateQuery` 的实现,并注入到 `UIRenderer` 等需要状态数据的渲染模块。
+5. WHEN `game.lua` 处理用户输入事件时,THE `game.lua` SHALL 将事件转发给对应的 Systems 层方法或通过 EventBus 发布,不直接操作渲染状态。
+6. THE `game.lua` 中的业务逻辑代码行数 SHALL 不超过重构前的 30%(即大部分逻辑应迁移到对应层)。
+
+---
+
+### 需求 6:使用 classic 库统一 Systems 层的类定义
+
+**用户故事:** 作为开发者,我希望 systems 层的主要类使用 classic 库定义,以便获得统一的继承、构造和方法调用规范。
+
+#### 验收标准
+
+1. THE `systems/farm.lua` SHALL 使用 `classic` 的 `Class:extend()` 定义 `FarmSystem` 类,并实现 `IFarmQuery` 接口。
+2. THE `systems/player.lua` SHALL 使用 `classic` 的 `Class:extend()` 定义 `PlayerSystem` 类,构造函数接收 `IWorldQuery` 接口实例。
+3. THE `systems/world_system.lua` SHALL 使用 `classic` 的 `Class:extend()` 定义 `WorldSystem` 类,并实现 `IWorldQuery` 接口。
+4. THE `systems/character.lua` SHALL 使用 `classic` 的 `Class:extend()` 定义 `Character` 类,构造函数接收 `IWorldQuery` 接口实例。
+5. WHEN 使用 classic 定义的类被实例化时,THE 类 SHALL 通过 `ClassName:new(...)` 或 `ClassName(...)` 方式创建实例,保持与 classic 库的一致性。
+6. THE `systems/pet.lua` SHALL 使用 `classic` 的 `Class:extend()` 定义 `Pet` 类,构造函数接收 `IWorldQuery` 接口实例。
+
+---
+
+### 需求 7:使用 classic 库统一 Render 层的类定义
+
+**用户故事:** 作为开发者,我希望 render 层的主要渲染器使用 classic 库定义,以便获得统一的接口注入和方法调用规范。
+
+#### 验收标准
+
+1. THE `render/ui_renderer.lua` SHALL 使用 `classic` 的 `Class:extend()` 定义 `UIRenderer` 类,构造函数接收 `IFarmQuery` 和 `IGameStateQuery` 接口实例。
+2. THE `render/farm_renderer.lua` SHALL 使用 `classic` 的 `Class:extend()` 定义 `FarmRenderer` 类,构造函数接收 `IFarmQuery` 接口实例。
+3. THE `render/world_renderer.lua` SHALL 使用 `classic` 的 `Class:extend()` 定义 `WorldRenderer` 类,构造函数接收 `IWorldQuery` 接口实例。
+4. WHEN Render 层类被实例化时,THE 类 SHALL 通过构造函数参数接收所需接口,不在方法内部 `require` Systems 层模块。
+5. THE `render/character_renderer.lua` SHALL 使用 `classic` 的 `Class:extend()` 定义 `CharacterRenderer` 类。
+
+---
+
+### 需求 8:保持现有功能完整性
+
+**用户故事:** 作为玩家,我希望重构后游戏的所有功能与重构前完全一致,以便重构不引入任何功能回归。
+
+#### 验收标准
+
+1. WHEN 重构完成后运行现有测试套件(`spec/` 目录下所有 `_spec.lua` 文件)时,THE 测试套件 SHALL 全部通过,无新增失败用例。
+2. THE 游戏 SHALL 在重构后正常启动,显示开始界面,支持新游戏和继续游戏。
+3. WHEN 玩家执行农场操作(耕地、播种、浇水、收获)时,THE FarmSystem SHALL 产生与重构前相同的状态变更和事件。
+4. WHEN 玩家移动时,THE PlayerSystem SHALL 通过 `IWorldQuery` 接口获得与直接调用 `WorldSystem` 相同的碰撞检测结果。
+5. WHEN 游戏存档和读档时,THE SaveSystem SHALL 正确序列化和反序列化所有游戏状态,与重构前格式兼容(version 字段保持为 17)。
+6. IF 重构后任何现有 spec 测试失败,THEN THE 开发者 SHALL 在合并前修复该测试。
+
+---
+
+### 需求 9:层间依赖方向约束
+
+**用户故事:** 作为开发者,我希望架构中的依赖方向是单向的,以便防止循环依赖和架构腐化。
+
+#### 验收标准
+
+1. THE Engine_Layer SHALL 不依赖 Data 层、Systems 层或 Render 层的任何模块。
+2. THE Data_Layer SHALL 不依赖 Engine 层、Systems 层或 Render 层的任何模块。
+3. THE Systems_Layer SHALL 只依赖 Engine 层和 Data 层,不依赖 Render 层。
+4. THE Render_Layer SHALL 只依赖 Engine 层,通过接口(Interface)与 Systems 层通信,不直接 `require` Systems 层或 Data 层模块。
+5. WHEN 新增文件时,THE 开发者 SHALL 在文件开头的层归属注释中声明所属层,并确保 `require` 调用不违反上述依赖方向约束。
+6. THE `engine/interface.lua` SHALL 只依赖 `classic.lua`,不依赖任何其他项目模块。

+ 186 - 0
app/src/embed/assets/.kiro/specs/layered-architecture-refactor/tasks.md

@@ -0,0 +1,186 @@
+# 实现计划:分层架构重构
+
+## 概述
+
+将 LÖVE2D Lua 农场游戏重构为清晰的五层架构(Love适配层 → Engine → Data → Systems → Render),通过 `classic` 库统一接口定义,消除跨层调用和反向依赖。**核心原则:只有 `love_adapter/` 目录下的文件允许直接调用 `love.*`;Engine 层通过适配层接口访问框架能力;Render 层通过 `IGraphics` 接口绘图;Systems 层完全不接触 love。更换引擎时只需重写 `love_adapter/`。** 重构按以下顺序推进:先建立接口基础设施和适配层,再逐层修改各模块,最后重构协调层并补充测试。
+
+## 任务
+
+- [x] 1. 引入 classic 库并创建接口基础设施
+  - 确认 `classic.lua` 已存在于项目根目录,若不存在则从 rxi/classic 获取并放置
+  - 创建 `engine/interface.lua`,定义 `Interface_Base` 基类,使用 `makeUnimplemented` 工厂函数为每个接口方法生成默认抛错实现
+  - 在 `engine/interface.lua` 中定义 `IWorldQuery` 接口,声明全部 14 个方法
+  - 在 `engine/interface.lua` 中定义 `IFarmQuery` 接口,声明全部 4 个方法
+  - 在 `engine/interface.lua` 中定义 `IGameStateQuery` 接口,声明全部 10 个方法
+  - 文件第一行添加 `-- [Engine层] 跨层接口基础设施定义` 注释
+  - _需求:1.1、1.2、1.3、1.4、1.5、1.6、9.6_
+
+  - [x] 1.1 为接口基础设施编写属性测试
+    - **属性 1:接口未实现方法抛出格式化错误**
+    - **验证:需求 1.3**
+    - 创建 `spec/layered_arch/property_interface_spec.lua`,对随机方法名循环 100 次验证错误格式
+    - **属性 2:IWorldQuery 接口方法完整性**(需求 1.4)
+    - **属性 3:IFarmQuery 接口方法完整性**(需求 1.5)
+    - **属性 4:IGameStateQuery 接口方法完整性**(需求 1.6)
+
+- [x] 1.5 创建 Love 适配层(love_adapter/)
+  - 创建 `love_adapter/` 目录,这是整个项目**唯一允许调用 `love.*` 的目录**
+  - 在 `engine/interface.lua` 中补充定义框架接口:`IGraphics`(绘图)、`IFilesystem`(文件系统)、`IAudio`(音频)、`IWindow`(窗口/屏幕尺寸)
+  - 创建 `love_adapter/graphics.lua`,实现 `LoveGraphics`(继承 `IGraphics`),每个方法委托给对应的 `love.graphics.*` 调用,文件第一行添加 `-- [Love适配层] IGraphics 的 LÖVE2D 实现` 注释
+  - 创建 `love_adapter/filesystem.lua`,实现 `LoveFilesystem`(继承 `IFilesystem`),封装所有 `love.filesystem.*` 调用,文件第一行添加 `-- [Love适配层] IFilesystem 的 LÖVE2D 实现` 注释
+  - 创建 `love_adapter/audio.lua`,实现 `LoveAudio`(继承 `IAudio`),封装 `love.sound.newSoundData`、`love.audio.newSource`,文件第一行添加 `-- [Love适配层] IAudio 的 LÖVE2D 实现` 注释
+  - 创建 `love_adapter/window.lua`,实现 `LoveWindow`(继承 `IWindow`),封装屏幕尺寸查询,文件第一行添加 `-- [Love适配层] IWindow 的 LÖVE2D 实现` 注释
+  - _需求:9.4(扩展)、love 隔离原则_
+
+- [x] 2. 为所有现有文件添加层归属注释
+  - 为 `engine/` 目录下所有 `.lua` 文件第一行添加 `-- [Engine层] 说明` 注释
+  - 为 `data/` 目录下所有 `.lua` 文件第一行添加 `-- [Data层] 说明` 注释
+  - 为 `systems/` 目录下所有 `.lua` 文件第一行添加 `-- [Systems层] 说明` 注释
+  - 为 `render/` 目录下所有 `.lua` 文件第一行添加 `-- [Render层] 说明` 注释
+  - 为 `game.lua`、`main.lua` 添加 `-- [协调层] 说明` 注释
+  - _需求:2.1、2.2、2.3、2.4、2.5、2.6_
+
+  - [x] 2.1 为层归属注释编写属性测试
+    - **属性 5:所有 Lua 源文件包含层归属注释**
+    - **验证:需求 2.1、2.2、2.3、2.4、2.5**
+    - 创建 `spec/layered_arch/property_layer_deps_spec.lua`,扫描各目录下所有 `.lua` 文件,验证第一行匹配 `-- [*层] ` 格式
+
+- [x] 3. 重构 systems/world_system.lua 实现 IWorldQuery
+  - 将 `WorldSystem` 改为通过 `IWorldQuery:extend()` 定义,使其成为 `IWorldQuery` 的实现类
+  - 确保 `WorldSystem:new(seed)` 构造函数使用 classic 风格
+  - 实现 `IWorldQuery` 的全部 14 个方法,将现有逻辑迁移进来
+  - 文件第一行注释更新为 `-- [Systems层] 世界地图数据与逻辑,实现 IWorldQuery 接口`
+  - _需求:3.1、6.3、8.4_
+
+  - [x] 3.1 为 IWorldQuery 接口等价性编写属性测试
+    - **属性 8:通过接口访问与直接调用等价**
+    - **验证:需求 8.4**
+    - 在 `spec/layered_arch/property_equivalence_spec.lua` 中,对随机坐标循环 100 次,验证通过接口调用与直接调用 `WorldSystem` 方法结果相同
+
+- [x] 4. 重构 systems/farm.lua 实现 IFarmQuery
+  - 将 `FarmSystem` 改为通过 `IFarmQuery:extend()` 定义,使其成为 `IFarmQuery` 的实现类
+  - 确保 `FarmSystem:new()` 构造函数使用 classic 风格
+  - 实现 `IFarmQuery` 的全部 4 个方法(`getTile`、`getProgress`、`getRemaining`、`countNeighborCrops`)
+  - 保留 `FarmSystem` 自有方法(`till`、`plant`、`water`、`harvest`、`setWorldPave` 等)
+  - 文件第一行注释更新为 `-- [Systems层] 农场业务逻辑,实现 IFarmQuery 接口`
+  - _需求:6.1、8.3_
+
+  - [x] 4.1 为农场状态机编写属性测试
+    - **属性 9:农场操作状态机转换正确性**
+    - **验证:需求 8.3**
+    - 在 `spec/layered_arch/property_farm_spec.lua` 中,对随机坐标循环 100 次,验证 `EMPTY→TILLED→SEEDED` 状态转换正确
+
+- [x] 5. 重构 systems/character.lua 接收 IWorldQuery
+  - 将 `Character` 改为通过 `Object:extend()` 定义(classic 风格)
+  - 修改 `Character:new(startRow, startCol, worldQuery)` 构造函数,接收 `IWorldQuery` 实例替代注入函数
+  - 将内部所有 `_isPath` 函数调用替换为 `self._world:isPath(row, col)`
+  - 添加 `assert(worldQuery, "Character:new 需要 IWorldQuery 实例")` 断言
+  - 文件第一行注释更新为 `-- [Systems层] 角色状态机与寻路移动`
+  - _需求:3.4、6.4_
+
+- [x] 6. 重构 systems/pet.lua 接收 IWorldQuery
+  - 将 `Pet` 改为通过 `Object:extend()` 定义(classic 风格)
+  - 修改 `Pet:new(worldQuery)` 构造函数,接收 `IWorldQuery` 实例
+  - 将内部所有碰撞检测函数调用替换为 `self._world:isCollision(row, col)`
+  - 添加 `assert(worldQuery, "Pet:new 需要 IWorldQuery 实例")` 断言
+  - 文件第一行注释更新为 `-- [Systems层] 宠物跟随与离线收益`
+  - _需求:3.3、6.6_
+
+- [x] 7. 重构 systems/player.lua 接收 IWorldQuery
+  - 将 `PlayerSystem` 改为通过 `Object:extend()` 定义(classic 风格)
+  - 修改 `PlayerSystem:new(farmSystem, gameState, blockSystem, worldQuery)` 构造函数,接收 `IWorldQuery` 实例
+  - 将模块级 `_world` 变量替换为 `self._world`,消除对 `WorldRenderer` 的直接调用
+  - 添加 `assert(worldQuery, "PlayerSystem:new 需要 IWorldQuery 实例")` 断言
+  - 确认文件中不含任何 `require("render/` 字符串
+  - 文件第一行注释更新为 `-- [Systems层] 玩家控制与工具操作`
+  - _需求:3.1、3.2、6.2_
+
+- [x] 8. 检查点 — Systems 层重构完成
+  - 确保所有测试通过,向用户确认如有疑问请提出。
+
+- [x] 8.5 重构 Engine 层模块消除直接 love 调用
+  - 重构 `engine/particles.lua`:修改为接收 `IGraphics` 注入(通过构造函数或 setter),将所有 `love.graphics.*` 调用替换为接口调用,确认文件中不含 `love.` 字符串
+  - 重构 `engine/engine.lua`:将 `love.graphics.*` 调用替换为通过注入的 `IGraphics` 接口,将 `love.filesystem.*` 调用替换为通过注入的 `IFilesystem` 接口,确认文件中不含 `love.` 字符串
+  - 重构 `engine/mod_loader.lua`:将所有 `love.filesystem.*` 调用替换为通过注入的 `IFilesystem` 接口(新增 `ModLoader.setFS(fs)` setter),确认文件中不含 `love.` 字符串
+  - 确认 `engine/save.lua`、`engine/audio.lua`、`engine/camera.lua` 已满足(无 `love.` 调用)
+  - _需求:Engine 层 love 隔离_
+
+- [x] 9. 重构 render/world_renderer.lua 接收 IWorldQuery 和 IGraphics
+  - 将 `WorldRenderer` 改为通过 `Object:extend()` 定义(classic 风格)
+  - 修改 `WorldRenderer:new(worldQuery, graphics)` 构造函数,接收 `IWorldQuery` 和 `IGraphics` 实例
+  - 移除文件中所有顶层 `require("systems/` 调用
+  - 将所有 `love.graphics.*` 调用替换为 `self._gfx:对应方法()`
+  - 确认文件中不含任何 `love.` 字符串
+  - 文件第一行注释更新为 `-- [Render层] 世界地图渲染`
+  - _需求:4.1、7.3_
+
+- [x] 10. 重构 render/farm_renderer.lua 接收 IFarmQuery 和 IGraphics
+  - 将 `FarmRenderer` 改为通过 `Object:extend()` 定义(classic 风格)
+  - 修改 `FarmRenderer:new(farmQuery, graphics)` 构造函数,接收 `IFarmQuery` 和 `IGraphics` 实例
+  - 移除文件中所有顶层 `require("systems/` 调用,改为通过 `self._farm` 接口访问
+  - 将所有 `love.graphics.*` 调用替换为 `self._gfx:对应方法()`
+  - 确认文件中不含任何 `love.` 字符串
+  - 文件第一行注释更新为 `-- [Render层] 农场格子渲染`
+  - _需求:4.4、7.2_
+
+- [x] 11. 重构 render/ui_renderer.lua 接收 IFarmQuery、IGameStateQuery 和 IGraphics
+  - 将 `UIRenderer` 改为通过 `Object:extend()` 定义(classic 风格)
+  - 修改 `UIRenderer:new(farmQuery, gameStateQuery, graphics)` 构造函数,接收三个接口实例
+  - 移除文件中所有顶层 `require("systems/farm")` 和 `require("systems/state")` 调用
+  - 将所有直接访问 `FarmSystem` 的调用替换为 `self._farm:方法名()`
+  - 将所有直接访问 `State` 的调用替换为 `self._gameState:方法名()`
+  - 将所有 `love.graphics.*` 调用替换为 `self._gfx:对应方法()`
+  - 确认文件中不含任何 `love.` 字符串
+  - 文件第一行注释更新为 `-- [Render层] HUD、商店、消息等 UI 渲染`
+  - _需求:4.1、4.2、4.3、7.1_
+
+- [x] 11.5 重构其余 Render 层文件接收 IGraphics
+  - 对 `render/block_renderer.lua`、`render/character_renderer.lua`、`render/crop_renderer.lua`、`render/drops_renderer.lua`、`render/effect_renderer.lua`、`render/inventory_renderer.lua`、`render/pet_renderer.lua`、`render/talent_renderer.lua`、`render/workbench_renderer.lua`、`render/dungeon_renderer.lua`、`render/forge_renderer.lua`、`render/render_pipeline.lua` 逐一:
+    - 将类改为通过 `Object:extend()` 定义(classic 风格)
+    - 构造函数接收 `IGraphics` 实例(`graphics` 参数)
+    - 将所有 `love.graphics.*` 调用替换为 `self._gfx:对应方法()`
+    - 确认文件中不含任何 `love.` 字符串
+  - _需求:7.4、7.5_
+
+- [x] 12. 检查点 — Render 层重构完成
+  - 确保所有测试通过,向用户确认如有疑问请提出。
+
+  - [x] 12.1 为层依赖约束编写属性测试
+    - **属性 6:Systems 层不依赖 Render 层**(需求 3.1、3.6、9.3)
+    - 扫描 `systems/` 目录所有 `.lua` 文件,验证不含 `require("render/` 字符串
+    - **属性 7:Render 层不直接 require Systems 层**(需求 4.1、4.5、9.4)
+    - 扫描 `render/` 目录所有 `.lua` 文件,验证不含顶层 `require("systems/` 字符串
+    - **属性 10:Engine 层不依赖上层模块且不直接调用 love**(需求 9.1)
+    - 扫描 `engine/` 目录所有 `.lua` 文件,验证不含 `require("systems/`、`require("render/`、`require("data/` 或 `love.` 字符串
+    - **属性 11:Render 层不直接调用 love API**
+    - 扫描 `render/` 目录所有 `.lua` 文件,验证不含 `love.` 字符串
+    - **属性 12:Systems 层不直接调用 love API**
+    - 扫描 `systems/` 目录所有 `.lua` 文件,验证不含 `love.` 字符串
+    - **属性 13:Love 适配层是唯一调用 love 的层**
+    - 扫描除 `love_adapter/` 外所有 `.lua` 文件,验证均不含 `love.` 字符串
+
+- [x] 13. 重构 game.lua 为薄协调层
+  - 在 `game.lua` 中创建 `GameStateAdapter` 类(实现 `IGameStateQuery`)
+  - 修改 `Game:new()` 按以下顺序组装对象:
+    1. 创建 Love 适配层实例(`LoveGraphics`、`LoveFilesystem`、`LoveAudio`、`LoveWindow`),这是整个项目中唯一 `require("love_adapter/...")` 的地方
+    2. 将适配层实例注入 Engine 层模块(`Save.setFS`、`Audio.setLove`、`ModLoader.setFS` 等)
+    3. 创建所有 Systems 层对象(`WorldSystem`、`FarmSystem`、`BlockSystem` 等)
+    4. 创建 `GameStateAdapter` 包装 `gameState`
+    5. 创建 Render 层对象并注入接口(`WorldRenderer(worldSystem, graphics)`、`FarmRenderer(farmSystem, graphics)`、`UIRenderer(farmSystem, gameStateAdapter, graphics)`,其余 Renderer 同样注入 `graphics`)
+    6. 创建需要接口的 Systems 对象(`PlayerSystem(farm, state, blocks, worldSystem)`、`Pet(worldSystem)` 等)
+  - 移除 `game.lua` 中所有业务逻辑代码,确保业务逻辑行数不超过重构前 30%
+  - 文件第一行注释更新为 `-- [协调层] 游戏主控制器,负责组装各层对象并传递接口`
+  - _需求:5.1、5.2、5.3、5.4、5.5、5.6_
+
+- [x] 14. 最终检查点 — 全量回归测试
+  - 运行 `spec/` 目录下所有现有测试套件,确保全部通过,无新增失败用例
+  - 确保所有测试通过,向用户确认如有疑问请提出。
+  - _需求:8.1、8.2、8.5、8.6_
+
+## 备注
+
+- 标有 `*` 的子任务为可选项,可跳过以加快 MVP 进度
+- 每个任务均引用具体需求条款,保证可追溯性
+- 检查点任务确保增量验证,避免积累问题
+- 属性测试验证跨所有输入的通用正确性,单元测试验证具体示例和边界条件
+- 测试框架使用 busted(已配置于 `.busted`),属性测试每个属性最少运行 100 次迭代

+ 403 - 0
app/src/embed/assets/DATA_GUIDE.md

@@ -0,0 +1,403 @@
+# 数据配置指南
+
+所有游戏数值均在 `data/` 目录下的 Lua 文件中定义,格式为 `return { ... }`,无副作用,可直接编辑。
+
+---
+
+## data/config.lua — 全局参数
+
+```lua
+return {
+    window = {
+        title  = "种田游戏",
+        width  = 480,       -- 窗口宽度(像素)
+        height = 854,       -- 窗口高度(像素)
+        vsync  = 1,
+    },
+    player = {
+        startMoney  = 200,              -- 新游戏初始金币
+        startSeeds  = { wheat = 5 },    -- 初始种子 { cropId = 数量 }
+        startItems  = { herbicide = 6 },-- 初始物品 { itemId = 数量 }
+        startTool   = "till",           -- 初始工具
+        moveDelay   = 0.13,             -- 移动速度(秒/格,越小越快)
+    },
+    season = {
+        daysPerSeason = 7,              -- 每季天数
+    },
+    shop = {
+        seedBatchSize = 3,              -- 商店每次购买种子数量
+    },
+}
+```
+
+---
+
+## data/crops.lua — 作物
+
+```lua
+{
+    id        = "wheat",        -- 唯一ID(字母+下划线)
+    name      = "小麦",
+    growHours = 10,             -- 现实时间生长小时数
+    sellPrice = 16,             -- 基础出售价格(金币)
+    seedPrice = 80,             -- 种子购买价格
+    color     = {1, 0.85, 0.15}, -- RGB 颜色(用于图标着色,0~1)
+    -- 可选:节气售价倍率(未列出的节气默认1.0)
+    solarMult = {
+        ["芒种"] = 1.6,         -- 该节气售价 × 1.6
+        ["大暑"] = 0.8,
+    },
+}
+```
+
+**图片资源:** `assets/crops/{id}_00.png` ~ `{id}_05.png`(6帧生长动画,00=种子,05=成熟)
+
+**节气名称(24个):** 小寒、大寒、立春、雨水、惊蛰、春分、清明、谷雨、立夏、小满、芒种、夏至、小暑、大暑、立秋、处暑、白露、秋分、寒露、霜降、立冬、小雪、大雪、冬至
+
+---
+
+## data/items.lua — 消耗品与可放置物品
+
+```lua
+{
+    id        = "stamina_potion",
+    name      = "精力药",
+    desc      = "恢复10点精力",
+    price     = 80,             -- 商店售价(nil = 不在商店出售)
+    color     = {0.3, 0.8, 0.4},
+    icon      = "stamina_potion", -- assets/ui/<icon>.png
+    placeable = false,          -- 是否可放置为方块
+    -- placeable=true 时必填:
+    blockId   = "my_block",     -- 放置后对应的方块ID
+    -- 消耗品动作(placeable=false 时):
+    action    = "restore_stamina",
+}
+```
+
+**内置 action 类型:**
+
+| action | 效果 |
+|--------|------|
+| `restore_stamina` | 恢复精力(小药+10,大药+20,由 id 区分) |
+| `gain_exp` | 获得100经验 |
+| `gain_exp_large` | 获得200经验 |
+| `speed_boost` | 移速+50%,持续10分钟 |
+| `speed_boost_large` | 移速+50%,持续20分钟 |
+| `pave_all` | 修路药水:全图铺路 |
+| `clear_grass` | 除草剂:清除周围高草 |
+
+---
+
+## data/blocks.lua — 方块
+
+```lua
+{
+    id          = "chest",
+    name        = "箱子",
+    solid       = true,         -- 是否阻挡移动/寻路
+    interactive = true,         -- 是否可空手交互
+    action      = "open_chest", -- 交互触发的动作(game.lua 处理)
+    itemId      = "chest",      -- 挖掉后返回背包的物品ID
+    icon        = "chest",      -- assets/ui/<icon>.png
+    color       = {0.85, 0.65, 0.25},
+    -- 可选
+    tileIcon    = true,         -- 使用 assets/tiles/<icon>.png 而非 ui/
+    isFence     = true,         -- 是否为栅栏(自动连接纹理)
+}
+```
+
+---
+
+## data/tools.lua — 工具
+
+```lua
+{
+    id     = "till",
+    name   = "耕地",
+    action = "till",    -- 工具动作(player.lua 处理)
+    icon   = "shovel",  -- assets/ui/<icon>.png
+}
+```
+
+**内置 action:** `till`(耕地)、`water`(浇水)、`harvest`(收获)、`axe`(砍树)、`pickaxe`(采矿)、`basket`(收获篮)
+
+---
+
+## data/talents.lua — 天赋
+
+```lua
+{
+    id       = "crop_speed",
+    name     = "催熟之手",
+    desc     = "作物成长速度 +1%",
+    stat     = "cropSpeed",     -- 影响的属性(见下方完整列表)
+    value    = 0.01,            -- 每次获得该天赋增加的数值
+    maxStack = 50,              -- 最大叠加次数(nil = 无限)
+}
+```
+
+**完整 stat 属性列表:**
+
+| stat | 类型 | 说明 |
+|------|------|------|
+| `cropSpeed` | 倍率加法 | 作物成长速度(基础1.0,+0.01=+1%) |
+| `moveSpeed` | 倍率加法 | 移动速度 |
+| `shopSellBonus` | 倍率加法 | 出售价格加成 |
+| `shopBuyDiscount` | 倍率加法(负值=折扣) | 购买价格折扣 |
+| `maxStaminaBonus` | 整数加法 | 精力上限加值 |
+| `staminaRegen` | 倍率加法 | 精力回复速率 |
+| `harvestCountMult` | 倍率加法 | 收获数量倍率 |
+| `woodChopMult` | 倍率加法 | 砍树木材数量倍率 |
+| `lightRange` | 整数加法 | 夜晚照明范围(格数) |
+| `toolRange` | 整数加法 | 工具作用范围 |
+| `noStaminaCost` | bool | true = 所有操作不消耗精力 |
+| `flying` | bool | true = 飞行,穿越障碍 |
+| `dailyInterest` | 倍率加法 | 每日金币利息倍率 |
+| `extraEquipSlots` | 整数加法 | 额外装备槽数量 |
+| `keepTilled` | bool | true = 收获后保留耕地状态 |
+| `harvestKeepSeed` | bool | true = 收获必得种子 |
+| `breedChance` | 倍率加法 | 基因杂交概率 |
+| `rainChance` | 倍率加法 | 下雨概率 |
+| `expGain` | 倍率加法 | 经验获取倍率 |
+| `axeStaminaCost` | 倍率加法 | 斧子精力消耗倍率 |
+| `offlineBonus` | 倍率加法 | 离线收益倍率 |
+| `petUpgradeCostMult` | 倍率加法 | 宠物升级费用倍率 |
+
+---
+
+## data/races.lua — 种族
+
+```lua
+{
+    id   = "elf",
+    name = "精灵",
+    desc = "收获后保留耕地,但精力回复变慢",
+    effects = {
+        { stat="keepTilled",   value=true  },
+        { stat="staminaRegen", value=-0.4  }, -- 回复速度 ×0.6
+    },
+    -- 可选
+    startMoney = 0,      -- 开局金币(0=清零,nil=使用config默认值)
+    startTool  = "axe",  -- 开局额外赠送工具
+}
+```
+
+---
+
+## data/classes.lua — 职业
+
+```lua
+{
+    id   = "farmer",
+    name = "农夫",
+    desc = "作物成长+10%,精力上限+10",
+    effects = {
+        { stat="cropSpeed",       value=0.1 },
+        { stat="maxStaminaBonus", value=10  },
+    },
+    -- 可选
+    startEquip = true,   -- 开局赠送一件随机普通扑克装备
+    startTool  = "axe",  -- 开局额外赠送工具
+}
+```
+
+---
+
+## data/achievements.lua — 成就
+
+```lua
+{
+    id    = "first_harvest",
+    name  = "初次收获",
+    desc  = "收获第一次作物",
+    icon  = "★",            -- 显示图标(ASCII或单个汉字,像素字体不支持复杂Unicode)
+    event = "tile_harvested", -- 触发检查的事件名(可以是字符串或字符串数组)
+    check = function(progress, data, gameState)
+        -- progress: 该成就的进度表(持久化,初始为{})
+        -- data:     事件携带的数据
+        -- gameState: 当前游戏状态
+        progress.count = (progress.count or 0) + 1
+        return progress.count >= 1  -- 返回 true = 解锁
+    end,
+}
+```
+
+**常用事件名:** `tile_harvested`、`tile_planted`、`tree_chopped`、`player_levelup`、`craft_success`、`dungeon_entered`(完整列表见 README)
+
+---
+
+## data/recipes.lua — 合成配方
+
+```lua
+{
+    id      = "my_recipe",
+    name    = "神秘合成",
+    level   = 5,            -- 解锁所需玩家等级
+    inputs  = {
+        { type="crop", id="wheat",      count=5 },
+        { type="item", id="iron_ingot", count=1 },
+    },
+    outputs = {
+        { type="item",  id="stamina_potion", count=2 },
+        { type="money", count=50 },             -- 直接给金币(id可省略)
+        { type="equipment", id="clubs_02" },    -- 给装备
+    },
+}
+-- type 可选: "crop" | "item" | "equipment" | "money"
+```
+
+---
+
+## data/equipment.lua — 装备
+
+装备系统基于扑克牌和骰子,通常不需要手动添加,由 `cardChain` 函数自动生成。
+
+**花色:** `clubs`(梅花)、`diamonds`(方块)、`hearts`(红心)、`spades`(黑桃)
+**点数:** `02`~`10`、`J`、`Q`、`K`、`A`、`_suit`(徽章)、`other_suit_<花色>`(纹章)
+
+装备 ID 格式:`{花色}_{点数}`,如 `clubs_02`、`hearts_K`。
+
+每件装备有 `stat` 和 `value` 字段,效果叠加到 TalentStats。
+
+---
+
+## data/equipment_sets.lua — 套装效果
+
+```lua
+-- 同花色套装(装备2件同花色触发)
+EquipmentSets.suitSets = {
+    clubs = {
+        name    = "丰收之约",
+        desc    = "同装两件梅花:作物成长+30%,收获量+20%",
+        effects = {
+            { stat="cropSpeed",        value=0.30 },
+            { stat="harvestCountMult", value=0.20 },
+        },
+    },
+    -- diamonds / hearts / spades ...
+}
+
+-- 同点数套装(装备2件同点数触发)
+EquipmentSets.rankSets = { ... }
+
+-- 素数套装(装备点数之和为素数触发)
+EquipmentSets.primeSets = { ... }
+```
+
+---
+
+## data/forge.lua — 锻造链
+
+锻造链定义装备的升级路径。通常不需要修改,除非添加新装备系列。
+
+```lua
+-- 升级链:{ {id, value}, ... },从低到高排列
+-- value 决定材料等级:≤6=木材,7-11=石头,≥12=铁锭
+-- 升级成功率 = 1 / (nextValue - currentValue + 1)
+
+local CHAINS = {
+    clubs = cardChain("clubs"),  -- 自动生成梅花2→3→...→A→徽章→纹章
+    dice_small = {               -- 手动定义骰子链
+        { id="dice_small_1", value=7 },
+        { id="dice_small_2", value=8 },
+        -- ...
+    },
+}
+```
+
+---
+
+## data/pets.lua — 宠物
+
+```lua
+{
+    id   = "cat",
+    name = "小猫",
+    -- 升级材料:levelCost[i] = 从 i 级升到 i+1 级的材料
+    levelCost = {
+        { type="item", id="wood",  count=5  },  -- 1→2级
+        { type="item", id="stone", count=10 },  -- 2→3级
+        { type="item", id="iron_ingot", count=3 }, -- 3→4级
+    },
+    -- 离线收益:offlineReward[i] = 第 i 级每小时的掉落表
+    offlineReward = {
+        {  -- 1级
+            { type="money", minCount=5,  maxCount=15, weight=10 },
+            { type="item",  id="wood",   minCount=1,  maxCount=3, weight=5 },
+        },
+        {  -- 2级
+            { type="money", minCount=10, maxCount=30, weight=10 },
+            { type="seed",  id="wheat",  minCount=1,  maxCount=2, weight=3 },
+        },
+    },
+}
+```
+
+---
+
+## data/solar_terms.lua — 节气
+
+```lua
+{
+    name       = "芒种",
+    month      = 6,
+    day        = 6,
+    rainChance = 0.45,          -- 当天下雨概率 0~1
+    weather    = { sunny=3, cloudy=4, rainy=3, stormy=1 }, -- 天气权重
+    uiColor    = {0.35, 0.55, 0.30},  -- 顶部栏颜色 RGB
+    leafColor  = {0.35, 0.65, 0.25},  -- 树叶颜色
+    bgColor    = {0.30, 0.55, 0.22},  -- 地面草地色调
+}
+```
+
+---
+
+## data/redeem_codes.lua — 兑换码
+
+```lua
+return {
+    ["ABC123"] = {
+        rewards = {
+            { type="money", count=500 },
+            { type="item",  id="stamina_potion", count=3 },
+            { type="seed",  id="wheat", count=5 },
+        },
+        desc = "新手礼包",
+    },
+}
+-- type 可选: "money" | "item" | "seed" | "equipment"
+```
+
+---
+
+## data/quiz.lua — 问答题库
+
+```lua
+{
+    question = "小麦属于哪个科?",
+    options  = {
+        { text="禾本科", correct=true  },
+        { text="豆科",   correct=false },
+        { text="菊科",   correct=false },
+        { text="蔷薇科", correct=false },
+    },
+    reward = {
+        { type="money", count=50 },
+        { type="item",  id="stamina_potion", count=1 },
+    },
+}
+```
+
+---
+
+## 图片资源规范
+
+| 类型 | 路径 | 尺寸 |
+|------|------|------|
+| 作物生长帧 | `assets/crops/{id}_00.png` ~ `{id}_05.png` | 16×16px |
+| UI 图标 | `assets/ui/{icon}.png` | 16×16px |
+| 地图瓦片 | `assets/tiles/{name}.png` | 16×16px |
+| 宠物 | `assets/pets/pet_lv{1-4}_{1-3}.png` | 16×16px |
+| 角色 | `assets/characters/human/` | 16×16px |
+
+所有图片使用 `nearest` 过滤(像素风格),游戏内以 2× 缩放显示(实际 32×32px)。

+ 490 - 0
app/src/embed/assets/MOD_GUIDE.md

@@ -0,0 +1,490 @@
+# Mod 编写指南
+
+## 一、目录结构
+
+```
+user_mods/
+└── my_mod/
+    ├── mod.lua        # 必须,Mod 入口
+    └── assets/        # 可选,自定义图片
+        └── crops/
+            └── my_crop_05.png
+```
+
+**安装路径:**
+- Windows: `%APPDATA%/LOVE/farming-game/user_mods/my_mod/`
+- Android: `/sdcard/Android/data/<包名>/files/user_mods/my_mod/`
+- Linux:   `~/.local/share/love/farming-game/user_mods/my_mod/`
+
+---
+
+## 二、mod.lua 基本格式
+
+```lua
+return {
+    priority = 50,   -- 加载顺序,数字越小越先(默认50)
+    init = function(api)
+        api.log("Mod 加载!")
+    end,
+}
+```
+
+---
+
+## 三、数据注册
+
+### 3.1 注册作物
+
+```lua
+api.registerCrop({
+    id        = "tomato",
+    name      = "番茄",
+    growHours = 8,           -- 现实时间生长小时数
+    sellPrice = 20,
+    seedPrice = 90,
+    color     = {1, 0.2, 0.1},
+    solarMult = { ["芒种"]=1.5, ["大暑"]=0.8 },  -- 可选,节气售价倍率
+})
+```
+
+作物图片放在 `assets/crops/` 下,命名 `{id}_00.png` ~ `{id}_05.png`(6帧生长)。
+
+### 3.2 注册物品
+
+```lua
+-- 消耗品
+api.registerItem({
+    id       = "my_potion",
+    name     = "神秘药水",
+    desc     = "回复全部精力",
+    price    = 100,          -- nil = 不在商店出售
+    color    = {0.5, 0.2, 1},
+    icon     = "potion_blue_large",  -- assets/ui/<icon>.png
+    placeable = false,
+})
+
+-- 可放置物品(放置后变方块)
+api.registerItem({
+    id        = "my_machine",
+    name      = "神秘机器",
+    desc      = "放置后自动工作",
+    price     = 500,
+    color     = {0.8, 0.8, 0.8},
+    icon      = "stopwatch",
+    placeable = true,
+    blockId   = "my_machine",  -- 对应方块ID
+})
+```
+
+### 3.3 注册方块
+
+```lua
+api.registerBlock({
+    id          = "my_machine",
+    name        = "神秘机器",
+    solid       = true,
+    interactive = true,
+    action      = "my_action",  -- 交互时发出的事件名
+    itemId      = "my_machine", -- 挖掉后返回的物品
+    icon        = "stopwatch",
+    color       = {0.8, 0.8, 0.8},
+})
+```
+
+### 3.4 注册工具
+
+```lua
+api.registerTool({
+    id     = "magic_wand",
+    name   = "魔法棒",
+    action = "magic",
+    icon   = "stopwatch",
+})
+-- 配合 api.addToolAction 定义行为(见第五节)
+```
+
+### 3.5 注册天赋
+
+```lua
+api.registerTalent({
+    id       = "my_talent",
+    name     = "神秘天赋",
+    desc     = "作物成长速度 +5%",
+    stat     = "cropSpeed",
+    value    = 0.05,
+    maxStack = 10,
+})
+```
+
+**可用 stat:**
+
+| stat | 说明 |
+|------|------|
+| `cropSpeed` | 作物成长速度倍率 |
+| `moveSpeed` | 移动速度倍率 |
+| `shopSellBonus` | 出售加成 |
+| `shopBuyDiscount` | 购买折扣 |
+| `maxStaminaBonus` | 精力上限加值 |
+| `staminaRegen` | 精力回复速率 |
+| `harvestCountMult` | 收获数量倍率 |
+| `woodChopMult` | 砍树木材倍率 |
+| `lightRange` | 夜晚照明范围(格) |
+| `toolRange` | 工具范围 |
+| `noStaminaCost` | 免精力消耗(true) |
+| `flying` | 飞行穿越障碍(true) |
+| `dailyInterest` | 每日利息倍率 |
+| `extraEquipSlots` | 额外装备槽 |
+| `keepTilled` | 收获后保留耕地(true) |
+| `harvestKeepSeed` | 收获必得种子(true) |
+| `breedChance` | 基因杂交概率 |
+| `rainChance` | 下雨概率 |
+| `expGain` | 经验倍率 |
+
+### 3.6 注册种族/职业
+
+```lua
+api.registerRace({
+    id   = "robot",
+    name = "机器人",
+    desc = "不消耗精力,但移速慢",
+    effects = {
+        { stat="noStaminaCost", value=true },
+        { stat="moveSpeed",     value=-0.3 },
+    },
+    startMoney = 0,      -- 可选,开局金币(0=清零)
+    startTool  = "axe",  -- 可选,开局赠送工具
+})
+
+api.registerClass({
+    id   = "botanist",
+    name = "植物学家",
+    desc = "作物成长+20%",
+    effects = { { stat="cropSpeed", value=0.2 } },
+    startEquip = true,   -- 可选,开局赠送随机装备
+})
+```
+
+### 3.7 注册成就
+
+```lua
+api.registerAchievement({
+    id    = "tomato_king",
+    name  = "番茄大王",
+    desc  = "收获100个番茄",
+    icon  = "★",
+    event = "tile_harvested",
+    check = function(progress, data, gameState)
+        if data.cropId == "tomato" then
+            progress.count = (progress.count or 0) + 1
+        end
+        return (progress.count or 0) >= 100
+    end,
+})
+```
+
+### 3.8 注册合成配方
+
+```lua
+api.registerRecipe({
+    id      = "my_recipe",
+    name    = "神秘合成",
+    level   = 1,
+    inputs  = {
+        { type="crop", id="tomato",     count=3 },
+        { type="item", id="iron_ingot", count=1 },
+    },
+    outputs = {
+        { type="item",  id="my_potion", count=1 },
+        { type="money", count=50 },
+    },
+})
+-- type 可选: "crop" | "item" | "equipment" | "money"
+```
+
+---
+
+## 四、覆盖已有数据与替换资源
+
+```lua
+-- 修改已有数据(只改指定字段)
+api.overrideCrop("wheat",   { sellPrice=999, growHours=1 })
+api.overrideItem("stamina_potion", { price=50 })
+api.overrideBlock("chest",  { solid=false })
+api.overrideTool("axe",     { name="超级斧" })
+api.overrideTalent("crop_speed", { value=0.05 })
+
+-- 替换图片(游戏原始路径 → Mod目录相对路径)
+api.replaceAsset(
+    "assets/crops/wheat_05.png",
+    "assets/my_wheat.png"
+)
+```
+
+---
+
+## 五、修改游戏逻辑
+
+### 5.1 包装现有函数
+
+拦截任意模块的函数,在前后插入逻辑或完全替换:
+
+```lua
+-- 拦截收获:番茄额外奖励5金币
+api.wrapFunction("systems/farm", "harvest",
+    function(original, self, row, col, stats)
+        local cropId, gene, dropSeed = original(self, row, col, stats)
+        if cropId == "tomato" then
+            api.addMoney(5)
+        end
+        return cropId, gene, dropSeed
+    end
+)
+
+-- 拦截玩家移动速度计算
+api.wrapFunction("systems/talent_stats", "calc",
+    function(original, talentMap, raceId, classId, equippedSlots)
+        local stats = original(talentMap, raceId, classId, equippedSlots)
+        -- 夜晚移速减半
+        local Time = require("systems/time")
+        if Time.getDayNightState() == Time.NIGHT then
+            stats.moveSpeed = (stats.moveSpeed or 1) * 0.5
+        end
+        return stats
+    end
+)
+```
+
+### 5.2 自定义消耗品行为
+
+```lua
+-- 先注册物品
+api.registerItem({
+    id="super_potion", name="超级药水",
+    desc="精力全满+100经验", price=200,
+    color={0.5,0.2,1}, icon="potion_blue_large",
+})
+
+-- 再定义使用行为
+api.addConsumable("super_potion", function(gameState, farm, worldSystem)
+    gameState.stamina = gameState.maxStamina
+    require("systems/state").addExp(gameState, 100)
+    return "超级药水:精力全满 +100经验"
+    -- 返回字符串 = 显示消息;返回 false = 不处理(交给默认逻辑)
+end)
+```
+
+### 5.3 自定义工具行为
+
+```lua
+api.registerTool({ id="magic_wand", name="魔法棒", action="magic", icon="stopwatch" })
+
+api.addToolAction("magic_wand", function(player, row, col, stats)
+    -- 点击任意格子立即收获
+    local g = api.getGame()
+    if g then
+        local cropId = g.farm:harvest(row, col, stats)
+        if cropId then
+            api.showMsg("魔法收获:" .. cropId)
+            return true   -- true = 已处理,不走默认逻辑
+        end
+    end
+    return false  -- false = 交给默认逻辑
+end)
+```
+
+---
+
+## 六、事件系统
+
+### 6.1 监听事件
+
+```lua
+api.on("tile_harvested", function(data)
+    -- data.cropId, data.row, data.col, data.count, data.gene, data.dropSeed
+    if data.cropId == "tomato" then
+        api.addExp(5)
+        api.showMsg("收获了番茄!")
+    end
+end)
+```
+
+### 6.2 发送事件
+
+```lua
+api.emit("my_custom_event", { value=42 })
+
+-- 其他地方监听
+api.on("my_custom_event", function(data)
+    api.log("收到事件:" .. data.value)
+end)
+```
+
+### 6.3 全部内置事件
+
+| 事件名 | 触发时机 | data 字段 |
+|--------|----------|-----------|
+| `tile_harvested` | 收获作物 | `cropId, row, col, count, gene, dropSeed` |
+| `tile_planted` | 播种 | `cropId, row, col, gene` |
+| `tile_tilled` | 耕地 | `row, col` |
+| `tile_watered` | 浇水 | `row, col` |
+| `tile_paved` | 铺路 | `row, col` |
+| `tile_unpaved` | 拆路 | `row, col` |
+| `tree_chopped` | 砍树 | `row, col, count` |
+| `bush_chopped` | 砍灌木 | `row, col, count` |
+| `mushroom_picked` | 采蘑菇 | `row, col, itemId` |
+| `gravel_mined` | 采碎石堆 | `row, col, count` |
+| `block_placed` | 放置方块 | `row, col, blockId` |
+| `block_mined` | 挖掉方块 | `row, col, blockId` |
+| `block_interact` | 交互方块 | `row, col, blockId, action` |
+| `player_levelup` | 玩家升级 | `level` |
+| `gain_exp` | 获得经验 | `amount` |
+| `item_bought` | 购买物品 | `id, type` |
+| `seed_bought` | 购买种子 | `cropId` |
+| `tool_bought` | 购买工具 | `toolId` |
+| `sold` | 出售作物 | `cropId, count, earned` |
+| `craft_success` | 合成成功 | `{}` |
+| `equip_changed` | 装备变化 | `{}` |
+| `talent_picked` | 选择天赋 | `talentId` |
+| `pet_levelup` | 宠物升级 | `level` |
+| `weather_changed` | 天气变化 | `weather` |
+| `dungeon_entered` | 进入副本 | `{}` |
+| `quiz_correct` | 答题正确 | `{}` |
+| `achievement_unlocked` | 解锁成就 | `id` |
+| `save_success` | 存档成功 | `{}` |
+
+---
+
+## 七、定时器与键盘
+
+```lua
+-- 每60秒触发一次
+api.addTick(60, function()
+    api.addMoney(10)
+    api.showMsg("每分钟奖励10金币")
+end)
+
+-- 按 F9 触发
+api.addKey("f9", function()
+    api.addItem("tomato", 5)
+    api.showMsg("获得5个番茄")
+end)
+```
+
+---
+
+## 八、自定义绘制
+
+```lua
+-- "world" = 在地图层绘制(角色下方)
+-- "ui"    = 在UI层绘制(最顶层)
+api.addDraw("ui", function()
+    love.graphics.setColor(1, 1, 0, 0.8)
+    love.graphics.print("[MOD]", 4, 4, 0, 0.8, 0.8)
+    love.graphics.setColor(1, 1, 1)
+end)
+```
+
+---
+
+## 九、运行时 API
+
+```lua
+-- 获取游戏对象(只在 on/addTick 回调里调用,init 阶段返回 nil)
+local game      = api.getGame()       -- 游戏主控制器
+local farm      = api.getFarm()       -- FarmSystem
+local player    = api.getPlayer()     -- PlayerSystem
+local blocks    = api.getBlocks()     -- BlockSystem
+local gs        = api.getGameState()  -- gameState 表
+local world     = api.getWorld()      -- WorldSystem
+local registry  = api.getRegistry()  -- Registry(数据查询)
+local bus       = api.getBus()        -- EventBus
+
+-- 玩家操作
+api.showMsg("消息", 2.5)              -- 显示底部消息(秒)
+api.addFloat("文字", x, y, r, g, b)  -- 飘字
+api.addMoney(100)
+api.addExp(50)
+api.addStamina(10)
+api.addItem("tomato", 3)
+
+-- 世界地图
+api.getWorldTile(row, col)            -- 获取瓦片ID
+api.setWorldTile(row, col, tileId)    -- 设置瓦片
+api.placeBlock(row, col, "chest")     -- 放置方块
+api.mineBlock(row, col)               -- 挖掉方块
+
+-- 农场
+api.getFarmTile(row, col)             -- 获取农场格子数据
+api.tillFarm(row, col)                -- 耕地
+api.plantFarm(row, col, "tomato")     -- 播种
+api.harvestFarm(row, col)             -- 收获
+
+-- 调试
+api.log("调试信息")                   -- 输出到控制台
+```
+
+---
+
+## 十、完整示例
+
+```lua
+-- user_mods/lucky_farmer/mod.lua
+return {
+    priority = 50,
+    init = function(api)
+
+        -- 注册幸运草药水
+        api.registerItem({
+            id="lucky_potion", name="幸运草药水",
+            desc="下一次收获数量翻倍", price=150,
+            color={0.2,0.9,0.3}, icon="potion_green_large",
+        })
+
+        -- 定义使用行为
+        api.addConsumable("lucky_potion", function(gs, farm, world)
+            gs._luckyActive = true
+            return "幸运草药水:下次收获翻倍!"
+        end)
+
+        -- 拦截收获,翻倍后清除状态
+        api.wrapFunction("systems/farm", "harvest",
+            function(orig, self, row, col, stats)
+                local cropId, gene, dropSeed = orig(self, row, col, stats)
+                local gs = api.getGameState()
+                if cropId and gs and gs._luckyActive then
+                    gs._luckyActive = false
+                    -- 再收获一次(不触发事件)
+                    api.showMsg("幸运触发!额外获得一份 " .. cropId)
+                    -- 直接给物品
+                    api.addItem(cropId, 1)
+                end
+                return cropId, gene, dropSeed
+            end
+        )
+
+        -- 每天0点随机给一个幸运草药水
+        api.on("weather_changed", function(data)
+            if math.random(10) == 1 then
+                api.addItem("lucky_potion", 1)
+                api.showMsg("幸运降临!获得幸运草药水")
+            end
+        end)
+
+        api.log("幸运农夫 Mod 加载完成")
+    end,
+}
+```
+
+---
+
+## 十一、注意事项
+
+1. **init 阶段**不能调用 `getGame()`、`getFarm()` 等运行时 API,游戏对象尚未创建。请在 `api.on()` 或 `api.addTick()` 的回调里使用。
+
+2. **ID 唯一性**:作物、物品、方块、工具的 `id` 不能与游戏内置或其他 Mod 重复,建议加前缀,如 `mymod_tomato`。
+
+3. **wrapFunction** 在 `loadAll()` 后立即执行,如果目标模块尚未加载,会自动延迟到首次 `require` 时应用。
+
+4. **启用/禁用**在主菜单 → Mod 管理 里操作,**下次启动生效**。
+
+5. **错误处理**:Mod 内的错误不会导致游戏崩溃,会打印到控制台(`game.log`)并跳过该 Mod。

+ 287 - 0
app/src/embed/assets/README.md

@@ -0,0 +1,287 @@
+# 荷锄 — 种田游戏
+
+基于 LÖVE2D (LuaJIT) 的竖屏触控农场游戏,支持 Android 打包与 Mod 系统。
+
+---
+
+## 快速上手
+
+```bash
+# 运行游戏(桌面)
+& 'C:\Program Files\LOVE\lovec.exe' --console .
+
+# 运行测试
+busted .
+```
+
+存档位置:
+- Windows: `%APPDATA%\LOVE\farm\`
+- Linux:   `~/.local/share/love/farm/`
+- Android: `/sdcard/Android/data/<包名>/files/`
+
+日志文件:`%APPDATA%\LOVE\farm\game.log`(运行时错误会写入此处)
+
+---
+
+## 分层架构
+
+```
+main.lua          ← LÖVE2D 回调入口,只做转发,不含逻辑
+    │
+engine/           基础设施层:引擎主循环、存档、事件总线、Mod加载
+    │
+love_adapter/     LÖVE2D 适配层:将 love API 包装为接口,隔离平台依赖
+    │
+game.lua          协调层:组装所有子系统,处理输入分发与绘制调度
+    │
+systems/          游戏逻辑层:农场、玩家、世界、背包等纯逻辑
+    │
+render/           渲染层:所有绘制代码,依赖 systems/ 提供的接口
+    │
+data/             纯数据层:Lua table 定义,无副作用,无依赖
+```
+
+**依赖规则:** 上层可依赖下层,同层通过 `engine/eventbus` 解耦,禁止下层依赖上层。
+
+---
+
+## 关键设计决策
+
+### 接口隔离(engine/interface.lua)
+
+所有跨层传递的对象都通过接口定义,用 `classic.lua` 的 `Object:extend()` 实现:
+
+- `IWorldQuery` — systems 层查询世界地图(WorldSystem 实现)
+- `IFarmQuery` — render 层查询农场状态(FarmSystem 实现)
+- `IGameStateQuery` — render 层查询玩家状态(game.lua 内 GameStateAdapter 实现)
+- `IGraphics` — 绘图接口(love_adapter/graphics.lua 实现,测试用 MockGraphics)
+- `IFilesystem` — 文件系统接口(love_adapter/filesystem.lua 实现)
+- `IAudio` / `IWindow` — 音频/窗口接口
+
+这样 systems/ 和 render/ 完全不直接调用 `love.*`,可以在无 LÖVE 环境下单元测试。
+
+### 对象实例化(classic.lua)
+
+项目使用 [classic](https://github.com/rxi/classic) 作为 OOP 基础。**正确的实例化方式是调用类本身**(触发 `__call`),而不是 `:new()`:
+
+```lua
+-- 正确
+local obj = MyClass(arg1, arg2)
+
+-- 错误(:new() 直接调用方法,返回 nil)
+local obj = MyClass:new(arg1, arg2)
+```
+
+`new()` 方法里直接给 `self` 赋字段,不需要 `return self`(`__call` 负责返回实例)。
+
+### 适配层注入顺序(game.lua)
+
+`Game.new()` 按固定顺序初始化,顺序不能乱:
+
+1. 创建 love_adapter 实例(`LoveGraphics()` 等)
+2. 将适配层注入 engine 模块(`Save.setFS`、`ModLoader.setFS`、`Anim.setGraphics` 等)
+3. 创建 systems 层对象(`FarmSystem()`、`WorldSystem()` 等)
+4. 创建 GameStateAdapter(包装 gameState 为 IGameStateQuery)
+5. 创建 render 层对象,注入接口
+6. 创建依赖接口的 systems 对象(`PlayerSystem`)
+7. 注入跨系统引用(`TickSystem.setWorld`、`Save.setWorld` 等)
+
+### Save 模块的调用方式
+
+`engine/save.lua` 里的 `_fs` 是 `IFilesystem` 实例,所有调用必须用冒号语法:
+
+```lua
+_fs:exists(path)   -- 正确
+_fs.exists(path)   -- 错误(self 会变成 path,导致 getInfo(nil))
+```
+
+---
+
+## 目录结构
+
+```
+main.lua              LÖVE2D 回调入口
+conf.lua              窗口配置
+game.lua              游戏主控制器(协调层,~3000行)
+classic.lua           轻量 OOP 库
+assets/sphere.glsl        球面畸变 shader(世界层后处理)
+
+engine/
+  engine.lua          主循环、错误捕获、自动存档(每5分钟)
+  eventbus.lua        发布/订阅事件总线
+  registry.lua        统一数据访问(getCrop/getItem/getBlock/getTool)
+  save.lua            多存档读写(save_1~3.lua),版本17
+  camera.lua          摄像机(环形世界坐标、zoom、tileToScreen)
+  mod_loader.lua      Mod 加载、API 工厂、钩子分发
+  interface.lua       跨层接口定义 + MockGraphics(测试用)
+  audio.lua           程序化音频合成
+  sfx.lua             音效预合成与事件绑定
+  anim.lua            动画帧计时
+  pathfinding.lua     A* 寻路(环形地图)
+  particles.lua       粒子效果
+
+love_adapter/
+  graphics.lua        IGraphics → love.graphics
+  filesystem.lua      IFilesystem → love.filesystem
+  audio.lua           IAudio → love.audio
+  window.lua          IWindow → love.graphics.getWidth/Height
+
+systems/
+  state.lua           gameState 创建与操作(金币/经验/精力/天赋)
+  farm.lua            农场格子逻辑(耕/种/浇/收)
+  world_system.lua    100×100 环形地图(实现 IWorldQuery)
+  player.lua          玩家移动 + 工具操作(整合 Character + A*)
+  character.lua       角色像素坐标、动画状态机
+  inventory.lua       背包(30格 + 快捷栏6格)
+  block_system.lua    可放置方块(箱子/工作台/栅栏等)
+  tick_system.lua     物流方块定时驱动(每0.5秒)
+  time.lua            游戏时间、昼夜、节气
+  weather.lua         天气系统(晴/阴/雨/暴雨)
+  genetics.lua        作物基因系统(杂交、表现型、高产)
+  talent_stats.lua    天赋+种族+职业+装备 → stats 表
+  market.lua          动态市场价格
+  drops.lua           掉落物(吸引+自动拾取)
+  dungeon.lua         副本地图数据
+  pet.lua             宠物(跟随、离线收益)
+  achievement_system.lua  成就系统
+  consumable_system.lua   消耗品使用逻辑
+  equipment_system.lua    装备穿脱与套装效果
+  shop_system.lua         商店购买逻辑
+  workbench_system.lua    工作台合成
+  forge_system.lua        扑克工厂锻造
+  quiz_npc_system.lua     问答NPC
+  lighting_system.lua     夜晚光照贴图
+
+render/
+  world_renderer.lua      地图底层/覆盖层精灵(y-sort)
+  farm_renderer.lua       农场格子与作物生长动画
+  character_renderer.lua  玩家行走/动作动画(strip 精灵表)
+  ui_renderer.lua         HUD、商店、消息、飘字、Mod管理面板
+  inventory_renderer.lua  背包/快捷栏/装备槽/拖拽
+  block_renderer.lua      方块精灵
+  crop_renderer.lua       作物图标工具
+  pet_renderer.lua        宠物渲染与升级面板
+  talent_renderer.lua     天赋选择/查看面板
+  workbench_renderer.lua  工作台合成面板
+  forge_renderer.lua      扑克工厂锻造面板
+  dungeon_renderer.lua    副本地图渲染
+  drops_renderer.lua      掉落物渲染
+  effect_renderer.lua     粒子特效封装
+  render_pipeline.lua     渲染调度框架
+
+data/                     纯数据(return { ... },无副作用)
+  config.lua              全局配置(窗口/玩家初始值)
+  crops.lua               作物定义
+  items.lua               消耗品/可放置物品
+  blocks.lua              方块定义
+  tools.lua               工具定义
+  talents.lua             天赋定义
+  equipment.lua           装备(扑克牌+骰子)
+  equipment_sets.lua      套装效果
+  races.lua               种族定义
+  classes.lua             职业定义
+  achievements.lua        成就定义
+  recipes.lua             工作台配方
+  forge.lua               锻造链
+  levels.lua              升级经验表
+  quiz.lua                问答题库
+  solar_terms.lua         24节气
+  seasons.lua             季节定义
+  sfx.lua                 音效参数
+  music.lua               背景音乐
+  pets.lua                宠物定义
+  redeem_codes.lua        兑换码
+
+mods/
+  seasonal_trader/        内置示例 Mod(季节商人)
+
+spec/                     busted 单元测试
+```
+
+---
+
+## 核心常量
+
+```lua
+-- engine/camera.lua
+Camera.TILE_PX  = 32    -- 每格像素
+Camera.MAP_COLS = 100   -- 地图宽(格)
+Camera.MAP_ROWS = 100   -- 地图高(格)
+
+-- engine/save.lua
+version = 17            -- 存档版本,不兼容时提示删除旧存档
+```
+
+---
+
+## EventBus 主要事件
+
+模块间通信通过 `Bus.emit(event, data)` / `Bus.on(event, fn)` 解耦。
+
+| 事件 | 触发时机 | data 字段 |
+|------|----------|-----------|
+| `tile_harvested` | 收获作物 | `cropId, row, col, count, gene, dropSeed` |
+| `tile_tilled` | 耕地 | `row, col` |
+| `tile_watered` | 浇水 | `row, col` |
+| `tree_chopped` | 砍树 | `row, col, wood` |
+| `gravel_mined` | 采碎石堆 | `row, col, count` |
+| `block_placed/mined` | 放置/挖掉方块 | `row, col, blockId` |
+| `block_interact` | 交互方块 | `row, col, action, blockData` |
+| `gain_exp` | 获得经验 | `amount` |
+| `player_levelup` | 玩家升级 | `level` |
+| `sold` | 出售作物 | `amount` |
+| `day_changed` | 游戏日切换 | — |
+| `weather_changed` | 天气/节气变化 | `solarTerm` |
+| `achievement_unlocked` | 解锁成就 | `id` |
+| `show_msg` | 显示底部消息 | `text` |
+| `item_drop` | 背包满掉落 | `item, pixX, pixY` |
+
+---
+
+## 测试
+
+```bash
+busted .
+```
+
+| 文件 | 覆盖内容 |
+|------|---------|
+| `spec/regression_core_spec.lua` | Genetics/Inventory/EventBus/Market/State/Save |
+| `spec/phase1~6_*_spec.lua` | 各层路径与接口验证 |
+| `spec/layered_arch/` | 属性测试(层依赖、接口契约) |
+| `spec/helpers/love_mock.lua` | love API stub,无需 LÖVE 运行时 |
+
+---
+
+## Mod 系统
+
+详见 `MOD_GUIDE.md`。
+
+Mod 放在存档目录的 `user_mods/<mod_name>/mod.lua`,内置 Mod 在 `mods/` 下。主菜单有 Mod 管理面板,可按存档槽独立启用/禁用。
+
+```lua
+-- mod.lua 最小结构
+return {
+    priority = 50,  -- 加载顺序,越小越先
+    init = function(api)
+        -- api.registerCrop/Item/Block/Tool/Talent/Race/Class/Achievement/Recipe
+        -- api.overrideCrop/Item/Block/Tool/Talent
+        -- api.wrapFunction(modulePath, fnName, wrapper)
+        -- api.addConsumable / addToolAction / addDraw / addTick / addKey
+        -- api.on(event, fn) / api.emit(event, data)
+        -- api.replaceAsset(virtualPath, modRelativePath)
+        -- 运行时(只在回调里用):getGame/getFarm/getPlayer/getGameState
+        -- api.addMoney/addExp/addStamina/addItem/showMsg
+    end,
+}
+```
+
+---
+
+## 存档版本历史
+
+| 版本 | 变更 |
+|------|------|
+| 15 | 基础版本 |
+| 16 | 新增 quizDoneDay |
+| 17 | 新增 achievements、enabledMods、多存档(save_1~3.lua) |

BIN
app/src/embed/assets/assets/VonwaonBitmap-16px.ttf


BIN
app/src/embed/assets/assets/characters/human/axe/base_axe_strip10.png


BIN
app/src/embed/assets/assets/characters/human/axe/bowlhair_axe_strip10.png


BIN
app/src/embed/assets/assets/characters/human/axe/curlyhair_axe_strip10.png


BIN
app/src/embed/assets/assets/characters/human/axe/longhair_axe_strip10.png


BIN
app/src/embed/assets/assets/characters/human/axe/mophair_axe_strip10.png


BIN
app/src/embed/assets/assets/characters/human/axe/shorthair_axe_strip10.png


BIN
app/src/embed/assets/assets/characters/human/axe/spikeyhair_axe_strip10.png


BIN
app/src/embed/assets/assets/characters/human/axe/tools_axe_strip10.png


BIN
app/src/embed/assets/assets/characters/human/carry/base_carry_strip8.png


BIN
app/src/embed/assets/assets/characters/human/carry/bowlhair_carry_strip8.png


BIN
app/src/embed/assets/assets/characters/human/carry/curlyhair_carry_strip8.png


BIN
app/src/embed/assets/assets/characters/human/carry/longhair_carry_strip8.png


BIN
app/src/embed/assets/assets/characters/human/carry/mophair_carry_strip8.png


BIN
app/src/embed/assets/assets/characters/human/carry/shorthair_carry_strip8.png


BIN
app/src/embed/assets/assets/characters/human/carry/spikeyhair_carry_strip8.png


BIN
app/src/embed/assets/assets/characters/human/carry/tools_carry_strip8.png


BIN
app/src/embed/assets/assets/characters/human/dig/base_dig_strip13.png


BIN
app/src/embed/assets/assets/characters/human/dig/bowlhair_dig_strip13.png


BIN
app/src/embed/assets/assets/characters/human/dig/curlyhair_dig_strip13.png


BIN
app/src/embed/assets/assets/characters/human/dig/longhair_dig_strip13.png


BIN
app/src/embed/assets/assets/characters/human/dig/mophair_dig_strip13.png


BIN
app/src/embed/assets/assets/characters/human/dig/shorthair_dig_strip13.png


BIN
app/src/embed/assets/assets/characters/human/dig/spikeyhair_dig_strip13.png


BIN
app/src/embed/assets/assets/characters/human/dig/tools_dig_strip13.png


BIN
app/src/embed/assets/assets/characters/human/doing/base_doing_strip8.png


BIN
app/src/embed/assets/assets/characters/human/doing/bowlhair_doing_strip8.png


BIN
app/src/embed/assets/assets/characters/human/doing/curlyhair_doing_strip8.png


BIN
app/src/embed/assets/assets/characters/human/doing/longhair_doing_strip8.png


BIN
app/src/embed/assets/assets/characters/human/doing/mophair_doing_strip8.png


BIN
app/src/embed/assets/assets/characters/human/doing/shorthair_doing_strip8.png


BIN
app/src/embed/assets/assets/characters/human/doing/spikeyhair_doing_strip8.png


BIN
app/src/embed/assets/assets/characters/human/doing/tools_doing_strip8.png


BIN
app/src/embed/assets/assets/characters/human/idle/base_idle_strip9.png


BIN
app/src/embed/assets/assets/characters/human/idle/bowlhair_idle_strip9.png


BIN
app/src/embed/assets/assets/characters/human/idle/curlyhair_idle_strip9.png


BIN
app/src/embed/assets/assets/characters/human/idle/longhair_idle_strip9.png


BIN
app/src/embed/assets/assets/characters/human/idle/mophair_idle_strip9.png


BIN
app/src/embed/assets/assets/characters/human/idle/shorthair_idle_strip9.png


BIN
app/src/embed/assets/assets/characters/human/idle/spikeyhair_idle_strip9.png


BIN
app/src/embed/assets/assets/characters/human/idle/tools_idle_strip9.png


BIN
app/src/embed/assets/assets/characters/human/mining/base_mining_strip10.png


BIN
app/src/embed/assets/assets/characters/human/mining/bowlhair_mining_strip10.png


BIN
app/src/embed/assets/assets/characters/human/mining/curlyhair_mining_strip10.png


BIN
app/src/embed/assets/assets/characters/human/mining/longhair_mining_strip10.png


BIN
app/src/embed/assets/assets/characters/human/mining/mophair_mining_strip10.png


BIN
app/src/embed/assets/assets/characters/human/mining/shorthair_mining_strip10.png


BIN
app/src/embed/assets/assets/characters/human/mining/spikeyhair_mining_strip10.png


BIN
app/src/embed/assets/assets/characters/human/mining/tools_mining_strip10.png


BIN
app/src/embed/assets/assets/characters/human/walking/base_walk_strip8.png


BIN
app/src/embed/assets/assets/characters/human/walking/bowlhair_walk_strip8.png


BIN
app/src/embed/assets/assets/characters/human/walking/curlyhair_walk_strip8.png


BIN
app/src/embed/assets/assets/characters/human/walking/longhair_walk_strip8.png


BIN
app/src/embed/assets/assets/characters/human/walking/mophair_walk_strip8.png


BIN
app/src/embed/assets/assets/characters/human/walking/shorthair_walk_strip8.png


BIN
app/src/embed/assets/assets/characters/human/walking/spikeyhair_walk_strip8.png


BIN
app/src/embed/assets/assets/characters/human/walking/tools_walk_strip8.png


BIN
app/src/embed/assets/assets/characters/human/watering/base_watering_strip5.png


BIN
app/src/embed/assets/assets/characters/human/watering/bowlhair_watering_strip5.png


BIN
app/src/embed/assets/assets/characters/human/watering/curlyhair_watering_strip5.png


BIN
app/src/embed/assets/assets/characters/human/watering/longhair_watering_strip5.png


BIN
app/src/embed/assets/assets/characters/human/watering/mophair_watering_strip5.png


BIN
app/src/embed/assets/assets/characters/human/watering/shorthair_watering_strip5.png


BIN
app/src/embed/assets/assets/characters/human/watering/spikeyhair_watering_strip5.png


BIN
app/src/embed/assets/assets/characters/human/watering/tools_watering_strip5.png


BIN
app/src/embed/assets/assets/crops/beet_juice_05.png


BIN
app/src/embed/assets/assets/crops/beet_sugar_05.png


BIN
app/src/embed/assets/assets/crops/beetroot_00.png


BIN
app/src/embed/assets/assets/crops/beetroot_01.png


BIN
app/src/embed/assets/assets/crops/beetroot_02.png


BIN
app/src/embed/assets/assets/crops/beetroot_03.png


BIN
app/src/embed/assets/assets/crops/beetroot_04.png


BIN
app/src/embed/assets/assets/crops/beetroot_05.png


BIN
app/src/embed/assets/assets/crops/cabbage_00.png


BIN
app/src/embed/assets/assets/crops/cabbage_01.png


BIN
app/src/embed/assets/assets/crops/cabbage_02.png


BIN
app/src/embed/assets/assets/crops/cabbage_03.png


BIN
app/src/embed/assets/assets/crops/cabbage_04.png


BIN
app/src/embed/assets/assets/crops/cabbage_05.png


BIN
app/src/embed/assets/assets/crops/carrot_00.png


BIN
app/src/embed/assets/assets/crops/carrot_01.png


BIN
app/src/embed/assets/assets/crops/carrot_02.png


BIN
app/src/embed/assets/assets/crops/carrot_03.png


BIN
app/src/embed/assets/assets/crops/carrot_04.png


BIN
app/src/embed/assets/assets/crops/carrot_05.png


BIN
app/src/embed/assets/assets/crops/carrot_juice_05.png


BIN
app/src/embed/assets/assets/crops/carrot_vinegar_05.png


BIN
app/src/embed/assets/assets/crops/cauliflower_00.png


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است