Skip to content

About

Core Extraction — a tunable grab & extract micro-game (Unity 2022.3 LTS)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Core Extraction — a tunable "grab & extract" micro-game

A 2D top-down prototype built in Unity 2022.3 LTS (C#). One round lasts ~30–90s: grab cores scattered around a small arena, then reach the green extraction zone before the rising pressure (faster enemies, traps, alert stages) catches you. Every extra core is more score — but staying longer is deadlier.

Everything is generated in code: the level, the geometry-shape sprites, and the sine/square/triangle sound effects. There are no binary art or audio assets.

Task spec: CHALLENGE.md · AI assistant rules: CLAUDE.md


Run it

./run.sh             # build (if needed) and play, preset = normal
./run.sh easy        # play the easy preset
./run.sh hard        # play the hard preset
./run.sh generated   # play the AI-generated preset (after ./run.sh gen)
./run.sh build       # build for the CURRENT platform
./run.sh build all   # build macOS + Windows + Linux
./run.sh build win   # build a specific platform: current | mac | win | linux | all
./run.sh sim         # headless bot: 20 runs per preset, prints + saves a summary
./run.sh sim hard 30
./run.sh gen normal 0.5      # AI level-gen: find a layout with ~50% bot win rate
./run.sh gen hard 0.3 7 16 16  # preset target seed candidates runs

run.sh auto-detects the current OS and launches the matching prebuilt player, building it first if needed. Builds live in per-platform folders so several can coexist for cross-platform testing:

build/macOS/ExtractionPrototype.app
build/Windows/ExtractionPrototype.exe
build/Linux/ExtractionPrototype.x86_64

If no prebuilt player exists, it finds a Unity editor (via UNITY_PATH or Unity Hub) and builds in batchmode first.

If no Unity is found (≤3 steps)

  1. Install Unity 2022.3 LTS (Unity Hub) and open the project at ./unity.
  2. Press Play — the game self-bootstraps, so no scene setup is needed.
  3. (Or) set UNITY_PATH=/path/to/Unity and re-run ./run.sh.

The repo ships project source. Produce the desktop build once (./run.sh build, or Unity menu ExtractionGame ▸ Build Standalone) and the binary lands in ./build.


Controls

Action Keys
Move WASD / Arrow keys
Dash (short burst, has cooldown) Space
Start round (menu) 1/2/3 pick preset, Enter/Space start
Restart same preset (result) R
Back to menu (result) M

Goal: collect at least one core, then enter the pulsing green ring to extract. Blue circle = you, red triangles = enemies, yellow diamonds = cores, orange diamonds = traps, gray = walls.

The menu also has a 玩家画像 / Player Persona row and (after ./run.sh gen) a GENERATED (AI) difficulty — both are described below.


Data-driven tuning

All gameplay numbers come from unity/Assets/StreamingAssets/tuning/*.json. Edit them and replay — no recompile, no code changes. Three presets ship: easy.json, normal.json, hard.json. Pick a preset in the menu, with ./run.sh <preset>, or via the EXTRACT_PRESET env var / -preset arg.

In-game tuning panel (中文)

For non-technical playtesters, the main menu has a 「调参面板 Tuning」 button that opens a Chinese visual panel of sliders for every category. Adjust values and:

  • 应用并开始 — start a round immediately with the tweaked values (no file edit).
  • 保存到 JSON — write the values back to StreamingAssets/tuning/<preset>.json.
  • 恢复预设 / 关闭 — reset to the preset's defaults / close.

This lets a designer feel out a tuning change in seconds, then persist it.

Five tunable categories (spec requires ≥4):

  • player — moveSpeed, dashSpeed, dashCooldown, dashDuration, maxHealth, invulnTime
  • level — mapWidth, mapHeight, resourceCount, obstacleDensity, seed, extractionRule
  • risk — enemyCount, enemyBaseSpeed, enemySpeedGrowthPerSec, alertGrowthPerSec, trapCount, trapDamage, enemyDamage
  • scoring — baseScorePerResource, comboBonus, extractionBonus, timeoutPenalty, survivalBonusPerSec
  • time — roundDuration, alertStageInterval, resourceRespawn

Player personas (archetype overlays)

A persona is a small overlay layered on top of the chosen difficulty, not a separate difficulty. Difficulty answers "how hard is the level"; persona answers "what kind of player am I tuning for". Overlays live in unity/Assets/StreamingAssets/personas/*.json (tunable, no code) and apply multipliers/deltas to the loaded preset. Pick one in the menu's persona row; it's recorded per round and shown on the result screen and in the heatmap.

Persona What changes Who it serves
新手 Novice +1 HP, enemies ×0.85, dash cooldown ×0.8, time ×1.15 A learning player: bigger margins so a mistake isn't fatal and the "grab vs leave" decision can be practised safely.
熟练 Skilled nothing (neutral) Plays the preset exactly as authored — the calibrated risk/reward baseline.
冒险型 Adventurer +2 cores, score ×1.25, enemies ×1.1, time ×0.9 A score-chaser: more loot on the map and bigger payouts, paid for with faster enemies and a tighter clock.

The two axes compose: e.g. hard + novice is "a tough map made survivable for a newcomer," while easy + adventurer is "a gentle map that still rewards greed." Personas are applied once at round start, so R (replay) reuses them.

Heatmap / replay review

Every round samples the player position ~4 Hz into the telemetry path, plus enough level info (size, seed, density) to rebuild the geometry. On the result screen, 复盘热力图 Heatmap opens a review overlay with two modes:

  • 本局路径 (this run) — the level redrawn with a dwell heatmap (where you lingered, cool→hot), your movement/resource path as fading dots, and event markers (start, pickups, damage, extraction, death). Use ◀/▶ to step through recent sessions.
  • 聚合死亡点 (all deaths) — every non-extraction end position across recent sessions of that preset binned onto the map, surfacing kill-zones to rebalance.

This is the designer's "why did they die / where did they get stuck" view.

AI level generation / validation

./run.sh gen <preset> <targetWin> [seed] [candidates] [runs] runs a generate-then-validate loop driven from a level_seed:

  1. From the base preset it generates several candidate layouts, varying the level seed, obstacle density, and resource count (deterministically from level_seed).
  2. Degenerate candidates are rejected with the existing BFS check (exit must be reachable, enough reachable cores).
  3. Each surviving layout is validated by the bot: the shared headless SimCore plays it runs times with a jittered BotPlayer (different bail boldness per trial, seeded from level_seed), yielding an estimated win rate for that exact layout.
  4. The layout whose estimated win rate is closest to targetWin is written to unity/Assets/StreamingAssets/tuning/generated.json (playable via ./run.sh generated or the menu's GENERATED button); a ranked telemetry/level_gen_report.json lists every candidate.

Because the bot, layout generator, and scoring are the same code the live game uses, the estimated win rate is meaningful rather than a separate toy model.

How AI tools were used here

This generator was itself built with the AI loop described in AI_WORKFLOW.md. The non-obvious part was the prompt/heuristic iteration: a deterministic bot produces a single win/lose per seed, so the first AI draft (one run per seed) couldn't express a rate. The fix — prompted and reviewed across a few iterations — was to hold the layout fixed and instead jitter the bot's bail thresholds per trial, so a single layout yields a spread of outcomes. Candidates are then filtered by (a) the reachability gate and (b) distance to the target win rate, and ranked in the report so a human can sanity -check the pick rather than trusting it blindly.

Telemetry & review

Each finished round writes telemetry/session_YYYYMMDD_HHmmss_fff.json with: preset, persona, win/lose, duration, score, cores collected, combo pickups, damage taken, fail reason, end position, a timestamped event timeline (pickup, alert_stage, damage, near_extraction, game_end), a level block (size/seed/density, used to rebuild geometry for the heatmap) and a sampled path (the ~4 Hz position trail that drives the dwell heatmap).

An aggregate telemetry/summary.json is regenerated after every round (per-preset win rate, avg score, avg duration, avg cores, most common fail reason). The same data is shown on the in-game result/review screen (recent sessions + table).

./run.sh sim writes one telemetry/sim_*.json per bot run plus telemetry/sim_summary.json, so you can estimate how a preset feels before playing it yourself.


Project structure

run.sh                      one-command launcher (play / build / sim / gen)
telemetry/                  generated session + summary files
unity/Assets/Scripts/
  Config/                   TuningConfig + loader; PersonaConfig + PersonaLoader
  Core/                     GameBootstrap, GameManager (state machine), Rules
  Gameplay/                 LevelLayout (deterministic), LevelBuilder, Player, Enemy, ...
  Telemetry/                SessionModels (path/level/persona), Recorder, Aggregator
  Sim/                      SimCore (headless), BotPlayer, BotSimulator, LevelGenerator
  Audio/Util/UI/            procedural audio, shape sprites, seeded RNG, IMGUI HUD + HeatmapRenderer
unity/Assets/Editor/        BuildScript (batchmode build + sim + level-gen entry points)
unity/Assets/Tests/EditMode tests (config, scoring, alert, telemetry, layout, sim, persona, heatmap, level-gen)
unity/Assets/StreamingAssets/tuning/     easy.json / normal.json / hard.json (+ generated.json)
unity/Assets/StreamingAssets/personas/   novice.json / skilled.json / adventurer.json

The live game and the bot simulator share the same LevelLayout generator and Rules math, so the simulator faithfully reflects the real experience.


Tests

EditMode tests (Unity Test Framework) cover the core requirements and the bonus features: config load/override, scoring math, alert progression, telemetry serialization (incl. path/level/persona), summary aggregation, deterministic + reachable level layout, a full headless round via SimCore, persona overlay math, heatmap binning, and the level generator (determinism + closest-to-target pick).

Run from the editor (Window ▸ General ▸ Test Runner) or batchmode:

"$UNITY_PATH" -batchmode -runTests -projectPath unity \
  -testPlatform EditMode -testResults unity/test-results.xml


中文说明 (Chinese Version)

以下为上文英文文档的完整中文翻译,便于中文评审阅读。命令、路径与代码标识保持英文原样。

Core Extraction —— 一款可调参的「夺核撤离」微型游戏

基于 Unity 2022.3 LTS (C#) 开发的 2D 俯视角原型。单局约 30–90 秒:在小型竞技场中 收集散落的核心 (core),然后在压力持续上升(敌人提速、陷阱、警报阶段)之前抵达绿色撤离区。 多拿一个核心就多一份分数——但停留越久越危险。

所有内容均由代码生成:关卡、几何形状精灵 (sprite) 以及正弦/方波/三角波音效。 项目不含任何二进制美术或音频资源。

任务说明:CHALLENGE.md · AI 助手规则:CLAUDE.md


运行方式 (Run it)

./run.sh             # 按需打包并试玩,预设 = normal
./run.sh easy        # 试玩 easy 预设
./run.sh hard        # 试玩 hard 预设
./run.sh generated   # 试玩 AI 生成的预设(需先执行 ./run.sh gen)
./run.sh build       # 为当前平台进行打包/构建
./run.sh build all   # 构建 macOS + Windows + Linux
./run.sh build win   # 构建指定平台:current | mac | win | linux | all
./run.sh sim         # 无头模拟器/自动化 Bot:每个预设跑 20 局,输出并保存聚合结果
./run.sh sim hard 30
./run.sh gen normal 0.5      # AI 关卡生成:寻找 Bot 通关率约 50% 的布局
./run.sh gen hard 0.3 7 16 16  # 预设 目标通关率 种子 候选数 每候选试跑次数

run.sh 会自动识别当前操作系统,启动对应的预构建播放器 (player);若不存在则先行构建。 构建产物按平台分目录存放,便于多平台并存测试:

build/macOS/ExtractionPrototype.app
build/Windows/ExtractionPrototype.exe
build/Linux/ExtractionPrototype.x86_64

若没有预构建产物,脚本会通过 UNITY_PATH 或 Unity Hub 查找 Unity 编辑器,并先以 batchmode(批处理模式)进行构建。

若未找到 Unity(≤3 步)

  1. 安装 Unity 2022.3 LTS(通过 Unity Hub),并打开位于 ./unity 的工程。
  2. 点击 Play —— 游戏会自启动 (self-bootstrap),无需搭建场景。
  3. (或)设置 UNITY_PATH=/path/to/Unity 后重新运行 ./run.sh。

仓库提交的是工程源码。先执行一次桌面构建(./run.sh build,或使用 Unity 菜单 ExtractionGame ▸ Build Standalone),产物会落到 ./build。


操作方式 (Controls)

操作 按键
移动 WASD / 方向键
冲刺(短促爆发,有冷却) Space
开始单局(菜单) 1/2/3 选择预设,Enter/Space 开始
以相同预设重开(结算页) R
返回菜单(结算页) M

目标: 至少收集一个核心,再进入脉动的绿色光环完成撤离。 蓝色圆=玩家,红色三角=敌人,黄色菱形=核心,橙色菱形=陷阱,灰色=墙体。

菜单中还包含 玩家画像 / Player Persona 一行,以及(执行 ./run.sh gen 后出现的) GENERATED (AI) 难度——两者均在下文说明。


数据驱动调参 (Data-driven tuning)

所有玩法数值均来自 unity/Assets/StreamingAssets/tuning/*.json。 修改后重玩即可生效——无需重新编译、无需改动代码。内置三套预设: easy.json、normal.json、hard.json。可在菜单中选择,或通过 ./run.sh <preset>, 亦可使用 EXTRACT_PRESET 环境变量 / -preset 启动参数。

游戏内调参面板(中文)

面向非技术试玩者,主菜单提供 「调参面板 Tuning」 按钮,打开后是一个中文可视化滑块面板, 覆盖全部类别。调整数值后可:

  • 应用并开始 —— 立即以改后的数值开局(不写文件)。
  • 保存到 JSON —— 将数值写回 StreamingAssets/tuning/<preset>.json。
  • 恢复预设 / 关闭 —— 恢复该预设默认值 / 关闭面板。

这让策划能在数秒内感受一次调参改动,再决定是否持久化。

五个可调类别(任务要求 ≥4 类):

  • player(玩家) —— moveSpeed、dashSpeed、dashCooldown、dashDuration、maxHealth、invulnTime
  • level(关卡) —— mapWidth、mapHeight、resourceCount、obstacleDensity、seed、extractionRule
  • risk(风险) —— enemyCount、enemyBaseSpeed、enemySpeedGrowthPerSec、alertGrowthPerSec、trapCount、trapDamage、enemyDamage
  • scoring(评分) —— baseScorePerResource、comboBonus、extractionBonus、timeoutPenalty、survivalBonusPerSec
  • time(时间) —— roundDuration、alertStageInterval、resourceRespawn

玩家画像(画像叠加层)

画像 (persona) 是叠加在所选难度之上的一层薄叠加,并非独立的难度。 难度回答「这张关卡有多难」;画像回答「我在为哪类玩家调参」。叠加层位于 unity/Assets/StreamingAssets/personas/*.json(可调、无需改代码),对已加载的预设施加 乘数/增减量。在菜单画像行中选择即可;它会被逐局埋点记录,并在结算页与热力图中展示。

画像 改变了什么 服务于谁
新手 Novice +1 生命,敌人 ×0.85,冲刺冷却 ×0.8,时间 ×1.15 学习中的玩家:更大的容错空间,让失误不致命,可以安全地练习「多拿还是撤离」的取舍。
熟练 Skilled 无变化(中性) 完全按作者设定来玩——经过校准的风险/收益基线。
冒险型 Adventurer +2 核心,分数 ×1.25,敌人 ×1.1,时间 ×0.9 刷分型玩家:地图上有更多战利品与更高回报,代价是敌人更快、时钟更紧。

两条轴可以组合:例如 hard + novice 即「一张硬核地图,但对新手而言可生还」; 而 easy + adventurer 即「一张温和地图,但仍奖励贪婪」。画像在开局时仅应用一次, 因此 R(重玩)会沿用同一画像。

热力图 / 回放复盘

每局都会以约 4 Hz 把玩家位置采样进遥测 (telemetry) 的 path,并记录足够的 level 信息(尺寸、种子、密度)以重建几何。结算页的 复盘热力图 Heatmap 会打开一个复盘浮层, 含两种模式:

  • 本局路径 (this run) —— 重绘关卡,叠加停留热力图(你逗留之处,由冷到热)、 以渐隐圆点表示的移动/取资源路径,以及事件标记(起点、拾取、受伤、撤离、死亡)。 使用 ◀/▶ 翻阅最近的若干局。
  • 聚合死亡点 (all deaths) —— 将该预设最近若干局中所有「非撤离」结束位置归并 (bin) 到地图网格上,凸显需要再平衡的死亡热区 (kill-zone)。

这正是策划用来回答「玩家为何死亡 / 在哪里卡住」的视图。

AI 关卡生成 / 验证

./run.sh gen <preset> <targetWin> [seed] [candidates] [runs] 会以 level_seed 驱动一套**「先生成、再验证」**的流程:

  1. 从基础预设出发,生成若干候选布局,对关卡种子、障碍密度 (obstacle density) 与 资源数量 (resource count) 进行变化(由 level_seed 决定,可复现)。
  2. 退化的候选会被既有的 BFS 检查剔除(撤离点必须可达、可达核心数量足够)。
  3. 每个存活布局都由 Bot 进行验证:共享的无头 SimCore 用带扰动 (jitter) 的 BotPlayer(每次试跑的「撤离果断度」不同,且由 level_seed 播种)将其试玩 runs 次, 从而估算该布局的通关率。
  4. 估算通关率最接近 targetWin 的布局会被写入 unity/Assets/StreamingAssets/tuning/generated.json(可通过 ./run.sh generated 或菜单 GENERATED 按钮试玩);同时生成一份按优劣排序的 telemetry/level_gen_report.json 候选清单。

由于 Bot、关卡生成器与评分使用的都是实机同一套代码,因此估算出的通关率是有意义的, 而非另起炉灶的玩具模型。

此处如何使用 AI 工具

该生成器本身也是用 AI_WORKFLOW.md 中描述的 AI 流程构建的。 不显然之处在于提示词/启发式的迭代:确定性 Bot 对每个种子只会产出单一的胜/负结果, 因此 AI 的初版(每个种子只跑一次)无法表达「通关率」这一概念。修正方案——经过数轮提示与 人工审阅——是固定布局不变,转而逐次试跑扰动 Bot 的撤离阈值,使同一布局产生一组分布化的 结果。随后再对候选进行筛选:(a) 可达性闸门;(b) 与目标通关率的距离;并在报告中排序, 以便人工对最终选择进行 sanity-check,而非盲目信任。

遥测与复盘 (Telemetry & review)

每局结束都会写入 telemetry/session_YYYYMMDD_HHmmss_fff.json,内容包括: preset、persona、胜/负、时长、得分、收集核心数、连击拾取数、受伤次数、失败原因、 结束位置、带时间戳的事件时间线(pickup、alert_stage、damage、near_extraction、 game_end)、一段 level 信息块(尺寸/种子/密度,用于为热力图重建几何)以及一段采样 path(约 4 Hz 的位置轨迹,驱动停留热力图)。

每局之后都会重新生成聚合文件 telemetry/summary.json(按预设统计通关率、平均分、平均时长、 平均核心数、最常见失败原因)。相同数据也会展示在游戏内的结算/复盘界面(最近若干局 + 表格)。

./run.sh sim 会为每次 Bot 试跑写入一个 telemetry/sim_*.json,外加 telemetry/sim_summary.json,让你在亲自试玩之前就能估算某个预设的手感。


工程结构 (Project structure)

run.sh                      一条命令的启动器(试玩 / 构建 / 模拟 / 生成)
telemetry/                  生成的单局与聚合文件
unity/Assets/Scripts/
  Config/                   TuningConfig + 加载器;PersonaConfig + PersonaLoader
  Core/                     GameBootstrap、GameManager(状态机)、Rules
  Gameplay/                 LevelLayout(确定性)、LevelBuilder、Player、Enemy……
  Telemetry/                SessionModels(path/level/persona)、Recorder、Aggregator
  Sim/                      SimCore(无头)、BotPlayer、BotSimulator、LevelGenerator
  Audio/Util/UI/            程序化音频、形状精灵、播种 RNG、IMGUI HUD + HeatmapRenderer
unity/Assets/Editor/        BuildScript(batchmode 构建 + 模拟 + 关卡生成入口)
unity/Assets/Tests/EditMode 测试(config、scoring、alert、telemetry、layout、sim、persona、heatmap、level-gen)
unity/Assets/StreamingAssets/tuning/     easy.json / normal.json / hard.json(+ generated.json)
unity/Assets/StreamingAssets/personas/   novice.json / skilled.json / adventurer.json

实机游戏与 Bot 模拟器共用同一套 LevelLayout 生成器与 Rules 数学, 因此模拟器能忠实反映真实体验。


测试 (Tests)

EditMode 测试(Unity Test Framework)覆盖了核心要求与加分项功能: 配置加载/覆盖、评分数学、警报推进、遥测序列化(含 path/level/persona)、聚合统计、 关卡布局的确定性 + 可达性、经由 SimCore 的完整无头单局、画像叠加数学、热力图归并, 以及关卡生成器(确定性 + 最接近目标的选择)。

可在编辑器中运行(Window ▸ General ▸ Test Runner)或使用 batchmode:

"$UNITY_PATH" -batchmode -runTests -projectPath unity \
  -testPlatform EditMode -testResults unity/test-results.xml

About

Core Extraction — a tunable grab & extract micro-game (Unity 2022.3 LTS)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages