Skip to content
2 changes: 1 addition & 1 deletion docs/contests/RL9/pve/faq/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

> Q: 观测向量怎么理解?
>
> A: 32 维 float32,包含位置、HP、背包、现金、市场价格相位、最近资源点/市场/算力中心相对位置等。详见 [公开接口](../interface/gym.md)。
> A: 58 维 float32,包含位置、HP、背包(按商品分)、现金、算力、市场 OU 价格、工厂库存、科技 one-hot、资源/算力中心相对位置等。详见 [公开接口](../interface/gym.md)。

> Q: 怎么写规则策略(不用 RL)?
>
Expand Down
108 changes: 42 additions & 66 deletions docs/contests/RL9/pve/game/actions.md
Original file line number Diff line number Diff line change
@@ -1,88 +1,64 @@
# 动作空间

PVE 使用 **28 个离散动作**(`N_ACTIONS = 28`),分为基础操作、商品交易、生产链、科技四大类
PVE **28 个离散动作**(`N_ACTIONS = 28`)。

## 基础动作(0–5)

| 编号 | 动作 | 含义 | 有效性条件 |
|:----:|:----:|------|------------|
| 编号 | 动作 | 含义 | 有效条件 |
|:----:|:----:|------|----------|
| 0 | `WAIT` | 等待一个 tick | 始终有效 |
| 1 | `MOVE_UP` | 向上移动 (x−1) | 目标格可通行,单位不 busy |
| 2 | `MOVE_DOWN` | 向下移动 (x+1) | 同上 |
| 3 | `MOVE_LEFT` | 向左移动 (y−1) | 同上 |
| 4 | `MOVE_RIGHT` | 向右移动 (y+1) | 同上 |
| 5 | `BUY` | 在相邻市场购买利润最高的可负担商品 | Manhattan ≤1 有市场,背包有空间,现金充足 |
| 1 | `MOVE_UP` | x−1 | 目标格可通行,busy_ticks=0 |
| 2 | `MOVE_DOWN` | x+1 | 同上 |
| 3 | `MOVE_LEFT` | y−1 | 同上 |
| 4 | `MOVE_RIGHT` | y+1 | 同上 |
| 5 | `BUY` | 买入跨市场套利空间最大的可负担商品 | Manhattan≤1 有市场,背包有空间,现金足够 |

## 卖出动作(6–10,按商品类型
## 卖出动作(6–10)

| 编号 | 动作 | 含义 | 有效性条件 |
|:----:|:----:|------|------------|
| 6 | `SELL_0` | 卖出背包内所有**半导体** | Manhattan ≤1 有市场,背包有该商品 |
| 7 | `SELL_1` | 卖出背包内所有**药品** | 同上 |
| 8 | `SELL_2` | 卖出背包内所有**小商品** | 同上 |
| 9 | `SELL_3` | 卖出背包内所有**服饰** | 同上 |
| 10 | `SELL_4` | 卖出背包内所有**食品** | 同上 |
| 编号 | 动作 | 有效条件 |
|:----:|:----:|----------|
| 6 | `SELL_0` | Manhattan≤1 有市场,背包有**半导体** |
| 7 | `SELL_1` | 同上,有**药品** |
| 8 | `SELL_2` | 同上,有**小商品** |
| 9 | `SELL_3` | 同上,有**服饰** |
| 10 | `SELL_4` | 同上,有**食品** |

- **SELL_pid**:卖出指定类型的所有商品,获得当前市场价
- 卖出成功时:`score += revenue × score_factor`(默认 ×10),`money += revenue`
- SELL_pid 卖出非当前市场来源的商品(禁止同市场原地套利,商品追踪 `prod_origin`)。成功时 `score += revenue × 10`。

## 生产链动作(11–18)

| 编号 | 动作 | 含义 | 有效性条件 |
|:----:|:----:|------|------------|
| 11 | `HARVEST` | 从附近资源点采集原材料 | Manhattan ≤2 有未耗尽资源,背包有空间 |
| 12 | `DEPOSIT` | 将背包原材料存入工厂 | 在工厂格,背包 raw_inv > 0 |
| 13 | `PRODUCE_0` | 工厂开始生产**半导体**(消耗 5 原材料) | 在工厂格,工厂 raw_stock ≥ 5,生产队列未满 |
| 14 | `PRODUCE_1` | 工厂开始生产**药品**(消耗 3 原材料) | 在工厂格,工厂 raw_stock ≥ 3,生产队列未满 |
| 15 | `PRODUCE_2` | 工厂开始生产**小商品**(消耗 1 原材料) | 在工厂格,工厂 raw_stock ≥ 1,生产队列未满 |
| 16 | `PRODUCE_3` | 工厂开始生产**服饰**(消耗 4 原材料) | 在工厂格,工厂 raw_stock ≥ 4,生产队列未满 |
| 17 | `PRODUCE_4` | 工厂开始生产**食品**(消耗 2 原材料) | 在工厂格,工厂 raw_stock ≥ 2,生产队列未满 |
| 18 | `LOAD` | 从工厂仓库装载已完成成品到背包 | 在工厂格,工厂有成品,背包有空间 |

