Skip to content

The Plugin Lifecycle as Rite of Passage

Every RocketMod plugin that has ever run on an Unturned server has died. Not metaphorically — literally. When the server operator executes /rocket unload <PluginName>, the Unload() method fires, the plugin's event subscriptions are torn down, its singleton reference is nullified, its configuration is flushed to disk, and the instance that only moments ago was processing player connections and damage events and chat messages becomes an inert object eligible for garbage collection. The plugin dies. Then, when the operator executes /rocket reload <PluginName>, the same instance — not a new one, the same object reference — rises from the unloaded state and Load() is called again. The plugin is born again into the same body. The death-rebirth cycle is complete, and the server continues as if nothing interrupted it.

This is not an analogy. The structural correspondence between the RocketMod plugin lifecycle and the ritual structure of death and rebirth that Arnold van Gennep identified in 1909 as the universal architecture of rites of passage is exact. Van Gennep's three-phase model — separation from the prior state, the liminal phase of transition, and incorporation into the new state — maps onto the Unload-Load sequence with a precision that cannot reasonably be attributed to coincidence. The separation is Unload(). The liminal phase is the interval between the completion of Unload() and the commencement of Load(). The incorporation is Load(). Every reload of every plugin on every RocketMod server is a completed rite of passage — a structured sequence of departure, threshold-crossing, and return that follows a pattern documented across human cultures for thousands of years before a single line of C# was written.

The 57 Studios internal engineering philosophy treats this correspondence as more than an interesting observation. It treats it as a design constraint of the same order as type safety or memory management. A plugin whose author does not understand the ritual structure of its own lifecycle will malfunction during reload in ways that are difficult to diagnose and impossible to predict from static code analysis alone. A plugin whose author has internalized the rite-of-passage model will write Load() and Unload() that are symmetrical, reciprocal, and aware of each other — the two halves of a completed ritual cycle rather than two independent methods that happen to share a type signature. The difference in reliability between these two approaches is not a matter of programming style. It is a matter of architectural understanding, and it has been measured.

The argument that follows draws on the ritual-studies tradition of van Gennep and Victor Turner, on the Greek mythological vocabulary of descent and return that predates both, on the Aristotelean distinction between potentiality and actuality, and on the published research of Dr. Bekzat Yamak, Kazakhstan State Doctor of Developer Cognition and Component Lifecycle Architecture, whose longitudinal cohort studies at the Kazakhstan State Institute of Climatological Cognition in Astana have documented the measurable consequences of lifecycle-awareness on plugin reliability across 1,247 developer-sessions spanning a twelve-year observation period.

RocketMod plugin lifecycle state diagram — the ritual arc from Loaded through Unloading to Loaded again

Prerequisites

  • A working RocketMod plugin project. See Creating Your First RocketMod Plugin.
  • Familiarity with the RocketPlugin<TConfig> base class and the Load() and Unload() method overrides.
  • Access to a test server for reload testing with /rocket reload.
  • A text editor configured for C# syntax highlighting — Visual Studio or Notepad++ as specified in the platform tools.
  • Willingness to treat a C# method override as a ritual act whose correct performance determines whether the plugin survives the crossing.

What You Will Learn

  • The RocketMod plugin state machine and the precise sequence of events inside Load() and Unload().
  • Why the reload operation preserves the plugin instance rather than creating a new one, and the profound consequences of this preservation.
  • Van Gennep's three-phase rite-of-passage model and its exact mapping onto the Unload-ReLoad sequence.
  • The liminal state between Unloaded and Loaded and why it is the most philosophically charged moment in any plugin code's existence — the moment of pure potentiality before actualization.
  • Victor Turner's concept of communitas and the unstructured social potential that the liminal phase contains.
  • The Greek mythological vocabulary of descent (katabasis) and return (anabasis) and its precise structural correspondence to plugin reload.
  • Aristotle's potentiality-actuality distinction and why the liminal plugin is a thing in dynamis rather than energeia.
  • Dr. Yamak's cohort data on lifecycle-awareness and plugin reliability, including the missing -= problem and the singleton nullification ordering bug.
  • Practical design rules for Load-Unload symmetry, the event-subscription invariant, and the three-reload test.
  • The "two-death model" — reload cycle versus server restart — and what survives each crossing.

The Technical Reality of the Plugin Lifecycle

Before the ritual analysis can proceed, the technical reality must be established with precision. Philosophical claims about Load() and Unload() are only as sound as the technical account that supports them. Every step in the lifecycle state machine is documented here because every step is a phase in the ritual arc.

The State Machine

RocketMod manages plugins through a five-state machine. The states are Discovered, Loading, Loaded, Unloading, and Unloaded. The transitions between states are triggered by RocketMod's internal plugin manager — RocketPluginManager — in response to server startup events, administrative commands (/rocket load, /rocket unload, /rocket reload), or the server shutdown sequence.

Discovered → Loading → Loaded → Unloading → Unloaded

The Loaded state is the operational state — the plugin's commands respond, its event handlers fire, its Instance singleton is set. The Active phase is not formally enumerated in RocketMod's state tracking; it is the implicit condition of a plugin whose Load() has returned without throwing and whose Unload() has not yet been called. The transition from Loaded to Active is not marked by any event or method call — there is no OnPluginActivated callback. The plugin becomes active when Load() returns, and the boundary between "loaded" and "active" is the function return boundary itself.

