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.
- 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)¶