Torchbound - From a Rules-Coupled Engine to a Pluggable Rulesets engine?

- Posted in D&D by

Goal

Torchbound started as (and it still is, as I write this) an implementation of Five Torches Deep. I still believe that it was the right choice: implementing a real game gave me concrete rules, contracts, tests, persistence requirements, dungeon interactions, combat, progression, and UI behavior instead of designing an abstract RPG engine upfront.

As Torchbound grows, however, I am wondering if it could be possible to preserve an important architectural option (which is a new idea, it just crossed my mind, so I'm letting my uncontrollable ramblings flow here):

Torchbound Core should not assume that Five Torches Deep is the definition of an RPG.

The long-term goal is not to turn Torchbound into a universal RPG framework. It would rather be to make the ruleset an explicit boundary, so that another system could eventually replace 5TD without requiring Torchbound itself to be rewritten.

Possible future rulesets include Nimble and an adapted 2D6 Dungeon ruleset.


Torchbound today

The current architecture already contains a large amount of reusable infrastructure:

  • campaign and session management
  • SQLite persistence
  • commands and events
  • dungeon import from Delveforge
  • dungeon identity, provenance and spatial state
  • save/reload
  • navigation and UI
  • player-facing projections
  • world-state management

However, the Engine also directly knows about many concepts belong specifically to 5TD.

Examples include checks, combat rules, proficiency, Supply, death and stabilization, resting, progression, spells, treasure rules and other game-specific mechanics.

Conceptually, the architecture still looks roughly like this:

                         TORCHBOUND TODAY

                         ╭─────────────╮
                         │     UI      │
                         ╰──────┬──────╯
                                │
                                ▼
                    ╭──────────────────────╮
                    │  Campaign / Session  │
                    ╰──────────┬───────────╯
                               │
                               ▼
                    ╭──────────────────────╮
                    │        ENGINE        │
                    ╰──────────┬───────────╯
                               │
                ┌──────────────┼──────────────┐
                │              │              │
                ▼              ▼              ▼
          ╭───────────╮  ╭───────────╮  ╭─────────────╮
          │  Dungeon  │  │   State   │  │ Persistence │
          ╰───────────╯  ╰───────────╯  ╰─────────────╯
                               │
                               ▼
               ╭──────────────────────────────╮
               │       5TD assumptions        │
               │                              │
               │  checks       combat         │
               │  Supply       death          │
               │  rest         progression    │
               │  spells       treasure       │
               ╰──────────────┬───────────────╯
                              │
                              ▼
                   ╭────────────────────╮
                   │ Five Torches Deep  │
                   ╰────────────────────╯

This does not mean Torchbound would have to be rewritten from scratch to use another system.

It does mean that changing rulesets today would still require invasive surgery because infrastructure, orchestration and rules responsibilities are not yet consistently separated.


The architectural constraint from now on

The new rule is deliberately simpler than "build a multi-ruleset engine":

New Torchbound Core code must not introduce an implicit dependency on a Five Torches Deep rule.

For every new feature, from now I want to distinguish four responsibilities:

Core: Torchbound infrastructure and orchestration.

Rules: Mechanics belonging to the selected game system.

Content: Authored Torchbound adventures, encounters, profiles and configuration that may intentionally use a particular ruleset.

External contracts: Delveforge and other external representations that describe things to Torchbound without defining its game rules.

Existing mixed code does not need to be rewritten immediately. I will first audit it and then separate responsibilities where there is a demonstrated architectural benefit.


Target architecture

The desired direction is approximately:

                          TORCHBOUND

                    ╭────────────────────╮
                    │         UI         │
                    ╰─────────┬──────────╯
                              │
                              ▼
             ╭────────────────────────────────╮
             │        Campaign / Core         │
             │                                │
             │   state          identity      │
             │   commands       events        │
             │   persistence    projections   │
             │   dungeon        orchestration │
             ╰───────────────┬────────────────╯
                             │
                             ▼
                 ╭──────────────────────╮
                 │ Explicit Rules API   │
                 ╰──────────┬───────────╯
                            │
               ┌────────────┼────────────┐
               │            │            │
               ▼            ▼            ▼
        ╭────────────╮ ╭────────────╮ ╭────────────╮
        │    5TD     │ │   Nimble   │ │   2D6D*    │
        │ Rules Pack │ │ Rules Pack │ │ Rules Pack │
        ╰────────────╯ ╰────────────╯ ╰────────────╯

                 Interchangeable Rules Packs

            * adapted to use DF-provided dungeons

The Core should therefore know primarily about concepts such as:

  • identities
  • state
  • commands
  • events
  • spaces and positions
  • persistence
  • transitions
  • sessions
  • projections

It should avoid assuming that every RPG necessarily has:

  • STR / DEX / CON / INT / WIS / CHA
  • d20 checks and DCs
  • AC
  • Supply
  • spell slots
  • 5TD death mechanics
  • 5TD progression
  • 5TD treasure tables

Those concepts can exist in Torchbound — but their owner is the Rules Pack, not necessarily Torchbound Core.


Delveforge remains a separate responsibility

This distinction is particularly important for dungeon generation.

Torchbound should not become a procedural dungeon generator merely because one supported ruleset normally includes dungeon generation.

The intended responsibility split is:

                       ╭────────────────╮
                       │   DELVEFORGE   │
                       ╰───────┬────────╯
                               │
                      describes / generates
                               │
                               ▼
                       ╭────────────────╮
                       │    DUNGEON     │
                       ╰───────┬────────╯
                               │
                               ▼
                 ╭──────────────────────────╮
                 │     TORCHBOUND CORE      │
                 │                          │
                 │  identity                │
                 │  spatial state           │
                 │  persistence             │
                 │  world state             │
                 │  orchestration           │
                 ╰────────────┬─────────────╯
                              │
                     interprets / resolves
                              │
                              ▼
                       ╭──────────────╮
                       │  RULES PACK  │
                       ╰──────────────╯

In other words:

Delveforge says what the dungeon is.
Torchbound says what exists and what happens in it.
The Rules Pack says how actions and consequences are resolved.

This remains true even for an adapted 2D6 Dungeon Rules Pack.

2D6 Dungeon normally includes procedural dungeon generation as part of its gameplay loop. In Torchbound, I would deliberately remove that responsibility from the Rules Pack and replace it with Delveforge.

Torchbound itself should not gain procedural dungeon generation merely to reproduce that part of 2D6 Dungeon.


Three architectural tests

I can use three rulesets as conceptual tests without implementing any of them yet.

Five Torches Deep - baseline

5TD is the existing implementation.

The question is:

Can I identify and eventually remove the 5TD rules without removing Torchbound infrastructure with them?

This reveals the current Core/Rules coupling.

Nimble - substitution test

Nimble is another fantasy RPG, so it will share some concepts with 5TD while implementing them differently.

The question becomes:

Could I replace 5TD with Nimble without changing code that genuinely belongs to Torchbound Core?

This is important because otherwise I could accidentally create abstractions that are merely the common denominator of 5TD and Nimble and mistake them for generic Torchbound concepts.

2D6 Dungeon - partial-ruleset / DF-substitution test

2D6 Dungeon is structurally more different.

For Torchbound, I would deliberately remove its procedural dungeon-generation responsibility and replace it with Delveforge.

The question becomes:

Can a Rules Pack surrender a responsibility already owned by another Torchbound component while retaining its coherent resolution, combat, treasure and progression mechanics?

This prevents me from solving the opposite problem by making Torchbound Core absurdly broad.


What I am deliberately NOT doing

I am not building a generic RPG framework today and trying to predict every RPG mechanic I may encounter in the future.

Instead, I can use the existing 5TD implementation to discover the first real boundary.

Later, an actual Nimble implementation can challenge that boundary.

If Nimble exposes incorrect assumptions, I can adjust it.

A substantially different system such as adapted 2D6 Dungeon can challenge it again.

The abstraction therefore emerges from real implementations and failing assumptions, rather than speculative generalization.


Desired end state

Eventually, changing the game system should conceptually approach:

                    ╭────────────────────╮
                    │  Torchbound Core   │
                    ╰─────────┬──────────╯
                              │
              ┌───────────────┼────────────────┐
              │               │                │
              ▼               ▼                ▼
       ╭─────────────╮ ╭───────────────╮ ╭──────────────╮
       │ Delveforge  │ │ Campaign / UI │ │  Rules Pack  │
       │   Dungeon   │ │ Saves / State │ ╰──────┬───────╯
       ╰─────────────╯ ╰───────────────╯        │
                                                ▼
                                     ╭────────────────────╮
                                     │ Five Torches Deep  │
                                     ╰────────────────────╯
                                                │
                                           swap rules
                                                │
                                                ▼
                                         ╭────────────╮
                                         │   Nimble   │
                                         ╰────────────╯

rather than:

                     ╭────────────────╮
                     │ Change ruleset │
                     ╰───────┬────────╯
                             │
                             ▼
                     rewrite engine.lua
                             │
                             ▼
                 rewrite campaign_session
                             │
                             ▼
                    rewrite dungeon logic
                             │
                             ▼
                    rewrite persistence
                             │
                             ▼
                        rewrite UI
                             │
                             ▼
                    rewrite everything
                             │
                             ▼
                            :(

That being said, I am far from being close to that yet.

But Torchbound is still early enough that adopting this constraint now is cheap compared with extracting 5TD assumptions after several more major gameplay systems have been built.

The immediate strategy is therefore:

Audit the existing coupling. Do not over-engineer it. Stop introducing new coupling today. Extract Rules boundaries only where real dependencies demonstrate that they are needed.

That keeps Five Torches Deep as Torchbound's first fully supported ruleset while leaving the door open for Torchbound to become, later, a game capable of running the same underlying campaign/dungeon infrastructure through genuinely different Rules Packs.


Yes, I know why you smile.

As if... this challenge wasn't already out of proportion to my technical abilities and my long-term commitment :)


Peace. Out.

Torchbound progress status

- Posted in D&D by

And... (drumroll) here's the first version of TorchBound, showing a player at the start of a DelveForge dungeon! enter image description here This is a god mode view, the dark squares are unknown to the player.
And the player can’t go any further because… the next square contains a pit and… the “obstacle” handling isn’t implemented yet, so the player is stuck :)
(Yes, starting with a pit right behind the dungeon entrance is pretty mean, but that’s how DelveForge generated the map, and I left it as is)
(Life is hard, especially for dungeon explorers.)

That said, it’s nice to “see” something after hours (and hours) of implementing rules and automated tests (1,723 and still counting).

Peace. Out.

DelveForge and Torchbound are on a boat...

- Posted in D&D by

DelveForge & Torchbound — where are the boundaries?

The relationship between DelveForge and Torchbound became clear:
DelveForge creates places.
Torchbound makes stories happen inside these places.

They share a world, but they do not have the same responsibility.


DelveForge: the world builder

DelveForge answers one question:

What does this place look like?

DelveForge is an architect and decorator of an unexplored place. It generates:

DelveForge
|
├── Spatial generation
|     ├── levels
|     ├── rooms
|     ├── corridors
|     └── connections
|
├── Dungeon structure
|     ├── topology
|     ├── accessibility
|     └── constraints
|
└── Static features
      ├── doors
      ├── chests
      ├── skeletons (relics / remains)
      ├── pits
      ├── stairs
      └── other points of interest

A DelveForge dungeon is a coherent location.

Example generated by DelveForge:

Forgotten Chamber

  • ancient walls
  • locked door
  • forgotten chest
  • skeleton remains
  • dangerous pit
  • staircase leading deeper

DelveForge does not decide what happens there, it only says:

Here is a place with a history.


Who can experience a DelveForge world?

The generated world is not tied to a single consumer. The same dungeon can become:

A classic TTRPG adventure

Using:

  • printed PNG map
  • human imagination
  • a Game Master

A VTTRPG adventure

Using:

  • map export
  • structured dungeon data
  • virtual tabletop features

Torchbound

Using:

  • the modeled dungeon representation
  • game rules
  • persistence
  • gameplay systems

Torchbound: the world storyteller

Torchbound answers another question:

What happens here?

Torchbound gives life to the place. It adds:

Torchbound
|
├── Characters
├── Creatures
├── Events
├── Encounters
├── Decisions
├── Consequences
└── Progression

DelveForge creates:

There is a chest in this forgotten room.

Torchbound decides:

What is this chest?

It could contain:

  • a potion
  • a trap
  • a key
  • nothing

DelveForge creates:

A skeleton lies here.

Torchbound decides:

What does this mean?

It could be:

  • simple scenery
  • a fallen adventurer
  • a clue
  • part of a larger story

Same world, different experiences

The same dungeon can therefore live several lives. DelveForge exports a description:

{
  "room": 42,
  "features": [
    "chest",
    "skeleton"
  ]
}

A consumer interprets it according to its purpose:

  • a GM invents a story
  • a VTTRPG provides interaction
  • Torchbound creates an adventure

The simple rule

DelveForge creates the stage. Torchbound directs the play.

Or even shorter:

DelveForge builds worlds. Torchbound tells stories inside them.

Peace. Out.

Torchbound progress status

- Posted in D&D by

I'm proud to announce that Isometric Dungeon Generator is dead, long live Delveforge!
As I’m about to try to get Torchbound to load a dungeon generated by IDG, I figured it deserved a real name...
So, here’s Delveforge.
And its gorgeous future splash screen.
Delveforge
Yeah, sometimes I need to work on lighter projects to take my mind off things a bit :)
(yes, it’s AI-generated)
(but a quick look at my artistic talents instantly exonerates me for this despicable misdeed)
(better pretty and cheated than ugly and completely homemade)