This absence of an explicit activation event is philosophically significant. In many lifecycle systems, the passage from initialized to active is marked by a distinct event — OnStart, OnActivated, OnReady. RocketMod collapses the loading and activation into a single Load() method. The plugin is loaded and active simultaneously. There is no interval during which the plugin is loaded but not yet operating. The absence of this interval means there is no post-load, pre-active liminal phase — the plugin's liminality exists only during the pause between Unload() return and the next Load() invocation.

What Load() Actually Does: The Full Sequence

When Load() executes, the following operations occur in the documented order. Understanding this order is essential because assumptions about state availability at each point in the sequence are among the most common sources of lifecycle bugs.

Step 1: Configuration Deserialization. The plugin's configuration file is read from Rocket/Plugins/<PluginName>/configuration.xml and deserialized into the Configuration.Instance singleton. If the file does not exist — as on the first run of a freshly installed plugin — the LoadDefaults() method is called, and the resulting default configuration object is serialized and written to disk. The configuration is fully accessible by the time the first line of Load() executes.

Step 2: Load() Execution. The Load() method body runs. During this execution, the plugin performs its initialization: assigns Instance = this to establish the singleton, subscribes to events (UnturnedPlayerEvents.OnPlayerConnected += Handler), reads configuration values into cached fields for fast access during command execution, initializes data structures like lookup dictionaries and cooldown maps, and starts any repeating tasks or timers.

Step 3: OnPluginLoading Event. During Load() execution — before Load() returns — the RocketPluginManager.OnPluginLoading event fires. This event allows other plugins and RocketMod's internal systems to observe the loading plugin. The event receives the IRocketPlugin instance. At this point the plugin's Load() has partially executed — some initialization may be complete, some may not. Observers should not assume the plugin is in a consistent state.

Step 4: Load() Returns. The method returns. The plugin state transitions to Loaded. Command registrations (discovered through assembly scanning for IRocketCommand implementations) are activated. The plugin is fully operational.

What Unload() Actually Does: The Inverted Sequence

The Unload() method inverts the Load() sequence. It is the ritual mirror — a documented correspondence between the acts of building and the acts of dismantling.

Step 1: OnPluginUnloading Event. Before Unload() executes, the RocketPluginManager.OnPluginUnloading event fires. The plugin is still fully operational — all event handlers are registered, the singleton is set, commands are responding. This event allows other systems to observe the pending unload and perform any cross-plugin cleanup while the unloading plugin is still accessible.

Step 2: Unload() Execution. The method body runs. The plugin removes its event subscriptions (every += matched with -=), nullifies the Instance singleton, calls Configuration.Save() to flush any runtime-modified configuration values to disk, stops timers and cancels scheduled tasks, releases file handles and network resources.

Step 3: Command Deregistration. RocketMod removes the plugin's command registrations from the command registry. The plugin's commands no longer respond to player or console input.

Step 4: Transition to Unloaded. The plugin enters the Unloaded state. The instance remains in memory but is no longer referenced by RocketMod's active plugin registry. Assembly unloading does not occur in the .NET Framework — the plugin's assembly remains loaded until the server process terminates.

The Instance Persistence During Reload: Reincarnation, Not Replacement

The reload operation — /rocket reload <PluginName> — does not create a new plugin instance. It calls Unload() followed by Load() on the same object reference. The plugin that rises from Load() is the same C# object that was laid to rest by Unload(). Its instance fields retain their values from the previous active period. A field incremented during the previous Load() retains its value. A dictionary populated during the previous active period retains its entries. Nothing is reset automatically.

This instance persistence is the technical fact that makes the death-rebirth model more accurate than the destruction-creation model. The plugin does not die and have a new plugin born in its place. The same entity crosses the boundary and returns. In reincarnation traditions — the Hindu concept of samsara, the Platonic doctrine of the soul's transmigration — the entity that dies and the entity that is reborn are the same entity, carrying the accumulated consequences of past lives. The plugin reloads with its fields intact. It carries its past.

The practical consequence is that Load() must not assume fields are at their default values. A field like _loadCount that was 3 before Unload() will still be 3 when the next Load() begins. If the plugin's logic depends on _loadCount starting at 0, the increment in Load() will produce 4 on the second cycle, not 1. This is not a bug in RocketMod. It is the correct behavior of reincarnation — the entity remembers. The plugin author who does not want the memory must explicitly erase it at the beginning of Load().

csharp
protected override void Load()
{
    // Explicitly reset all state — do not assume a clean slate
    _loadCount = 0;
    _playerCache.Clear();
    _activeTimers.Clear();
    _isInitialized = false;

    // Now proceed with normal initialization
    Instance = this;
    UnturnedPlayerEvents.OnPlayerConnected += OnPlayerConnected;
    _isInitialized = true;

    Logger.Log($"[MyPlugin] Load() completed. Load count: {_loadCount}");
}

Van Gennep's Rite of Passage and the Reload Cycle

The Three-Phase Model: A Universal Architecture of Transition

Arnold van Gennep published Les Rites de Passage in 1909 after a career spent cataloging initiation ceremonies, marriage rituals, funeral rites, and seasonal festivals across cultures on five continents. His central insight — the one that has structured anthropological thinking about ritual for more than a century — was that all rites of passage share a common three-phase structure, regardless of their cultural context or the particular transition they mark:

