Files
settled-reach/docs/design/entity-color-system.md
T
jpmschweitzerandClaude Opus 4.6 934d4df4b0 docs(docs): add frontmatter to all design docs
Standardized YAML frontmatter on all 40 docs/design/ files with title,
description, type, status, ticket, decision_refs, and author fields.
Covers specs, wireframes, voice cards, spatial layouts, and system
designs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 15:22:17 +01:00

14 KiB
Raw Blame History

title, description, type, status, ticket, decision_refs, author, created, updated
title description type status ticket decision_refs author created updated
Entity Color System — Visual Specification Canonical palette and rendering rules for relationship-based entity colors derived from D-033 design active #304
D-033
D-043
D-044
D-048
D-049
D-059
D-060
Araminta 2026-02-20 2026-02-20

Entity Color System — Visual Specification

Version: v0.1 (Sprint 14) Author: Araminta (Visual Designer) Date: 2026-02-20 Ticket: #304 Status: Active — input to client implementation (future sprint), constrains #318 (THE FRIEND visual treatment) Foundation: Visual Grammar v0.1 (§3), Decision D-033


1. Core Principle

Entity color encodes the player character's subjective relationship to an NPC — not an objective property of the NPC. The same entity can appear as different colors to the detective and the smuggler simultaneously. Color is a client-side derivation from the RelationshipState field on each VisibleEntity in the server's ObserverSnapshot. The server never says "this NPC is hostile" — it sends the relationship state the client derives the color from.

This is asymmetric information rendered visually. It is the most important single system in the game to get right because it is the visual language through which the player reads the world.


2. Relationship → Color Mapping

2.1 Canonical Palette

RelationshipState Color name Hex Visual quality Notes
Unknown Cool teal #4a9ebb Default for unassessed entities. Cooler than sky, not clinical. Every new NPC the player hasn't built a view of
Known / Friendly Soft green #6bc9a6 Trusted. Known person. Not safe — just trusted. People the character knows and has reason to trust
PersonOfInterest Warm amber #e8c547 Monologue or case file has flagged something. Not necessarily hostile — flagged
Hostile Muted red #d45d5d Player character perceives subjective danger. Not omniscient. Red means danger TO YOUR CHARACTER, not danger in the abstract

2.2 Non-Entity Colors

Entity type Color name Hex Notes
Static objects Muted grey #8b8ba0 See §5 for what counts as static
Player — Detective Cool blue-white #e0e8ff Near-neutral. "Self." No relationship loading.
Player — Smuggler Warm cream #e8e0d0 Near-neutral. "Self." Slightly warmer than detective.

2.3 Color Psychology Notes

