# Architecture

This page covers the internals of Spark's four core pillars and how they connect. Understanding this architecture will make everything else in the Developer Guide click.

## The Spark Service Locator

`Spark.cs` is a static class that acts as the central registry for all plugins. Every plugin registers itself here at startup, and any system can retrieve a plugin by its interface type.

### API

```csharp
// Register a plugin instance
Spark.RegisterPlugin<IMyPlugin>(instance);

// Retrieve a plugin (returns null if not registered)
IMyPlugin plugin = Spark.GetPlugin<IMyPlugin>();

// Remove a plugin
Spark.UnregisterPlugin<IMyPlugin>();

// Access the network provider
INetworkProvider network = Spark.Network;

// Listen for network provider readiness
Spark.OnNetworkProviderReady += (provider) => { /* ... */ };
```

### How Registration Works

Plugins register using Unity's `[RuntimeInitializeOnLoadMethod]` attribute, which runs automatically before any scene loads. Each plugin follows this pattern:

```csharp
public class MyPlugin : IMyPlugin
{
    private static MyPlugin _instance;
    private static bool _hasRegistered = false;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
    private static void Reset()
    {
        _hasRegistered = false;
        _instance = null;
    }

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    private static void RegisterPlugin()
    {
        if (!_hasRegistered)
        {
            _instance = new MyPlugin();
            Spark.RegisterPlugin<IMyPlugin>(_instance);
            _hasRegistered = true;
        }
    }

    #if UNITY_EDITOR
    [UnityEditor.InitializeOnLoadMethod]
    private static void RegisterPluginInEditor()
    {
        Reset();
        RegisterPlugin();
    }
    #endif
}
```

The two-phase approach (Reset at `SubsystemRegistration`, Register at `BeforeSceneLoad`) handles Unity's domain reload correctly. The `#if UNITY_EDITOR` block ensures the plugin is also available in edit mode for the Spark Editor window.

### Initialization Order

Unity runs `RuntimeInitializeOnLoadMethod` in this order:

1. `SubsystemRegistration` - Reset static state (domain reload safety)
2. `AfterAssembliesLoaded` - Assemblies ready
3. `BeforeSplashScreen` - Before splash
4. `BeforeSceneLoad` - Plugin registration happens here
5. `AfterSceneLoad` - Scene is loaded, entities are active

Spark itself initializes at `SubsystemRegistration` to clear its internal dictionaries, then plugins register at `BeforeSceneLoad`.

## SparkDatabaseRegistry

The database registry is a static cache of all `SparkDatabaseEntry` ScriptableObjects in the project. It loads entries at startup and provides fast lookups.

### How It Loads

In the editor, the registry uses `AssetDatabase` to find all entries. In builds, it loads from `Resources/` folders. An `AssetPostprocessor` handles hot-reloading when entries are created, modified, or deleted in the editor.

Entries are indexed in two ways:

* **By ID**: `Dictionary<string, SparkDatabaseEntry>` for O(1) lookup by unique ID
* **By Type**: `Dictionary<Type, List<SparkDatabaseEntry>>` for retrieving all entries of a given type

### API

```csharp
// Get a specific entry by ID
ItemEntry item = SparkDatabaseRegistry.GetEntry<ItemEntry>("iron_sword");

// Get all entries of a type
List<QuestEntry> quests = SparkDatabaseRegistry.GetAllEntries<QuestEntry>();

// Check if an entry exists
bool exists = SparkDatabaseRegistry.HasEntry("iron_sword");

// Get total count
int total = SparkDatabaseRegistry.GetTotalEntryCount();

// Force refresh (usually not needed)
SparkDatabaseRegistry.Refresh();
```

### Entry ID Format

Entry IDs are auto-generated in lowercase with underscores. They follow the pattern `entrytype_name` or similar. Once created, an ID cannot be changed. This ensures references to entries remain stable.

## SparkEventBus

The event bus is the primary way plugins communicate with each other. It uses a priority-based publish/subscribe pattern.

### Publishing Events

```csharp
// Publish immediately (handlers run synchronously)
SparkEventBus.Publish(new QuestCompletedEvent(questId, characterId));

// Queue for batch processing
SparkEventBus.QueueEvent(new ExperienceGainedEvent(charId, 50));
SparkEventBus.QueueEvent(new ExperienceGainedEvent(charId, 25));
SparkEventBus.ProcessQueuedEvents(); // Both fire now
```

### Subscribing to Events

```csharp
// Subscribe a handler
SparkEventBus.Subscribe<QuestCompletedEvent>(new MyQuestHandler());

// Unsubscribe
SparkEventBus.Unsubscribe<QuestCompletedEvent>(handler);
```