- **HARVEST**:采集范围 2 格(Manhattan 距离),每次采集 `unit_harvest_rate × time_step` 单位
- **DEPOSIT**:将背包中原材料存入工厂仓库,供后续生产使用
- **PRODUCE_pid**:将工厂原材料转换为产品,加入生产队列
- **LOAD**:从工厂仓库取出已完成的成品装入背包
| 编号 | 动作 | 消耗/条件 |
|:----:|:----:|-----------|
| 11 | `HARVEST` | Manhattan≤2 有未耗尽资源,背包有空间 |
| 12 | `DEPOSIT` | 在工厂格,背包 raw_inv > 0 |
| 13 | `PRODUCE_0` | 在工厂格,工厂 raw_stock≥5,队列未满 |
| 14 | `PRODUCE_1` | raw_stock≥3 |
| 15 | `PRODUCE_2` | raw_stock≥1 |
| 16 | `PRODUCE_3` | raw_stock≥4 |
| 17 | `PRODUCE_4` | raw_stock≥2 |
| 18 | `LOAD` | 在工厂格,工厂有成品,背包有空间 |

## 算力与科技动作(19–27)

| 编号 | 动作 | 含义 | 消耗 | 前置 |
|:----:|:----:|------|:----:|:----:|
| 19 | `OCCUPY` | 推进相邻算力中心的占领进度 | — | — |
| 20 | `TECH_0` | **降低成本**:商品购买成本 −2 | 50 算力 | — |
| 21 | `TECH_1` | **效率提升**:生产时间 ×0.5 | 40 算力 | — |
| 22 | `TECH_2` | **市场营销**:卖价 ×1.1 | 80 算力 | — |
| 23 | `TECH_3` | **耐久强化**:单位 max HP +50% | 30 算力 | — |
| 24 | `TECH_4` | **多产线**:工厂 +1 生产线 | 60 算力 | — |
| 25 | `TECH_5` | **路径优化**:移动不再产生 busy tick | 50 算力 | TECH_1 |
| 26 | `TECH_6` | **市场分析**:观测中标记已购买(可重复) | 40 算力 | — |
| 27 | `TECH_7` | **算力扩张**:算力积累速率 +30% | 70 算力 | — |
| 编号 | 动作 | 效果 | 消耗 |
|:----:|:----:|------|:----:|
| 19 | `OCCUPY` | 推进相邻算力中心占领进度 | — |
| 20 | `TECH_0` | 商品购买成本 −2 | 50 |
| 21 | `TECH_1` | 生产时间 ×0.5 | 40 |
| 22 | `TECH_2` | 卖价 ×1.1 | 80 |
| 23 | `TECH_3` | 单位 max HP +50% | 30 |
| 24 | `TECH_4` | 工厂 +1 生产线 | 60 |
| 25 | `TECH_5` | 移动不产生 busy_tick(需 TECH_1) | 50 |
| 26 | `TECH_6` | 观测中标记(可重复购买) | 40 |
| 27 | `TECH_7` | 算力速率 +30% | 70 |

- **OCCUPY**:需相邻有未开放的算力中心,每次 tick 推进 `time_step` 秒进度
- **TECH_0~5, TECH_7**:持久科技,每种只能购买一次,在工厂格执行
- **TECH_6**:非持久科技,可重复购买
- 科技动作需要在工厂格、算力足够、前置科技满足时才有效
科技在工厂格执行。持久科技(TECH_0~5,7)每种限一次。

## 通用规则

- `busy_ticks > 0` 时单位忙碌,**仅 WAIT 有效**,其他动作均被掩码屏蔽
- 移动默认消耗 1 busy_tick(购买 TECH_5 后变为 0)
- BUY/SELL/LOAD 消耗 busy_ticks(`0.25 / time_step` 个 tick)
- 执行无效动作不会报错,但受到 **−0.05 奖励惩罚**并浪费步数
- `busy_ticks > 0` 时仅 WAIT 有效;移动消耗 1 tick(TECH_5 后为 0)
- 无效动作不报错,但受 **−0.02 奖励惩罚**

## 动作掩码

环境提供 `action_masks()` 方法,返回 `(28,)` 布尔数组,`True` 表示该动作当前有效。使用 `MaskablePPO` 可以自动过滤无效动作:

```python
from sb3_contrib import MaskablePPO
from sb3_contrib.common.wrappers import ActionMasker

def mask_fn(env):
return env.unwrapped.action_masks()

masked_env = ActionMasker(env, mask_fn)
model = MaskablePPO("MlpPolicy", masked_env)
```