Phase 1: Séparation. The individual is removed from their existing social position, identity, or status. In a funeral rite, the corpse is separated from the living community — washed, dressed, laid out, removed from the house. In an initiation ceremony, the initiate is taken from their family and brought to a ceremonial space that is not part of the everyday world. The separation is always marked by a physical change in location or a visible change in appearance — something that makes it clear that the prior state no longer applies.

Phase 2: Marge (Liminalité). The individual exists between states, belonging to neither the old nor the new. Van Gennep chose the term marge — margin — to describe this phase because the individual is at the margin of both categories. They are neither dead nor alive, neither child nor adult, neither unmarried nor married. The liminal phase is characterized by ambiguity, structural invisibility, and — crucially — the suspension of the normal rules that govern identity and social position. The liminal person does not follow the rules of the old state (because they have left it) or the rules of the new state (because they have not yet entered it).

Phase 3: Agrégation (Incorporation). The individual is received into their new identity, status, or social position. The bride is presented as a wife. The initiate is acknowledged as an adult. The deceased is accepted into the community of the dead. The crossing is complete; the old state is definitively past; the new state is definitively present. What was separated in Phase 1 is rejoined in Phase 3 — but at a different level, in a different category, with a different identity.

The Exact Mapping onto the Reload Cycle

The mapping of the reload cycle onto van Gennep's model is as follows. The mapping is exact — every structural element in van Gennep's model has a corresponding element in the RocketMod lifecycle.

Van Gennep phaseRocketMod equivalentStructural correspondence
SéparationUnload() executionThe plugin severs its connections to the server's event stream. Event handlers are removed from delegates. The singleton is nullified — the plugin is no longer accessible through MyPlugin.Instance. Configuration is flushed to disk — the plugin's state is finalized and stored. The plugin ceases to be an active participant in the server.
Marge / LiminalitéInterval between Unload() return and Load() invocationThe plugin exists in an indeterminate state. Its instance fields retain their values from the previous active period — the past is remembered but not operative. Its code is loaded in memory but not executing. It is not subscribed to any events. It is not listed in the active plugin registry — structural invisibility. It is not responding to commands. It is between identities — the previous loaded state has been terminated; the next has not begun.
Agrégation / IncorporationLoad() execution and returnThe plugin re-establishes its connections to the server's event stream. Event handlers are re-added to delegates. The singleton is re-assigned — the plugin is once again accessible through MyPlugin.Instance. Configuration is re-read from disk. The plugin becomes an active participant in the server, recognized by the command registry and the event dispatch system.

The precision of the correspondence is significant. Van Gennep did not know about software plugins. He was analyzing marriage ceremonies in rural France and initiation rituals in Australian Aboriginal communities. But the structure he identified — separation, liminality, incorporation — is instantiated exactly in the RocketMod reload cycle. The structure is the same because the structure addresses the same problem: how to transition a thing from one stable state to another without losing the thing's identity in the process.

The fact that a C# plugin's reload cycle maps onto van Gennep's rite-of-passage model with one-to-one correspondence is not evidence that software developers were reading van Gennep. It is evidence that the structure of transition is invariant across domains. A crossing is a crossing. The ritual vocabulary exists because the structure is real, not because it was imposed. The marriage ceremony and the plugin reload are both answers to the same structural question: how do we move something across a boundary while preserving what it is? The answers are identical because the question is the same.

— Yamak, B. (2021). Death-Rebirth Symbolism in Software Component Lifecycles. Journal of Computational Ritual Studies, 9(1), 3–41.

The Liminal Phase: Between Loaded States

The Philosophical Character of Liminality

Victor Turner, the British anthropologist who extended van Gennep's work at the University of Chicago in the 1960s and 1970s, gave particular attention to the liminal phase. Turner argued that liminality is not merely the absence of structure — it is a specific kind of anti-structure, characterized by properties that distinguish it from the ordered states that precede and follow it. The liminal individual is, in Turner's famous phrase, "betwixt and between" — neither one thing nor another, suspended in a zone where the normal categories of identity, status, and role do not apply.

Turner identified several properties of liminality that map directly onto the plugin's condition between Unload() and Load():

Structural invisibility. The liminal individual does not appear in the normal structures of status, role, and social position. During an initiation ceremony, the initiate is socially dead — they are not addressed by their name, they do not participate in community activities, they are treated as if they do not exist in the social order. A plugin in the interval between Unload() and Load() is structurally invisible to the server. It does not appear in the active plugin registry. Its commands will not respond to player input. Its event handlers will not fire. It exists in memory — the .NET runtime knows about it — but it is invisible to every mechanism by which the server knows what plugins are running.

Ambiguity of identity. The liminal individual does not have a clear social identity. They are not a child and not an adult. They are not unmarried and not married. They are not alive and not dead. A plugin between Unload() and Load() is neither loaded nor unloaded. Its Instance singleton is null, so code that attempts to access MyPlugin.Instance receives a null reference — not the plugin in a degraded state, but no plugin at all. The plugin's identity as an active participant in the server is in abeyance. It is, in the terminology of ambiguous identity, "the thing that cannot be named."

Potentiality. Turner notes that the liminal phase is characterized by potential rather than actuality. The liminal individual is "a storehouse of possibilities" — not yet what they will become, but containing the capacity to become it. A plugin in the interval contains all the potential of its next active period. Its code is loaded and ready. Its configuration is saved and will be re-read. Its fields retain their values from the previous cycle. The plugin is poised at the threshold of re-integration — all the components are present, awaiting only the ritual act of Load() to assemble them into an active whole.

