- Python 67.8%
- Lua 32.2%
Two batches of work that had accumulated uncommitted in this repo, plus the Python/docs half of battle_engine TODO #29. Plugin migration (attributes unification, battle_engine TODO #25). The layout, faction, status and persistence plugins and their tests move onto the unified attribute model; battle_engine's tests/run.lua re-enables all four suites, so the two repos now have to move together — `luajit tests/run.lua` reads across the repo boundary and fails if this repo lags. Adds a shared plugins/_effect_tree.lua helper, a layout Python-roundtrip test with a generated fixture, and scripts/gen_layout_roundtrip_fixture.py that produces it. Effectiveness removal, Python side. The engine deleted the effectiveness subsystem and no longer dispatches either effect type, so ModifyEffectivenessEffect / ModifyEffectivenessCapEffect were constructors that built effects failing at runtime. Removed from models/effects.py (classes, Effect union, __all__), their two display branches, and their two dedicated test_display cases. test_scale_value_resolves_in_effect_context was NOT deleted — it used ModifyEffectivenessEffect only as a vehicle and actually asserts two-part ScaleValue rendering, so it is re-hosted onto AlterAttributeEffect. Suite 159 -> 157. Docs sweep for the same removal, across description_syntax, clause_dispatch_spec, weapon_design_rationale, known_gaps, CLAUDE.md and verb_vocabulary — the last of which was not on the checklist and had the most: the Whet/Blunt/Temper rows and the undefined-keyword tally. Temper is retired with no successor, since it named modify_effectiveness_cap and keen's reciprocal mapping is asymptotic. Where the replacement `keen` attribute is named, it is marked unbuilt rather than described as available. Adds TODO.md (items 1-3 from the fifth-weapon design session, plus #4 recording that `uv run pytest` is broken here: pyproject declares an editable dependency on battle_engine, which has no pyproject.toml since it became LuaJIT-only). Tests: 157 passing via `python -m pytest`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X5FSJmTaHxyg1kBoBeyHwN |
||
|---|---|---|
| .claude | ||
| battle | ||
| docs | ||
| scripts | ||
| world | ||
| .gitignore | ||
| CLAUDE.md | ||
| off-index-rune-weapon-synthesis.md | ||
| pyproject.toml | ||
| README.md | ||
| TODO.md | ||
| uv.lock | ||
Data
Game content definitions. Pure data, no engine logic. Changes here affect balance and content without touching battle_engine, server, or world_generation.
Format
Python files instantiating models from battle/models/. One file per concept (rune name, weapon name), named by concept ID. Content files import exclusively from battle.models — never from battle_engine or battle/plugins/ directly.
A file may contain multiple tier variants as separate entry variables, listed in __all__ in ascending tier order:
# runes/the_bell.py
from battle.models import entries, Clause, triggers, effects, value_sources, query
from battle.models import Tier
the_bell_novice = entries.RuneEntry(
id="the_bell_novice",
tier=Tier.NOVICE,
name="Bell",
index_status="listed",
clauses=[
Clause(
trigger=triggers.OnAttackTrigger(),
effect=effects.DamageEffect(targets=query.ENEMY_TEAM, amount=value_sources.Flat(2)),
),
],
)
the_bell_journeyman = entries.RuneEntry(
id="the_bell_journeyman",
tier=Tier.JOURNEYMAN,
previous_tier=the_bell_novice,
clauses=[...], # delta only — novice clause accumulated automatically via collect_clauses()
)
the_bell_novice.next_tier = the_bell_journeyman
__all__ = ["the_bell_novice", "the_bell_journeyman"]
Clause.effect is a single required effect — a leaf effect or one of five composite
nodes (Sequence, Pipeline, Gated, Repeat, Spend) that express costs, ordering,
and repetition. See CLAUDE.md's Clause authoring section for the common
"pay a cost, then do N things" shape (effects.ChargeGatedEffects).
Full IDE autocompletion. Pydantic validates on construction. No JSON schemas to maintain.
Structure
data/
docs/ # Writing guide and content authoring references
plugins/ # Engine extension plugins — Lua runtime only (layout, faction, status)
models/ # Author-facing model library — the only import source for content files
entries/ # RuneEntry, WeaponEntry, TeamEntry, WorldEntry, SlotEntry, SlotEdge, SlotGraph, Tier
runes/ # RuneEntry definitions (one file per rune, all tiers)
weapons/ # WeaponEntry definitions
world/
encounters/ # Events that happen on the map
geography/ # Terrain types, biomes, movement costs
topography/ # Layers, zones, landmarks
scripts/ # Utility scripts (not definitions)
Entry Types
The battle/models/entries/ package provides entry types that wrap the engine's game-agnostic models:
| Entry type | Represents | Key method |
|---|---|---|
RuneEntry |
A rune at a specific tier | to_engine_instance(faction) |
WeaponEntry |
A weapon with rune slots | to_engine_instance(faction) |
TeamEntry |
A team with weapon slots | to_engine_instance() |
WorldEntry |
The battle root (two teams) | to_engine_instance() |
A battle is run as:
log = run_battle(world.to_engine_instance(), config)
Schema Ownership
Entry types in battle/models/ are the data layer's own models — changes to them are data changes only. Changes to the underlying engine models (EntityDefinition, Clause, triggers, effects) require an engine change first.
Asset Mapping
Sprites live in client/assets/. The id field is the contract:
runes/the_bell.py(id:the_bell_novice) →client/assets/runes/the_bell.png
See Also
CLAUDE.md— full authoring reference: models, target constants, HP model, scope boundaries, plugin system.runes/README.md— rune system design, naming conventions, tier model.docs/charge_system.md— source of truth for charge, attributes, production/toll, and loop-safety rules.docs/rune_design_rationale.md— single source of truth for rune design: design axes, tier model, and the morphological design grid.docs/writing_guide.md— description format and style rules for player-facing text.battle_engine/docs/keywords.md— canonical token→display-text mappings.