.. _architecture: ################ Architecture ################ These tools define Domain Specific Languages (DSLs) for some broad categories of complex TTRPG constructs, including magical spells (and miracles) as well as characters (and monsters.) Consistent with the `C4 Model `_, we'll look at `Context`_, `Containers`_, `Components`_, and `Code`_. This is a summary and overview of the common features and design approach. The details of each DSL are in separate `Code`_ sections. Context ======= We've got a number of use cases for two primary audiences, a **Software/DSL designer**, and a **Campaign designer**. The campaign designer's work product will often be shared with players and game masters; this sharing isn't really part of this application. Here's a summary of the context for these tools: .. uml:: @startuml 'https://plantuml.com/use-case-diagram skinparam actorStyle awesome title OpenD6 Tools Use Cases actor Designer usecase "Create and change\nelements of a world.\nCompute derived values.\nConsider the overall design." as uc_1 usecase "Publish documents for\ngamemasters and players." as uc_2 Designer --> uc_1 Designer --> uc_2 GM --> uc_2 Player --> uc_2 @enduml The **Publish documents** use case is implemented as processing the publication pipeline to create HTML, PDF, or EPUB documents. The **Create and change elements** use case is summarized as the **Change**-**Compute**-**Consider** cycle. This can include fine-tunning a spell's difficulty; see :external:ref:`fantasy.magic.adjusting_and_readjusting`. It can also include adjusting the dice budget for characters and creatures. This use case helps assure the rules and examples are internally consistent. Using software tools does limit the author's flexibility, in the interest of assuring consistency. Implicitly, there's also a use case to be sure this implementation is correct and consistent with OpenD6 rules. This has a number of challenges, and isn't really part of the user's experience. It's a technical consideration that arises when inconsistencies are found in the published rules. See `Area Effect Aspect Volume Computations `_ for an in-depth analysis of a tiny inconsistency. The **Change**-**Compute**-**Consider** cycle is how a designer arrives at a spell's details. The DSL assures the difficulty of a magical spell or invocation meets the needs of the overall rules or a specific campaign. The *OpenD6* rules suggest, for example, that different religions will have distinct characteristics for invocations. This suggests some care to make sure the various religions have a balanced mix of strengths and weaknesses. Creating unique campaign documents is a matter of authoring new documents using RST markup. This can then be converted to HTML or PDF or EPUB for publication. Before looking at the C4 model: Context, Containers, Components, and Code, we'll consider some non-functional requirements and how they constrain the architecture. Non-Functional Requirements --------------------------- These non-functional requirements are part of the design and development process. They're not part of the final DSL. We'll use the FURPS model: Functionality, `Usability`_, `Reliability`_, `Performance`_, and `Supportability`_. Functionality is generally described by the C4 model, including Context, Containers, Components, and Code, covered elsewhere in this document. Usability ~~~~~~~~~ As noted in the `Context`_ section, these tools are focused on desktop publication of TTRPG rules -- world books, campaign books, scenario details, etc. These software tools are not for interactive play. The tools need to work with a document production pipeline. More importantly, the spell definitions are plain text files that can be edited and processed by a wide variety of tools. This means the essential content can be extracted from these tools for use in other tools. For more thoughts on usability, see :ref:`notes.dsl_design`. Reliability ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Since the tools run on a desktop, any reliability, availability, and security questions are pushed to the desktop OS. Performance ~~~~~~~~~~~ We don't spend any time optimizing these tools for high performance. They're run rarely. Once an author has a good design, they'll publish it and move on to the next project. Supportability ~~~~~~~~~~~~~~~~ The most important question is this: **How can we be sure the DSL "works"?** We're done defining the DSL when it can reproduce the published content. This means that we can have a suite of automated tests to create Spells, Characters, Creatures, etc. which have correct computed difficulties. This test suite needs reflect the source documents. The published documents are difficult to parse and contain some editorial errors. Therefore, the DSL will not *trivially* reproduce the published results. It will reproduce those results which are clearly free of errors. For more on the source documents used to validate the DSL, see :ref:`notes.opend6_challenges`. Containers ========================= The tools will run on a desktop. The idea is to have Python module files with spell definitions (or character definitions.) The actors can then process these files to compute difficulty or dice budget values. There are two general modes of operation. - The interactive **Change**-**Compute**-**Consider** cycle. - The final publication pipeline. An interactive environment is required to permit the actors to fiddle around with spell aspects (or character attributes) to get the budget correct. For interactive exploration, a tool like Jupyter Lab is ideal. The spell can be created and tested within a lab notebook. Later, an ``opend6_tools`` application can extract the notebook content to create the required Python module. This will feed the document publicatiojn pipeline. The document publication pipeline is best handled by a "static site generator" or similar tool. The **Sphinx** tool works well for this, since it can produce HTML, PDF, and EPUB outputs. (Other tools like **Hugo** or **Pelican** can be used. The only requirement is to consume source in RST notation.) This leads to using four applications on the user's desktop: :Jupyter Lab: This supports interactive work with spell and character definitions. It enables the **Change-Compute-Consider** workflow. :Text Editor or IDE: This is used create and modify campaign documents in RST. The source text that includes spells, characters, items, etc. :Publication Tool: A tool like **Sphinx** will produce final, published documents from the source RST. This can include HTML, EPUB, and PDF. :The make tool: The **make** tool is ideal for coordinating the various steps in publication. The **Sphinx** publication tool uses the **make** tool to determine what has changed and what need to be rebuilt. We'll configure the **make** tool to use the :py:mod:`opend6_tools.notebook_extract` application to create the RST files form notebooks. The desktop computer has a number of interacting applications. The following diagram shows how the actor interacts with these components: .. uml:: @startuml 'https://plantuml.com/deployment-diagram skinparam actorStyle awesome title Container for the *OpenD6* tools node desktop as "Desktop Computer" { boundary Browser boundary Terminal component jupyter component sphinx boundary Editor folder notebooks { file "spell notebook" as nb <<.ipynb>> } folder campaign { folder source { file "spell module" as py <<.py>> file "other docs" <<.rst>> } folder build { file "campaign book" as book <<.html>> } } component notebook_extract notebook_extract --> nb : "consumes" notebook_extract --> py : "creates" jupyter --> nb : "creates" Browser --> jupyter sphinx --> py : "reads" sphinx --> book : "writes" component make make --> sphinx : "runs" make --> notebook_extract : "runs" Terminal --> make component magic component character nb ..> magic nb ..> character py ..> magic py ..> character Editor --> "other docs" } actor "Campaign Designer" as gm gm --> Browser : "**Create-Compute-Consider**" gm --> Terminal : "**Publish**" gm --> Editor cloud "players and GM's" as players book ----> players @enduml Viewed superficially, a number of tools are used to create a variety of source files for publication. What's important is the use of DSL's, defined in Python, to define spells, items, characters, and creatures. These DSL's have an interesting collection of components that provide the required class definitions Components ========== At the highest level, the :py:mod:`opend6_tools` components have two purposes: - Definitions of DSLs for Spells, Characters, etc. These will support the "Validates design details via **Change**-**Compute**-**Consider** cycle" part of the use case. They are tested by the "Reproduces published rules" use case. - Some small applications to support publication. The goal is to produce RST-formatted details for publication from the DSLs used to create, compute, consider, and change the design. This supports the "Publish campaign documents" use case. The DSLs are used to build a Python module with a CLI. The Python module, when run as an application, creates the neede RST files. There's little programming involved in using the DSL. The idea is to create Python objects which are static description of a Spell or Character. The Python module can be created manually or extracted from a notebook. Generally, spell and character definitions depend on the :py:mod:`opend6_tools` modules in several distinct ways. - The definition DSLs depend on the :py:mod:`opend6_tools.magic` (for spells, invocations, and items) or :py:mod:`opend6_tools.character` module (for characters and creatures.) - The extraction of a spell module from a Jupter Notebook is performed by the :py:mod:`opend6_tools.notebook_extract`. application. - The conversion of a spell module to RST is an internal feature of the spell module. The spell module is **actually** a CLI application with a few command-line features to display and debug the contents of the module. These relationships are shown below. .. uml:: @startuml 'https://plantuml.com/component-diagram title OpenD6 Tools Components Overview folder opend6_tools <> { component magic <> component character <> component notebook_extract <> } folder notebooks { file "spells notebook" as nb <<.ipynb>> nb ..> magic : "imports" file "characters notebook" as nb <<.ipynb>> nb ..> character : "imports" } folder "document source" { component "spells module" as spells <> spells ..> magic : "imports" artifact "spells source" as spellsrst <> spells --> spellsrst : "creates" component "characters module" as chars <> chars ..> character : "imports" artifact "characters source" as charrst <> chars --> charrst : "creates" artifact "source RST" as srcrst <> } folder build { artifact "target document" as doc <> } nb <-- notebook_extract : "reads" notebook_extract ---> spells : "writes" notebook_extract ---> chars : "writes" component sphinx <> spellsrst --> sphinx : "via ..include::" charrst --> sphinx : "via ..include::" srcrst --> sphinx : "source" sphinx --> doc : "writes" @enduml The publication pipeline is controlled by the **make** tool; it's not depicted explicitly, because it touches everything. Because the **make** tool relies on file definitions, it helps to isolate spell books, or bestiaries into distinct Python modules. Any of a number of organizing principles could be used for spells, including by difficulty, or by the skill used. Similarly, monsters or characters can be grouped by any number of attributes. Keeping spell books in separate files makes testing and publication relatively straightforward because the **make** tool can build the required RST-format files automatically. The following diagram provides a more detailed look at the ``document source`` folder, shown above. Spell Use: .. uml:: @startuml 'https://plantuml.com/component-diagram title Document Source for Spells folder opend6_tools { component magic component character } folder document_source { artifact index.rst <> artifact magic.rst <> artifact characters.rst <> folder spells { artifact spells_subsection.py <> { component SomeSpell } artifact spells_subsection.txt <> spells_subsection.py --> spells_subsection.txt : "Create by display command" } folder characters { artifact characters_subsection.py <> { component SomeCharacter } artifact characters_subsection.txt <> characters_subsection.py --> characters_subsection.txt : "Created by display command" } index.rst --> magic.rst : """..toctree::"" directive" index.rst --> characters.rst : """..toctree::"" directive" magic.rst --> spells_subsection.txt : """..include::"" directive" characters.rst --> characters_subsection.txt : """..include::"" directive" } characters_subsection.py ...> character : import spells_subsection.py ...> magic : import @enduml The source documents include manually-prepared ``.rst`` files with ordinary text, tables, examples, etc. These are exemplified by the ``index.rst``, which is typically modified manually. It's common for the ``index.rst`` to use the ``.. toctree::`` directive to identifies a tree of parts, chapters, sections, etc., that build the overall document. In this case, two chapters are shown: ``magic.rst`` and ``characters.rst``. Each of these chapters depends on content in separate folders. The ``magic.rst`` depends on files in a ``spells`` folder. Similarly, the ``characters.rst`` depends on a ``characters`` folder. Within these subfolders are source material to define Spells or Characters. The source material is defined in ``.py`` files. In the diagram, there's a ``spells_subsection.py`` file and a ``characters_subsection.py`` file. These use the Spell and Character DSLs to define the spells and characters. The output from these files is included in the appropriate chapters using the docutil ``.. include::`` directive. This will the spell or character details from separate files. These files are created by the OpenD6 tools, based on the Spell or Character definitions. The ``.txt`` files uses RST markup to define the display text for a spell. The files names don't end in ``.rst`` to make the automatically generated files clearly distinct from the manually authored files. Code ===== The implementation code is covered in separate documents: - :ref:`dice_module`. - :ref:`magic_package`. - :ref:`character_package`. - :ref:`notebook_extract_app`. - :ref:`misc_comp`.