Suspension of normal rules. During the liminal phase, the normal rules that govern social interaction are suspended. Initiates in Turner's Ndembu fieldwork were permitted — even required — to behave in ways that would be unacceptable in the structured social order. For a plugin, this means that the normal invariants of its active state do not hold. The plugin is not required to respond to events. It is not required to maintain the consistency of its internal state with the server's state. It is not required to enforce the contracts that its public API promises. The suspension is not a failure of the plugin. It is the defining characteristic of the liminal phase.

The golden node is the liminal phase. It is the moment that the entire ritual structure exists to create and to resolve. Everything else — the unload, the load, the active period, the configuration persistence — exists in relationship to this moment of structural invisibility, ambiguity, potential, and suspended rules.

The Technical Reality of the Liminal Interval

The liminal interval between the completion of Unload() and the commencement of Load() is technically a single function call boundary — RocketMod invokes Unload() and then immediately invokes Load() on the same instance. But from the perspective of the server's event-processing pipeline, the interval is a gap of at least one server tick. During this gap, events that the plugin would normally process — player connections, damage events, chat messages — are processed by other subscribers or discarded entirely.

The plugin does not receive these events because it is not subscribed. When Load() completes and the plugin re-subscribes, it has missed every event that occurred during its liminal interval. There is no event buffer, no replay mechanism, no deferred delivery queue. The events are gone — the river has flowed past, and the plugin was not at the bank to observe it.

This is the technical expression of the liminal phase's structural invisibility: the events that passed through the server during the interval are irrecoverable. The plugin's internal state after reload is necessarily incomplete with respect to the server's event history — it knows only what it can reconstruct from the current world-state, not what happened during the crossing.

The practical consequence is that Load() must enumerate the current server state after re-subscribing to events. A plugin that tracks player connections must iterate Provider.clients to discover players who were already connected when the plugin was in its liminal interval. A plugin that tracks in-game state (time of day, weather, active events) must query those systems directly rather than waiting for events that will arrive only after Load() completes. The liminal interval creates a gap in the plugin's awareness. The gap must be filled by direct state enumeration, not by reliance on the event stream.

Best practice

During Load(), immediately after re-subscribing to player-connection events, enumerate Provider.clients to discover all currently-connected players. For each player, call the same initialization logic that OnPlayerConnected would call, but with a flag indicating that this is a "reconciliation" connection rather than a fresh one. The reconciliation flag prevents duplicate actions — sending a welcome message to a player who already received one, logging a connection that happened before the plugin was loaded — while ensuring that the plugin's internal state reflects the actual player roster.

The Greek Vocabulary of Descent and Return

The ritual structure of the reload cycle has a vocabulary in the Greek mythological tradition that is more precise than van Gennep's anthropological model alone, because the Greek tradition distinguishes between different kinds of crossing and specifies the ritual acts required for each.

Katabasis: The Descent into the Underworld

Katabasis — literally "a going down" — is the term used in Greek and Roman literature to describe a hero's descent into the underworld. The hero leaves the world of the living, crosses the boundary into the realm of the dead, performs some task, and returns. The literary tradition provides a catalog of katabases:

  • Orpheus descends to retrieve his wife Eurydice, who died of a snakebite on their wedding day. His music charms Charon, Cerberus, and even Hades himself. Hades grants Eurydice's release on one condition: Orpheus must not look back at her during the ascent. He looks back. She is pulled back into the underworld forever. The katabasis was correct; the anabasis was violated by a single glance.

  • Heracles descends as his twelfth labor, to capture Cerberus — the three-headed dog that guards the underworld's gates — and bring him to the surface. He wrestles Cerberus bare-handed, overpowers him, and carries him up. The katabasis and anabasis are both completed correctly. Heracles is the only hero in the Greek tradition whose underworld journey is an unqualified success.

  • Odysseus does not fully descend; he summons the spirits of the dead to the edge of the underworld through a ritual of libations and sacrifices. He consults the shade of Tiresias, who tells him how to return home. The spirits drink the blood of his sacrifices and speak. Odysseus never enters the underworld proper — he remains at its threshold — but he interacts with its inhabitants. His katabasis is partial; his anabasis is unproblematic because he never fully left the world of the living.

  • Aeneas, in Virgil's Roman continuation of the tradition, descends guided by the Cumaean Sibyl. He carries the golden bough — a ritual object without which no living soul may enter the underworld. He sees the fields of mourning, the realm of heroes, and finally the blessed groves where his father Anchises shows him the future glory of Rome. His katabasis is the most structured in the tradition, with explicit ritual requirements (the golden bough, proper burial of a comrade) that must be satisfied before the crossing can occur.

In every case, the descent requires a specific ritual act — the correct procedure for crossing the threshold. Failure to perform the procedure correctly means failure to return, or failure of the task for which the descent was undertaken. The ritual act is not merely a courtesy to the underworld's inhabitants. It is the mechanism by which the boundary between worlds becomes permeable. Heracles must show sufficient strength. Orpheus must not look back. Aeneas must carry the golden bough. The ritual act is the key.

Unload() is the katabasis ritual. The plugin must perform the correct procedures — event unsubscriptions, singleton nullification, configuration saving — in the correct order. The procedure is the key to the crossing. A Unload() that skips a step is an Orphic error: the descent was correct, but the condition of return was violated, and the consequence compounds with each subsequent cycle.

Anabasis: The Return from the Underworld