建议所有策略先查询 `action_masks()` 再决定动作。
`action_masks()` 返回 `(28,)` bool 数组,True=有效。推荐 `MaskablePPO` + `ActionMasker` 自动过滤。
25 changes: 17 additions & 8 deletions docs/contests/RL9/pve/game/reward.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,29 @@ score += revenue × score_factor(默认 × 10)

## 单步奖励(RL Reward)

训练时的辅助信号,由以下部分叠加:
训练时的辅助信号,由以下部分叠加(`mode = "standard"`)

| 来源 | | 说明 |
| 来源 | 默认值 | 说明 |
|------|:--:|------|
| 现金变化 Δmoney | × 0.01 | 正负均有 |
| 得分变化 Δscore | × 0.01 | 仅卖出时为正 |
| 时间惩罚(每步) | −0.002 | 鼓励高效路径 |
| 采集奖励(每单位) | +0.001 | 采集塑形 |
| 算力中心解锁(一次性) | +0.5 | 进度奖励 |
| 无效动作惩罚 | −0.05 | 每步 |
| 现金变化 Δmoney | × 0.02 | 正负均有 |
| 卖出得分 Δscore | × 0.001 | 仅卖出时为正 |
| 时间惩罚(每步) | −0.001 | 鼓励高效路径 |
| 采集奖励(每单位) | +0.0 | 默认关闭,可通过 RewardConfig 开启 |
| 算力中心解锁(每个) | +1.0 | 进度奖励 |
| 科技购买(每个) | +1.0 | 进度奖励 |
| 无效动作惩罚 | −0.02 | 每步 |
| 破产惩罚(terminated 时) | −10.0 | 终端惩罚 |

> 奖励是训练辅助信号。**最终排名以 `info["score"]` 为准**,不是累计奖励。

`RewardConfig` 支持两种模式:
- `"standard"`(默认):密集可学习信号,适用于 PPO/DQN 训练
- `"adversarial"`:收割陷阱模式,仅使用采集奖励 + 终端得分

### 技术细节

`RewardCalculator.compute()` 在 `game_env.py` 的 `step()` 中调用,计算奖励时先评估终止条件,再结算奖励(确保 terminal bonus 正确触发)。

## `step()` 返回的 info 字典

| 字段 | 类型 | 含义 |
Expand Down
106 changes: 37 additions & 69 deletions docs/contests/RL9/pve/game/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,116 +2,84 @@

## 时间系统

| 参数 | 值 |
|:----:|:--:|
| 每 tick 时长 | 0.25s |
| easy/medium 总时长 | 300s(1200 ticks) |
| hard 总时长 | 500s(2000 ticks) |
每 tick **0.25s**。easy/medium 300s(1200 tick),hard 500s(2000 tick)。

## 单位属性
## 单位

| 属性 | 值 |
|:----:|:--:|
| 血量 | 300 |
| HP | 300 |
| 背包容量 | 30 |
| 采集速率 | 10/s |
| 算力中心占领时间 | 10s |
| 占领耗时 | 10s |

- 背包分为**原材料**(`raw_inv`)和**成品**(`prod_inv[pid]`)两部分
- `busy_ticks > 0` 时单位忙碌,仅 WAIT 有效
- 移动默认消耗 1 busy_tick(购买 path_optimization 科技后变为 0)
- BUY/SELL/LOAD 消耗 `int(0.25 / time_step)` 个 busy_tick
- 背包分原材料(`raw_inv`)和成品(`prod_inv[pid]`);商品追踪购买来源市场(`prod_origin`)
- `busy_ticks > 0` 时仅 WAIT 有效;移动消耗 1 tick(TECH_5 后为 0);BUY/SELL/LOAD 消耗 `0.25/dt` tick

## 工厂

| 属性 | 值 |
|:----:|:--:|
| 位置 | (0, 0) |
| 位置 | (0,0) |
| 仓储上限 | 300 |
| 初始生产线数 | 3 |

### 生产系统

工厂将原材料加工为成品。生产队列最大长度 = `生产线数 × 5`。
| 初始产线 | 3 |

生产流程:
1. 采集原材料(HARVEST)
2. 存入工厂(DEPOSIT)
3. 排队生产(PRODUCE_pid)——消耗原材料,加入生产队列
4. 等待工厂 tick 推进完成
5. 装载成品(LOAD)

生产时间受 efficiency 科技影响(×0.5)。
生产队列上限 = 产线数 × 5。流程:HARVEST → DEPOSIT → PRODUCE → 等待 tick 完成 → LOAD → SELL。

## 商品

