diff --git a/locale/schema_en.properties b/locale/schema_en.properties
new file mode 100644
index 0000000..3a3ef59
--- /dev/null
+++ b/locale/schema_en.properties
@@ -0,0 +1,9 @@
+battle/schema/builtin/battle_init = Start a battle
+battle/schema/builtin/battle_init_desc = A battle from a novel scene: enemies, the return scene, a quest
+battle/schema/field/enemies = Enemies
+battle/schema/field/scene = Scene
+battle/schema/field/quest = Quest
+battle/schema/field/tag = Tag
+battle/schema/builtin/set_core = Battle core
+battle/schema/builtin/set_core_desc = Which rules the next battle follows
+battle/schema/field/core = Core
diff --git a/locale/schema_es.properties b/locale/schema_es.properties
new file mode 100644
index 0000000..a1e977d
--- /dev/null
+++ b/locale/schema_es.properties
@@ -0,0 +1,9 @@
+battle/schema/builtin/battle_init = Empezar un combate
+battle/schema/builtin/battle_init_desc = Un combate desde una escena de novela: enemigos, escena de regreso, misión
+battle/schema/field/enemies = Enemigos
+battle/schema/field/scene = Escena
+battle/schema/field/quest = Misión
+battle/schema/field/tag = Etiqueta
+battle/schema/builtin/set_core = Núcleo de combate
+battle/schema/builtin/set_core_desc = Qué reglas sigue el próximo combate
+battle/schema/field/core = Núcleo
diff --git a/locale/schema_ja.properties b/locale/schema_ja.properties
new file mode 100644
index 0000000..e51772f
--- /dev/null
+++ b/locale/schema_ja.properties
@@ -0,0 +1,9 @@
+battle/schema/builtin/battle_init = 戦闘を始める
+battle/schema/builtin/battle_init_desc = ノベルのシーンからの戦闘:敵、戻り先のシーン、クエスト
+battle/schema/field/enemies = 敵
+battle/schema/field/scene = シーン
+battle/schema/field/quest = クエスト
+battle/schema/field/tag = タグ
+battle/schema/builtin/set_core = バトルコア
+battle/schema/builtin/set_core_desc = 次の戦闘がどのルールに従うか
+battle/schema/field/core = コア
diff --git a/locale/schema_ko.properties b/locale/schema_ko.properties
new file mode 100644
index 0000000..57a3e7f
--- /dev/null
+++ b/locale/schema_ko.properties
@@ -0,0 +1,9 @@
+battle/schema/builtin/battle_init = 전투 시작
+battle/schema/builtin/battle_init_desc = 노벨 장면에서의 전투: 적, 돌아올 장면, 퀘스트
+battle/schema/field/enemies = 적
+battle/schema/field/scene = 장면
+battle/schema/field/quest = 퀘스트
+battle/schema/field/tag = 태그
+battle/schema/builtin/set_core = 전투 코어
+battle/schema/builtin/set_core_desc = 다음 전투가 따를 규칙
+battle/schema/field/core = 코어
diff --git a/locale/schema_ru.properties b/locale/schema_ru.properties
new file mode 100644
index 0000000..879f720
--- /dev/null
+++ b/locale/schema_ru.properties
@@ -0,0 +1,9 @@
+battle/schema/builtin/battle_init = Начать бой
+battle/schema/builtin/battle_init_desc = Бой из сцены новеллы: враги, сцена возврата, квест
+battle/schema/field/enemies = Враги
+battle/schema/field/scene = Сцена
+battle/schema/field/quest = Квест
+battle/schema/field/tag = Метка
+battle/schema/builtin/set_core = Боевое ядро
+battle/schema/builtin/set_core_desc = Каким правилам подчиняется следующий бой
+battle/schema/field/core = Ядро
diff --git a/locale/schema_vi.properties b/locale/schema_vi.properties
new file mode 100644
index 0000000..0e95883
--- /dev/null
+++ b/locale/schema_vi.properties
@@ -0,0 +1,9 @@
+battle/schema/builtin/battle_init = Bắt đầu trận đấu
+battle/schema/builtin/battle_init_desc = Trận đấu từ cảnh tiểu thuyết: kẻ địch, cảnh quay về, nhiệm vụ
+battle/schema/field/enemies = Kẻ địch
+battle/schema/field/scene = Cảnh
+battle/schema/field/quest = Nhiệm vụ
+battle/schema/field/tag = Thẻ
+battle/schema/builtin/set_core = Lõi chiến đấu
+battle/schema/builtin/set_core_desc = Trận tới theo luật nào
+battle/schema/field/core = Lõi
diff --git a/locale/schema_zh.properties b/locale/schema_zh.properties
new file mode 100644
index 0000000..4ce1d22
--- /dev/null
+++ b/locale/schema_zh.properties
@@ -0,0 +1,9 @@
+battle/schema/builtin/battle_init = 开始战斗
+battle/schema/builtin/battle_init_desc = 来自小说场景的战斗:敌人、返回场景、任务
+battle/schema/field/enemies = 敌人
+battle/schema/field/scene = 场景
+battle/schema/field/quest = 任务
+battle/schema/field/tag = 标签
+battle/schema/builtin/set_core = 战斗核心
+battle/schema/builtin/set_core_desc = 下一场战斗遵循哪套规则
+battle/schema/field/core = 核心
diff --git a/schemas/turn_battle_base.json b/schemas/turn_battle_base.json
new file mode 100644
index 0000000..9a4317d
--- /dev/null
+++ b/schemas/turn_battle_base.json
@@ -0,0 +1,53 @@
+{
+ "Actions": [
+ {
+ "Id": "battle.init",
+ "Name": "battle/schema/builtin/battle_init",
+ "Description": "battle/schema/builtin/battle_init_desc",
+ "Category": "battle",
+ "Params": [
+ {
+ "Key": "enemies",
+ "Type": "ref_list",
+ "Kind": "character",
+ "Required": true,
+ "Name": "battle/schema/field/enemies"
+ },
+ {
+ "Key": "scene",
+ "Type": "scene",
+ "Name": "battle/schema/field/scene"
+ },
+ {
+ "Key": "quest_id",
+ "Type": "ref",
+ "Kind": "quest",
+ "Name": "battle/schema/field/quest"
+ },
+ {
+ "Key": "new_tag",
+ "Type": "text",
+ "Name": "battle/schema/field/tag"
+ }
+ ]
+ },
+ {
+ "Id": "battle.set_core",
+ "Name": "battle/schema/builtin/set_core",
+ "Description": "battle/schema/builtin/set_core_desc",
+ "Category": "battle",
+ "Params": [
+ {
+ "Key": "core",
+ "Type": "choice",
+ "Options": [
+ "ff_battle",
+ "card_battle"
+ ],
+ "Required": true,
+ "Name": "battle/schema/field/core"
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/Gauges/BattleGauge.cs b/src/Gauges/BattleGauge.cs
new file mode 100644
index 0000000..5e2e7db
--- /dev/null
+++ b/src/Gauges/BattleGauge.cs
@@ -0,0 +1,24 @@
+namespace Cthangover.Core.Battle
+{
+ ///
+ /// One extra bar of a battle unit next to its health (mana, rage, ammo…), as an
+ /// reads it at this moment.
+ ///
+ public class BattleGauge
+ {
+ /// Stable id (a stat id, for example) the cores key their rows by.
+ public string Id { get; set; }
+
+ /// Short label, a locale key or plain text ("MP").
+ public string Label { get; set; }
+
+ /// Current value.
+ public int Current { get; set; }
+
+ /// Maximum; a bar with Max <= 0 is not drawn.
+ public int Max { get; set; }
+
+ /// Fill colour as HTML ("#4a8cff"); null = the theme's info colour.
+ public string Color { get; set; }
+ }
+}
diff --git a/src/Gauges/BattleUnitGaugeConsumer.cs b/src/Gauges/BattleUnitGaugeConsumer.cs
new file mode 100644
index 0000000..a1352af
--- /dev/null
+++ b/src/Gauges/BattleUnitGaugeConsumer.cs
@@ -0,0 +1,17 @@
+namespace Cthangover.Core.Battle
+{
+ using Cthangover.Core.Mods.Consumers;
+
+ /// Registers every of a loaded mod assembly in .
+ public class BattleUnitGaugeConsumer : ModTypeConsumer
+ {
+ ///
+ public override string Id => "turn_battle_base.unit_gauges";
+
+ ///
+ protected override void Add(IBattleUnitGauge instance, string modId) => BattleUnitGauges.Add(instance);
+
+ ///
+ protected override void Remove(IBattleUnitGauge instance, string modId) => BattleUnitGauges.Remove(instance);
+ }
+}
diff --git a/src/Gauges/BattleUnitGauges.cs b/src/Gauges/BattleUnitGauges.cs
new file mode 100644
index 0000000..6180c1e
--- /dev/null
+++ b/src/Gauges/BattleUnitGauges.cs
@@ -0,0 +1,54 @@
+namespace Cthangover.Core.Battle
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using Cthangover.Core.Characters;
+ using Cthangover.Core.Utils;
+
+ ///
+ /// The registered s and the bars they give for a unit. A
+ /// provider calls when a value moved (mana spent), so the
+ /// cores redraw that unit; cores also read the bars whenever they redraw it themselves.
+ ///
+ public static class BattleUnitGauges
+ {
+ private static readonly List Providers = new();
+
+ /// Raised with a unit id (null = every unit) when its bars changed.
+ public static event Action Changed;
+
+ /// Registers a provider.
+ public static void Add(IBattleUnitGauge gauge)
+ {
+ if (gauge != null && !Providers.Contains(gauge))
+ Providers.Add(gauge);
+ }
+
+ /// Removes a provider (its mod is unloaded).
+ public static void Remove(IBattleUnitGauge gauge) => Providers.Remove(gauge);
+
+ /// Every bar of with a positive maximum, providers in order; a failing provider is logged and skipped.
+ public static List Read(Character unit)
+ {
+ var result = new List();
+ if (unit == null)
+ return result;
+ foreach (var provider in Providers.OrderBy(p => p.Order).ToList())
+ {
+ try
+ {
+ result.AddRange((provider.Read(unit) ?? Enumerable.Empty()).Where(g => g != null && g.Max > 0));
+ }
+ catch (Exception ex)
+ {
+ GameLogger.Log("BATTLE", $"gauge provider {provider.GetType().Name} failed for '{unit.ID}': {ex.Message}", LogLevel.Error);
+ }
+ }
+ return result;
+ }
+
+ /// Tells the cores that the bars of changed.
+ public static void NotifyChanged(string unitId) => Changed?.Invoke(unitId);
+ }
+}
diff --git a/src/Gauges/IBattleUnitGauge.cs b/src/Gauges/IBattleUnitGauge.cs
new file mode 100644
index 0000000..165c4e5
--- /dev/null
+++ b/src/Gauges/IBattleUnitGauge.cs
@@ -0,0 +1,20 @@
+namespace Cthangover.Core.Battle
+{
+ using System.Collections.Generic;
+ using Cthangover.Core.Characters;
+
+ ///
+ /// Supplies extra bars of battle units (found in mod assemblies by
+ /// ): a mod that adds a resource — mana of
+ /// battle_formulas, a rage meter — shows it in every battle core that draws
+ /// , without knowing the cores.
+ ///
+ public interface IBattleUnitGauge
+ {
+ /// Order among the providers (lower first).
+ int Order { get; }
+
+ /// The bars of now; empty when it has none.
+ IEnumerable Read(Character unit);
+ }
+}