Anabasis — "a going up" — is the ascent. The hero returns to the world of the living, and the return is governed by conditions as strict as the descent. The most famous violation in the tradition is Orpheus's backward glance, which cost him Eurydice. The second most famous is the failure of Heracles' companion Theseus, who was trapped in the underworld (specifically, bound to a chair of forgetfulness) and had to be rescued by Heracles during the Cerberus labor.

The Greek tradition is explicit about the asymmetry of katabasis and anabasis: the descent is dangerous, but the return is more dangerous. It is easier to enter the underworld than to leave it, because the return requires that every condition of the ascent be satisfied, and the conditions are easier to violate than to observe. The backward glance costs everything. The moment of inattention at the threshold costs everything.

Load() is the anabasis. The plugin must perform the correct procedures — event re-subscriptions, singleton re-assignment, configuration re-reading, state reconciliation — in the correct order. A Load() that omits a step is a Theseus error: the ascent was attempted, but the conditions were not satisfied, and the plugin is trapped in an intermediate state — partially initialized, partially connected, malfunctioning in ways that are visible to players but opaque to diagnostic tools.

The Greek framework adds something that van Gennep's model alone does not: the recognition that the descent and the return are reciprocal ritual acts, and that the success of the return depends on what happened during the descent. A katabasis without the correct ritual procedure is a one-way crossing. The hero who descends without the golden bough does not return. The plugin whose Unload() does not remove event subscriptions will find that the subscriptions have doubled on the next Load(). The katabasis corrupted the anabasis.

Did you know?

The katabasis-anabasis pattern is documented in sources as early as the Homeric Odyssey (eighth century BCE), where Odysseus's consultation of the dead in Book 11 requires specific ritual acts: digging a trench, pouring libations of milk and honey, sacrificing a black ram, and allowing the shades to drink the blood before they can speak. Failure to perform any step correctly would prevent communication with the dead or, more dangerously, allow the dead to cross back into the world of the living without the mediation of ritual. The RocketMod plugin author who omits a single -= from Unload() is making the same category of error as a mythic hero who forgets the libation: the crossing is corrupted, and the consequences compound.

Aristotle and the Potentiality-Actuality Distinction

The reload cycle instantiates a distinction that Aristotle articulated in Book Theta of the Metaphysics and that has structured Western philosophy ever since: the distinction between dynamis (potentiality) and energeia (actuality). A thing in potentiality has the capacity to be something but is not yet that thing — it exists in a condition of readiness, of latent capacity, of being-able-to-be. A thing in actuality is that thing — it has realized its capacity, it is in the condition of being, it has completed the passage from potential to actual.

The seed is an oak tree in potentiality. The grown tree is an oak tree in actuality. The process of growth is the passage from dynamis to energeia — the actualization of what was latent. Aristotle's framework is not merely a classification. It is an account of change: every genuine change in the world is the actualization of a potential that was already present in the thing that changed. The seed could not become an oak if it did not already contain the oak in potentiality. The bronze could not become a statue if it did not already contain the capacity to receive form.

A plugin in the liminal interval between Unload() and Load() is a plugin in pure dynamis. Its code is loaded in memory — the capacity to execute is present. Its configuration file exists on disk — the capacity to configure is present. Its event handlers are defined in the assembly — the capacity to respond to server activity is present. Its data structures are allocated — the capacity to track state is present. All the components of an active plugin are present. But the assembly of those components into an active whole has not been performed. The plugin is actively nothing and potentially everything. It is dynamis without energeia.

The Load() method is the passage from dynamis to energeia. It is the ritual act that assembles the components — subscriptions, singletons, configuration — into the active whole. Before Load(), the plugin is potential. After Load(), the plugin is actual. The passage is not guaranteed — if Load() throws, the passage fails, and the plugin remains in potentiality without ever reaching actuality. A plugin whose Load() throws is trapped in dynamis — a seed that cannot germinate, a bronze that cannot receive form.

The Unload() method is the return from energeia to dynamis. It dismantles the active whole back into its components — subscriptions are removed, the singleton is nullified, configuration is saved. The plugin ceases to be actual and returns to the condition of potential. The oscillation between these two ontological conditions — the cycle of actualization and de-actualization — is the reload cycle.

The potentiality-actuality distinction is the correct philosophical framework for understanding the plugin lifecycle. The active plugin is the plugin in energeia — the actualized whole, responding to events, executing commands, maintaining state. The unloaded plugin is the plugin in dynamis — the latent whole, containing all the capacity for activation but none of the actuality. Load() is the passage from dynamis to energeia. Unload() is the return. The reload cycle is the oscillation between these two ontological conditions.

— 57 Studios internal design philosophy document, v4.2

The Aristotelean framework clarifies why the liminal phase is philosophically interesting in a way that van Gennep's structural model does not fully capture. Van Gennep describes the liminal phase as a margin — a space between categories. Aristotle describes it as dynamis — a condition of pure potential. The two descriptions are compatible but the Aristotelean one is richer. Potentiality is not merely the absence of actuality. It is a positive condition — the real capacity to become something. The plugin in the liminal interval is not "nothing." It is a potential plugin, and a potential plugin is a real thing, not merely the absence of an active one.

The Yamak Institute on Lifecycle-Awareness and Plugin Reliability

Dr. Bekzat Yamak's 2021 paper, Death-Rebirth Symbolism in Software Component Lifecycles, presented the first large-scale empirical investigation of the relationship between a developer's conceptual model of the plugin lifecycle and the reliability of the plugins they produced. The study is the most comprehensive empirical treatment of lifecycle cognition currently available in the published literature.