By the way, there are some major updates in the works for Delveforge, but I'm taking it easy for now, Torchbound needs a steady stream of input if I want it to be able to import anything someday :) enter image description here

Peace. Out.

Torchbound progress status

- Posted in D&D by

Everyone in Europe saw the solar eclipse. Not me.
I was -and still am- too busy trying to make a playable pre-alpha version of Torchbound.

Right now, it’s not exactly visually that impressive: enter image description here

Despite everything that has been done behind the scenes:

      +-----------------------------------------------+
      |                    SUMMARY                    |
      +-----------------------------------------------+
  Files                    258
  Modules                  -
  CodeGraph nodes          4044
  CodeGraph edges          13291
  Functions analyzed       469
  Call relations           1114

  Entry points
    LOVE callbacks         6
    Bootstrap              1
    Module APIs            463
    Root candidates        217
    Documented             687


And if I try to gauge how far along I am in this project -which seemed small and easy at first but is becoming a bigger and bigger thing every day- I’d say:

Rules / Engine foundations       ████████████████████
Persistence                      ████████████████████
Campaign aggregate / session     ████████████████████
Playable encounters              ████████████████████
Playable travel                  ████████████████████
Interactive travel consequences  ████████████████████
Playable exploration             ████████████░░░░░░░░
Party management                 ░░░░░░░░░░░░░░░░░░░░
Generated encounters             ░░░░░░░░░░░░░░░░░░░░
Rewards / progression loop       ░░░░░░░░░░░░░░░░░░░░
Management UI                    ░░░░░░░░░░░░░░░░░░░░
Game UI                          ░░░░░░░░░░░░░░░░░░░░
Game tests                       ░░░░░░░░░░░░░░░░░░░░
Game assets                      █░░░░░░░░░░░░░░░░░░░
Game sounds                      ██░░░░░░░░░░░░░░░░░░

