A polished configuration solution
for game development
Define, Fill in, Export & Generate, Integrate, Leverage, Collaborate: Archmage forges every stage of config work into one integrated workflow, backed by thoughtfully designed specs and tools.
Why Archmage
Diverse data types
50+data types in 7 categories
- Basic
- Passive
- Compact
- Multi-Column
- Subtable
- Behavior
- Non-Leaf
Archmage’s type system goes beyond numbers and strings: enum, duration, cross-table reference, back-reference, l10n string, min–max range, weighted pool, condition, anchor, and more — many of which general-purpose config tools rarely offer.
Each type is end-to-end: type declaration, value fill-in, default export value, validation rules, data export, code generation, and runtime access. If you build an in-house tool, you'll have to decide, implement, test, and maintain each of these — type by type.
Even the simplest type, the integer, is not that simple: bit width, signedness, number bases such as hex, underscores as digit separators, the range each file format can hold, and constraints across programming languages.
One source, two pipelines
10+filtering mechanisms
index table#enabledbrace flagsxflagstagsminverswitch+caseconditioncflagsenv
Config files can be spreadsheets, tree-structured data files (e.g., YAML, JSON), or a mix of both.
You edit the config files. Archmage runs two pipelines. Data export produces runtime data. Code generation produces typed code. Both pipelines apply the same filters to carve out the same subset from the source. Data export also enforces validation rules and collects l10n strings for translation.
The Archmage SDK then brings the data and code together, parsing the runtime data using the corresponding config types.
Whenever changes happen, you re-export, regenerate, or both — no handoff docs to write. No hand-written code to update. No drift.
Built for humans and AI agents
2flags: --readable and --schema
| Type | Default | Readable |
|---|---|---|
| enum | 1 | fire |
| ref | 3 | emberstrike |
| duration | [0, 90] | 1m30s |
| l10n | spell[101].name | Fireball |
| anchor | heartseeker |
Runtime data is shaped for the game to load, not for reading. Export with--readable, and every value becomes readable on its own: an enum shows its item name, a cross-table reference resolves to its anchor text, a duration renders in a human-friendly form, and an l10n string shows its raw value.
Add --schema, and every exported config gets a .schema file beside it, naming every field once — its path, its type, and the description you gave it. Nothing about the structure is left to guess.
--readable puts the meaning back into values. --schema hands over the blueprint. With both flags, AI agents understand your configs better, straight from the Archmage output, and help you reason through decisions and iterate on your design.
- Supported Input File Formats
Spreadsheets:
.xlsx.xlsm.csvTree-structured data:
.yaml.json.json5.js.toml.xml- Supported Languages
- C#
- Go
More planned
- Supported Game Engines
- Unity
More planned
- Supported Languages for Enums
- C#
- Go
- Java
- Python
- TypeScript
- JavaScript
- Lua
- GDScript
- C++
- Rust
- PHP
- Protocol Buffers
One workflow. Every stage.
Define
You decide the layout of each set of config data and the data type of each field. You choose whatever file format works best: spreadsheets, tree-structured data files (e.g., YAML, JSON), or a mix of both.
| Desc | Spell name | Damage | Bound rune |
|---|---|---|---|
| Name | name | damage | rune |
| Type | l10n | >>minmax|int | ref@spell-rune |
ember-hunt:
title: The Ember Hunt
level: 3
repeatable: false
objectives:
- kind: kill
enemy: cinder-wisp
count: 8
- kind: collect
item: ember-shard
count: 12Fill In
A field’s data type is not just a label. It comes with carefully designed fill-in settings that make data input easier.
Data Types| Desc | Spell name | Damage | Bound rune |
|---|---|---|---|
| Name | name | damage | rune |
| Type | l10n | >>minmax|int | ref@spell-rune |
| 101 | Fireball | 40 | 55 | emberstrike |
| 102 | Frost Nova | 30 | 38 | deep-freeze |
Collaborate
Google Sheets (Optional)
Edit together live in Google Sheets — no file locks, no binary merge conflicts. Those sheets are then pulled down into your repository as spreadsheet files, fitting into your team’s existing version control workflow.
Team CollaborationExport
Archmage parses and validates config files and outputs the runtime data. It also collects l10n strings for translation, and optionally turns opaque numbers into text that is readable by humans and AI agents.
Data Export{
"101": {
"name": "spell[101].name",
"damage": { "min": 40, "max": 55 },
"rune": 3
},
"102": {
"name": "spell[102].name",
"damage": { "min": 30, "max": 38 },
"rune": 5
}
}Translate
Once exported, the l10n strings are ready for you to translate with your preferred translators, tools, and workflow.
Generate
Data export and code generation run as independent pipelines over the same config source. Code generation renders config structures into strongly-typed code and emits a ConfigAtlas type.
// Code generated by (shadop.dev) archmage. DO NOT EDIT.
public partial struct SpellCfgId
{
public long Value;
public readonly SpellCfg Cfg => ConfigAtlas.Instance.SpellTable[Value]!;
}
public partial class SpellTable : Dictionary<SpellCfgId, SpellCfg> { }
public partial class SpellCfg
{
[JsonProperty("id")] public SpellCfgId Id { get; set; }
/// <summary>Spell name</summary>
[JsonProperty("name")] public L10n Name { get; set; }
/// <summary>Damage</summary>
[JsonProperty("damage")] public MinMax<long> Damage { get; set; }
/// <summary>Bound rune</summary>
[JsonProperty("rune")] public XRef<SpellRuneCfgId, SpellRuneCfg> Rune { get; set; }
}// Code generated by (shadop.dev) archmage. DO NOT EDIT.
type SpellCfgID int64
type SpellTable map[SpellCfgID]*SpellCfg
type SpellCfg struct {
ID SpellCfgID `json:"id"`
// Spell name
Name L10n `json:"name"`
// Damage
Damage MinMax[int] `json:"damage"`
// Bound rune
Rune XRef[SpellRuneCfgID, SpellRuneCfg] `json:"rune"`
}
func (x SpellCfgID) Cfg() *SpellCfg {
return GetConfigAtlas().SpellTable[x]
}// Code generated by (shadop.dev) archmage. DO NOT EDIT.
public partial class ConfigAtlas : IAtlas
{
/// <summary>
/// The active ConfigAtlas. It must be set before calling any XxxCfgId.Cfg.
/// </summary>
public static ConfigAtlas Instance = null!;
/// <summary>The user-defined configuration extensions.</summary>
public AtlasExtension Extension { get; private set; }
/// <summary>The version info of the config repo at export time.</summary>
public VersionInfo DataVersion { get; private set; }
public QuestCfg QuestCfg { get; set; }
public SpellTable SpellTable { get; set; }
public SpellRuneTable SpellRuneTable { get; set; }
// ...
}// Code generated by (shadop.dev) archmage. DO NOT EDIT.
// GetConfigAtlas returns the active ConfigAtlas. It must be set before
// calling any XxxCfgId.Cfg.
var GetConfigAtlas func() *ConfigAtlas
// ConfigAtlas holds all game configuration tables and provides cross-table
// reference binding.
type ConfigAtlas struct {
AtlasExtension
// DataVersion is the version info of the config repo at export time.
DataVersion *archmage.VersionInfo
QuestCfg QuestCfg
SpellTable SpellTable
SpellRuneTable SpellRuneTable
}
// ...Integrate
Once the data and the types are in place, you bring them together with an Archmage SDK.
SDKsusing Shadop.Archmage.Sdk;
var i18n = new I18n("en");
i18n.MergeL10nFile("configs/l10n.json", "en");
i18n.MergeL10nFile("l10n/fr.json", "fr");
L10n.GetI18n = () => i18n;
L10n.GetPreferredLanguage = () => settings.Language;
// `atlas.json` is the manifest file generated by `archmage export`
var atlas = new ConfigAtlas();
Archmage.LoadAtlas("configs/atlas.json", "configs/", atlas);
ConfigAtlas.Instance = atlas;import "shadop.dev/pkg/sdk-go/archmage"
i18n := archmage.NewI18n(language.English)
i18n.MergeL10nFile("configs/l10n.json", language.English)
i18n.MergeL10nFile("l10n/fr.json", language.French)
conf.GetI18n = func() *archmage.I18n { return i18n }
conf.GetPreferredLanguage = func() language.Tag { return settings.Language }
// `atlas.json` is the manifest file generated by `archmage export`
atlas := &conf.ConfigAtlas{}
archmage.LoadAtlas("configs/atlas.json", "configs/", atlas)
conf.GetConfigAtlas = func() *conf.ConfigAtlas { return atlas }Leverage
Your game logic works with the ConfigAtlas. Every field is typed, and you enjoy the capabilities built into the types, especially those the Archmage SDK provides.
var spell = atlas.SpellTable[spellId];
label.Text = spell.Name.Text; // l10n → localized text
int dmg = spell.Damage.Sample(rng); // minmax → a random value in range
var elem = spell.Rune.Ref.Element; // ref → the rune's element, no lookupspell := atlas.SpellTable[spellID]
label.SetText(spell.Name.Text()) // l10n → localized text
dmg := spell.Damage.Sample(rng) // minmax → a random value in range
elem := spell.Rune.Ref.Element // ref → the rune's element, no lookupReadable Configs, AI Agents First
--readableputs the meaning back into values.--schemahands over the blueprint.
This matters most when an AI agent consumes the configs. Reviewing game balance, catching a wrong value, or iterating on core mechanics — an AI agent works best when the values mean something on their own. The clearer the values are, the less it guesses and the more it knows.
Readability & AI{
"101": {
"id": 101,
"name": "Fireball",
"damage": { "min": 40, "max": 55 },
"rune": "emberstrike"
},
"102": {
"id": 102,
"name": "Frost Nova",
"damage": { "min": 30, "max": 38 },
"rune": "deep-freeze"
}
}{
"101": {
"name": "spell[101].name",
"damage": { "min": 40, "max": 55 },
"rune": 3
},
"102": {
"name": "spell[102].name",
"damage": { "min": 30, "max": 38 },
"rune": 5
}
}---name: spellsource: spell.xlsx---$ map[int64]$.* {}$.*.id int64$.*.name l10n # Spell name$.*.damage minmax # Damage$.*.damage.min int$.*.damage.max int$.*.rune ref@spell-rune # Bound runeFast enough to run on every change
100 config files, 2,000 fields, 300,000 config entries, 6,290,153 values
- export time
- 4.75 s
- entries/s
- 63,127
- values/s
- 1,323,612
Apple M4 Pro, 14 cores
Enum code generation, Free and Unlimited
One enum definition, one set of enum capabilities — Archmage carries them into 12+ programming languages, each rendered in that language’s own idioms and type system.
Definition to Code- Flexible Underlying Types
int,int8/16/32/64,uint,uint8/16/32/64.- Conditional Generation
- Scenario flags for filtering enum types and enum items.
- Bitflags
- Bitwise-combinable enum types.
- Auto-Numbering
iotaauto-assigns values: 0, 1, 2, 3…; for bitflags: 0, 1, 2, 4, 8…- External Types
- Same enum type: native in one language, external in another.
__options__:
underlyingType: int32
xflags: "client,server"
__namespace__:
go: "game/enums"
cs: "Game.Enums"
Element:
None: 0
Fire: 1
Frost: 2
Nature: 3- Named Types
- Each enum type is defined as a named type.
- Typed Constants
- Each enum item is defined as a typed constant.
- Underlying Types
- From
int8touint64; parsing enforces the value range. - Enum ↔ String Conversion
- In both directions — by default the item name, overridable in the definition.
- Multiple Parsing Formats
- Item name in any case, TypeName(n), decimal, and hex are all supported.
- Safe Parsing
- Failures trigger explicit errors or exceptions instead of silent zero values.
- JSON Integer Serialization
- Enum values serialize as integers in JSON.
- Sentinel
- Boundary markers such as
Count, excluded from the valid values. - Duplicate Values
- Multiple items can share the same integer value; all names remain valid for parsing.
- Bitflag Enums
- Parses and formats flag combinations, e.g.,
"frost, fire". - AllValues
- Returns the full set of valid enum values as an ordered list.
- IsValid
- Checks if a given value corresponds to a defined enum item.
// Code generated by (shadop.dev) archmage. DO NOT EDIT.
namespace Game.Enums
{
public enum Element : int
{
None = 0,
Fire = 1,
Frost = 2,
Nature = 3,
}
public static partial class ElementExtensions
{
// ...
public static IReadOnlyList<Element> AllValues() { /* ... */ }
public static Element Parse(string str) { /* ... */ }
public static bool IsValid(this Element x) { /* ... */ }
public static string GetEnumString(this Element x) { /* ... */ }
}
}// Code generated by (shadop.dev) archmage. DO NOT EDIT.
package enums
type Element int32
const (
ElementNone Element = 0
ElementFire Element = 1
ElementFrost Element = 2
ElementNature Element = 3
)
// ...
func ElementValues() []Element { /* ... */ }
func ParseElement(str string) (Element, error) { /* ... */ }
func (x Element) IsValid() bool { /* ... */ }
func (x Element) String() string { /* ... */ }No cost. No restrictions. No strings attached.
Just enum code generation.
In a sea of scattered configs
