Skip to content

Developer Guide

forelius edited this page Aug 2, 2026 · 2 revisions

Developer Guide

For developers. This page documents FaDe's internals — it's aimed at module authors and contributors, not players or GMs.

This guide explains how Fantastic Depths (FaDe) is architected. It is aimed at developers who are curious about how the system works, or who want to write a Foundry module that changes or enhances one of FaDe's subsystems.

Work in progress. This guide is being built out incrementally. Sections will be expanded and linked to dedicated sub-pages over time.

Overview

Fantastic Depths (FaDe) is a TypeScript game system built on the Foundry VTT framework. Its relationship with TypeScript is deliberate and pragmatic: FaDe uses TypeScript for structure, interfaces, and type safety in the code it owns, but it does not attempt to fully resolve every object against Foundry's own type system. Many framework objects — documents, rolls, tokens, and similar — are handled loosely rather than importing and typing the entire Foundry API surface. The result is code that gets real value from TypeScript without fighting the framework's type system.

The architecture rests on a few core ideas:

  • Data models. In Foundry v12+, actors and items are backed by data models (*DataModel classes) that define their schema and derive values. FaDe treats these models as the single source of truth for actor and item data.
  • Items as a database. Much of the game's content is data, and FaDe stores that data as items. Classes, ancestries, saving throws, weapon masteries, and conditions are item documents that a finder utility resolves from the world first and then from compendium packs — so a world item can override anything shipped in the default compendium module. A complementary user tables subsystem lets GMs and modules add custom lookup tables (bonus, key/value, and JSON tables) to the system that other subsystems can read at runtime.
  • Sheets and mixins. Sheet classes present the data-model-backed actors and items to the user. Reusable UI behaviors — drag-and-drop, VS-group modifiers, and similar — live in shared mixins that sheets compose, rather than duplicated in each sheet.
  • Registry. Core game-rule mechanics are pluggable. The math behind the game — how attacks hit, how damage applies, how initiative is rolled, and more — is implemented as swappable systems registered under game.fade.registry. Rule variants can be selected through system settings, and a module can replace a system without touching FaDe's source.

Together, these ideas keep the system modular: data models define state, sheets present it, mixins share behavior across sheets, and the registry lets rule subsystems be swapped independently.

Project Structure

All source lives under src/, organized by concern:

  • actor/, item/ — actor and item classes, factories, data models, and field definitions.
  • sheets/ — actor and item sheets, plus shared mixins.
  • chat/, dialog/ — chat message builders and typed dialogs (each with its own factory).
  • sys/ — system-wide utilities and managers, the registry of swappable rule systems, and config.ts (the CONFIG.FADE constants).
  • apps/ — standalone app forms (trackers, XP dialog, user tables config).
  • utils/ — small helpers (document finder, collapser, etc.).

At the top level: fantastic-depths.ts is the entry point that wires everything together, index.ts re-exports the public API, and fadeHandlebars.ts / fadeSettings.ts register helpers and system settings.

Initialization & Hooks

Startup is driven by Foundry's lifecycle hooks in fantastic-depths.ts:

  • init — populates game.fade (managers, finder, registry), registers data models and custom document classes (actors, items, combat, chat message, active effect), registers sheets and system settings, and registers the default rule systems.
  • setup — light use; currently not much happens here.
  • ready — runs the data migrator, initializes the light/effect/socket/toast managers, binds chat-message click handlers, and registers GM-only hooks for world time and actor/item changes.

FaDe also exposes custom hooks so modules can run code around its own setup:

  • beforeFadeInit / afterFadeInit
  • beforeFadeReady / afterFadeReady

The Registry

game.fade.registry is where the swappable game-rule subsystems live. Each system is stored under an id as { id, type, instance }:

  • registerSystem(id, instance, type) — add or replace a system.
  • getSystem(id) — get a system's instance (pass true for the full { id, type, instance } object).
  • getSystemType(id) — get a system's class.

registerDefaultSystems() decides which implementation to use for each subsystem by reading the corresponding system setting (for example, which to-hit or initiative variant is selected). Because systems are resolved by id at runtime, a module can swap in its own implementation by registering under the same id — no FaDe source changes required.

See the dedicated Registry page for the full list of systems, how they depend on each other, and how to replace one.

Actors and Items

Actors and items are created through factories that dispatch by document type.

  • Actors. ActorFactory (a Proxy over the base actor class) routes construction to CharacterActor, MonsterActor, or FDVehicleActor based on the actor's type. Each type is backed by a data model registered in CONFIG.Actor.dataModels.
  • Items. ItemFactory routes construction to the item class matching the item's type (weapon, armor, spell, class, condition, and more). Each item type has its own class and a corresponding data model registered in CONFIG.Item.dataModels.

The factories keep Foundry's document system decoupled from FaDe's class hierarchy: Foundry instantiates the factory, and the factory picks the right subclass.

See the dedicated Actors and Items page for the class hierarchies, the data-model layer, and how the two layers divide responsibilities.

Sheets

Actor sheets build on a shared base, FDActorSheetV2, which composes the DragDropMixin; CharacterSheetBase, MonsterSheet, and FDVehicleSheet extend it. Item sheets all extend FDItemSheetV2, with per-type sheets composing the shared mixins as needed — for example, WeaponItemSheet is DragDropMixin(VsGroupModMixin(FDItemSheetV2)).

Two mixins provide the reusable behaviors: DragDropMixin (dragging items onto sheets) and VsGroupModMixin (editing VS-group modifiers). A small SheetTab class defines the tabbed layout.

Sheets are registered in fantastic-depths.ts for the document types they handle and marked as the default sheet for that type.

Chat & Dialogs

Rolls and actions produce chat messages through a factory + builder pipeline.

  • Chat. ChatFactory is a Proxy over ChatBuilder that dispatches by a CHAT_TYPE symbol to the matching builder class (ability check, attack roll, damage roll, spell cast, and so on). Each builder formats the message HTML; some also attach the click handlers that apply damage, heals, or conditions from the rendered message.
  • Dialogs. DialogFactory is an async function that dispatches on a dataset.dialog value to the matching dialog class (attack, saving throw, ability check, wrestling, damage type, and others), with fadeDialog providing generic yes/no dialogs.

Extending FaDe

Modules can change or enhance FaDe without touching its source. FaDe's public API is bundled in /systems/fantastic-depths/module/fantastic-depths.min.js, so modules can import its classes and factories directly. The fade-white-box-fmag module is a working example of all of the following:

  • Hook into the lifecycle with the custom init/ready hooks above (it uses all four to set defaults and register its systems).
  • Replace a rule subsystem by registering your own implementation under the same id in game.fade.registry (it swaps in its own moraleCheck system, built with FaDe's DialogFactory and fadeFinder).
  • Extend the actor data model by subclassing FaDe's data model and swapping it in via CONFIG.Actor.dataModels (its WBCharacterDataModel extends CharacterDataModel and adds retainer fields).
  • Override sheet UI by shipping your own Handlebars templates for the parts the sheets render (it provides a replacement for the character sheet's description section, backed by the extended data model).
  • Ship game content as compendium packs and point FaDe's pack settings at them from the beforeFadeReady hook.
  • Integrate with other modules the way FaDe itself does: listen for a third-party module's hooks and register system data through its API (see the Item Piles integration in sys/addonIntegration.ts).

Clone this wiki locally