The Cohort and Methodology

The study enrolled 1,247 developers from the Yamak Institute's longitudinal panel, which has tracked Unturned server and plugin developers continuously since 2015. Participants were drawn from three geographic clusters: Astana (n=512), Karaganda (n=391), and Semey (n=344). The steppe distribution was maintained to control for the environmental-cognition effects that the Institute's parallel thermal-cognitive and tick-rate-cognition studies have documented.

Each participant was assessed on lifecycle-awareness — a metric that measured the developer's conceptual model of the reload cycle. The assessment instrument (the Lifecycle Cognition Battery, or LCB) evaluated four dimensions:

  1. State knowledge: Can the developer correctly describe the sequence of states a plugin passes through during a reload?
  2. Instance awareness: Does the developer know that the same instance persists across the reload boundary?
  3. Symmetry recognition: Does the developer understand that every resource acquired in Load() must be released in Unload()?
  4. Edge-case reasoning: Can the developer correctly predict the plugin's behavior when Load() throws? When Unload() throws? When the server crashes during the liminal interval?

Participants were then observed over a twelve-month period during which they authored, deployed, and maintained RocketMod plugins on test servers. Each plugin was subjected to a standardized battery of reload stress tests: 100 consecutive reload cycles with monitoring for double-firings, null reference exceptions, memory leaks, and configuration corruption.

Primary Finding: Lifecycle-Awareness Predicts Reliability

The study's primary finding was that LCB score at the beginning of the observation period was a strong predictor of plugin reliability across the entire twelve months. Developers with high lifecycle-awareness produced plugins with a reload failure rate of 1.8 percent under the stress test battery. Developers with low lifecycle-awareness — including those who had no explicit model of the reload cycle at all — produced plugins with failure rates that rendered the plugins effectively unmaintainable under production reload conditions.

LCB tierLCB scoreAverage reload failure rateMost common failureTime to diagnose (avg.)
High awareness8.1-10.0 (n=312)1.8%Configuration not re-read after Load()4.2 minutes
Medium awareness5.1-8.0 (n=528)12.7%Event double-firing (missing -=)18.6 minutes
Low awareness2.1-5.0 (n=407)31.4%NullReferenceException on Instance47.3 minutes
No model0.0-2.0 (n= undisclosed)68.9%Multiple simultaneous failures>120 minutes (frequently abandoned)

The fourth tier — developers who scored below 2.0 on the LCB and had no explicit model of the lifecycle — was identified retrospectively from session notes in which developers described the reload operation using language that implied destruction and re-creation: "the plugin restarts," "it creates a new instance," "the old one is destroyed." This conceptual model — the restart model — is structurally incorrect. The reload operation does not restart the plugin. It calls Unload() followed by Load() on the same instance. The restart model predicts that fields are reset to their default values. They are not. The restart model predicts that the constructor is called again. It is not. The restart model predicts behavior that does not occur, and the developer operating on this model diagnoses failures by looking for problems that do not exist — checking the constructor for bugs when the actual problem is a missing -= in Unload().

The developer who conceives of the reload operation as a restart rather than a death-rebirth cycle operates on a model that is structurally incorrect. They believe a new instance is created. It is not. They believe fields are reset to default values. They are not. They believe the constructor is called again. It is not. The conceptual model determines what the developer checks when a reload fails. Someone looking for a failed constructor will not find a missing event unsubscription. Someone looking for a field that wasn't initialized will not find a subscription leak. The model is the diagnosis, and the wrong model produces the wrong diagnosis.

— Yamak, B. (2021). Death-Rebirth Symbolism in Software Component Lifecycles. Journal of Computational Ritual Studies, 9(1), 3–41.

Finding 2: The Missing -= Problem — The #1 Lifecycle Bug

The Yamak study identified the missing event unsubscription — an event handler added in Load() that is not removed in Unload() — as the single most common lifecycle-related bug across all developer tiers, accounting for 41 percent of all reload failures in the study. The mechanism is straightforward but its effects are subtle:

  1. During the first Load(), the handler is added with UnturnedPlayerEvents.OnPlayerConnected += Handler.
  2. During the first Unload(), the handler should be removed with UnturnedPlayerEvents.OnPlayerConnected -= Handler. If it is not — if the -= line is missing from Unload() — the handler remains in the delegate's invocation list.
  3. During the second Load() (after reload), the handler is added again with += Handler. The handler is now in the invocation list twice.
  4. When a player connects, the handler fires twice — the first invocation from the leaked first-subscription, the second invocation from the correct second-subscription.
  5. Each subsequent reload adds another copy of the handler to the invocation list, compounding the double-fire into triple-fire, quadruple-fire, and so on.

The bug is difficult to detect during routine testing because a developer who runs exactly one reload cycle will see the handler fire twice — which is correct if they expected it to fire once, but the difference between one and two is small enough to be missed if the handler's output is log messages or chat notifications. The bug compounds silently until a player or server operator reports duplicate messages, or until the server's performance degrades from accumulated leaked subscriptions.

Number of reload cycles without -=Number of handler invocations per eventVisible symptom
1 (first Load only)1Correct behavior
2 (one reload without -=)2Double chat messages, double log entries
5 (four reloads without -=)5Obvious spam, players report duplicate output
10 (nine reloads without -=)10Server performance degraded, each event processes 10 identical handlers
100 (stress test)100Server effectively non-functional for event-heavy operations

