diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..85d4ec2
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,177 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+This is a text-based idle adventure game (文字冒险游戏) built with uni-app, Vue 3, and Pinia. The game features skill-driven progression, idle mechanics, combat, crafting, and a dark post-apocalyptic theme.
+
+**Tech Stack:**
+- Framework: uni-app (Vue 3 with Composition API)
+- State Management: Pinia
+- Testing: Jest + Vitest + Playwright
+- Style: SCSS with global variables
+
+## Build and Run Commands
+
+### Development
+- Use HBuilderX IDE to run the project (uni-app standard)
+- Run to different platforms: mp-weixin (WeChat Mini Program), h5, app
+
+### Testing
+```bash
+npm test # Run Jest tests in watch mode
+npm run test:watch # Run tests in watch mode
+npm run test:coverage # Generate coverage report
+```
+
+## Architecture
+
+### Directory Structure
+```
+├── components/
+│ ├── common/ # Reusable UI components (ProgressBar, StatItem, etc.)
+│ ├── layout/ # Layout components (TabBar, Drawer)
+│ ├── panels/ # Main game panels (StatusPanel, MapPanel, LogPanel)
+│ └── drawers/ # Slide-out drawers (Inventory, Event, Shop, Crafting)
+├── config/ # Game configuration (skills, items, enemies, locations, etc.)
+├── store/ # Pinia stores (player.js, game.js)
+├── utils/ # Game systems (combat, skill, item, task, event, etc.)
+├── pages/ # Uni-app pages
+└── tests/ # Unit tests with fixtures and helpers
+```
+
+### Core Systems (utils/)
+- **skillSystem.js**: Skill unlocking, XP, leveling, milestone rewards, parent skills
+- **combatSystem.js**: AP/EP hit calculation, three-layer defense (evade/shield/armor), damage
+- **itemSystem.js**: Item usage, equipment, quality calculation, inventory management
+- **taskSystem.js**: Idle task management, mutual exclusion checks
+- **eventSystem.js**: Event triggering, NPC dialogue handling
+- **environmentSystem.js**: Environment penalties, passive skill XP
+- **gameLoop.js**: Main game tick (1-second interval), time progression, auto-save
+- **storage.js**: Save/load using uni.setStorageSync, offline earnings
+- **craftingSystem.js**: Recipe-based crafting with quality calculation
+- **levelingSystem.js**: Player level progression and stat scaling
+- **mapLayout.js**: Visual map representation
+
+### State Management (Pinia Stores)
+- **player.js**: Base stats, current stats (HP/stamina/sanity), level, skills, equipment, inventory, currency, location, flags
+- **game.js**: Current tab, drawer states, logs, game time, combat state, active tasks, market prices, current event
+
+### Configuration (config/)
+- **constants.js**: Game constants (quality levels, stat scaling, time scale, etc.)
+- **skills.js**: All skill definitions with milestones and unlock conditions
+- **items.js**: All item definitions (weapons, consumables, materials, books)
+- **enemies.js**: Enemy stats, drops, spawn conditions
+- **locations.js**: Area definitions, connections, activities, unlock conditions
+- **npcs.js**: NPC dialogue trees and interactions
+- **events.js**: Event definitions and triggers
+- **recipes.js**: Crafting recipes
+- **shop.js**: Shop inventory and pricing
+- **formulas.js**: Mathematical formulas for stat calculations
+
+## Key Game Concepts
+
+### Quality System (品质系统)
+Items have quality (0-250) that affects their power:
+- 0-49: 垃圾 (Trash)
+- 50-99: 普通 (Common)
+- 100-139: 优秀 (Good)
+- 140-169: 稀有 (Rare)
+- 170-199: 史诗 (Epic)
+- 200-250: 传说 (Legendary)
+
+Each tier has a multiplier that increases item stats.
+
+### AP/EP Combat System
+- AP (Attack Points) = 灵巧 + 智力×0.2 + skills + equipment + stance - enemyCountPenalty
+- EP (Evasion Points) = 敏捷 + 智力×0.2 + skills + equipment + stance - environmentPenalty
+- Hit rate is non-linear: AP=5×EP → ~98%, AP=EP → ~50%, AP=0.5×EP → ~24%
+
+### Three-Layer Defense
+1. Evasion (complete dodge)
+2. Shield absorption
+3. Defense reduction (min 10% damage)
+
+### Skill Milestones
+Skills provide permanent global bonuses at specific levels (e.g., all weapons +crit rate at level 5). Parent skills auto-level to match highest child skill level.
+
+### Time System
+- 1 real second = 5 game minutes
+- Day/night cycle affects gameplay
+- Markets refresh daily
+- Offline earnings available (0.5h base, 2.5h with ad)
+
+### Idle Tasks
+Active tasks (reading, training, working) and passive tasks (environment adaptation). Combat is mutually exclusive with most tasks.
+
+## Important Patterns
+
+### Adding New Skills
+1. Add config to `config/skills.js`
+2. Set unlockCondition (item, location, or skill level)
+3. Add milestones with permanent bonuses
+4. Link to parentSkill if applicable
+
+### Adding New Items
+1. Add config to `config/items.js`
+2. Set baseValue, baseDamage/defense, type
+3. Add to shop or enemy drop tables
+4. For equipment, quality affects final stats
+
+### Adding New Locations
+1. Add to `config/locations.js`
+2. Set connections, enemies, activities
+3. Add unlockCondition if needed
+4. Update `utils/mapLayout.js` for visual representation
+
+### Store Usage
+```javascript
+import { usePlayerStore } from '@/store/player.js'
+import { useGameStore } from '@/store/game.js'
+
+const player = usePlayerStore()
+const game = useGameStore()
+
+// Access state
+player.baseStats.strength
+game.logs
+
+// Call actions
+game.addLog('message', 'info')
+```
+
+### Color Variables (uni.scss)
+- $bg-primary: #0a0a0f (main background)
+- $accent: #4ecdc4 (primary accent)
+- $danger: #ff6b6b (combat/HP low)
+- Quality colors: $quality-trash through $quality-legend
+
+## Development Workflow
+
+### Game Loop Timing
+- Main loop runs every 1 second via `gameLoop.js`
+- Time advances by TIME_SCALE (5) minutes per tick
+- Auto-save triggers every 30 game minutes
+
+### Saving
+- `App.vue` handles onLaunch/onShow/onHide
+- Auto-saves on app hide
+- Manual save via `window.manualSave()`
+- Logs are NOT persisted (cleared on load)
+
+### Testing
+- Tests in `tests/unit/` use Vitest
+- Fixtures in `tests/fixtures/` provide mock data
+- Helpers in `tests/helpers/` mock timers, stores, random
+- See `tests/README.md` for testing guidelines
+
+## Known Gotchas
+
+1. **uni API availability**: Some uni APIs only work in specific platforms (H5 vs mini-program)
+2. **Store timing**: Always use `setActivePinia(createPinia())` in tests
+3. **Logs array**: Limited to 200 entries, shifts old entries
+4. **Skill level cap**: Cannot exceed player level × 2
+5. **Equipment quality**: Calculated as base × quality% × tierMultiplier
+6. **Combat stance**: Switching stance resets attack progress
+7. **Market saturation**: Selling same item reduces price (encourages exploration)
diff --git a/components/panels/MapPanel.vue b/components/panels/MapPanel.vue
index fd134ce..2867cb4 100644
--- a/components/panels/MapPanel.vue
+++ b/components/panels/MapPanel.vue
@@ -124,7 +124,7 @@ import { NPC_CONFIG } from '@/config/npcs.js'
import { ENEMY_CONFIG, getRandomEnemyForLocation } from '@/config/enemies.js'
import { getShopConfig } from '@/config/shop.js'
import { initCombat, getEnvironmentType } from '@/utils/combatSystem.js'
-import { triggerExploreEvent, tryFlee } from '@/utils/eventSystem.js'
+import { triggerExploreEvent, tryFlee, startNPCDialogue } from '@/utils/eventSystem.js'
import TextButton from '@/components/common/TextButton.vue'
import ProgressBar from '@/components/common/ProgressBar.vue'
import FilterTabs from '@/components/common/FilterTabs.vue'
@@ -252,16 +252,8 @@ const availableNPCs = computed(() => {
function talkToNPC(npc) {
game.addLog(`与 ${npc.name} 开始对话...`, 'info')
- // 打开 NPC 对话
- if (npc.dialogue && npc.dialogue.first) {
- game.drawerState.event = true
- game.currentEvent = {
- type: 'npc_dialogue',
- npcId: npc.id,
- dialogue: npc.dialogue.first,
- choices: npc.dialogue.first.choices || []
- }
- }
+ // 使用 eventSystem 的 startNPCDialogue 来处理对话
+ startNPCDialogue(game, npc.id, 'first', player)
}
function openInventory() {
@@ -360,14 +352,15 @@ function startCombat() {
// 获取环境类型
const environment = getEnvironmentType(player.currentLocation)
- // 使用 initCombat 初始化战斗
- game.combatState = initCombat(enemyConfig.id, enemyConfig, environment)
+ // 使用 initCombat 初始化战斗,传入偏好的战斗姿态
+ game.combatState = initCombat(enemyConfig.id, enemyConfig, environment, game.preferredStance)
game.inCombat = true
}
function changeStance(stance) {
if (game.combatState) {
game.combatState.stance = stance
+ game.preferredStance = stance // 记住玩家选择的姿态
game.addLog(`切换到${stanceTabs.find(t => t.id === stance)?.label}姿态`, 'combat')
}
}
diff --git a/components/panels/StatusPanel.vue b/components/panels/StatusPanel.vue
index c2f017c..9036b33 100644
--- a/components/panels/StatusPanel.vue
+++ b/components/panels/StatusPanel.vue
@@ -114,6 +114,19 @@
空
+
+ 义体
+
+
+ {{ player.equipment.prosthetic.icon }} {{ player.equipment.prosthetic.name }}
+
+
+ {{ formatProstheticStats(player.equipment.prosthetic.stats) }}
+
+ [{{ getProstheticSkillName(player.equipment.prosthetic.skill) }}]
+
+ 空
+
@@ -575,6 +588,26 @@ function cancelActiveTask(task) {
endTask(game, player, task.id, false)
game.addLog(`取消了 ${task.name}`, 'info')
}
+
+// 义体相关辅助函数
+function formatProstheticStats(stats) {
+ if (!stats) return ''
+ const parts = []
+ if (stats.strength) parts.push(`力+${stats.strength}`)
+ if (stats.agility) parts.push(`敏+${stats.agility}`)
+ if (stats.dexterity) parts.push(`灵+${stats.dexterity}`)
+ if (stats.intuition) parts.push(`智+${stats.intuition}`)
+ if (stats.vitality) parts.push(`体+${stats.vitality}`)
+ if (stats.attack) parts.push(`攻+${stats.attack}`)
+ if (stats.defense) parts.push(`防+${stats.defense}`)
+ return parts.join(' ')
+}
+
+function getProstheticSkillName(skillId) {
+ const { SKILL_CONFIG } = require('@/config/skills')
+ const skill = SKILL_CONFIG[skillId]
+ return skill ? skill.name : skillId
+}