acron.blueprints

Blueprint runtime: the contract between Studio-generated code and the game.

Generated blueprint modules contain exactly:

from acron.blueprints import blueprint, game

@blueprint class WorldBlueprint:

def blueprint_start(self): … def blueprint_update(self): … def blueprint_input(self, key): …

and the project’s main.py drives them from a plain ShowBase:

import acron.blueprints as bp from direct.showbase.ShowBase import ShowBase

app = ShowBase() bp.load() # import every module in <project>/blueprints/ bp.game.player = player # publish shared objects on the context bp.start() # instantiate + blueprint_start() bp.attach(app) # per-frame update() + key presses → input() app.run()

Generated code sees the world through acron.stock (the acron.actor object model plus stock actors and input/camera/time globals).

@blueprint is the whole registration story — an ejected file (plain Python, no graph) keeps working because it still carries the decorator.

Dispatch is error-isolated: a hook that raises gets its annotated traceback printed to stderr (the game keeps running), and after 3 consecutive failures that hook is disabled for that instance with one loud message — one broken blueprint cannot freeze every other one. A successful call resets the count. Set ACRON_BLUEPRINTS_STRICT=1 to re-raise on the first error instead (tests, CI). ACRON_BP_BREAK_TIMEOUT (float seconds) bounds how long a Breakpoint node may hold the game waiting for Studio’s continue.

Global Functions

action_matches(action, key)

True when key is bound to action in input.json. An unmapped action falls back to matching the key literally, so graphs run before any input.json exists.

attach(app=None)

Drive the lifecycle from a ShowBase: a per-frame task calls update() (skipped while acron.stock.application.paused); the actor world steps from the same task; and every key/mouse button press reaches input(key) with Panda’s button name (‘space’, ‘w’, ‘arrow_up’, ‘mouse1’). app defaults to the base builtin. Calling it twice is a no-op; reset() detaches.

axis(name)

The current value of axis name in [-1.0, 1.0], from held keys (pos bindings pull +1, neg -1). 0.0 when unmapped or the engine input state isn’t available (headless tests).

bind_event(name, handler)

Subscribe handler to dispatcher name (duplicate binds of the same handler are collapsed).

blueprint(cls)

Class decorator: register a blueprint class for start()/update()/input().

classes()
declare_enum(name, members)

Create (or fetch) the blueprint enum name.

members is a space/comma-separated string or a list of member names. Idempotent for an identical member list; redeclaring with different members is a hard error — two graphs disagreeing about an enum is a bug, not a merge.

declare_struct(name, **fields)

Create (or fetch) the blueprint struct name.

Each keyword argument is a field with its default value. Instances take any subset of the fields as keyword arguments; equality is field-wise. Idempotent for an identical field list; a different field list is a hard error (same contract as declare_enum).

default_ui_font()

Path of a UI font for the Create UI node, so an RmlUi overlay renders text without the project shipping one: $ACRON_UI_FONT, else the wheel’s bundled face (acron/fonts/Lato-Regular.ttf), else a common system font. Raises FileNotFoundError when none exists.

delay(seconds, fn)

Run fn() once, seconds from now (measured across update() calls). The Delay node’s continuation callback.

despawn(instance)

Remove a live instance from dispatch, calling its blueprint_destroy() (if defined; error-isolated) first. Despawning something that is not a live instance is a hard error, not a silent no-op.

detach()

Undo attach(): remove the update task and the key event hook.

fire_event(name, *args)

Fire dispatcher name: every bound handler runs (bind order), error-isolated — one raising handler cannot stop the others (ACRON_BLUEPRINTS_STRICT=1 re-raises).

get_class(name)

Look up a class by name (SpawnClass dynamic path): registered blueprint classes first, then the acron.stock namespace (Actor, Sky, …).

get_enum(name)

The blueprint enum name, loading the sibling graph that declares it if needed.

get_instance(name)

The FIRST live instance of the blueprint class named name (in start()/spawn() order — with several spawned instances of one class, iterate instances() to reach the others).