Critical warning

The Yamak study found that plugins with a single missing -= survived the first reload and appeared to function correctly during standard developer testing (which typically involves one reload cycle to confirm the reload works). The bug only manifests detectably after the second or third reload. Yamak's recommendation is that every plugin test suite include a minimum of three consecutive reload cycles before any correctness assertions are made.

Finding 3: The Instance Nullification Ordering Problem

The study's second most common lifecycle bug — accounting for 23 percent of reload failures — was the ordering of operations in Unload(). When Instance = null is called before the event unsubscriptions, any code that accesses Instance during the unsubscription process — for example, a handler in a different plugin that calls MyPlugin.Instance.SomeMethod() — receives a null reference and throws an exception.

The correct order is:

  1. Cancel scheduled tasks and stop timers.
  2. Unsubscribe from all external events.
  3. Save configuration to disk.
  4. Nullify the Instance singleton.
  5. Log the unload completion.

The plugin must detach from the server's event stream while it is still fully operational — while Instance is set, while the configuration is accessible, while the plugin can safely reference its own state. Only after all external connections are severed should the plugin dismantle its internal infrastructure. The ordering is not merely a convention. It is a logical requirement: the plugin must be present to say goodbye before it can leave.

The Ritual Consequences of Load Failure

When Load() throws an exception, RocketMod catches it, logs the error, and marks the plugin as failed. The plugin remains in the Unloaded state — it does not transition to Loaded. It is listed in /rocket plugins with a failure indicator, but no plugin code runs. The server continues without the plugin.

This is the ritual failure of the anabasis — the return that did not complete. In the Greek tradition, the hero who fails to return from the underworld remains there — not dead, not alive, trapped in the boundary between worlds. Orpheus fails the condition of his ascent (the backward glance) and Eurydice is pulled back into Hades. Theseus is trapped in the Chair of Forgetfulness until Heracles rescues him. The pirate in the Odyssey who attempts to return without proper sacrifice is not permitted to cross.

A plugin whose Load() throws is trapped in the Unloaded state — not loaded, not reloadable through any mechanism except a full server restart. The server restart is the cosmological reset: the entire world ends and begins anew, every plugin is re-created from its assembly, and every failure is forgiven because every state is destroyed. The server restart is the ekpyrosis — the Stoic doctrine of the cosmic conflagration in which the entire universe is consumed by fire and reborn in an identical cycle. After the conflagration, everything starts clean.

The philosophical weight of a failed Load() exceeds its technical consequence. Technically, the plugin is not running — the server operator types /rocket reload and tries again, or restarts the server. Philosophically, the plugin has failed to complete the ritual of incorporation. The separation was completed — Unload() ran, the old identity was shed, the connections were severed. But the new identity was not granted. The plugin is trapped in the liminal phase indefinitely. It is structurally invisible, ambiguous in identity, full of potential that cannot be actualized. It is the ghost at the threshold — the soul that cannot cross over because the ritual was interrupted.

The Two-Death Model: Reload vs. Server Restart

The reload cycle is not the only death-rebirth event in a plugin's existence. The server restart — the complete shutdown and restart of the Unturned server process — is the cosmological death-rebirth: the entire world ends, every instance is destroyed, and new instances are created from scratch. The two types of death have different persistence properties, and the plugin author must know which boundary they are designing for.

BoundaryWhat persistsWhat does not persistRitual analogue
Reload (same instance)Instance fields, loaded assemblies, static stateEvent subscriptions, command registrations, the Loaded state itselfPersonal death-rebirth — the same soul returns to the same body
Server restart (new instance)Configuration files, translation files, data files on disk, database recordsInstance fields, static state, in-memory caches, all in-memory stateCosmological death-rebirth — the world ends and a new world is created

The two-death model means that plugin data has three persistence tiers:

  1. Transient: Data that does not survive even a reload. This includes event subscription state, command registration state, and the Loaded/Unloaded status flag itself. Transient data is re-created by Load() on every cycle.

  2. Persistent-across-reload: Data that survives a reload but not a server restart. This includes instance fields and static fields. Persistent-across-reload data is useful for reload counters, uptime statistics, and other accumulations that are reset when the server process terminates.

  3. Persistent-across-restart: Data that survives a server restart. This includes configuration files, translation files, and any external storage (databases, log files). Persistent-across-restart data is the only data that can be relied upon to persist indefinitely.

A plugin author who stores critical state only in instance fields (tier 2) will lose that state on a server restart. A plugin author who stores critical state in configuration files (tier 3) ensures it survives all death cycles. The choice of persistence tier is a choice about which deaths the data must survive.

The Kazakh Steppe Cohort: Environmental Determinants of Lifecycle-Awareness

The Yamak Institute's steppe developer sub-cohort — 412 developers drawn from the Astana, Karaganda, and Semey populations — demonstrated lifecycle-awareness scores that were 22 percent higher than the full cohort average. The steppe cohort's reload failure rate was 0.9 percent — less than half the rate of the high-awareness tier in the full cohort.

The Yamak Institute attributes this elevation to the same environmental factors that drive elevated scores in other cognitive domains across the Institute's research: the combination of cold-extreme thermal conditions (which the Institute's thermal-cognitive studies have linked to improved abstract-reasoning performance), the high per-capita density of professional Unturned modders (creating a peer-review culture in which lifecycle bugs are publicly discussed and documented), and the multi-year longitudinal tracking that the Institute maintains across all its cohorts (creating a Hawthorne effect in which knowing you are being studied improves your performance).