### Handler Priority

Handlers run in priority order, highest first. This lets you control execution order when multiple systems need to respond to the same event.

```csharp
public class UrgentHandler : ISparkEventHandler<DamageEvent>
{
    public int HandlerPriority => 100; // Runs first
    public bool Handle(DamageEvent evt) { /* ... */ return true; }
}

public class NormalHandler : ISparkEventHandler<DamageEvent>
{
    public int HandlerPriority => 50; // Runs second
    public bool Handle(DamageEvent evt) { /* ... */ return true; }
}
```

### Event Consumption

If an event implements `IConsumableEvent`, a handler can stop propagation by returning `true` from `Handle()`. This prevents lower-priority handlers from receiving the event.

### Type Matching

The event bus supports compatible type matching. If you subscribe to a base event class or an interface, your handler will receive all events that inherit from that class or implement that interface.

### Event Contract

All events implement `ISparkEvent`:

```csharp
public interface ISparkEvent
{
    string EventType { get; }
    float Timestamp { get; }
    string Source { get; }
    int Priority { get; }
    bool CanBeCancelled { get; }
}
```

The `SparkEventBase` abstract class provides a default implementation where `Timestamp` is set at construction and `Priority` defaults to `0`.

## SparkEntityRegistry

The entity registry tracks all `SparkEntity` instances in the scene. Entities register themselves automatically when they awaken and unregister when destroyed.

### Lookup Methods

All lookups are O(1) dictionary access:

```csharp
// By entity ID string
SparkEntity entity = SparkEntityRegistry.GetEntityById("player_01");

// By GameObject reference
SparkEntity entity = SparkEntityRegistry.GetEntityFromGameObject(gameObject);

// By Collider (memoized)
SparkEntity entity = SparkEntityRegistry.GetEntityFromCollider(collider);

// The player entity (cached)
SparkEntity player = SparkEntityRegistry.GetPlayerEntity();

// By tag
List<SparkEntity> enemies = SparkEntityRegistry.GetEntitiesByTag("Enemy");

// All entities
List<SparkEntity> all = SparkEntityRegistry.GetAllEntities();
```

### TryGet Variants

Every lookup method has a `TryGet` variant that returns `bool` and outputs the result, avoiding null checks:

```csharp
if (SparkEntityRegistry.TryGetEntity("npc_01", out SparkEntity npc))
{
    // Use npc
}
```

### Entity IDs

Each `SparkEntity` has an ID source that determines how its ID is generated:

* **Local**: Auto-generated GUID + timestamp. Used for dynamically spawned objects.
* **Static**: Manually assigned in the inspector. Used for persistent world objects (NPCs, interactables) that must be the same across sessions.
* **Networked**: Assigned by the server at runtime. Used for multiplayer-synced objects.

## Assembly Definitions

Spark uses assembly definitions extensively. Each plugin has its own `Spark.PluginName.asmdef` for the runtime code and optionally `Spark.PluginName.Editor.asmdef` for editor code.

This structure provides:

* **Compile isolation**: Changes to one plugin don't recompile others.
* **Dependency control**: Plugins declare explicit references to only the assemblies they need.
* **Optional dependencies**: A plugin can check for another plugin's existence at runtime without requiring a compile-time reference.

The core assembly is `Spark.Core`. All plugins reference it. Plugins that need to interact with each other (like extensions) add explicit assembly references.

### Optional Dependencies Pattern

When a plugin wants to interact with another plugin that may or may not be installed:

```csharp
var combatPlugin = Spark.GetPlugin<ICombatPlugin>();
if (combatPlugin != null)
{
    // Combat plugin is available, use it
}
```

This null-check pattern is used throughout Spark. Plugins never assume other optional plugins are present.

## Data Flow Summary

Here is the complete lifecycle of a typical game action:

```
1. Player Input
   |
2. Command Created (e.g., UseItemCommand)
   |
3. Command Executed via Spark.Network.ExecuteCommand()
   |
4. Command Handler validates and processes
   |
5. State changes applied (inventory updated, stats modified)
   |
6. Event published (e.g., ItemUsedEvent)
   |
7. Event handlers react:
       - UI handler updates the inventory display
       - Quest handler checks if an objective was completed
       - Sound handler plays a use sound
       - Screen text handler shows a floating message
```

This pattern is consistent across all of Spark. Once you understand it, you can predict how any system works.

## Next Steps

Now that you understand the architecture, continue to [Creating Your First Plugin](/documentation/developer-guide/creating-your-first-plugin.md) to put this knowledge into practice.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.sparkframework.dev/documentation/developer-guide/core-systems/architecture.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
