> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nameplatebuilder.frotty27.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Architecture

> Internal systems, data flow, and project structure.

<Info>This documentation is written for **NameplateBuilder >= v4.260326.7** with **API >= v2.2.0**.</Info>

## Project Modules

NameplateBuilder is split into three Gradle modules:

| Module                  | Purpose                                                                                                                   |
| :---------------------- | :------------------------------------------------------------------------------------------------------------------------ |
| `nameplate-api`         | Lightweight API jar for mod developers. Contains `NameplateAPI`, `NameplateData`, `SegmentTarget`, and exception classes. |
| `nameplate-server`      | The server plugin. Contains the aggregator, UI, persistence, admin config, and all internal systems.                      |
| `nameplate-example-mod` | Working example mod demonstrating all API patterns.                                                                       |

## File Tree

```
NameplateBuilder/
  nameplate-api/
    src/main/java/.../api/
      NameplateAPI.java                    Static entry point
      NameplateData.java                   ECS component (Map<String, String>)
      SegmentTarget.java                   Entity target enum
      INameplateRegistry.java              Internal registry interface
      NameplateException.java              Base exception
      NameplateNotInitializedException.java
      NameplateArgumentException.java

  nameplate-server/
    src/main/java/.../server/
      NameplateBuilderPlugin.java          Server plugin entry point
      NameplateAggregatorSystem.java       Per-tick compositor + death cleanup
      DefaultSegmentSystem.java            Built-in segments
      NameplateRegistry.java               Segment metadata store
      NameplateBuilderPage.java            Player UI page
      NameplateBuilderCommand.java         /npb command
      NameplateBenchmarkCommand.java       /npbbench benchmark command
      NameplateIdentifyCommand.java        /npbid entity identification
      NameplatePreferenceStore.java        Per-player JSON persistence
      AdminConfigStore.java                Admin JSON config persistence
      AnchorEntityManager.java             Anchor entity lifecycle
      EntitySourceService.java             NPC source detection (vanilla vs mod)
      SimpleJson.java                      Minimal JSON reader/writer
      SegmentKey.java                      record(pluginId, segmentId)
      SegmentBuilderImpl.java              SegmentBuilder implementation
      ActiveTab.java                       Sidebar tab enum
      AdminSubTab.java                     Admin sub-tab enum
      UiState.java                         UI state snapshot
      SettingsData.java                    Client/server UI events
      SegmentView.java                     Segment metadata view
    src/main/resources/
      Common/UI/Custom/Pages/
        NameplateBuilder_Editor.ui         UI layout definition

  nameplate-example-mod/
    src/main/java/.../example/
      NameplateExamplePlugin.java          Describe + register systems
      ArchaeopteryxNameplateSystem.java    NPC spawn + live health
      LifetimeNameplateSystem.java         Per-entity tick updates
```

## Data Flow

### Registration Flow

1. Mods call `NameplateAPI.define()` during `setup()` to register UI metadata and optional resolvers
2. `NameplateRegistry` stores display names, targets, examples, variant lists, and resolver functions
3. The UI reads from `NameplateRegistry` to populate Available Blocks

### Runtime Flow

1. Resolvers compute text per entity automatically, or mods call `NameplateAPI.setText()` / write to `NameplateData` directly
2. `NameplateData` stores segment text as `Map<String, String>` on the entity
3. `NameplateAggregatorSystem` ticks every frame

### Aggregation Flow (Per Frame)

For each visible entity with `NameplateData`:

1. Check per-viewer Enable Nameplates toggle - skip entirely if disabled for this viewer (sends EMPTY\_UPDATE immediately when disabled)
2. Check crouch state via `MovementStatesComponent.crouching` - skip player entities that are crouching
3. Check regex pattern blacklist - skip NPC entities whose role name matches any configured pattern
4. Resolve segment keys from the component (skip `_`-prefixed and admin-disabled keys)
5. Apply viewer preferences: ordering, enabled/disabled, format variants
6. Resolve variant text: check suffixed key, fall back to base key
7. Apply prefix/suffix wrapping and bar fill replacement
8. Enforce admin-required segments
9. Composite all segments with per-block separators
10. Queue nameplate update to each viewer

### Entity Source Detection

The aggregator identifies entity sources to determine which entities receive nameplates. Hytale-namespace NPCs are auto-seeded as before. Mod-provided NPCs are detected by inspecting the entity's source plugin, allowing NameplateBuilder to correctly attribute NPC entities that originate from other mods.

### Resolver Key Cache Invalidation

Resolver results are cached per entity based on `cacheTicks()`. When the admin configuration changes (e.g. a segment is required or disabled), all resolver key caches are invalidated immediately. This ensures that admin config changes take effect without waiting for individual cache timers to expire.

### Death Cleanup

When an entity receives a `DeathComponent`:

1. Aggregator sends empty nameplate to all viewers
2. `NameplateData` component is removed from the entity
3. If anchor entities exist for this entity, they are cleaned up

### Anchor Entities

When a player configures a vertical offset for **NPC** entities:

1. An invisible "anchor" entity (`ProjectileComponent` + `Intangible` + `TransformComponent` + `NetworkId`) is spawned above the real NPC
2. Nameplate text is routed to the anchor instead of the real entity
3. Anchor follows the real entity every tick using **velocity-based predictive positioning** to compensate for the 1-tick CommandBuffer delay
4. Anchors use `AddReason.LOAD` to prevent disk persistence during chunk saves
5. **No explicit removal**: when entities die or are removed, anchors are simply untracked and left in the world with blank nameplates until chunk unload (avoids chunk serialization race conditions)

**Player entities** always use vanilla client-side nameplate rendering and never create anchor entities, regardless of offset configuration. This ensures zero lag for player nameplates.

### Crouch Detection

Player nameplates are hidden when the player is crouching. The aggregator checks `MovementStatesComponent.crouching` on the target entity each tick. When `crouching` is true, the nameplate is replaced with an empty update for all viewers.

### View-Cone Filtering

When enabled per-viewer (applies to NPC nameplates only - player nameplates are always visible):

1. Dot-product math checks if the viewer is looking at the entity
2. \~25 degree half-angle cone, up to 12 blocks range
3. Entities outside the cone receive an empty nameplate update

## Key Design Decisions

### Why ECS Components?

`NameplateData` is a standard Hytale ECS component. This means:

* It participates in archetype queries - the aggregator only ticks entities that have nameplate data
* It's automatically cleaned up when entities are destroyed
* Multiple mods can read/write to it without coordination
* The `CommandBuffer` pattern handles structural changes safely

### Why Per-Viewer?

Each player sees their own customized view of every nameplate. This enables:

* Personal segment ordering and selection
* Format variant choices per segment per player
* Admin-required segments overlaid on personal preferences
* View-cone filtering based on the viewer's camera direction

### Why Tick-Based?

The aggregator runs every frame to ensure nameplate text always reflects the latest data. Since `setText()` is just a `HashMap.put()`, the cost of frequent updates is minimal.