This is how one graph calls another graph’s functions (cross-graph Call Graph Function node): get_instance('WorldBlueprint').spawn_wave(3). Instances exist only after bp.start().

get_struct(name)

The blueprint struct name, loading the sibling graph that declares it if needed.

implements(obj, interface)

True when obj’s blueprint class declares interface in its graph’s implements list (compile-time-checked marker).

input(key)

Dispatch blueprint_input(key) on every live instance (error-isolated).

instances()
interface_call(target, function_name, *args)

Call target.function_name(*args) with UE interface-message semantics: a target that doesn’t implement the function is silently skipped (returns None). A None target is skipped too.

load(directory=None)

Import every *.py module in the project’s blueprints/ directory.

Importing runs the @blueprint decorators, which is all registration takes. directory defaults to $ACRON_PROJECT_ROOT/blueprints, else ./blueprints. The _ejected/ archive subdirectory is never scanned.

load_input_map(directory=None)

(Re)load blueprints/input.json into the action/axis map. Called by load(); call directly after editing the file at runtime.

node_for_traceback(exc)

Map an exception’s traceback to the graph node it crashed in.

Returns "[nID] Title (file.py:line)" for the innermost frame inside a *_generated.py file, or None when the crash wasn’t in generated code.

parent_class(name)

The @blueprint class name, for graph inheritance (class BossBlueprint(parent_class('EnemyBlueprint')):). Loads the sibling module that defines it if it hasn’t imported yet.

reset()

Forget all registered classes and instances (tests, hot restart).

Every live instance gets its blueprint_destroy() (if defined; error-isolated) first, and the modules load() imported are purged from sys.modules so the next load() re-executes them — making reset(); load(); start() a full hot restart.

spawn(cls_or_name, /, **kwargs)

Create a live blueprint instance at runtime (SpawnClass dynamic path).

cls_or_name is a class, or a name resolved via get_class (registered blueprints first, then acron.stock). The instance is constructed with no arguments; every keyword argument is set as an attribute on it BEFORE its blueprint_start() runs (spawn('Enemy', position=p) sets inst.position = p). REGISTERED blueprint classes join update()/input() dispatch; stage/engine classes are constructed and returned unregistered — the engine owns their lifecycle, and registering them here would retain destroyed entities in the dispatch list forever.

start()

Instantiate every registered class and call its blueprint_start().

Idempotent per class (exact type — a live instance of the class itself, not of a subclass, is what skips it). The skip check is a SNAPSHOT taken before any class starts: an instance spawned from inside another class’s blueprint_start() never suppresses the auto-instance, so the resulting population is independent of registration (= file) order. Failures are isolated: a class whose constructor or blueprint_start() raises cannot stop the other blueprints from starting (ACRON_BLUEPRINTS_STRICT=1 re-raises).

start_timeline(owner, duration=1.0, on_tick=None, on_finished=None, loop=False)

Start a timeline owned by owner: every update() calls on_tick(alpha) with alpha running 0→1 over duration seconds, then on_finished() fires (or the timeline wraps when loop).

stop_timelines(owner)

Cancel every running timeline owned by owner (no on_finished).

trace_tail(n=20)

The ids of the last n graph nodes entered ([‘n3’, ‘n7’, …]).

Empty unless the project’s blueprints were compiled with --debug. Use after a crash to see where execution was: the last entry is the node that was running (or the last one that completed) when things went wrong.

unbind_event(name, handler=None)

Unsubscribe handler from dispatcher name (None = all handlers). Unbinding something never bound is a no-op.

unwatch_collisions(owner, actor=None)

Stop collision watches owned by owner (all of them, or just the one on entity).

update()

Dispatch blueprint_update() on every live instance (error-isolated), advance latent work (Delay/Timeline), and poll collision watches.

watch_collisions(owner, actor)

Start dispatching owner.blueprint_collision_enter/exit(actor, other) for contact changes on actor (needs a collider). Watching the same (owner, actor) twice is collapsed.