Skip to content

Instantly share code, notes, and snippets.

@mbutler
Last active October 15, 2025 01:02
Show Gist options
  • Select an option

  • Save mbutler/5e4f9725b348fe3ad6ce0b0319cde323 to your computer and use it in GitHub Desktop.

Select an option

Save mbutler/5e4f9725b348fe3ad6ce0b0319cde323 to your computer and use it in GitHub Desktop.
D&D 4e Power Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/4e-power.schema.json",
"title": "D&D 4e Power Schema with Augments",
"type": "object",
"required": ["name", "level", "type", "action_type", "range", "targeting"],
"properties": {
"name": { "type": "string" },
"level": { "type": "integer", "minimum": 1 },
"class": { "type": "string" },
"subtype": { "type": "string" },
"type": {
"type": "string",
"enum": ["At-Will", "Encounter", "Daily", "Utility", "Item", "Monster"]
},
"scope": { "type": "string", "enum": ["power", "item"] },
"usage": {
"type": "object",
"properties": {
"frequency": {
"type": "string",
"enum": ["At-Will", "Encounter", "Daily", "Recharge"]
},
"charges": { "type": "integer" },
"per": { "type": "string", "enum": ["character", "item"] },
"consumed_on": { "type": "string", "enum": ["activation", "hit"] }
}
},
"action_type": {
"type": "string",
"enum": ["Standard", "Move", "Minor", "Free", "Immediate Interrupt", "Immediate Reaction", "Opportunity", "No Action"]
},
"keywords": {
"type": "array",
"items": { "$ref": "#/$defs/keyword" }
},
"source": {
"type": "object",
"properties": {
"book": { "type": "string" },
"page": { "type": "integer" }
}
},
"flavor_text": { "type": "string" },
"range": {
"type": "object",
"required": ["type"],
"properties": {
"type": {
"type": "string",
"enum": ["Melee", "Ranged", "Close", "Area", "Personal", "Aura"]
},
"distance": { "type": "integer" },
"shape": { "type": "string", "enum": ["burst", "blast"] },
"radius": { "type": "integer" },
"reach": { "type": "integer" }
}
},
"targeting": {
"type": "object",
"required": ["target_type", "target_number"],
"properties": {
"target_type": { "type": "string" },
"target_number": { "type": "string" },
"target_ref": { "type": "string" },
"selection": { "type": "string" }
}
},
"attack": {
"type": "object",
"properties": {
"ability_used": { "type": "string" },
"vs_defense": { "type": "string", "enum": ["AC", "Fortitude", "Reflex", "Will"] },
"weapon_keyword": { "type": "boolean" },
"implement_keyword": { "type": "boolean" },
"on_hit": { "$ref": "#/$defs/hitEffect" },
"on_miss": { "$ref": "#/$defs/hitEffect" },
"critical": { "$ref": "#/$defs/hitEffect" }
}
},
"effects": {
"type": "array",
"items": { "$ref": "#/$defs/effectBlock" }
},
"augment": {
"type": "array",
"items": {
"type": "object",
"required": ["cost"],
"properties": {
"cost": { "type": "integer" },
"notes": { "type": "string" },
"attack": { "$ref": "#/properties/attack" },
"effects": {
"type": "array",
"items": { "$ref": "#/$defs/effectBlock" }
}
}
}
},
"sustain": { "$ref": "#/$defs/sustainBlock" },
"special": {
"type": "array",
"items": { "$ref": "#/$defs/specialBlock" }
},
"notes": { "type": "string" },
"custom_logic": { "type": "string" }
},
"$defs": {
"condition": {
"type": "string",
"enum": [
"Blinded", "Dazed", "Deafened", "Dominated", "Helpless",
"Immobilized", "Marked", "Petrified", "Prone", "Restrained",
"Slowed", "Stunned", "Surprised", "Unconscious", "Weakened",
"Ongoing Damage", "Grants Combat Advantage"
]
},
"damageType": {
"type": "string",
"enum": [
"Acid", "Cold", "Fire", "Force", "Lightning", "Necrotic",
"Poison", "Psychic", "Radiant", "Thunder", "Untyped"
]
},
"keyword": {
"type": "string",
"enum": [
"Arcane", "Divine", "Martial", "Primal", "Psionic", "Shadow",
"Healing", "Fear", "Charm", "Polymorph", "Teleportation",
"Invigorating", "Reliable", "Augmentable", "Zone", "Stance",
"Aura", "Conjuration", "Summoning", "Disease", "Gaze", "Poison",
"Sleep", "Spirit", "Mount", "Elemental", "Runic", "Enchantment",
"Evocation"
]
},
"damage": {
"type": "object",
"properties": {
"amount": { "type": "string" },
"type": { "$ref": "#/$defs/damageType" }
}
},
"hitEffect": {
"type": "object",
"properties": {
"damage": { "$ref": "#/$defs/damage" },
"effects": {
"type": "array",
"items": { "$ref": "#/$defs/effectBlock" }
}
}
},
"effectBlock": {
"type": "object",
"required": ["timing", "effect_type"],
"properties": {
"id": { "type": "string" },
"timing": {
"type": "string",
"enum": ["on_hit", "on_miss", "after_attack_resolution", "start_of_turn", "end_of_turn", "immediate", "triggered", "sustain", "reappearance"]
},
"trigger": { "$ref": "#/$defs/triggerCondition" },
"effect_type": {
"type": "string",
"enum": ["condition", "damage", "healing", "movement", "teleportation", "forced_movement", "zone", "summon", "conjuration", "buff", "debuff", "stance", "aura", "custom"]
},
"condition": { "$ref": "#/$defs/conditionEffect" },
"damage": { "$ref": "#/$defs/damage" },
"healing": {
"type": "object",
"properties": {
"amount": { "type": "string" },
"temp": { "type": "boolean" }
}
},
"movement": {
"type": "object",
"properties": {
"type": { "type": "string", "enum": ["slide", "push", "pull"] },
"distance": { "type": "integer" }
}
},
"teleportation": {
"type": "object",
"properties": {
"distance": { "type": "integer" }
}
},
"zone": { "$ref": "#/$defs/zoneEffect" },
"applies_while": { "$ref": "#/$defs/conditionExpr" },
"grants": {
"type": "array",
"items": { "$ref": "#/$defs/modifierGrant" }
},
"ends_if": {
"type": "array",
"items": { "$ref": "#/$defs/endCondition" }
},
"duration": { "$ref": "#/$defs/duration" },
"custom_text": { "type": "string" }
}
},
"triggerCondition": {
"type": "object",
"properties": {
"event": { "type": "string" },
"timing": { "type": "string" },
"source": { "type": "string" },
"context": { "type": "object" }
}
},
"conditionEffect": {
"type": "object",
"properties": {
"type": { "$ref": "#/$defs/condition" },
"target": { "type": "string" }
}
},
"modifierGrant": {
"type": "object",
"properties": {
"type": { "type": "string", "enum": ["bonus", "penalty", "override"] },
"target": { "type": "string" },
"attribute": { "type": "string" },
"amount": { "type": ["integer", "string"] },
"bonus_type": { "type": "string", "enum": ["power", "feat", "item", "untyped"] },
"applies_while": { "$ref": "#/$defs/conditionExpr" }
}
},
"conditionExpr": {
"type": "object",
"properties": {
"type": { "type": "string" },
"a": { "type": "string" },
"b": { "type": "string" },
"within_squares": { "type": "integer" },
"of": { "type": "string" },
"who": { "type": "string" },
"condition": { "$ref": "#/$defs/condition" },
"expr": { "$ref": "#/$defs/conditionExpr" },
"all": {
"type": "array",
"items": { "$ref": "#/$defs/conditionExpr" }
},
"any": {
"type": "array",
"items": { "$ref": "#/$defs/conditionExpr" }
}
}
},
"endCondition": {
"type": "object",
"properties": {
"type": { "type": "string" },
"expr": { "$ref": "#/$defs/endCondition" },
"condition": { "$ref": "#/$defs/conditionExpr" }
}
},
"duration": {
"oneOf": [
{ "type": "object", "properties": { "type": { "const": "save_ends" } } },
{
"type": "object",
"properties": {
"type": { "enum": ["end_of_next_turn", "start_of_next_turn"] },
"agent": { "enum": ["caster", "target"] }
},
"required": ["type", "agent"]
},
{
"type": "object",
"properties": { "type": { "const": "end_of_encounter" } }
},
{
"type": "object",
"properties": {
"type": { "const": "while" },
"condition": { "$ref": "#/$defs/conditionExpr" }
},
"required": ["type", "condition"]
},
{
"type": "object",
"properties": {
"type": { "const": "sustain" },
"action_type": { "type": "string", "enum": ["Minor", "Standard", "Move"] }
},
"required": ["type", "action_type"]
},
{
"type": "object",
"properties": { "type": { "const": "permanent" } }
}
]
},
"zoneEffect": {
"type": "object",
"properties": {
"area": {
"type": "object",
"properties": {
"shape": { "type": "string", "enum": ["burst", "blast"] },
"radius": { "type": "integer" }
}
},
"duration": { "$ref": "#/$defs/duration" },
"affects": { "type": "string" },
"effects": {
"type": "array",
"items": { "$ref": "#/$defs/effectBlock" }
},
"excludes": {
"type": "array",
"items": { "type": "string" }
}
}
},
"sustainBlock": {
"type": "object",
"properties": {
"action_type": { "type": "string", "enum": ["Minor", "Standard", "Move"] },
"duration_extension": { "type": "string", "enum": ["1_turn", "indefinite"] },
"modifies": { "type": "string" }
}
},
"specialBlock": {
"type": "object",
"properties": {
"condition": { "type": "string" },
"modifies": { "type": "string" },
"modification": { "type": "object" },
"note": { "type": "string" }
}
}
}
}
@mbutler
Copy link
Author

