No description
  • Python 67.8%
  • Lua 32.2%
Find a file
McNieps 735d1ae17e Migrate plugins onto unified attributes; drop the effectiveness effect types
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
2026-08-08 20:16:17 +02:00
.claude Implement bound constraints, genericize charge-gain/ready hooks, unify attributes 2026-07-26 18:59:58 +02:00
battle Migrate plugins onto unified attributes; drop the effectiveness effect types 2026-08-08 20:16:17 +02:00
docs Migrate plugins onto unified attributes; drop the effectiveness effect types 2026-08-08 20:16:17 +02:00
scripts Migrate plugins onto unified attributes; drop the effectiveness effect types 2026-08-08 20:16:17 +02:00
world docs: add rune/weapon/event design rationale for playtest batch v1 2026-06-13 20:28:36 +02:00
.gitignore chore: layout graph positions, faction Lua helper, doc link fixes, sprite generator 2026-06-12 01:11:35 +02:00
CLAUDE.md Migrate plugins onto unified attributes; drop the effectiveness effect types 2026-08-08 20:16:17 +02:00
off-index-rune-weapon-synthesis.md Migrate plugins onto unified attributes; drop the effectiveness effect types 2026-08-08 20:16:17 +02:00
pyproject.toml feat: Lua engine integration — comparison script, profiler, serialization 2026-05-20 16:04:40 +02:00
README.md Implement bound constraints, genericize charge-gain/ready hooks, unify attributes 2026-07-26 18:59:58 +02:00
TODO.md Migrate plugins onto unified attributes; drop the effectiveness effect types 2026-08-08 20:16:17 +02:00
uv.lock feat: Lua engine integration — comparison script, profiler, serialization 2026-05-20 16:04:40 +02:00

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.