At least this time—I didn't start with the visuals :)

And of course, I have dozens of brilliant ideas every day that I meticulously log in my personal local instance of LineAgent (yes, Linear is great, but it’s a non-open-source SaaS service, so I switched both the product and the hosting for my issues).
And since LineAgent doesn't expose a user interface -it is headless and REST only- well... I built a little interface on a small self-hosted server, just to take my mind off things for a bit.

enter image description here

(perhaps an english version on day)

So Torchbound will probably never be finished, but deep down inside I’ve always known that :)
Sticks and stones...
Yeah, I admit this post is a bit all over the place. But that's okay.

Peace. Out.

Torchbound

- Posted in D&D by

So I dove into developing my game, which will use the dungeon I now know how to generate.
After much thought, I’ve decided to call it Torchbound, since it’s based on the rules of Five Torches Deep.

Version 1, aka “Optimism and carefree”

I thought to myself, “OK, it’s a simplified version of D&D—this will work easily,” and of course, as always, I started playing around with the UI.
And I built a UI in Lua—how should I put it?—it’s pretty old-school :)
And of course I created the logical sequence of screens for character creation: Race, Attibutes, Classes, Equipment—the usual stuff.
Then, of course, the inevitable happened: “Damn, the rules aren’t that simple,” and “Hello, unmaintainable if… then… else statements.”
So, I… stopped everything and… started over from scratch (which just shows that WIS doesn't necessarily come with age).
(Well, OK, I’ll probably keep some assets like logos, buttons, and sounds)

Version 2, “Welcome to the Real World”

The UI will come later; I need to start with the game engine: the FTD rules.

Phase 1: The Rules
So I started by actually reading the rules—which, let’s face it, I’d only skimmed before. Well, even though it’s a minimalist version of D&D 5th Edition, it’s not as straightforward as it seems. I ended up with a rules file that’s… 120 KB, including 24 reference tables. Plain text, mind you—not a Word document with images.

Phase 2: Modeling the Rules
Then, after careful consideration, I settled on the following model:

      ┌→ Specific item (AC) ────────→ Plain text 'Contract' ────→ Lua 'Tests' ────→ Lua 'Logic'
      ├→ Specific item (Armor)──────→ Plain text 'Contract' ────→ Lua 'Tests' ────→ Lua 'Logic'
RULES ┼→ Specific item (Attack) ────→ Plain text 'Contract' ────→ Lua 'Tests' ────→ Lua 'Logic'
      ├→ ...
      └→ Specific item (Weapon) ────→ Plain text 'Contract' ────→ Lua 'Tests' ────→ Lua 'Logic'

And now I think I've covered everything with a little less than... 90 subdomains (from “AC” to “Weapon”). It's just huge compared to what I imagined, but at least the foundations will be isolated, well-defined, and their code and tests will be dedicated.

Phase 3: Implementing the Rules
For example, for AC:

Contract 'contract.md':

# CONTRACT: ARMOR CLASS

Every combatant, PC or NPC, has an Armor Class (AC).
AC is the number an attacker must meet or exceed to hit the target in combat.
AC is therefore the DC used to damage a target.
FTD defines, among others:
    Heavy Armor
        AC 15
    Light Armor
        AC 12 + DEX mod
A shield increases AC.
Further AC calculation details depend on the equipment and applicable rules.

## SOFTWARE INTERPRETATION
The contract receives the values required to calculate AC.
For heavy armor:
    AC = 15
For light armor:
    AC = 12 + DEX mod
The contract does not resolve the attack check, roll dice, select armor,
calculate the DEX modifier, or decide whether an attack hits.

## API
Module:
    Modules.Engine.ac

Entry point:
    ac.heavy()
    ac.light(dex_modifier)

## RETURN
The functions return:
    {ac = <valeur d'AC>}

## INVARIANTS
Heavy armor provides a base AC of 15.
Light armor provides a base AC of 12 before applying the DEX modifier.
The contract never modifies DEX, calculates the DEX modifier, or rolls dice.
It is deterministic for identical inputs.


## TEST CASES
The following cases must be covered:
- heavy armor
- light armor with a zero modifier
- light armor with a positive modifier
- light armor with a negative modifier
- determinism


## OUT OF SCOPE

The AC contract does not perform:
- RNG ;
- CHECK ;
(...)

## VALIDATION
The contract is considered validated when:
    lua Tests/run_tests.lua
returns zero failures and all contract cases are covered.

Tests 'test-AC.lua':

TestAC = {}
-- HEAVY ARMOR
function TestAC:test_heavy_armor()
    local result = ac.heavy()
    lu.assertEquals(result.ac, 15)
end
(...)
return TestAC

Logic 'ac.lua':

local ac = {}
function ac.heavy()
    return {
        ac = 15
    }
end
function ac.light(dex_modifier)
    return {
        ac = 12 + dex_modifier
    }
end
return ac

Phase 4: Writing All These Items All the {specifications, tests, code} for the 90 domains have been written, and I am verifying that:

  • the {specifications, tests, code} actually exist:
.\Specifications\Contracts\Rules_Check.ps1
 TORCHBOUND - RULES CHECK

===== COUNTS =====
Contracts             : 86
Contract modules      : 86
Contract tests        : 86
Infrastructure tests  : 5
Total tests           : 91

===== CONTRACT BIJECTION =====
Contracts          : 86
Modules            : 86
Contract tests     : 86
OK   complete 1:1 contract matrix.

 RULES CHECK : PASS
  • the tests are OK:
lua .\Tests\run_tests.lua
Ran 967 tests in 0.072 seconds, 967 successes, 0 failures

Nearly 1,000 unit tests are run with every change.

Phase 5: The engine that drives all these rules
The approach adopted is as follows:

                        +------------------+
                        |    INPUT / UI    |
                        +--------+---------+
                                 |
                                 | command
                                 v
                        +------------------+
                        | COMMAND VALIDATOR|
                        +--------+---------+
                                 |
                                 | valid command
                                 v
                        +------------------+
                        |  COMMAND QUEUE   |
                        +--------+---------+
                                 |
                                 | one command per update
                                 v
+----------------+      +--------+---------+      +----------------+
| ACTOR STATE    |<---->|      ENGINE      |<---->| COMBAT / TURN  |
| HP, SUP, class |      | orchestration    |      | current actor  |
| actions, etc.  |      +--------+---------+      +----------------+
+----------------+               |
                                 | selects resolver
              +------------------+------------------+
              |                  |                  |
              v                  v                  v
     +----------------+ +----------------+ +----------------+
     | ACTION         | | ATTACK         | | SPELLCASTING   |
     | RESOLVER       | | RESOLVER       | | RESOLVER       |
     +-------+--------+ +-------+--------+ +-------+--------+
             |                  |                  |
             |                  v                  v
             |          +---------------+  +---------------+
             |          | DAMAGE / HP   |  | SPELL CHECK   |
             |          +---------------+  +-------+-------+
             |                                     |
             |                             failure |
             |                                     v
             |                             +---------------+
             |                             | MAGIC MISHAP  |
             |                             +---------------+
             |
             +------------------+
                                |
                                | state changes
                                | and events
                                v
                       +-------------------+
                       |   EVENT BUFFER    |
                       +---------+---------+
                                 |
                                 | dispatch
                                 v
                       +-------------------+
                       | EVENT HANDLERS    |
                       +---------+---------+
                                 |
                    +------------+------------+
                    |                         |
                    v                         v
           +----------------+        +----------------+
           | COMBAT LOG     |        | FOLLOW-UP RULE |
           | observable     |        | death, etc.    |
           +----------------+        +----------------+

In practice, it looks something like this when you're playing around with a Goblin:

[CALL] PUBLIC engine.update(0)
[CALL] command_queue.pop
[RETURN] command_queue.pop
[STATE] queue 1 -> 0
[STATE] consumed command=Attack
[CALL] combat_command_resolver.execute
  [CALL] engine.use_action
  [RETURN] engine.use_action
  [STATE] Active false -> true
  [STATE] action accepted=true
  [CALL] combat_resolver.execute
    [CALL] attack.resolve
    [RETURN] attack.resolve
    [STATE] attack hit=true
    [CALL] damage.resolve
    [RETURN] damage.resolve
    [STATE] damage total=5
    [CALL] hp.apply_damage
    [RETURN] hp.apply_damage
    [STATE] hp result 5 -> 0
    [CALL] events.push
    [RETURN] events.push
    [EVENT] damage_applied
    [STATE] events 0 -> 1
  [RETURN] combat_resolver.execute
  [STATE] goblin.hp 5 -> 0
  [STATE] combat hit=true
[RETURN] combat_command_resolver.execute
[STATE] attack command success=true
[CALL] event_dispatcher.dispatch_all
  [CALL] event_dispatcher.dispatch
    [CALL] combat_log.on_damage_applied
    [RETURN] combat_log.on_damage_applied
    [STATE] logs 0 -> 1
    [CALL] death_handler.on_damage_applied
    [RETURN] death_handler.on_damage_applied
    [STATE] goblin.incapacitated nil -> true
    [STATE] goblin.dying nil -> true
    [STATE] death handler result=false
  [RETURN] event_dispatcher.dispatch
  [EVENT] dispatched damage_applied
[RETURN] event_dispatcher.dispatch_all
[STATE] events 1 -> 0
[STATE] logs 0 -> 1
[RETURN] PUBLIC engine.update(0)
[STATE] queue 1 -> 0
[STATE] logs 0 -> 1
[STATE] Active false -> true
[STATE] goblin.hp 5 -> 0
[STATE] goblin.incapacitated nil -> true
[STATE] goblin.dying nil -> true

So I’m still working on integrating all of this.

Once all the rules are properly implemented (especially since I’ve learned along the way that there’s an SRD for FTD, and of course it doesn’t match my rules 100%), I’ll start working on more visual aspects, but we’re not there yet.

A quick note on tools:

  • It’s obvious, but I’ll say it anyway: without Git, there’s no hope (I’m working entirely locally).
  • CodeGraph is simply indispensable as the code grows; it lets you generate things like this:
combat_resolver
├── actor::get_ac
│   ├── actor::get_ac_bonus
│   ├── armor::ac
│   └── shield::apply
├── actor::is_dead
│   └── actor::has_condition
├── attack::resolve
│   └── check::resolve
├── damage::resolve
├── engine::get_actor
├── engine::get_state
├── equipment::get
├── events::push
├── hp::apply_damage
│   ├── hp
│   │   └── supply::max
│   └── supply::max
├── weapon::attack_bonus
└── weapon::damage_bonus

and:

engine::rest_actor
├── engine::get_actor [Modules/Engine/engine.lua:204]
└── rest_handler::apply [Modules/Engine/engine.lua:273, Modules/Engine/rest_handler.lua:12]
    ├── actor::clear_temporary_injuries [Modules/Engine/actor.lua:142, Modules/Engine/rest_handler.lua:12]
    │   └── actor.ensure_injuries [Modules/Engine/actor.lua:75]
    ├── actor::has_condition [Modules/Engine/actor.lua:201, Modules/Engine/rest_handler.lua:12]
    ├── actor::remove_condition [Modules/Engine/actor.lua:222, Modules/Engine/rest_handler.lua:12]
    ├── hp::apply_healing [Modules/Engine/hp.lua:34, Modules/Engine/rest_handler.lua:12]
    │   └── hp.normalize_amount [Modules/Engine/hp.lua:4]
    │       └── supply::max [Modules/Engine/hp.lua:4]
    └── rest::resolve [Modules/Engine/rest.lua:3, Modules/Engine/rest_handler.lua:12]

And, most importantly, to find out what I've already integrated and what I haven't:

Runtime call tree reachable from main.lua.

main.lua
├── love.draw
│   └── engine.draw
├── love.load
│   └── engine.init
│       ├── combat.create
│       │   ├── combat_round.create
│       │   │   └── combat_turn.create
│       │   └── combat_round.create
│       │       └── combat_turn.create
│       ├── combat.create
│       │   ├── combat_round.create
│       │   │   └── combat_turn.create
│       │   └── combat_round.create
│       │       └── combat_turn.create
│       ├── command_queue.new
│       ├── command_queue.new
│       └── event_setup.init
└── love.update
    └── engine.update
        ├── action_resolver.execute
        │   ├── engine.get_actor
        │   ├── engine.get_state
        │   ├── engine.use_action
        │   │   ├── actions.can_follow
        │   │   ├── actions.can_take
        │   │   ├── actions.use
        │   │   │   └── actions.can_take
        │   │   ├── actions.use
        │   │   │   └── actions.can_take
        │   │   ├── actor.can_act
        │   │   │   ├── actor.create
        │   │   │   │   ├── supply.load
        │   │   │   │   └── supply.max
        │   │   │   └── actor.has_condition
        │   │   ├── engine.build_used_actions
        │   │   ├── engine.get_action_key
        │   │   ├── engine.get_actor
        │   │   └── engine.set_action_used
        │   ├── events.create
        │   └── events.push
        ├── actor.has_condition
        ├── command_queue.pop
        ├── death_handler.resolve_deadline
        │   ├── actor.add_condition
        │   ├── actor.has_condition
        │   ├── actor.remove_condition
        │   ├── death.resolve
        │   └── events.push
        ├── death_handler.resolve_deadline
        │   ├── actor.add_condition
        │   ├── actor.has_condition
        │   ├── actor.remove_condition
        │   ├── death.resolve
        │   └── events.push
        └── event_dispatcher.dispatch_all
  • Last but not least, to keep from getting lost amid development, integration, bugs, and testing, Linear is a lifesaver too. enter image description here

Well, I’ve spent about half my vacation on this, but I’m having fun :)
That was a (very) long post—sorry.
Have a safe trip, and don't forget your torches if you're going to seedy places—especially ones that aren't well-lit.
Peace, out.

The Return of the Dungeon Generator

- Posted in D&D by

So, no, I hadn't forgotten about it, but with my tendency to procrastinate as soon as life gives me a break, this project had been gathering dust for quite some time.
Against all odds, I dug it out from the depths of a folder on my hard drive with the idea of reworking it, or even (crazy and pretentious as I am) finishing it, trying at the same time to conquer—for once—one of my most persistent demons: projects started but never finished. Thanks to global warming, I was able to spend a few days cooped up indoors to escape the heat and work on continuing this project.
Well, actually, I started it all over from scratch :)
Still built on Lua (which reminds me of my first love, Tcl) and its Löve framework (for doing 2D without reinventing the wheel).
So here’s a more or less stable version of Isometric Dungeon Generator (aka IDG). And yes, I know, it’s not an isometric projection, but who cares—it’s just its nickname.