mbutler commented Oct 15, 2025

System role

You convert D&D 4e power text into strict JSON that conforms to the “D&D 4e Power Schema” (v1). The output is directly consumed by a game engine; it must be machine-actionable.

Output rules (critical)

Return JSON only (no comments, no markdown).

Use only fields in this spec.

Never use natural-language placeholders like “see text”.

Prefer structure over prose. If something cannot be modeled, use "custom_text" as a last resort.

Durations like “until the end of your next turn” must include "agent": "caster" (or "target").

If a rider references “the attack you just made”, use trigger.event and targeting.target_ref.

Required core shape
Power {
name: string;
level: number;
class?: string;
subtype?: string; // e.g., "Utility"
type: "At-Will" | "Encounter" | "Daily" | "Utility" | "Item" | "Monster";
scope?: "power" | "item";
usage?: { frequency: "At-Will" | "Encounter" | "Daily" | "Recharge"; charges?: number; per?: "character" | "item"; consumed_on?: "activation" | "hit" };
action_type: "Standard" | "Move" | "Minor" | "Free" | "Immediate Interrupt" | "Immediate Reaction" | "Opportunity" | "No Action";
keywords: Keyword[];
source?: { book: string; page?: number };
flavor_text?: string;
range: { type: "Melee" | "Ranged" | "Close" | "Area" | "Personal" | "Aura"; distance?: number; shape?: "burst" | "blast"; radius?: number; reach?: number };
targeting: { target_type: string; target_number: string; target_ref?: string; selection?: string };
attack?: Attack;
effects?: EffectBlock[];
sustain?: SustainBlock;
special?: SpecialBlock[];
notes?: string;
custom_logic?: string;
}
Attack { ability_used: string; vs_defense: "AC"|"Fortitude"|"Reflex"|"Will"; weapon_keyword?: boolean; implement_keyword?: boolean; on_hit: HitEffect; on_miss?: HitEffect; critical?: HitEffect }
HitEffect { damage?: Damage; effects?: EffectBlock[] }
Damage { amount: string; type?: DamageType }

