把规则、架构与实现路线写成仓库文档。
后续改内容或结算顺序时有冻结依据,不必再翻原始稿才能对齐。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,35 @@
|
|||||||
|
# Card Roguelike
|
||||||
|
|
||||||
|
Simulation-first PvE card roguelike. Godot comes after a headless rules engine.
|
||||||
|
|
||||||
|
Start here: `docs/FOUNDATION.md`, `docs/ARCHITECTURE.md`, `docs/DESIGN.md`, `docs/SURFACES.md` (headless vs interactive).
|
||||||
|
|
||||||
|
## Implementation roadmap
|
||||||
|
|
||||||
|
1. **CLI combat** — shared starting package, attack/activate, guaranteed field, guard/cult core.
|
||||||
|
2. **CLI full run (current)** — reward → Holder assignment → simple upgrades → next fight.
|
||||||
|
3. **Playtest client** — `Game.Client` browser UI; emoji/SVG placeholders. Godot still later.
|
||||||
|
4. **Art pass** — replace placeholders.
|
||||||
|
5. **Expansion card packs** — near the art pass.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
CLI combat + CLI full run + browser playtest client. Core owns rules and in-run progression (`RunProgress`). Client only renders and posts legal actions. Placeholder art: emoji + SVG.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Requires [.NET 8 SDK](https://dotnet.microsoft.com/download).
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
dotnet test CardGame.sln
|
||||||
|
dotnet run --project tools/HeadlessBattle -- --auto
|
||||||
|
dotnet run --project tools/HeadlessBattle -- --run --auto
|
||||||
|
dotnet run --project tools/HeadlessBattle -- --run --seed 42 --fights 3
|
||||||
|
dotnet run --project src/Game.Client
|
||||||
|
```
|
||||||
|
|
||||||
|
`--run`: fight → draft from unlocked packs → assign Holder → simple upgrade → next fight (default 3). `--auto` picks for both seats and between-fight choices.
|
||||||
|
|
||||||
|
Browser client: http://127.0.0.1:5088 — you play P0; P1 auto-resolves after each of your actions.
|
||||||
|
|
||||||
|
Interactive combat: enter action numbers for P0; P1 is AI. `a` = auto one step for P0, `q` = quit.
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
# AI
|
||||||
|
|
||||||
|
AI v1 consumes `Game.Core`. It does not reimplement rules.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- Own current turn only
|
||||||
|
- Not: MCTS, minimax, multi-turn planning, predicting the player’s next turn, predicting player responses, LLM play
|
||||||
|
|
||||||
|
Pipeline: legal actions → simulate (`Clone` + `Execute`) → evaluate → shallow beam search → near-optimal pick.
|
||||||
|
|
||||||
|
Implemented in `TurnBeamSearch` (Game.Core). Playtest client shows each AI step’s top scored candidates.
|
||||||
|
|
||||||
|
## Core loop
|
||||||
|
|
||||||
|
```
|
||||||
|
state
|
||||||
|
→ GetLegalActions
|
||||||
|
→ simulate each action (CloneState + Execute)
|
||||||
|
→ evaluate
|
||||||
|
→ keep best candidates (beam width)
|
||||||
|
→ expand
|
||||||
|
→ stop at EndTurn or depth limit
|
||||||
|
```
|
||||||
|
|
||||||
|
After each **real** action, search again. Do not search the opponent’s next turn.
|
||||||
|
|
||||||
|
## Evaluation terms
|
||||||
|
|
||||||
|
`PlayerDamage`, `EnemyUnitRemoved`, `OwnBoardValue`, `EnemyBoardValue`, `Heal`, `Draw`, `Energy`, `HandValue`, `CountdownValue`, `StatusValue`.
|
||||||
|
|
||||||
|
Killing the player: `WIN_SCORE` far above ordinary terms. Weights per enemy and board (e.g. more draw weight on empty hand). Do not show weights in UI.
|
||||||
|
|
||||||
|
## Near-optimal randomness
|
||||||
|
|
||||||
|
Compute `BestScore`. Sample uniformly (via `GameRng`) among lines with `Score >= BestScore * Threshold`. Avoids both obvious blunders and fully deterministic puppeteering.
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
Limit beam width and action depth. Dedupe identical/equivalent states. Do not simulate player responses in v1. Deep-copy until clone/`Execute` is the measured bottleneck.
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
# ARCHITECTURE
|
||||||
|
|
||||||
|
Current prototype implementation. May be rewritten.
|
||||||
|
|
||||||
|
Source: *AI-First Technical Architecture Plan · v0.1*, Layer 2.
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
- Godot .NET — later Steam/desktop presentation
|
||||||
|
- `Game.Client` — current playtest browser; no rules
|
||||||
|
- C# / .NET 8 — rules and tools
|
||||||
|
- JSON + JSON Schema — content
|
||||||
|
- xUnit — tests
|
||||||
|
- First platform: Windows desktop / Steam prototype
|
||||||
|
|
||||||
|
## Repository layout
|
||||||
|
|
||||||
|
```
|
||||||
|
src/Game.Core
|
||||||
|
src/Game.AI
|
||||||
|
src/Game.Content
|
||||||
|
src/Game.Client
|
||||||
|
tests/Game.Core.Tests
|
||||||
|
tests/Game.AI.Tests
|
||||||
|
tests/Game.Content.Tests
|
||||||
|
content/cards
|
||||||
|
content/characters
|
||||||
|
content/enemies
|
||||||
|
content/encounters
|
||||||
|
schemas
|
||||||
|
tools
|
||||||
|
docs
|
||||||
|
AGENTS.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Game.Core
|
||||||
|
|
||||||
|
Pure C#; no Godot reference.
|
||||||
|
|
||||||
|
v1 types: `GameState`, `PlayerState`, `Entity`, `CardDefinition`, `CardInstance`, `Zone`, `GameAction`, `GameEvent`, `Effect`, `Condition`, `Trigger`, `ResponseWindow`, `GameRng`.
|
||||||
|
|
||||||
|
Do not build a universal framework. Implement only what current rules need.
|
||||||
|
|
||||||
|
## Core API
|
||||||
|
|
||||||
|
```
|
||||||
|
GetLegalActions(state)
|
||||||
|
Execute(state, action)
|
||||||
|
CloneState(state)
|
||||||
|
ResolveUntilStable(state)
|
||||||
|
```
|
||||||
|
|
||||||
|
AI and the player client must call this same pair of legal-action and execute paths.
|
||||||
|
|
||||||
|
## Action vs event
|
||||||
|
|
||||||
|
- `GameAction`: intent — `PlayCard`, `Attack`, `Activate`, `Respond`, `EndTurn`, `ChooseTarget`
|
||||||
|
- `GameEvent`: what happened — `Damage`, `Heal`, `Draw`, `Discard`, `Destroy`, `EnterField`, `LeaveField`, `CountdownChanged`, `SupplyGenerated`
|
||||||
|
|
||||||
|
Rules process actions. UI plays events.
|
||||||
|
|
||||||
|
## Atomic resolution and response
|
||||||
|
|
||||||
|
Complex effects split into atomic steps. An atomic step is an engine unit, not a player-response unit. Response windows open only at rule-defined timings.
|
||||||
|
|
||||||
|
Do not implement free “anywhere second-speed.” Responses bind to events (on attack, before damage, on target, on enter, about to destroy). Response is not a card type. Show a window only if a legal response exists.
|
||||||
|
|
||||||
|
## Content model
|
||||||
|
|
||||||
|
Card types v1: Creature / Spell / Field. Long-lived entities share one battlefield. Board size 10 is a test parameter.
|
||||||
|
|
||||||
|
## Standard effects
|
||||||
|
|
||||||
|
`Damage`, `Heal`, `Draw`, `Discard`, `Destroy`, `Summon`, `MoveCard`, `ModifyStat`, `ModifyCountdown`, `AddKeyword`, `RemoveKeyword`, `GenerateEnergy`, `AddStatus`, `RemoveStatus`.
|
||||||
|
|
||||||
|
Ordinary cards compose these. Custom C# handlers only when composition is unreasonable. Do not invent a card scripting language.
|
||||||
|
|
||||||
|
## Validation, RNG, replay
|
||||||
|
|
||||||
|
CLI validator (later): unique IDs, dangling refs, structure, required fields, numeric ranges, tags, effect args. PT and dead-ref reports later.
|
||||||
|
|
||||||
|
All randomness: `GameRng`. `GameState` stores RNG. Tests pin seed. A battle records initial state, seed, and action sequence for 100% replay.
|
||||||
|
|
||||||
|
## AI v1 (see `docs/AI.md`)
|
||||||
|
|
||||||
|
Current-turn only. No MCTS, minimax, multi-turn planning, opponent-future prediction, response prediction, or LLM AI.
|
||||||
|
|
||||||
|
Shallow beam search + near-optimal randomness among scores `≥ BestScore × Threshold`.
|
||||||
|
|
||||||
|
Performance depends on clone + `Execute` speed, not algorithm prestige. Deep-copy until it hurts.
|
||||||
|
|
||||||
|
## PT v1
|
||||||
|
|
||||||
|
Linear value table for damage, heal, draw, energy, attack, health. DelayedValue ≈ BaseValue × DelayDiscount. No forced PT for highly contextual effects.
|
||||||
|
|
||||||
|
## Godot client (Phase F)
|
||||||
|
|
||||||
|
`Render(GameState)` and `Input → GameAction → Core.Execute → GameEvents → Animation`. Never “fix” a rules bug in animation code.
|
||||||
|
|
||||||
|
## Prototype order
|
||||||
|
|
||||||
|
| Phase | Focus | Deliverable |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| A | Core skeleton | Entities, zones, state, action, event, RNG, clone, tests; no UI |
|
||||||
|
| B | Basic combat | Draw, hand, energy, creatures, fields, attack, damage, heal, destroy, GY, turns |
|
||||||
|
| C | Special rules | Cost, effect text, trigger, response, countdown, supply, fatigue, tags |
|
||||||
|
| D | Headless battle | Full fight in CLI |
|
||||||
|
| E | AI | Random legal → utility → current-turn beam |
|
||||||
|
| F | Godot client | Hand, board, HP, energy, targeting, response prompt, basic anim |
|
||||||
|
| G | Content tools | Schema, validator, PT report, load tests |
|
||||||
|
| H | Vertical slice | First characters, cards, fields, enemies, one boss |
|
||||||
|
|
||||||
|
**Current milestone: Phase A.** Do not start Godot until Core + tests + a CLI fight can draw, play, attack, end turn, and run a simple AI.
|
||||||
|
|
||||||
|
## Success
|
||||||
|
|
||||||
|
Headless full combat; full replay; automated core tests; ordinary new cards usually need no code; AI legal turns and 2–4 card combos; sim fast enough for live decisions; Godot only presents the same simulation.
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
# CONTENT_SCHEMA
|
||||||
|
|
||||||
|
Draft formats. Change this file together with JSON Schema and any validator.
|
||||||
|
|
||||||
|
Ordinary cards are data + standard effects. `customEffect` is an escape hatch.
|
||||||
|
|
||||||
|
## Identity
|
||||||
|
|
||||||
|
- `id`: stable string, unique in its catalog
|
||||||
|
- `name`: display name
|
||||||
|
- `tags`: string array, `kebab-case`
|
||||||
|
|
||||||
|
## Cards (`content/cards/*.json`)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "card.example.spark",
|
||||||
|
"name": "Spark",
|
||||||
|
"type": "Spell",
|
||||||
|
"cost": 1,
|
||||||
|
"tags": ["damage"],
|
||||||
|
"effects": [
|
||||||
|
{ "op": "Damage", "amount": 1, "target": "ChosenEnemy" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`type`: `Creature` | `Spell` | `Field`.
|
||||||
|
|
||||||
|
Creatures/fields may add `attack`, `health`, `countdown`, `triggers`, `activate`.
|
||||||
|
|
||||||
|
### Activate
|
||||||
|
|
||||||
|
```json
|
||||||
|
"activate": [{ "op": "TransferStatsThenDie" }],
|
||||||
|
"activateNeedsFriendly": true
|
||||||
|
```
|
||||||
|
|
||||||
|
`activateNeedsEnemy` for targeted enemy activates. Default: once per own turn; usable the turn the creature enters.
|
||||||
|
|
||||||
|
### Play targeting flags
|
||||||
|
|
||||||
|
`playNeedsFriendly`, `playNeedsEnemy` (e.g. Vile Fire needs both).
|
||||||
|
|
||||||
|
### Tags (v1, partial)
|
||||||
|
|
||||||
|
Only these are recognized by rules/content checks. Extra kebab-case tags may exist as data and are ignored by the engine.
|
||||||
|
|
||||||
|
`damage`, `heal`, `creature`, `field`, `supply`, `countdown`
|
||||||
|
|
||||||
|
Do not grow this list unless a rule actually reads the new tag.
|
||||||
|
|
||||||
|
### Triggers
|
||||||
|
|
||||||
|
`when`: `TurnStart` | `OnDestroyed`. Effects use the same `op` list as play effects.
|
||||||
|
|
||||||
|
`effects` on a card are on-play / on-cast. Do not duplicate them as `OnEnter` yet.
|
||||||
|
|
||||||
|
## Standard `op` values
|
||||||
|
|
||||||
|
`Damage`, `Heal`, `Draw`, `Discard`, `Destroy`, `Summon`, `MoveCard`, `ModifyStat`, `ModifyCountdown`, `AddKeyword`, `RemoveKeyword`, `GenerateEnergy`, `AddStatus`, `RemoveStatus`.
|
||||||
|
|
||||||
|
Unknown `op` is a content error unless `customEffect` names a registered C# handler.
|
||||||
|
|
||||||
|
## Characters (`content/characters/*.json`)
|
||||||
|
|
||||||
|
Each character is **starting deck + skill** (see `docs/DESIGN.md`). `coreActions` / `bridge` are design metadata.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "char.example.ash",
|
||||||
|
"name": "Ash",
|
||||||
|
"coreActions": ["Damage", "Heal"],
|
||||||
|
"bridge": "Damage+Heal",
|
||||||
|
"skill": { "id": "skill.ash.ember", "summary": "..." },
|
||||||
|
"startingDeck": ["card.example.spark"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Packs (`content/packs/*.json`)
|
||||||
|
|
||||||
|
In-run reward pools. `base: true` is always available this run. Other packs unlock when **all** `allOf` conditions are true. Evaluation is live against current run stats and current owned cards (not sticky, not meta).
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "pack.example.vitality",
|
||||||
|
"name": "Vitality",
|
||||||
|
"base": false,
|
||||||
|
"coreActions": ["Heal"],
|
||||||
|
"cards": ["card.example.salve"],
|
||||||
|
"allOf": [{ "kind": "HealReceivedAtLeast", "amount": 2 }]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`kind`: `HealReceivedAtLeast`, `AttacksAtLeast`, `FriendlyDestroyedAtLeast`, `FatigueAtLeast`, `DeckTagAtLeast` (+ `tag`), `KillTagAtLeast` (+ `tag`).
|
||||||
|
|
||||||
|
World tags such as `cult` may appear in `KillTagAtLeast` without being v1 rules tags.
|
||||||
|
|
||||||
|
## Enemies / encounters
|
||||||
|
|
||||||
|
Same effect language as cards where possible. AI weights live in enemy data (`docs/AI.md`), not in the engine.
|
||||||
|
|
||||||
|
## Validation (Phase G)
|
||||||
|
|
||||||
|
Unique ids, existing references, required fields, numeric ranges, tag shape, effect arguments. PT report is optional and must not block load of contextual cards.
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# DESIGN
|
||||||
|
|
||||||
|
Design freeze for content validation. Do not expand primitive rules in this phase.
|
||||||
|
|
||||||
|
Source: *Next-Step Design Direction · v0.6*.
|
||||||
|
|
||||||
|
## Stage conclusion
|
||||||
|
|
||||||
|
Current primitives are enough to validate content. Freeze world direction, the core action dictionary, and **in-run card-pack triggers**. Design characters, cards, packs, and a minimum playable slice on top of them. Do not keep inventing new base rules.
|
||||||
|
|
||||||
|
## World and art
|
||||||
|
|
||||||
|
Do not treat “classic xianxia” as a hard frame. Prefer Japanese-fantasy / Eastern-fantasy mix: keep cultivation, yao/mo, spirits, sects, martial arts, and rites; also allow machines, otherfolk, alchemy, ruins, and magitech.
|
||||||
|
|
||||||
|
Worldbuilding explains mechanics, characters, and fantasy. It does not veto mechanics. If a mechanic is fun, find a diegetic reading; do not delete it for genre purity.
|
||||||
|
|
||||||
|
## Core action dictionary (freeze)
|
||||||
|
|
||||||
|
These 11 should exist as primitive events or base state changes. Archetypes are combinations, conditions, or conversions — not new primitives.
|
||||||
|
|
||||||
|
| Action | Meaning |
|
||||||
|
| --- | --- |
|
||||||
|
| Damage taken | Life falls from a damage event. Paying life vs losing life may stay distinct in rules; decks may still connect them. |
|
||||||
|
| Heal | Restore life. Links to damage, half HP, overheal, max HP. |
|
||||||
|
| Attack | Creature or character attacks. May emit on-attack / after-attack / combat-destroy. |
|
||||||
|
| Destroy | Entity is destroyed by rule or effect, then leaves the field. Record combat vs effect destroy. |
|
||||||
|
| Enter field | Entity enters the battlefield. Summon, flash, special enter all map here. |
|
||||||
|
| Draw | Take a card from deck. Links to hand size, fatigue, GY, deck thickness. |
|
||||||
|
| Fatigue | Draw with empty deck: damage/penalty, not instant loss. Empty deck can be a strategy. |
|
||||||
|
| Discard | Hand → GY, active or forced. Equivalence with other “hand to GY” is a grammar question. |
|
||||||
|
| Supply | Generate cost at a timing. Not a land keyword; creatures, characters, and fields may have it. |
|
||||||
|
| Graveyard | A real zone for revive, recycle, copy, count, conditions — a resource domain, not only a log. |
|
||||||
|
| Countdown | Lifetime/progress that ticks down; at 0, follow that entity’s rule (often destroy). Fields, creatures, tokens. |
|
||||||
|
|
||||||
|
**Not primitives:** half-HP, last rites, combo, haste, self-harm, heal-deck, swarm, GY-deck, economy-deck. Those are conditions + core actions, combinations, keyword templates, or archetype names.
|
||||||
|
|
||||||
|
## Combination-first
|
||||||
|
|
||||||
|
Design from edges between core actions, not from “a complete archetype.”
|
||||||
|
|
||||||
|
- Damage + Heal → self-harm, low-life, yo-yo, heal-to-damage
|
||||||
|
- Enter + Destroy → swarm, sacrifice, last rites, deathrattle, combo
|
||||||
|
- Draw + Fatigue → thin deck, turbo draw, empty-deck payoff, fatigue tank
|
||||||
|
- Supply + Countdown → burst economy, long investment, borrow-from-future, lifespan extend, protect the engine
|
||||||
|
- GY + Enter / Destroy → revive, reuse, farm GY, copy, recycle
|
||||||
|
|
||||||
|
## In-run card packs
|
||||||
|
|
||||||
|
Character identity is **starting deck + character skill**, not a fixed class pool. Random rewards use **in-run pack unlocks**.
|
||||||
|
|
||||||
|
- The starting deck sets where the player sits on the action graph. The skill changes the value of those actions.
|
||||||
|
- The full card catalog is grouped into small packs. A pack has a few core-action cards and bridges. It enters this run’s reward pool only after its conditions are met.
|
||||||
|
- All pack state belongs to the current run and **resets when the run ends**. No cross-run unlock. This is not meta progression.
|
||||||
|
|
||||||
|
Conditions may AND together:
|
||||||
|
|
||||||
|
| Kind | Reads |
|
||||||
|
| --- | --- |
|
||||||
|
| Deck | Core-action tags present, counted, or combined |
|
||||||
|
| Combat | Heal received, attacks, friendly destroys, fatigue, … |
|
||||||
|
| World | Kill enemies with a world tag, factions, bosses, regions, events (world tags are data; not the v1 rules-tag allowlist) |
|
||||||
|
| Mix | e.g. GY-tagged deck + many friendly destroys + kills of a tagged enemy |
|
||||||
|
|
||||||
|
Examples: heal a threshold → vitality pack; kill N `cult` enemies → faction/loot pack. **Numeric thresholds are not frozen.**
|
||||||
|
|
||||||
|
Rewards draft from the always-on base pack plus unlocked packs. Multi-tag cards are bridges: they can satisfy new packs and grow this run’s pool.
|
||||||
|
|
||||||
|
Conditions describe how this run was built, fought, and traveled — not class gates. Internal thresholds may be exact; **do not show a progress bar** that invites grinding.
|
||||||
|
|
||||||
|
**Not frozen yet:** whether an unlocked pack stays on for the rest of the run; whether removing cards can lock it again; reward weights among active packs. Decide after Vertical Slice play.
|
||||||
|
|
||||||
|
## Vertical Slice 0.1
|
||||||
|
|
||||||
|
Goal: prove the action graph grows distinct builds. Not balance, not a full world.
|
||||||
|
|
||||||
|
1. Draw the 11-node graph: natural links, needed bridges, deliberate isolation.
|
||||||
|
2. Character = starting deck + skill → now **Faction = held cards + relic + upgrades**. Deck touches 2–3 core actions and at least one bridge.
|
||||||
|
3. Small content: two starting factions (官兵/魔教), guaranteed field, later in-run packs.
|
||||||
|
4. Validate: same start + different pack paths → different builds; multi-tag cards open packs; combat/world conditions do not invite empty grinding.
|
||||||
|
|
||||||
|
**Expansion pack factions** (death / draw / GY / enter) are scheduled with the later content/art pass — after CLI combat and CLI full-run work.
|
||||||
|
|
||||||
|
## Current non-goals
|
||||||
|
|
||||||
|
- Full class system and final class pool ratios
|
||||||
|
- World map, long-run event pool, **cross-run pack unlocks**, full meta progression
|
||||||
|
- Final art, factions, lore wiki
|
||||||
|
- Exact card balance and final PT
|
||||||
|
- Advanced AI (multi-turn, opponent-response simulation)
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
# FOUNDATION
|
||||||
|
|
||||||
|
Long-lived engineering principles. Change only if the project itself changes.
|
||||||
|
|
||||||
|
Source: *AI-First Technical Architecture Plan · v0.1*, Layer 1.
|
||||||
|
|
||||||
|
## Project nature
|
||||||
|
|
||||||
|
Single-player PvE card roguelike: light presentation, heavy rules, data, and simulation.
|
||||||
|
|
||||||
|
Serve:
|
||||||
|
|
||||||
|
- Fast add/edit of cards, characters, enemies, and fields
|
||||||
|
- Dense rule interaction and deterministic resolution
|
||||||
|
- AI simulating action sequences within the current turn
|
||||||
|
- Automated tests, headless batch simulation, and balance analysis
|
||||||
|
- High-frequency AI coding, refactor, and iteration
|
||||||
|
|
||||||
|
Do not prioritize AAA graphics, physics, multiplayer sync, or open-world systems.
|
||||||
|
|
||||||
|
## Simulation vs presentation
|
||||||
|
|
||||||
|
| World | Owns | Must not own |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Simulation | Rules, entities, zones, cost, events, triggers, responses, AI, RNG, combat state | Godot, UI, animation, audio, scene nodes |
|
||||||
|
| Presentation | Display `GameState`, input, play `GameEvent`, UI, VFX, SFX | Rules |
|
||||||
|
|
||||||
|
UI may express rules. UI must not possess rules.
|
||||||
|
|
||||||
|
## Technology direction
|
||||||
|
|
||||||
|
- Godot .NET: client / presentation
|
||||||
|
- C#: primary language and a Godot-free simulation core
|
||||||
|
- JSON (or similar text) for content
|
||||||
|
- Git for code and data
|
||||||
|
- Automated tests on core rules
|
||||||
|
|
||||||
|
Engine, library, and tool versions are implementation details, not this document.
|
||||||
|
|
||||||
|
## Deterministic simulation
|
||||||
|
|
||||||
|
Same initial state, seed, and action sequence → identical result. All randomness comes from game RNG state.
|
||||||
|
|
||||||
|
Must support `Seed + Action Log →` full combat replay. Determinism underpins AI search, bug reproduction, tests, balance sims, and playback.
|
||||||
|
|
||||||
|
## Simulation first
|
||||||
|
|
||||||
|
Every core mechanic must run without Godot, graphics, or animation. The AI simulator and the player rules engine are the same code. Do not ship a simplified second ruleset for AI.
|
||||||
|
|
||||||
|
## State must be simulatable
|
||||||
|
|
||||||
|
`GameState` must copy, mutate, compare, enumerate legal actions, and simulate a short distance.
|
||||||
|
|
||||||
|
v1 may deep-copy. Snapshot, copy-on-write, undo, or structural sharing only after a real performance need.
|
||||||
|
|
||||||
|
The goal is future-optimizable semantics, not premature optimization.
|
||||||
|
|
||||||
|
## Data-driven content
|
||||||
|
|
||||||
|
Most content is data + standard effects/events/conditions + tags. Rare designs may use `CustomEffect` / C# handlers.
|
||||||
|
|
||||||
|
Not “100% zero-code cards.” Ordinary content should be cheap; special content may still need code.
|
||||||
|
|
||||||
|
## Rules before cards
|
||||||
|
|
||||||
|
Do not change primitives to ship one card. A new mechanic must state: event type, atomic step, whether it is an effect, whether it opens a response window, who can listen, and how it is tested.
|
||||||
|
|
||||||
|
## AI consumes the rules engine
|
||||||
|
|
||||||
|
AI does not own rules. The engine exposes visible state and legal actions; AI only simulates and chooses.
|
||||||
|
|
||||||
|
v1 is not global-optimal play. Enemies should notice simple in-turn combos and express style through result weights.
|
||||||
|
|
||||||
|
## AI evaluation language
|
||||||
|
|
||||||
|
Score **outcomes**, not card names: player damage, board clear, own board, heal, draw, energy, hand value, countdown, status, etc.
|
||||||
|
|
||||||
|
Per-enemy weights may shift with the board (e.g. draw weight rises when the hand is empty). Weights are not shown to the player.
|
||||||
|
|
||||||
|
## Content value model (PT)
|
||||||
|
|
||||||
|
Internal PT / value model for easy-to-quantify effects (damage, heal, draw, stats, cost, delayed value). PT is a balance reference, not an auto-card formula. Context-heavy copy, conversion, and open combos need not have exact PT.
|
||||||
|
|
||||||
|
## Tests are design work
|
||||||
|
|
||||||
|
Core rules have automated tests. Important bugfixes should leave a reproduction test. Prefer coverage of zones, event order, cost, effects, text, responses, destroy, graveyard, countdown, energy, and RNG determinism.
|
||||||
|
|
||||||
|
## AI coding
|
||||||
|
|
||||||
|
Prefer strong types, clear module edges, small APIs, tests, searchability, little implicit behavior, little global state.
|
||||||
|
|
||||||
|
The human does not need to read every implementation file, but must understand data, rule docs, test names, logs, and debug output.
|
||||||
|
|
||||||
|
## Documentation hierarchy
|
||||||
|
|
||||||
|
| Doc | Role |
|
||||||
|
| --- | --- |
|
||||||
|
| `FOUNDATION.md` | Stable principles |
|
||||||
|
| `ARCHITECTURE.md` | Current implementation; may be rewritten |
|
||||||
|
| `RULES.md` | Game rule grammar |
|
||||||
|
| `CONTENT_SCHEMA.md` | Data formats |
|
||||||
|
| `AI.md` | AI v1 |
|
||||||
|
| `DESIGN.md` | Design freeze (world + action dictionary) |
|
||||||
|
| `AGENTS.md` | Coding-agent constraints |
|
||||||
|
|
||||||
|
## Engineering loop
|
||||||
|
|
||||||
|
Design mechanic → change data/code → compile → tests → content validate → headless sim → client play → tune data → repeat.
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# RULES
|
||||||
|
|
||||||
|
Game grammar for the simulation. Aligned with *Core Game Rules · v0.1* and the CLI combat slice.
|
||||||
|
|
||||||
|
## Entities and zones
|
||||||
|
|
||||||
|
Long-lived combat entities share one battlefield (`Board`, capacity 10 including system fields).
|
||||||
|
|
||||||
|
Zones: deck, hand, board, graveyard. Card type 场域 is `FieldCard`.
|
||||||
|
|
||||||
|
**CardInstance** keeps current `Cost` / stats / `HolderId` across zone moves. `CardDefinition` is the template only.
|
||||||
|
|
||||||
|
## Faction & Holder
|
||||||
|
|
||||||
|
A player’s held cards from all factions merge into one shared deck/hand/graveyard for combat. `HolderId` records which faction owns the instance (for upgrades). Who may draw/play is not limited by Holder.
|
||||||
|
|
||||||
|
## In-run progression (CLI full run)
|
||||||
|
|
||||||
|
`RunProgress` owns the persistent card list between fights. Combat mutations do not write back; only reward adds and explicit simple upgrades (`ReduceCost` / `+Attack` / `+Health`) change owned copies. `RunStats` and pack unlock evaluation are live for the current run and reset when the run ends.
|
||||||
|
|
||||||
|
## Economy
|
||||||
|
|
||||||
|
No automatic mana ramp. Energy persists and increases when effects/fields **GenerateEnergy** (supply).
|
||||||
|
|
||||||
|
**Guaranteed system field** (`card.system.base_field`): 0C, TurnStart supply 1, countdown 3. Placed on board at match start; not part of the 8+8 held cards; not in the draw pile.
|
||||||
|
|
||||||
|
## Attack & Activate
|
||||||
|
|
||||||
|
Independent once-per-turn resources (`AttacksRemaining`, `ActivatesRemaining`).
|
||||||
|
|
||||||
|
- Enter on your turn: attack count 0, activate count 1 (if any).
|
||||||
|
- Enter via opponent-turn-end summon: next BeginTurn grants attack count.
|
||||||
|
- Attack requires effective attack > 0.
|
||||||
|
- Activate does not consume attack and may be used the turn a creature enters.
|
||||||
|
|
||||||
|
## Damage / Heal
|
||||||
|
|
||||||
|
Events record Requested and Applied amounts. Applied may be 0.
|
||||||
|
|
||||||
|
## Countdown
|
||||||
|
|
||||||
|
At end of controller’s turn, countdown > 0 decreases by 1; at 0 the entity is destroyed (including system field).
|
||||||
|
|
||||||
|
## Triggers (v1)
|
||||||
|
|
||||||
|
`TurnStart`, `OnDestroyed`, `OnEnterBoard` (listeners when a **creature** enters), `OpponentTurnEnd`, `OnSupply`.
|
||||||
|
|
||||||
|
## Core actions
|
||||||
|
|
||||||
|
Unchanged 11-action dictionary in `docs/DESIGN.md`.
|
||||||
|
|
||||||
|
## Randomness
|
||||||
|
|
||||||
|
Only `GameRng`. Same seed + action log must replay.
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
# Surfaces
|
||||||
|
|
||||||
|
How the project ships two usable fronts on **one** rules engine.
|
||||||
|
|
||||||
|
This is product/engineering framing, not a second ruleset.
|
||||||
|
|
||||||
|
## Verdict
|
||||||
|
|
||||||
|
Yes. We maintain **two surfaces**:
|
||||||
|
|
||||||
|
| # | Surface | Who uses it | Job |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | **Headless** | Dev, AI, batch sims, CI, balance | Fast, no UI (or terminal-only). Same rules as the player game. |
|
||||||
|
| 2 | **Interactive** | Human players (and playtesters) | Click/type legal actions; see `GameState` and play `GameEvent`s. |
|
||||||
|
|
||||||
|
They are not two games. They are two **adapters** over `Game.Core` (+ in-run progression such as `RunProgress`).
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────┐
|
||||||
|
│ Game.Core │
|
||||||
|
│ rules · RNG · AI │
|
||||||
|
│ RunProgress · packs │
|
||||||
|
└──────────┬──────────┘
|
||||||
|
┌───────────────┼───────────────┐
|
||||||
|
▼ ▼
|
||||||
|
Headless surface Interactive surface
|
||||||
|
tools / tests / batch playtest client → later Godot
|
||||||
|
```
|
||||||
|
|
||||||
|
## 1. Headless surface
|
||||||
|
|
||||||
|
**Purpose:** AI search, auto fights, full-run loops, regression tests, future balance batches. Anything that must be cheap to run and easy to pin with a seed.
|
||||||
|
|
||||||
|
**Today:**
|
||||||
|
|
||||||
|
- `tools/HeadlessBattle` — single fight and `--run` full run (terminal I/O is convenience, not a second rules layer)
|
||||||
|
- `tests/Game.Core.Tests` — pinned scenarios
|
||||||
|
- In-process AI (`TurnBeamSearch`) calling `GetLegalActions` / `Execute` / `Clone`
|
||||||
|
|
||||||
|
**Later (same surface family):**
|
||||||
|
|
||||||
|
- Batch sims / PT reports / seed sweeps
|
||||||
|
- Headless full-run without printing every step
|
||||||
|
- Replay from `seed + action log`
|
||||||
|
|
||||||
|
**Must:**
|
||||||
|
|
||||||
|
- Depend only on Core (and content JSON), never on Godot or browser UI
|
||||||
|
- Stay deterministic: same seed + choices → same result
|
||||||
|
- Be the path AI and automated tests use in production of balance data
|
||||||
|
|
||||||
|
**Must not:**
|
||||||
|
|
||||||
|
- Reimplement card text, pack unlocks, or combat resolution “for speed”
|
||||||
|
- Become a simplified AI-only ruleset
|
||||||
|
|
||||||
|
## 2. Interactive surface
|
||||||
|
|
||||||
|
**Purpose:** What a human actually plays. Input becomes `GameAction`; screen shows state and events. Presentation only.
|
||||||
|
|
||||||
|
**Today:**
|
||||||
|
|
||||||
|
- `Game.Client` — browser playtest (emoji/SVG placeholders)
|
||||||
|
- Optional: interactive mode of `HeadlessBattle` (numbered legal actions) as a thin terminal client — still surface 2 in spirit, but not the player-facing target
|
||||||
|
|
||||||
|
**Later:**
|
||||||
|
|
||||||
|
- Godot .NET desktop / Steam presentation
|
||||||
|
- Art pass replaces placeholders; rules stay in Core
|
||||||
|
|
||||||
|
**Must:**
|
||||||
|
|
||||||
|
- Call the same Core APIs as headless (`GetLegalActions`, `Execute`, …)
|
||||||
|
- Own layout, input, animation, audio, UX copy
|
||||||
|
- Refuse to “fix” rules bugs in the UI
|
||||||
|
|
||||||
|
**Must not:**
|
||||||
|
|
||||||
|
- Encode damage formulas, pack conditions, legal-move filters, or win checks locally
|
||||||
|
- Drift into a parallel progression model for “the real game”
|
||||||
|
|
||||||
|
## Shared core (the only rules owner)
|
||||||
|
|
||||||
|
Both surfaces consume:
|
||||||
|
|
||||||
|
- Combat: `GameState`, `GameAction`, `GameEvent`, `GameRng`
|
||||||
|
- Progression: `RunProgress`, rewards, Holder, simple upgrades, in-run packs
|
||||||
|
- AI: evaluation + current-turn search that **reads** Core, does not own it
|
||||||
|
|
||||||
|
If a feature matters for “how the game works,” it lands in Core (and tests) first. Surfaces only expose or automate it.
|
||||||
|
|
||||||
|
## How work usually splits
|
||||||
|
|
||||||
|
| Change type | Land in | Headless | Interactive |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| New rule / card effect / pack condition | Core + tests | exercise via CLI/tests | render/input only if needed |
|
||||||
|
| Faster AI / batch balance | Core + tools | primary | optional “show AI candidates” |
|
||||||
|
| UX, targeting UX, animation, art | Client (later Godot) | no | primary |
|
||||||
|
| Full-run loop (reward → upgrade → next fight) | Core + headless first | prove it | then wire the same loop in the client |
|
||||||
|
|
||||||
|
Default order stays **simulation first**: headless (or tests) prove the loop; interactive follows.
|
||||||
|
|
||||||
|
## Roadmap mapping
|
||||||
|
|
||||||
|
1. CLI combat / CLI full run → headless surface (also a crude interactive terminal)
|
||||||
|
2. Playtest client → interactive surface v1
|
||||||
|
3. Art pass / Godot → interactive surface v2 (same Core)
|
||||||
|
4. Expansion packs → content + Core; both surfaces pick them up automatically
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- Two rulebooks (player vs AI)
|
||||||
|
- Meta / cross-run unlocks on either surface
|
||||||
|
- Making the browser client a permanent architecture requirement (it is a playtest skin; Godot may replace it)
|
||||||
|
|
||||||
|
## Related docs
|
||||||
|
|
||||||
|
- Principles: `docs/FOUNDATION.md` (simulation vs presentation)
|
||||||
|
- Layout & phases: `docs/ARCHITECTURE.md`
|
||||||
|
- AI constraints: `docs/AI.md`
|
||||||
|
- Run / Holder grammar: `docs/RULES.md`
|
||||||
Reference in New Issue
Block a user