Kissrule

This is a lightweight rule-based system for game AI micro-decisions, written in C++20. It lets you register object hierarchies as fact sources, load rule sets from files, and evaluate them at runtime with lazy fact fetching and caching.

Copyright © 2026 Laurent Couvidou
Contact: hello@lorancou.net
Source: https://codeberg.org/lorancou/kissrule

Library features

Usage

// 1. Declare your C++ types.
// Let's say WeaponType, Weapon, Attacker are your own game types.
Registry registry;
registry.register_enum<WeaponType>(
  "weapon_type"_sym,
  {{"sword"_sym, WeaponType::sword},
   {"gun"_sym, WeaponType::gun}});
registry.register_fact_source<Weapon>(
  "weapon"_sym,
  {{"type"_sym, &Weapon::get_type},
   {"ammo"_sym, &Weapon::get_ammo}});
registry.register_fact_source<Attacker>(
  "attacker"_sym,
  {{"health"_sym, &Attacker::get_health}},   // value facts
  {{"weapon"_sym, &Attacker::get_weapon}});  // child fact sources

// 2. Define a rule spec: a type of rule matching a use case.
// Here for example, let's define rules for an NPC to attack.
registry.register_rule_spec(
  "attack_spec"_sym,
  {{"attacker"_sym, "self"_sym}});

// 3. Define a rule set. Built in C++ here, but this is what loads from JSON/TOML.
// Here for example: "If health is low and the gun still has ammo, retreat and shoot."
const RuleSet rules = {
  "attack_spec"_sym,
  "retreat_and_shoot"_sym,
  {{{{"self.health"_sym_p, LessThan{0.3f}},
     {"self.weapon.type"_sym_p, EqualTo{WeaponType::gun}},
     {"self.weapon.ammo"_sym_p, GreaterThan{0}}},
    {{"retreat"_sym}, {"shoot"_sym}}}}};

// 4. Bind runtime fact sources and response executors.
Attacker self;
RuleContext context(registry);
context.bind_fact_source(self, "self"_sym);
context.response_executors["retreat"_sym] = [] { /* run retreat logic */ };
context.response_executors["shoot"_sym]   = [] { /* run shoot logic */ };

// 5. Compile once -- this is costly.
RuleEvaluator evaluator(context, rules);

// 6. Evaluate often, e.g. every frame. This is cheap!
evaluator.evaluate();

Coding convention

We're loosely following the LLVM coding standards: https://llvm.org/docs/CodingStandards.html, with these naming rules: CamelCase for types (structs, classes, enums, type aliases) and template parameters (full words, e.g. Type, not T), snake_case for everything else (functions, variables, enum values, namespaces, file names). Private member variables end with an underscore: member_variable_; public member variables don't. For std types we use CamelCase aliases (String, Vector, UniquePtr, ...), in hope to make data structures more easy to the eye.

Known limitations

Third-party dependencies

All vendored libraries live under third_party/. Each directory contains a license file.

Library Purpose License
doctest Test framework MIT
nlohmann/json JSON parsing MIT
tomlplusplus TOML parsing MIT
wyhash String hashing + PRNG Unlicense (public domain)

Future plans

License

Kissrule is distributed under the Mozilla Public License 2.0. See LICENSE.txt.