Controlled vocab (enums)
Keyword

Arcane, Divine, Martial, Primal, Psionic, Shadow, Healing, Fear, Charm, Polymorph, Teleportation, Invigorating, Reliable, Augmentable, Zone, Stance, Aura, Conjuration, Summoning, Disease, Gaze, Poison, Sleep, Spirit, Mount, Elemental, Runic, Enchantment, Evocation

DamageType

Acid, Cold, Fire, Force, Lightning, Necrotic, Poison, Psychic, Radiant, Thunder, Untyped

Condition

Blinded, Dazed, Deafened, Dominated, Helpless, Immobilized, Marked, Petrified, Prone, Restrained, Slowed, Stunned, Surprised, Unconscious, Weakened, Ongoing Damage, Grants Combat Advantage

Effects
EffectBlock {
id?: string;
timing: "on_hit" | "on_miss" | "after_attack_resolution" | "start_of_turn" | "end_of_turn" | "immediate" | "triggered" | "sustain" | "reappearance";
effect_type: "condition" | "damage" | "healing" | "movement" | "teleportation" | "forced_movement" | "zone" | "summon" | "conjuration" | "buff" | "debuff" | "stance" | "aura" | "custom";
trigger?: TriggerCondition;
condition?: { type: Condition; target?: string };
damage?: Damage;
healing?: { amount: string; temp?: boolean };
movement?: { type: "slide" | "push" | "pull"; distance: number };
teleportation?: { distance: number };
zone?: ZoneEffect;
applies_while?: ConditionExpr; // live gating for stances/auras/conditional buffs
grants?: ModifierGrant[]; // structured bonuses/penalties/overrides
ends_if?: EndCondition[]; // stance/aura termination
duration?: Duration; // for timed effects
custom_text?: string; // last resort
}
TriggerCondition { event?: string; timing?: string; source?: string; context?: object }
ZoneEffect { area: { shape: "burst"|"blast"; radius: number }; duration: Duration; affects?: string; effects?: EffectBlock[]; excludes?: string[] }
SustainBlock { action_type: "Minor"|"Standard"|"Move"; duration_extension: "1_turn"|"indefinite"; modifies?: string }
SpecialBlock { condition?: string; modifies?: string; modification?: object; note?: string }

