-
-
Notifications
You must be signed in to change notification settings - Fork 3
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.
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 (
*DataModelclasses) 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.
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, andconfig.ts(theCONFIG.FADEconstants). -
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.
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
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 (passtruefor 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 are created through factories that dispatch by document type.
-
Actors.
ActorFactory(a Proxy over the base actor class) routes construction toCharacterActor,MonsterActor, orFDVehicleActorbased on the actor'stype. Each type is backed by a data model registered inCONFIG.Actor.dataModels. -
Items.
ItemFactoryroutes construction to the item class matching the item'stype(weapon, armor, spell, class, condition, and more). Each item type has its own class and a corresponding data model registered inCONFIG.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.
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.
Rolls and actions produce chat messages through a factory + builder pipeline.
-
Chat.
ChatFactoryis a Proxy overChatBuilderthat dispatches by aCHAT_TYPEsymbol 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.
DialogFactoryis an async function that dispatches on adataset.dialogvalue to the matching dialog class (attack, saving throw, ability check, wrestling, damage type, and others), withfadeDialogproviding generic yes/no dialogs.
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 ownmoraleChecksystem, built with FaDe'sDialogFactoryandfadeFinder). -
Extend the actor data model by subclassing FaDe's data model and swapping it in via
CONFIG.Actor.dataModels(itsWBCharacterDataModelextendsCharacterDataModeland 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
beforeFadeReadyhook. -
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).