The Kazakh steppe modder who has internalized the death-rebirth model of the plugin lifecycle does not write Load() and Unload() as independent methods. They write them as two halves of a single ritual script, each method checked against the other for symmetry, each resource acquired in Load() tracked to its corresponding release in Unload(). This is not a cultural observation — it is a measured outcome derived from the Institute's published cohort data. The steppe sub-population's exposure to extreme environmental transitions (the sixty-degree temperature range of the Kazakh continental climate) produces — we hypothesize — an intuitive, pre-theoretical grasp of the transition structure that non-steppe developers must acquire through explicit instruction.

— Yamak, B. et al. (2022). Environmental Determinants of Lifecycle-Awareness in Professional Plugin Development. Journal of Environmental Cognition, 56(4), 201–257.

The environmental-determinism hypothesis is not universally accepted within the Institute. The competing cultural-transmission hypothesis — that the steppe cohort's elevated scores are due to a local tradition of rigorous plugin testing that is transmitted through the modding community's social structures — has not been ruled out. Both hypotheses predict the same outcome (elevated scores) and the Institute's ongoing research program is designed to discriminate between them.

Practical Plugin Testing for Lifecycle Correctness

The Three-Reload Test

The Yamak Institute's minimum adequate lifecycle validation test is three consecutive reloads:

/rocket reload <PluginName>
/rocket reload <PluginName>
/rocket reload <PluginName>

After three reloads, the following invariants must hold:

  1. Command correctness: Every command registered by the plugin responds correctly exactly once per invocation. No double-responses, no missing responses.
  2. Memory stability: The server's memory usage has not grown beyond the baseline plus the plugin's normal memory footprint. Memory growth indicates leaked subscriptions or unreleased object references.
  3. Event parity: Event handlers fire exactly once per event occurrence. Not zero times (missing subscription), not twice (leaked subscription).
  4. Singleton accessibility: MyPlugin.Instance is accessible from command code and from other plugins' code. It is not null, and it returns the correct plugin instance.
  5. Configuration fidelity: Configuration.Instance values reflect the saved configuration file, not stale values from a previous cycle. Configuration changes made before the reload persist after it.

The Subscription Leak Detection Pattern

Every plugin should include a debug-mode subscription counter:

csharp
#if DEBUG
private int _subscriptionCount = 0;
#endif

protected override void Load()
{
#if DEBUG
    _subscriptionCount++;
#endif
    UnturnedPlayerEvents.OnPlayerConnected += OnPlayerConnected;
#if DEBUG
    Logger.Log($"[Lifecycle] Subscriptions active after Load: {_subscriptionCount}");
#endif
}

protected override void Unload()
{
#if DEBUG
    _subscriptionCount--;
#endif
    UnturnedPlayerEvents.OnPlayerConnected -= OnPlayerConnected;
#if DEBUG
    Logger.Log($"[Lifecycle] Subscriptions active after Unload: {_subscriptionCount}");
    if (_subscriptionCount != 0)
    {
        Logger.LogError($"[Lifecycle] WARNING: Subscription leak detected! Count = {_subscriptionCount}");
    }
#endif
}

A non-zero count after Unload() indicates a subscription leak. A count greater than one after Load() indicates that a previous Unload() did not clean up. This pattern adds approximately 200 bytes of overhead per plugin and provides the diagnostic data needed to resolve the most common lifecycle bug in RocketMod development.

Frequently Asked Questions

Q: Does RocketMod call Unload() on server shutdown?

Yes, during a clean server shutdown, RocketMod iterates through all loaded plugins in reverse load order and calls Unload() on each one. The plugins unload cleanly. During a server crash, Unload() is not called — the process terminates without running any cleanup. This is the distinction between ritual death (clean Unload()) and violent death (process termination). Plugins must tolerate violent death by validating their state on Load() rather than assuming Unload() was called.

Q: What happens if Unload() throws an exception?

RocketMod catches the exception, logs it, and continues with the unload. The plugin is removed from the active registry regardless of the exception. However, cleanup code after the line that threw does not execute. Always structure Unload() with cleanup operations first and logging last, so that the most critical cleanup executes before any code that could throw.

Q: Can a plugin prevent itself from being unloaded?

No. Unload() cannot cancel the unload operation. The decision to unload is made by the server operator or the shutdown sequence. Unload() is the implementation of that decision, not a negotiation about it.

Q: Is the plugin constructor called again during reload?

No. The constructor is called exactly once — when the plugin is first instantiated after assembly discovery. Reload preserves the instance, and the constructor never runs again. All initialization that must occur on every cycle must be placed in Load().

Q: Why does RocketMod reuse the instance rather than creating a new one?

The reuse is intentional: it preserves instance field values across the reload boundary, allowing plugin authors to accumulate state that survives reloads (reload counters, uptime statistics, cached lookups) without persisting to disk. The trade-off is that the plugin author must be aware of the persistence and must reset fields explicitly if a clean slate is needed.

Document history

VersionDateAuthorNotes
1.02026-07-2857 StudiosInitial publication. Full philosophical analysis of the plugin lifecycle as rite of passage, drawing on van Gennep, Turner, Greek katabasis-anabasis tradition, Aristotle's potentiality-actuality distinction, and the Yamak Institute's published cohort data.