H3lp Yours3lfDocs
concept

State and Context

Separate module access, script permissions, ownership, and lifetime.

Before choosing a helper, answer two questions: where does this code run, and who owns its state? A convenient wrapper changes neither answer.

Modules versus installed interfaces

A plain module returns a function or table when required. Signal, Pool, and normalizePath do not import OpenMW APIs.

A runtime helper uses engine facilities. Debounce reads an OpenMW clock, but still requires your script to call it.

An installed interface is provided by another registered script. S3lf is obtained through I.s3.lf; requiring an implementation file is not a substitute for installing its provider.

Synchronous calls versus engine events

Signal listeners run during fire. OpenMW events cross an engine-managed delivery boundary; sending one does not mean its handler has already completed. Use the engine's event facilities for cross-context communication, not two independently constructed Signal objects.

Lifetime belongs to the owner

ValueOwnership rule
A normalized stringKeep the returned value as long as needed.
A Signal connectionRetain its handle and disconnect when no longer needed.
A pooled tableBorrow until release; never use it afterward.
A debounce input tableThe helper keeps your reference, not a snapshot.
An engine-backed S3lf fieldEngine context/type restrictions still apply; do not assume it is serializable plain data.

Ordinary Lua locals are not automatically a save format. Decide which values are runtime scratch state and which belong in your script's explicit save/load contract. Avoid saving closures, connection handles, or pool internals.

The bootstrap demonstrates a script with no persistent state. The pooling example demonstrates a deliberately shorter lifetime: one synchronous dispatch.

Cod3x explains the larger OpenMW constraints in Contexts, Object Lifetime, and Storage and Lifecycle.