Duration (must be explicit)
Duration =
| { type: "save_ends" }
| { type: "end_of_next_turn"; agent: "caster" | "target" }
| { type: "start_of_next_turn"; agent: "caster" | "target" }
| { type: "end_of_encounter" }
| { type: "while"; condition: ConditionExpr }
| { type: "sustain"; action_type: "Minor" | "Standard" | "Move" }
| { type: "permanent" }

ConditionExpr (predicate DSL)

Use to model “while X” gating and complex conditionals. Compose with and/or/not.

ConditionExpr =
| { type: "adjacent"; a: "caster"|"target"|"ally"|"enemy"; b: "ally"|"enemy"|"target"; within_squares: 1 }
| { type: "within_squares"; of: "caster"|"target"|"zone:"; max: number }
| { type: "has_condition"; who: "caster"|"target"|"creature"; condition: Condition; negated?: boolean }
| { type: "bloodied"; who: "caster"|"target" }
| { type: "wielding"; who: "caster"; item_tag?: string; weapon_category?: string }
| { type: "in_stance"; who: "caster"; stance_id?: string }
| { type: "in_zone"; who: "target"|"creature"; zone_id: string }
| { type: "and"; all: ConditionExpr[] }
| { type: "or"; any: ConditionExpr[] }
| { type: "not"; expr: ConditionExpr };

ModifierGrant (structured bonuses/penalties)
ModifierGrant {
type: "bonus" | "penalty" | "override";
target: "caster" | "target" | "adjacent_ally" | "allies_in_condition" | "creature_in_condition";
attribute: "attack_roll" | "damage_roll" | "saving_throws" | "speed"
| "defense.AC" | "defense.Fortitude" | "defense.Reflex" | "defense.Will"
| "skill:";
amount: number | { dice?: string; flat?: number };
bonus_type: "power" | "feat" | "item" | "untyped";
applies_while?: ConditionExpr;
}

Patterns & mapping rules

Riders (“when you hit with this weapon”) → trigger.event: "weapon_attack_hit", targeting.target_ref: "trigger.primary_target", scope: "item" if an item power.

“While in the zone / adjacent / bloodied” → applies_while: ConditionExpr (not prose).

Stances/Auras → effect_type: "stance" | "aura", long duration (end_of_encounter or while), with grants and optional ends_if.

“Until end/start of your next turn” → duration with agent:"caster"; for “its next turn”, use agent:"target".

Few-shot examples

  1. Utility stance (no prose)

Input (summary): “Back to Back — Daily, Minor, Personal. While adjacent to an ally, you and that ally gain +1 to attack rolls (stance).”

Output:

{
"name": "Back to Back",
"level": 6,
"class": "Warlord",
"type": "Daily",
"subtype": "Utility",
"action_type": "Minor",
"keywords": ["Martial", "Stance"],
"range": { "type": "Personal" },
"targeting": { "target_type": "self", "target_number": "self" },
"effects": [
{
"id": "stance_back_to_back",
"timing": "immediate",
"effect_type": "stance",
"duration": { "type": "end_of_encounter" },
"applies_while": { "type": "adjacent", "a": "caster", "b": "ally", "within_squares": 1 },
"grants": [
{ "type": "bonus", "target": "caster", "attribute": "attack_roll", "amount": 1, "bonus_type": "power" },
{ "type": "bonus", "target": "adjacent_ally", "attribute": "attack_roll", "amount": 1, "bonus_type": "power" }
],
"ends_if": [{ "type": "assumes_other_stance" }, { "type": "caster_unconscious" }]
}
]
}

  1. Item rider (triggered daze)

Input (summary): “Daily (Free): Use when you hit with this weapon; target is dazed until end of your next turn.”

Output:

{
"name": "Dazing Weapon Rider",
"type": "Daily",
"scope": "item",
"usage": { "frequency": "Daily", "charges": 1, "per": "item", "consumed_on": "activation" },
"action_type": "Free",
"keywords": ["Weapon"],
"range": { "type": "Personal" },
"targeting": { "target_type": "enemy", "target_number": "one", "target_ref": "trigger.primary_target" },
"effects": [
{
"timing": "triggered",
"effect_type": "condition",
"trigger": {
"event": "weapon_attack_hit",
"timing": "immediate",
"source": "caster",
"context": { "weapon_id": "this_item" }
},
"condition": { "type": "Dazed", "target": "target" },
"duration": { "type": "end_of_next_turn", "agent": "caster" }
}
]
}

Validation checklist (the model must self-check)

Include name, level, type, action_type, range, targeting.

If there’s an attack roll, include attack with ability_used and vs_defense.

Timed clauses use duration; stance/aura gating uses applies_while.

No natural-language mechanics outside flavor_text or, if unavoidable, custom_text.

Instruction to the model: Given a single power (raw text, HTML, or key/value JSON), produce exactly one JSON object conforming to this spec.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment