---
name: cross-world-ecs-access
description: "Cross-world ECS access — global world from scene systems, propagation, bridge components, PersistentEntities. Use when scene systems need global-world state, implementing propagation, or using ISceneIsCurrentListener."
user-invocable: false
---

# Cross-World ECS Access

## Sources

- `docs/architecture-overview.md` — World architecture (global vs scene worlds)
- `docs/development-guide.md` — ECS system design, component mutation rules

---

## Injection Chain

The global world reaches scene systems through a plugin injection chain:

1. `DynamicWorldContainer` creates the global `World` and `GlobalWorldFactory`
2. `StaticContainer` instantiates `IDCLWorldPlugin` instances, passing the global world to plugin constructors
3. Plugin stores `globalWorld` as a field
4. On scene creation, `InjectToWorld` passes `globalWorld` to system constructors

> **Note:** `ECSWorldInstanceSharedDependencies` provides scene-specific metadata (scene data, partition, CRDT writer) but does NOT carry the global world reference. The global world must be passed through the plugin constructor.

---

## Two Access Patterns

### Pattern A: Direct Global World Access

For O(1) reads/writes on known global entities. The scene system stores `globalWorld` and the target entity at construction, then calls `Get`/`TryGet` directly in queries. Best when the target entity is known at construction time (player input, camera state, settings).

### Pattern B: CRDT Bridge

For indirect sync without direct world references. Uses `IECSToCRDTWriter` to serialize data back to the scene runtime. Best for scene-to-runtime communication and CRDT-synchronized data. See **sdk-component-implementation** skill for `PutMessage`/`AppendMessage`/`DeleteMessage` patterns.

---

## PersistentEntities

Well-known entities created once per scene world, guaranteed alive for the world's lifetime. Received via `InjectToWorld`'s `in PersistentEntities persistentEntities` parameter.

```csharp
public struct PersistentEntities
{
    public readonly Entity Player;          // scene-local player entity
    public readonly Entity Camera;          // scene-local camera entity
    public readonly Entity SceneRoot;       // root entity (modifiable by scene creator)
    public readonly Entity SceneContainer;  // container of root (internal use only)
}
```

---

## Bridge Components

### PlayerCRDTEntity (global world)

Lives on global-world avatar entities. Holds references to the scene the player is assigned to and the corresponding scene-world entity.

```csharp
public struct PlayerCRDTEntity : IDirtyMarker
{
    public CRDTEntity CRDTEntity { get; }
    public ISceneFacade? SceneFacade { get; private set; }
    public Entity SceneWorldEntity { get; private set; }
    public bool AssignedToScene => SceneFacade != null;

    public void AssignToScene(ISceneFacade sceneFacade, Entity sceneWorldEntity) { ... }
    public void RemoveFromScene() { ... }
}
```

### PlayerSceneCRDTEntity (scene world)

Lives on scene-world entities that represent a player. Holds the CRDT entity ID for writing back to the scene runtime.

```csharp
public struct PlayerSceneCRDTEntity : IDirtyMarker
{
    public readonly CRDTEntity CRDTEntity;
}
```

**Relationship:** A global `PlayerCRDTEntity` points to a scene via `SceneFacade` + `SceneWorldEntity`. The scene entity has a `PlayerSceneCRDTEntity` pointing back via `CRDTEntity`. Propagation systems use this bidirectional link to sync data across worlds.

---

## Propagation Systems

- **Global-to-Scene (`PlayerTransformPropagationSystem`):** Runs in the global world, queries global entities, writes into scene worlds via `playerCRDTEntity.SceneFacade.EcsExecutor.World`.
- **Scene-to-Global:** Scene systems write to the global world directly using Pattern A. The system reads scene-world SDK components and writes the result to `globalWorld.Get<T>(playerEntity)`.

---

## ISceneIsCurrentListener Safety Pattern

When a scene system modifies global state, it must only do so while the scene is current. Implement `ISceneIsCurrentListener` to:
- Guard every `Update()` with `sceneStateProvider.IsCurrent`
- Re-apply state in `OnSceneIsCurrentChanged(true)`
- Reset to defaults in `OnSceneIsCurrentChanged(false)`
- Register via `sceneIsCurrentListeners.Add(system)` in the plugin

> `ISceneIsCurrentListener.OnSceneIsCurrentChanged` is called even if the scene has stopped with an error, ensuring global state is always cleaned up.

---

## Safety Rules

> See CLAUDE.md SS5 for full mutation rules.

1. **Use `ref var` for mutations, never `var` alone** — `var` copies the component; changes are lost
2. **No structural changes after obtaining `ref`** — `World.Add`/`World.Remove` invalidates outstanding `ref`/`in`/`out` pointers. Complete all reads/writes first, then apply structural changes
3. **Prefer `TryGet` for safety on known entities** — avoids exceptions when the component may not exist
4. **Guard global writes with `IsCurrent` check** — always check `sceneStateProvider.IsCurrent` in `Update()` and implement `ISceneIsCurrentListener` to reset on scene exit
5. **`ECSWorldInstanceSharedDependencies` provides scene metadata, NOT global world** — the global world must be injected through the plugin constructor
6. **Register for cleanup** — systems that modify global state should implement `IFinalizeWorldSystem` and be added to `finalizeWorldSystems` so global state is reset on world disposal

---

## Detailed Reference

For detailed code examples, see [reference.md](reference.md).

---

## Cross-References

- **ecs-system-and-component-design** — `ref var` mutation rules (CLAUDE.md SS5), component cleanup lifecycle, system design patterns
- **plugin-architecture** — `InjectToWorld` signature, `ECSWorldInstanceSharedDependencies`, plugin constructor injection chain
- **sdk-component-implementation** — `IECSToCRDTWriter` for CRDT bridge pattern (`PutMessage`/`AppendMessage`/`DeleteMessage`)
