Provides a Mono-side hook pattern for dispatching BattlePass XP events to analytics backends (Firebase, PlayFab, GameAnalytics, etc.) without each consumer implementing its own drain loop.
ProgressionAnalyticsSnapshotSystem runs before BattlePassXPSystem each ECS frame. It simulates the XP math locally to detect tier transitions. ProgressionAnalyticsBridge.LateUpdate drains the snapshot AFTER the ECS frame — so by LateUpdate the XP buffer is already cleared by BattlePassXPSystem. The bridge reads the pre-captured snapshot, not the live buffer.
Wires the boss-defeated → next-realm-unlock flow. RealmUnlockSystem reacts to DOTSCombat.Boss.BossCompletionFlag (an IEnableableComponent) firing on a player entity, reads CurrentRealm + ForgeLevel, and unlocks the next realm iff ForgeLevel.Value >= nextRealm.MinForgeLevelToUnlock.
int CurrentRealmId — the realm whose boss was just defeated
ForgeLevel
IComponentData
int Value — generic progression-tier marker; host populates alongside its own forge state (e.g. ChaosForgeDemo.ForgeState.CurrentForgeLevel)
RealmUnlockedTag
IComponentData (IEnableableComponent)
Per-realm-entity flag; enabled when that realm becomes available
BossUnlockProcessedTag
IComponentData (tag)
One-shot sentinel added via ECB so the same completion event never re-fires across frames
RealmConfig
IComponentData
int MinForgeLevelToUnlock per realm
RealmRegistry
IBufferElementData (singleton)
int RealmId, int MinForgeLevelToUnlock, Entity RealmEntity — registry of all realms
RealmRegistrySingletonTag
IComponentData (singleton)
Marks the registry-carrier entity (RequireForUpdate)
RealmUnlockedEvent
IBufferElementData (on registry singleton)
int UnlockedRealmId, int PreviousRealmId — payload event for game logic
RealmEra
enum (byte)
Opaque era index for a realm. Theme-neutral — values are Era0/Era1/Era2, NOT theme names. The library does not name or describe eras; the consumer demo maps each index to a display name, palette, and assets.
RealmUnlockSystem runs [UpdateAfter(typeof(BossDefeatedEventSystem))] so it reads the freshly-enabled BossCompletionFlag the same frame (no one-frame UI lag).
For each player with BossCompletionFlag enabled and NO BossUnlockProcessedTag: always add the processed tag (positive OR negative decision), compute nextRealmId = CurrentRealmId + 1, look it up in RealmRegistry.
If ForgeLevel.Value >= MinForgeLevelToUnlock: enable RealmUnlockedTag on the next realm entity, append a RealmUnlockedEvent + a sibling UIEventBus entry (EventType = UIEventTypeRegistry.RealmUnlocked, Payload = nextRealmEntity) on the registry singleton, drained by the Mono UIModalDirector.
A separate cleanup system clears the event buffer at OrderFirst next frame as a safety net.
Next-realm entity MUST be baked with a (disabled) RealmUnlockedTag — RealmUnlockSystem calls HasComponent(nextRealmEntity) and bails (continue) if the realm entity lacks the RealmUnlockedTag component. If realm entities are baked WITHOUT the disabled enableable tag, unlocks silently no-op — no event, no toast, no error. Bake every realm entity with RealmUnlockedTag disabled (SetComponentEnabled(false) in the baker), not just the starting realm.
BossUnlockProcessedTag is the run-controller’s responsibility to remove — the system adds it but never removes it. On a new run, the run-controller must remove BossUnlockProcessedTag AND disable BossCompletionFlag together, or the next boss defeat won’t re-trigger the unlock check.
Final-realm completion still adds the processed tag — when the player clears the last realm (nextRealmId not in registry), the system adds BossUnlockProcessedTag and no-ops the unlock. This is intentional (stops per-frame rescanning) — do not treat the missing event as a bug.
Already-unlocked realm skips the event — if RealmUnlockedTag is already enabled on the next realm (e.g. a cheat/manual unlock), the system skips the event emission to avoid a duplicate UI toast, but still adds the processed tag.
RealmEra is theme-neutral (Era0/Era1/Era2) — never put theme names in the library (Wave-4). The enum was renamed from baked theme names (Primitive/Modern/Apex) to opaque Era0/Era1/Era2; the demo owns the display names. Per the naming-charter, genre/theme tokens belong in consumer demos, not in library enum values. The old Primitive/Modern/Apex names are kept as [Obsolete] forwarding shims (Primitive = Era0, etc.) so un-migrated demo code compiles — migrate to the EraN form and let the demo map indices to names/palettes/assets. Persistence caveat:RealmEra is stored as byte; changing an existing value’s ordinal is a breaking change for any persisted CurrentRealm. Add new eras as new values only.
LevelUpSystem cascades until stable — use while, not if. A large XP grant (e.g., dungeon boss) can level the entity multiple times in one frame. Using if silently discards overflow XP and leaves Level wrong.
OfflineCatchUpSystem runs in OnCreate, not OnUpdate — it fires once at world load. If you accidentally move it to OnUpdate, it will apply catch-up EVERY frame, causing exponential resource accumulation.
Cap offline seconds — without MAX_OFFLINE_SECONDS, players who return after 30 days receive astronomically large catch-up. Default cap is 8 hours (28,800 s). Expose in FloorConfig or a ScriptableObject authoring component.
QuestStatus.Claimed is final — QuestCompletionSystem ignores already-claimed quests. If rewards need to be re-granted (debug flow), destroy the quest entity and re-bake.
DialogueNode buffer uses FixedString512Bytes — 512 bytes per node. If localized text exceeds 512 bytes in any language, the baker silently truncates. Author dialogue in short segments or load from external localization tables.
MetaCurrency is a singleton — one entity only — use SystemAPI.GetSingletonRW<MetaCurrency>(). Adding MetaCurrency to multiple entities corrupts global economy. Singleton entity is baked from authoring scene; do not create in runtime ECB.
Prestige destroys ALL run entities — ensure run-scoped entities are tagged (e.g., RunScopedTag) and the destroy query is exact. Missing the tag on a newly added entity causes orphaned entities after prestige.