So, what exactly does this revenant from beyond the grave do?
It lets you:

  • randomly generate dungeons with square rooms and straight corridors with turns
  • scatter doors (possibly secret ones), pillars, traps, pits, skeletons, and of course treasures throughout them
  • influence all that randomness by adjusting the weights that govern the spawning of these objects
  • render all of this using sprites that I half-stole (shh) and half-made myself
  • export a superb .png of the whole thing
  • and also export a .json file

What does it look like?
A zoomable overview of the dungeon:

A zoomable “internal” view of the dungeon that lets you visualize the underlying logic behind object generation:

A configuration window to control (to some extent) the parameters of the generated dungeon:



So, in the end, what’s the point of all this?

  • Well, for starters, so I can look at my screen and say out loud: “Did you see that? I did it!” (and, incidentally, to post here once every two years)
  • To accept that I’ll always be light-years away from the quality of projects like Dungeon Map Generator (acceptance's always the first step)
  • And it’s the first step toward my original idea: coding a single-player dungeon crawler, in the style of 2D6 Dungeon, even though lately I’ve been eyeing 5 Torches Deep more (I love the tension of managing torches)

See you in 3 years. And remember:

None of us are promised tomorrow. Life is uncertain. So... eat your dessert first.

Peace. Out.

(yes, the last sentences were a bit Matthew Colville influenced. Just a bit)

3D Gibberosa

- Posted in Cichlids by

It's pretty impressive how I can get a 3D Cyphotiliapia...
Starting with an image generated by NotebookLM, all I have to do is convert it to 3D and print the result.
I go to bed, and the next morning my Gibberosa is there!
Too bad I have no talent for painting :)

Neolamprologus Similis

- Posted in Cichlids by

To live happily, live hidden.

Let there be graphs

- Posted in Computer by

Some years ago I tried to monitor and display some domestic metrics, with my aquariums temperature as a main goal.
I thus bought a profilux monitor, which was expensive. It was also a close system (at least at the time, no API for external access, I quickly gave up trying to hack it), and... mine is now out of order...
So instead of fixing it, I decided to take the Home Assistant road, and, wow, what a trip :)
And guess what, temperature captors are available, easy to use and some of them are waterproof. See me coming?
Within a few hours, I had my long awaited water temperatures in a nice graph: enter image description here

Another proof, if needed, that happiness definitively lies in simple things.

ps: Yes, the Profilux can also monitor pH values, and I've yet to find a Zigbee device that can do that, other than some huge pool stuff. But unlike my temperatures, my pH is stable, so as long as I have to choose, I'd rather have my sexy temperature curves than ... nothing at all :)
And who knows, maybe tomorrow Amazon will offer me the Zigbee pH sensor I've been waiting for.
Peace out.