- sound-indicator-visual: D-069 → D-033/D-048 color reference, reconcile medium range S1 (3-8) vs S7 (3-20) contradiction, split dual values in quick-reference table - entity-color-system: D-046 → D-015 for peripheral rendering, tighten cognitive delay wording, add accessibility ticket tracking note - visual-grammar-v01: dialogue max-width corrected from ~70% to 640px per D-076 - the-friend-visual-treatment: D-063 citation title corrected - tell-visual-expression: soften D-024 attribution (axis, not categories) - environmental-text-standards: specify tile unit for terminal visibility - monologue-display-spec: header updated to reflect Sprint 14 inclusion - CHANGELOG: add entries for all 6 Sprint 14 design docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
14 KiB
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 visibleKnown— character has a relationship but nothing flaggedPersonOfInterest— knowledge graph or case file has flagged this entityHostile— 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 20–25 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) | 40–60% | #4a9ebb, #6bc9a6, #e8c547, #d45d5d |
| Objects (static + D-052 favorites) | 10–30% | #8b8ba0, dusty blue, warm terracotta |
| Structure (zone palette) | 5–15% | 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: 2–3px 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:
- Ensure
Constants.color_for_entity_kind()uses all five states above (including the player entity case keyed toGameState.player_entity_idandlattice_profile). - 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. - 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 |