# Overlay Visibility & Domain Design: How I Keep It Clean and Playable

When I think about overlays in a tabletop-style virtual environment—like range rings, blocked-status indicators, and vision cones—visibility becomes crucial. I want to make sure what players see on the screen reflects what their characters could logically perceive in the game world.

## Overlay Visibility – Requirements

### For Players

As a player, I only want overlays to show up **when they make sense**—meaning, I should only see an overlay if my character would be able to see the token it’s attached to. This helps preserve immersion and makes decision-making more intuitive.

If I have a token selected, I want its overlays to show—of course. But for overlays on *other* tokens, I only want to see them if:

* The other token is visible on the canvas
    
* It’s not hidden in the scene configuration
    
* It lies within the selected token’s vision polygon
    
* There are no walls or barriers blocking line of sight
    

When no token is selected, I want the system to fall back on visibility from *all* tokens I control—so overlays still show accurately based on what my characters could see collectively.

### For GMs

As a GM, it’s a different story. I want to see **everything**—all overlays, all the time. Full visibility means I can keep an eye on the battlefield, check for inconsistencies, and help players when needed.

## Domain Layer – My Approach

Now, to support this behaviour in code, I’ve been thinking carefully about how to model the domain. Here’s how I’ve structured things.

I use **four main layers** in my domain model:

1. **Entities** – Mutable objects with identity
    
2. **Value Objects** – Immutable domain concepts
    
3. **Interfaces** – Contracts for infrastructure and external services
    
4. **Types** – Lightweight, structural definitions (like enums, DTOs, configs)
    

### Entities – Things That Change and Persist

Entities are objects that have a **unique ID** and **mutable state**. For example:

```typescript
export class MoveableToken {
    constructor(
        public readonly id: string,
        private position: Vector2,
        private rotation: number,
        private collisionShape: CollisionShape
    ) {}

    move(newPosition: Vector2): void {
        this.position = newPosition;
    }

    rotate(newRotation: number): void {
        this.rotation = newRotation;
    }
}
```

What makes this an entity:

* It has identity (`id`)
    
* Its state can change (`position`, `rotation`)
    
* I consider two entities the same if they share the same ID—not necessarily the same data
    

### Value Objects – Immutable by Design

Value objects are all about immutability and equality by value:

```typescript
export class Vector2 {
    constructor(
        public readonly x: number,
        public readonly y: number
    ) {}

    add(other: Vector2): Vector2 {
        return new Vector2(this.x + other.x, this.y + other.y);
    }

    equals(other: Vector2): boolean {
        return this.x === other.x && this.y === other.y;
    }
}
```

I reach for value objects when I want:

* Domain expressiveness
    
* Predictability
    
* Thread safety
    
* Easy equality comparison
    

### Interfaces – Contracts for Flexibility

Interfaces help me define **what** needs to be done, not **how**.

```typescript
export interface LineOfSightChecker {
    isBlocked(origin: Vector2, destination: Vector2): boolean;
}

export interface TokenOwnershipProvider {
    getOwnedTokenIds(userId: string): string[];
}
```

This makes it easy for me to swap implementations, mock them during tests, or inject platform-specific logic without tying it to my domain.

### Types – Pure Data

I use types and enums to represent pure data without behaviour:

```typescript
export enum GridType {
    Gridless = 0,
    Square = 1,
    Hex = 2
}

export interface SceneConfig {
    gridType: GridType;
    gridSize: number;
    useElevation: boolean;
    sceneCeiling: number;
    enable3DPathfinding: boolean;
    maxStepHeight: number;
}
```

Types are lightweight and ideal for:

* Configs
    
* DTOs
    
* Enums and constraints
    
* Representing shape without logic
    

### Choosing the Right Tool for the Job

Purpose

|  | Entity | Value Object | Interface | Type |
| --- | --- | --- | --- | --- |
| What it represents | A thing with identity | A concept with value | A contract for a service | A data structure |
| Has identity? | O | X | X | X |
| Mutable? | O | X | X | X |
| Behaviour? | Rich methods | Domain-specific | Method signatures only | None |
| Example | `MoveableToken` | `Vector2`, `Distance` | `LineOfSightChecker` | `SceneConfig`, `GridType` |

### Why This Structure Works for Me

This approach keeps my codebase:

* **Testable** – Logic lives in pure functions and classes
    
* **Modular** – Interfaces separate infrastructure from domain
    
* **Extensible** – Easy to extend or replace parts of the system
    
* **Readable** – Clear boundaries make it easier to reason about
    

By thinking in terms of **entities**, **value objects**, **interfaces**, and **types**, I avoid the dreaded "everything is just a class" pitfall. It also helps me align gameplay logic (like overlay visibility) with clean architectural decisions.

I think this is a strong foundation for building features that are both fun to use and maintainable to develop. If you’ve got similar patterns—or different ones—I’d love to hear about them.