| ID | 名称 | 购买成本 | 原材料消耗 | 市场价格范围 | 生产时间 |
|:--:|:----:|:--------:|:----------:|:------------:|:--------:|
| ID | 名称 | 买价 | 原料消耗 | 市价范围 | 生产时间 |
|:--:|:----:|:----:|:--------:|:--------:|:--------:|
| 0 | 半导体 | 10 | 5 | 40–120 | 5.0s |
| 1 | 药品 | 5 | 3 | 20–60 | 4.0s |
| 2 | 小商品 | 1 | 1 | 4–12 | 2.0s |
| 3 | 服饰 | 8 | 4 | 32–96 | 6.0s |
| 4 | 食品 | 3 | 2 | 12–24 | 1.0s |

- 购买成本受 cost_reduction 科技影响(−2,最低为 0)
- 生产时间受 efficiency 科技影响(×0.5)
买价受 TECH_0(−2),生产时间受 TECH_1(×0.5) 影响。

## 市场定价
## 市场

每个市场对每种商品有独立的正弦价格函数
OU 随机游走价格,每 tick 更新

```
price(t) = base + amplitude × (1 + sin(2π·t / period + phase)) / 2
dP = θ(μ−P)·dt + σ·√dt·N(0,1)
```

- 不同市场的价格**相位随机不同步**,套利窗口随时间移动
- `price_volatility` 控制波动幅度(easy=0.3, medium=1.0, hard=2.0)
- 卖价受 marketing 科技影响(×1.1)

## 算力系统
θ=0.05,σ=amplitude×0.12,价格夹在 [lo, lo+amplitude] 内。`price_volatility` 控制振幅(easy=0.3, medium=1.0, hard=2.0)。

- **基础产出**:每个已开放算力中心产出 **1 算力/秒**
- 购买 compute_expansion 科技后:+30% 算力速率
- 可消耗算力招募新单位(40 算力/个,最多 5 个单位)——Phase 2
**套利规则**:BUY 选跨市场卖价最高的可负担商品。禁止同市场原地套利(商品追踪购买来源,卖出时排除当前市场来源部分)。卖价受 TECH_2(×1.1) 影响。

### 算力中心
## 算力

- 占领方式:在相邻格执行 OCCUPY,每次 tick 推进 0.25s 进度
- 进度达到 `unit_occupy_time`(10s后开放
- 开放后持续产出算力
- 每个开放算力中心 +1 算力/秒;TECH_7 后 +30%
- OCCUPY 每 tick 推进 0.25s,10s 后开放
- 招募新单位:40 算力/个,上限 5

## 科技树
## 科技

| 键名 | 效果 | 消耗 | 前置 | 持久 |
|:-----|------|:----:|:----:|:----:|
| cost_reduction | 商品购买成本 −2 | 50 | — | ✓ |
| efficiency | 生产时间 ×0.5 | 40 | — | ✓ |
| marketing | 卖价 ×1.1 | 80 | — | ✓ |
| durability | 单位 max HP +50% | 30 | — | ✓ |
| multi_line | 工厂 +1 生产线 | 60 | — | ✓ |
| path_optimization | 移动不产生 busy_tick | 50 | efficiency | ✓ |
| market_analysis | 观测中标记已购买 | 40 | — | ✗ |
| compute_expansion | 算力速率 +30% | 70 | — | ✓ |
| 键名 | 效果 | 消耗 | 前置 |
|:-----|------|:----:|:----:|
| cost_reduction | 买价 −2 | 50 | — |
| efficiency | 生产时间 ×0.5 | 40 | — |
| marketing | 卖价 ×1.1 | 80 | — |
| durability | HP +50% | 30 | — |
| multi_line | 产线 +1 | 60 | — |
| path_optimization | 移动无 busy_tick | 50 | efficiency |
| market_analysis | obs标记(可重复) | 40 | — |
| compute_expansion | 算力 +30% | 70 | — |

- 持久科技每种只能购买一次
- market_analysis 可重复购买
- 科技在工厂格执行
持久科技(除 market_analysis)限购一次。在工厂格执行。

## 资源再生

资源点库存会随时间缓慢再生:

```
regen(t) = rate × (1 + sin(2π·t / period)) / 2
regen(t) = rate × (1 + sin(2π·t/period)) / 2
```

- 再生倍率:easy=2.0, medium=1.0, hard=0.5
- 资源耗尽(累计采集量 ≥ max_stock)后停止再生
- 最大库存为初始库存的 2 倍
倍率:easy=2.0, medium=1.0, hard=0.5。耗尽(累计≥max_stock=2×初始)后停止。

## 得分与终止

- **得分**:`score += revenue × score_factor`(默认 ×10),仅在 SELL 成功时增加
- **terminated**:`money < 0`(破产)
- **truncated**:`step >= max_steps`(时间耗尽)
- 最终排名以多 seed 下的 `info["score"]` 平均为准
- `score += revenue × 10`(仅 SELL 成功时)
- `money < 0` → terminated;`step ≥ max_steps` → truncated
Loading