This Python package is an interactive storytelling engine, capable of handling numerous concurrent stories and multiple users for a dynamic, immersive narrative experience.

This summary provides an overview of the key layers and interface concepts, along with a brief outline of the project organization.

Testing: Our comprehensive unit test suite covers approximately 80-90% of the 2000+ lines of source code. We utilize pytest as our testing framework, with tests automatically run on every commit to the main repository.

Source Control: We use Git, Git-LFS, and GitHub for version control and collaboration.

Distribution: The project is configured and built with Poetry. We’re currently distributing on PyPI for testing. Additionally, we provide a Dockerfile as a quick-start solution for PAAS deployments.

Documentation: Our project is thoroughly documented with Sphinx, and documentation is accessible on ReadTheDocs. This includes user and story-author guides, as well as a comprehensive OpenApi specification for the REST API server.

class afc.protocols.NodeResponse
uid
text
class afc.protocols.BlockResponse
actions
media
uid
text
afc.protocols.StoryResponse

current list of block responses

afc.protocols.StatusResponse

list of kv attributes with formatting

afc.protocols.InfoResponse

dict of information about an object

class afc.protocols.World(*args, **kwargs)

A world is a collection of story templates and hooks that is in charge of instantiating new stories from templates and managing story object lifecycle hooks. There are relatively few worlds to be managed, and they are static after initialization, so they are implemented using a Singleton pattern.

uid
templates

story node templates for instantiating a new stor

pm

plugin hooks for story objects referencing this world

ns()

Cascading namespace for runtime calcs, includes static, story-independent variables

create_story(*args, user=None, **kwargs)
get_media(media_type, media_id)

Story-independent media by type and id

get_info()
class afc.protocols.Serializable(*args, **kwargs)

Serializable objects can interact with the data layer. Simplest is just to ensure the default ‘reduce’ behavior works and rely on Python’s pickle. Other backends may rely on more sophisticated serialization/deserialization using the to_dict and from_dict methods.

to_dict(**kwargs)
classmethod from_dict(**kwargs)
class afc.protocols.User(*args, **kwargs)

A User represents an entity that owns a collection of stories.

Security: Users are indexed by hashing their “secret”, a short phrase. Secret-hash indexing is not intended to provide security, authentication, or even guaranteed uniqueness. It serves as a self-selected identifier. The current project design only supports multiple users minimally, primarily for a public reference server, and does not include formal authentication. _However_, for a small population of users with unique secrets, secret-hash indexing can help obscure user accounts from each other, making exhaustive searches for valid user keys more difficult.

uid

unique id based on hashing the secret

secret

user selected secret

current_world

indicates which story-world the user is currently interacting with to the story manager

story_metadata

metadata for each story, play-throughs, achievements, etc.

ns()

Cascading namespace for runtime calcs, includes story medata for all stories

get_info()
class afc.protocols.StoryNode(*args, **kwargs)

Basic unit of story organization, they can be augmented by mixin classes for rendering, runtime evals and execs, checking conditions, traversing a graph, and owning other nodes.

Story nodes are subclassed to represent intuitive narrative features like Scenes (tree roots), Blocks (narrative beats), Actions (verbs/branching choices), Actors (npcs), and Assets (nouns/objects).

Certain story node subclasses may carry other specialized handlers, such as challenge, which wraps an interactive game handler that provides for more complex interactions than just branching storylines.

Story nodes may also have various media handlers for image or audio. Media handlers manage static media assets or generate dynamic assets as required (such as svg ‘paperdoll’ avatars).

uid
path

path to this node from the index, unique, human-readable

parent

StoryNodes are organized in trees (scenes) and collections (stories)

children
index

each node is indexed in a single story

ns()

Cascading namespace for runtime calcs, includes local variables and subclass-specific elements like game handlers, links to parent namespace or story namespace if it is a root

get_info()
class afc.protocols.Story(*args, **kwargs)

An indexed collection of story nodes. It provides centralized organization for navigating scene trees and tracking state.

uid
world

created by a single world from templates

user

owned by a single user

nodes

story objects and trees in this story

current_block

currently entered node

ns()

cascading namespace for runtime calcs, includes index of nodes by path and links to world and user namespaces

add_node(node)
find(node_id)
filter(filter_fn)
get_info()
class afc.protocols.StoryApi(*args, **kwargs)

Service layer wrapper for a single story, providing a limited client interface.

Concurrency: Three api calls, do_action, goto_node, and apply_effect, may mutate the story state. These require a read/write open-story context when called from the story manager, which in turn must be locked if calling from an asynchronous method.

story
get_update(**kwargs)
do_action(action, **kwargs)
get_status()
get_media(media_type, media_id)
goto_node(node)
get_node_info(node)
check_expr(expr)
apply_effect(expr)
class afc.protocols.StorageBackend

Data layer, StorageBackend wraps various flavors of persistent and non-persistent storage, such as in-memory, pickle and yaml files, and redis or bson databases. It presents as a MutableMapping.

class afc.protocols.StoryManager(*args, **kwargs)

The StoryManager’s basic purpose is to access Story and User objects and yield StoryApi instances. It organizes interactions between the service layer apis, the domain layer story and user objects, and the data layer.

Concurrency: The ‘open story’ context needs to be locked for atomicity when opened as read-write inside an asynchronous function like a FastApi endpoint.

storage
put_story(story)
get_story(user_id=None, world_id=None)
remove_story(user_id=None, world_id=None)
put_user(user)
get_user(user_id)
remove_user(user_id)
open_story(user_id, world_id, write_back=False)
update_user(user_id, secret=None, current_world=None, **kwargs)
class afc.protocols.StoryManagerApi(*args, **kwargs)

Service layer wrapper for a story manager, providing a limited interface that delegates api calls onto to the underlying story objects. Presentation-layer clients and servers can use the service api to specify stories and execute api methods or get data on them.

Concurrency: The`do_story_action`, goto_story_node, apply_story_effect, use a read-write story manager context, so they should be locked for atomicity when used in an asynchronous context. create, drop, and update functions should also be locked for their duration.

Clients: The package includes a FastApi REST endpoint server and cmd2 interactive cli that use the story manager api to interact the game logic.

story_manager
get_story_update(**identifiers)
do_story_action(action, **identifiers)
get_story_status(**identifiers)
get_story_media(media_type, media_id, **identifiers)
goto_story_node(node, **identifiers)
get_story_node_info(node, **identifiers)
check_story_expr(expr, **identifiers)
apply_story_effect(expr, **identifiers)
create_story(user_id, world_id)
drop_story(user_id, world_id)
create_user(secret)
get_user_info(user_id)
update_user_secret(user_id, secret)
update_user_current_world(user_id, world_id)
drop_user(user_id)
class afc.protocols.WorldApi(*args, **kwargs)
static get_world_info(world_id)
static get_world_media(world_id, media_type, media_id)
static get_world_scenes(world_id)
class afc.protocols.SystemApi(*args, **kwargs)
static get_system_info()
static get_key_for_secret(secret)
static reset_system(hard)

Indices and tables