The palette was selected for functional warmth (D-043), not conventional danger-coding. Teal (#4a9ebb) reads as neutral-curious rather than cold. Green (#6bc9a6) reads as familiar rather than "good." Amber (#e8c547) reads as noteworthy rather than "warning." Red (#d45d5d) is muted — subjective danger, not objective alarm.

This matters because the detective might see amber where the smuggler sees green for the same NPC. Both are correct. Neither color is lying — they are rendering different epistemic positions.


3. Runtime Derivation

3.1 Data Source

The server sends each VisibleEntity in the ObserverSnapshot with a relationship: RelationshipState field (server/src/bridge/types.rs, VisibleEntity struct, line 258). This field is computed per-observer on the server using the observer's knowledge graph — it is NOT a shared NPC property.

RelationshipState variants:

  • Unknown — no assessment, or newly visible
  • Known — character has a relationship but nothing flagged
  • PersonOfInterest — knowledge graph or case file has flagged this entity
  • Hostile — character perceives active danger from this entity

3.2 Client Lookup

entity_renderer.gd maps RelationshipState to color via Constants.color_for_entity_kind(). The current implementation (_color_for_kind, line 181) delegates to this function. The full color lookup should follow:

RelationshipState → Color
Unknown           → #4a9ebb
Known / Friendly  → #6bc9a6
PersonOfInterest  → #e8c547
Hostile           → #d45d5d
Static object     → #8b8ba0
Player entity     → #e0e8ff (detective) or #e8e0d0 (smuggler)

The player's own entity is identified via GameState.player_entity_id. Player color does not participate in the relationship lookup — it is a fixed constant per character selection.


4. Transition Behavior

4.1 Standard Transition

When an entity's RelationshipState changes between snapshots, the color shift is a 0.5-second smooth fade (linear lerp). This is already implemented in entity_renderer.gd via the _entity_tweens dictionary and COLOR_FADE_DURATION = 0.5 constant.

Never use an instant color swap. The visual transition is part of the information delivery — the player reads the relationship changing as a small dramatic moment.

4.2 THE FRIEND's First Shift — Staged Priority

THE FRIEND's transition from Known/Friendly (green #6bc9a6) to PersonOfInterest (amber #e8c547) must be the first relationship color change the player observes in the session. The opening 2025 minutes of gameplay must be staged so no other NPC's relationship state changes before THE FRIEND's shift.

This requires narrative coordination: the player must have enough time with THE FRIEND as green to internalize what green means. When amber arrives, the player has a reference point. Without that contrast, the color change means nothing.

See docs/design/the-friend-visual-treatment.md (#318) for staging details.

4.3 Edge Cases

Entity in fog (unrecognized): D-033 colors do not show through fog for unrecognized entities. An unrecognized entity in fog renders as a neutral grey #555566 blob with no silhouette features. Once the cognitive delay resolves (D-060, 0.6s base) and recognition completes, the blob transitions to the entity's D-033 color + identifying silhouette feature.

Entity in fog (recognized): If the character recognizes an entity in fog (their knowledge graph identifies them), the insert overlay (z-layer 6) can show a D-033 color glow + faint silhouette feature at ±0.5 tile approximate position. This is insert data, not visual data — the character knows where they were, not where they are.

Entity at periphery: Per D-015 (forward/peripheral/blind vision sectors), entities in the peripheral vision zone are rendered at reduced saturation and alpha. The modulate.a value is Constants.PERIPHERAL_ALPHA when visibility == "Peripheral". This is already implemented in entity_renderer.gd. The D-033 color itself does not change — saturation reduction is handled via the visibility dimming, not a color override.

Simultaneous transitions: Two entities changing relationship state in the same tick each get independent 0.5s tweens. There is no synchronization between entity transitions. This is intentional.


5. Static Objects — Definition and Color

5.1 What is a Static Object

Static objects are non-person entities that do not have a relationship to the player and are not part of the NPC simulation. They always render at #8b8ba0 regardless of any game state.

Static objects (always #8b8ba0):

  • Furniture: chairs, tables, desks, counters, crates
  • Fixtures: terminals, consoles, lockers, shelving
  • Environmental: doors (in closed state), hatches, panels
  • Props: datapads, mugs, comms units, cargo containers

Not static objects (use D-033 colors):

  • All NPCs including Background-tier
  • Player characters
  • Carried items (if items become carriable entities, they follow their carrier's relationship color — this is a future sprint consideration, default to static color in v0.1)

5.2 Why Grey, Not Zone Palette

Static objects use #8b8ba0 rather than blending into zone floor/wall colors because they need to be visually distinct from the environment while remaining subordinate to entity colors. Grey sits between structure (very low saturation zone palettes) and entities (moderate-to-high saturation D-033 colors) in the saturation hierarchy.

Saturation hierarchy (D-044):

Tier Saturation range Examples
Entity (D-033) 4060% #4a9ebb, #6bc9a6, #e8c547, #d45d5d
Objects (static + D-052 favorites) 1030% #8b8ba0, dusty blue, warm terracotta
Structure (zone palette) 515% Zone floor tiles, wall faces

Never add an object color that approaches entity saturation levels.


6. Color Blindness Assessment

6.1 Palette Under Common Deficiencies

The D-033 palette (#4a9ebb teal / #6bc9a6 green / #e8c547 amber / #d45d5d red) relies on hue differentiation as its primary signal. This creates accessibility challenges.

Deuteranopia (green-blind, ~5% of males): The green (#6bc9a6) and teal (#4a9ebb) may be difficult to distinguish. Both occupy the blue-green range. Under deuteranopia simulation, they compress toward a similar cool-blue appearance. The amber (#e8c547) and red (#d45d5d) differentiate better — amber reads as yellow, red reads as brownish-orange.

Protanopia (red-blind, ~1% of males): The red (#d45d5d) shifts toward olive/brown. More concerning: the red and green (#6bc9a6) may become difficult to distinguish. The amber (#e8c547) remains clearly distinct.

Tritanopia (blue-blind, rare): The teal (#4a9ebb) shifts toward green, potentially conflating Unknown and Known. Less critical as tritanopia is uncommon.

6.2 Risk Assessment

High risk: Deuteranopia — teal/green confusion. A deuteranopic player may not clearly distinguish Unknown from Known/Friendly NPCs.

Moderate risk: Protanopia — red/green confusion. A protanopic player may not clearly distinguish Hostile from Known/Friendly.

6.3 Mitigation Recommendation

The current palette does not include a secondary differentiation signal beyond hue. For v0.1 (functional placeholder stage with colored rectangles), this is acceptable — the game ships with boxes, not full art, and accessibility features are milestone work.

Recommended for v0.1.2+: Add a shape or pattern secondary signal to entity sprites — border dash pattern or icon badge — that persists independently of hue. E.g., Hostile gets a diamond border, PersonOfInterest gets a cross-hatch border, Unknown gets no border treatment. This would not require changing the D-033 colors (which have been visually designed and approved) but would layer a non-color signal on top.

Flag: This is a known gap to be addressed before the game exits early access. Tracked as future accessibility ticket (not yet created — flag during Sprint 15 planning).


7. Insert Overlay Interaction (D-048)

In the insert overlay (z-layer 6), entity D-033 colors gain soft halos: 23px gaussian blur at ~40% blend. This is the insert's interpretation of the relationship data — rendered with organic neural texture.

In the natural vision layer (z-layer 3), entity colors are rendered with 2px outline in the relationship color — hard edge, no bloom. This is the player's naked visual perception.

The difference: the insert annotates, the eye sees. Both use the same color, different rendering treatment.

When insert_active == false, the bloom halos disappear. The underlying hard-edge entity color remains (the character still sees the NPC). Only the insert's annotation layer suppresses.


8. Implementation Notes for Stig

The core lookup table is already partially implemented. The complete client-side mapping lives in client/scripts/constants.gd in the color_for_entity_kind() function. The entity renderer at client/scripts/rendering/entity_renderer.gd already:

  • Tracks relationship state per entity in _entity_relationships
  • Detects state changes and starts 0.5s color tweens
  • Handles peripheral visibility dimming independently

Outstanding for future implementation sprint:

  1. Ensure Constants.color_for_entity_kind() uses all five states above (including the player entity case keyed to GameState.player_entity_id and lattice_profile).
  2. Fog-entity rendering (recognized vs unrecognized) is handled by the fog system, not entity_renderer.gd. The fog renderer queries the knowledge graph for recognition state.
  3. Peripheral saturation reduction: currently implemented as alpha reduction (modulate.a). This is sufficient for v0.1. True saturation reduction (keeping brightness, reducing colorfulness) would require a shader and is deferred.

Appendix A — Decision Cross-References

Decision Relevance
D-033 Source of truth for relationship state → color mapping. Hex values in this spec are canonical per D-033 approval.
D-043 "Functional warmth" art direction. Palette tone and production principle.
D-044 Visual hierarchy (entity > object > structure). Saturation rules.
D-048 Neural insert overlay — bloom treatment for D-033 colors on z-layer 6.
D-049 Z-level rendering stack. Entities at z-layer 3. Insert at z-layer 6.
D-059 Fog shader — recognized vs unrecognized entity treatment in fog.
D-060 Cognitive delay — controls when grey fog blob transitions to D-033 color.

Appendix B — Quick Reference for Stig

State Hex Duration of transition
Unknown #4a9ebb 0.5s fade from prior color
Known/Friendly #6bc9a6 0.5s fade from prior color
PersonOfInterest #e8c547 0.5s fade from prior color
Hostile #d45d5d 0.5s fade from prior color
Static object #8b8ba0 Fixed — no transitions
Player (detective) #e0e8ff Fixed — not subject to relationship
Player (smuggler) #e8e0d0 Fixed — not subject to relationship
Fog blob (unrecognized) #555566 Transitions to D-033 over 0.3s during 0.6s cognitive delay (D-060)
Peripheral entities Any D-033 color at reduced alpha No separate color