把规则、架构与实现路线写成仓库文档。
后续改内容或结算顺序时有冻结依据,不必再翻原始稿才能对齐。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+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