# Stargate Network Roadmap

## Goal

Build a production-ready Stargate network for OpenSimulator with:

- In-world gates that register themselves and send regular location and health updates.
- Atlantis-style gate behavior including staged dialing animation, chevron lighting, and a recognizable dialing sequence.
- A backend API and database that track all known gates across regions and grids.
- A web front end for operators and players to browse the network.
- An in-world menu and address book that query the database for saved destinations and automated dialing options.
- Hypergrid dialing support with gameplay power requirements enforced through a ZPM system.

## Core Assumptions

- Gates will be deployed from a standard packaged object that includes the required scripts.
- Automatic discovery of new gates will happen through gate self-registration, not by magically scanning the grid.
- Hypergrid travel will depend on OpenSimulator hypergrid support plus backend validation rules.
- ZPM power is a roleplay mechanic enforced by the gate logic before a hypergrid dial is allowed.

## Current Script Deployment Notes

The current in-world package has working local gate-side behavior for lights, sounds, event horizon, and shield control. These notes should be treated as the baseline install state until the broader network stack is built.

### 2026-04-06 Breakpoint Snapshot

- Backend gate registry is live at `https://api.exoticsg.net`.
- Gate registration and heartbeat endpoints are verified from in-world.
- API bridge script `stargate_api_bridge_v1.lsl` is actively registering gates and recording heartbeats.
- Network default gate type is now Pegasus (`g_GATE_TYPE = "pegasus"`).
- DHD integration script `stargate_dhd_controller_v3.lsl` has been introduced for live address-book fetch via bridge channel `55302`.
- Pending after break: finish GDO/IDC authorization flow and complete end-to-end multi-gate dial test.

### Verified Scripts

- `stargate_lights_controller_v2.lsl`: production light controller with OpenSim-safe OFF payload parsing.
- `stargate_sound_controller_v2.lsl`: production sound controller with updated sound UUIDs and OpenSim-safe payload parsing.
- `_eh.lsl`: event horizon object script with calibrated close sound behavior and preserved runtime name `_eh`.
- `_shield.lsl`: shield object script with corrected plain-text command parsing, auto-die on close, and preserved runtime name `_shield`.
- `stargate_gate_controller_skeleton_v1.lsl`: production gate controller with the validated EH and shield lifecycle ported from the tester.

### Required Packaging Rules

- The rezzed object assets named `_eh` and `_shield` must contain the updated scripts internally. Updating only loose workspace files is not enough.
- The live gate object should use the updated `stargate_gate_controller_skeleton_v1.lsl` if this is the deployed production controller.
- If another gate controller is the real deployed script, port the same EH and shield behavior before live rollout.

### OpenSim Compatibility Notes

- For linked-message payload handling, prefer parsing the command payload from `str` before falling back to `id`. Some OpenSim builds deliver `NULL_KEY` text in the `id` slot and mask OFF or CLOSE-style commands.
- Do not JSON-parse mixed command traffic unconditionally. `_shield.lsl` accepts plain-text commands such as `size|5.0`, `close`, and `die`, so JSON parsing must only happen when the payload is actually a JSON object.

### Current FX Behavior

- Event horizon target diameter: `5.15`
- Shield target diameter: `5.0`
- Shield semantics: `UP` means rez and size the shield so it stays visible; `DOWN` means send `close`, then `die`, and clear controller state.
- EH close behavior now plays the close sound before the object de-rezzes.

### Short Deployment Checklist

1. Replace the live `_eh` object inventory script with the updated `_eh.lsl`.
2. Replace the live `_shield` object inventory script with the updated `_shield.lsl`.
3. Update the live gate controller to the current `stargate_gate_controller_skeleton_v1.lsl` logic.
4. Verify in-world: lights turn on and off, sounds trigger correctly, EH opens and closes at the new size, shield comes up and goes down cleanly.

## System Architecture

### 1. In-world OpenSimulator Components

Each Stargate deployment should include these script roles:

- Gate controller script: main dialing state machine, local gate status, iris state, incoming and outgoing connection handling.
- DHD controller script: player interaction, address entry, menu/UI, local permissions.
- Animation controller script: manages ring animation, symbol stepping, chevron light states, engage timing, kawoosh, and shutdown sequence.
- Address book script: fetches favorites, nearby gates, and permitted destinations from the backend for menu-driven dialing.
- Beacon or heartbeat script: sends periodic updates to the API with region name, coordinates, owner, online state, and software version.
- Registration script: obtains or renews a gate identity token and registers a newly deployed gate.
- ZPM power script: tracks available power, reports charge level, and authorizes or denies hypergrid dialing.
- Power management script: tracks all attached power devices (ZPMs and Naquadah generators), aggregates available power across multiple simultaneous units, reports combined charge and output to the API, authorizes or denies hypergrid dialing based on total available power, and broadcasts current bus power level to subscribed in-world consumers (lights, consoles, doors, and other electrical devices).
- Vendor script: in-world distribution terminal that delivers gate, DHD, and ZPM starter kit assets to players with owner-controlled inventory mapping and anti-spam cooldown.
- Update or config script: receives configuration such as API endpoint, gate public ID, heartbeat interval, and feature flags.

### Atlantis Gate Experience Requirements

The in-world gate should behave like the Atlantis-era Pegasus gate as closely as OpenSimulator allows:

- Dialing sequence with symbol-by-symbol progression.
- Chevron lighting and lock states that advance in order.
- Separate visual and audio states for idle, dialing, lock, wormhole open, active, and shutdown.
- Automated dialing from a menu-driven address book as well as optional manual entry.
- Failure states that stop the sequence cleanly if a symbol is invalid, a gate is offline, or ZPM power is insufficient.

Implementation note:

- Treat animation timing and network authorization as separate layers. The local script should drive the visual sequence, but final connection success should depend on backend authorization.

### 2. Backend Services

- API service: receives registrations, heartbeats, dialing requests, and administrative actions.
- Network service: resolves gate addresses, route rules, availability, and hypergrid eligibility.
- Auth service: gate credentials, admin logins, API keys, token rotation.
- Event processor: stores activity logs, last-seen state, alerts, and audit history.
- Optional webhook or message worker: handles async notifications and status changes.

### 3. Database

Minimum entities:

- gates
- gate_endpoints
- gate_addresses
- address_book_entries
- animation_profiles
- regions
- grids
- zpm_devices
- power_devices  (type: zpm | naquadah_generator; supports multiple units per gate or bus node)
- power_grid_nodes  (in-world electrical bus objects; track region, total available power, and total consumed power)
- power_consumers  (devices subscribed to a bus: lights, consoles, forcefields, doors, etc.)
- dial_events
- heartbeat_events
- software_versions
- operators
- access_policies

### 4. Web Front End

- Public network directory of active gates.
- Operator dashboard for gate health, last heartbeat, power level, and incidents.
- Map or region view showing gate placement.
- Admin panel for approvals, bans, feature flags, and configuration.
- Address book management for saved routes, aliases, and dialing permissions.

## Delivery Phases

### Phase 0: Discovery and Technical Validation

Objective: prove the assumptions before building the full stack.

Tasks:

- Confirm which scripting features are available in your OpenSimulator deployment, especially HTTP requests, timers, notecards, and any OSSL functions you plan to use.
- Decide whether gates will self-register using packaged scripts only or whether you also want an OpenSimulator region module.
- Validate how hypergrid destination data should be represented for your dialing model.
- Define what counts as a gate address in your lore and in code.
- Decide how closely the dialing visuals should mirror Atlantis canon versus what is practical in OpenSimulator prim and script limits.
- Define the ZPM gameplay rules: full charge, recharge rate, number of hypergrid jumps per charge, and failure behavior.
- Define the power gameplay rules: ZPM full charge, recharge rate, stacking behavior across multiple simultaneous ZPMs, Naquadah generator sustained output rate, power priority order (gate dialing > electrical grid consumers), and failure behavior when the bus drops below threshold.
- Define which actions require ZPM-class power versus Naquadah-generator-class power.

Deliverables:

- Confirmed technical constraints.
- Addressing model.
- Power model.
- Security model for gate registration.

### Phase 1: Domain Model and Protocol Design

Objective: define the protocol between gates and the backend.

Tasks:

- Design the gate registration flow.
- Design the heartbeat payload.
- Design the dialing authorization flow.
- Design the address book query flow for in-world menu use.
- Define how a gate reports region name, local position, rotation, owner, and software version.
- Define how hypergrid destinations are represented and validated.
- Define ZPM state fields and server-side checks.
- Define power device state fields: device type, slot index, charge percent (ZPM) or continuous output rate (Naquadah generator), combined bus total, and server-side authorization thresholds.
- Define electrical grid bus fields: region, node object key, total available, total consumed, consumer list, and priority rules.
- Define animation profile fields so different gate models can share the same backend.

Suggested API surface:

- POST /api/gates/register
- POST /api/gates/heartbeat
- POST /api/gates/dial/authorize
- POST /api/gates/dial/report
- POST /api/zpm/status
- POST /api/power/report  (replaces /api/zpm/status; accepts array of power devices per gate or bus node)
- GET /api/power/grid/:region  (current bus state: available, consumed, consumer list)
- GET /api/power/devices/:gate_id  (all power devices attached to a gate)
- GET /api/gates/:id/config
- GET /api/gates/:id/address-book
- GET /api/network/directory
- GET /api/network/address/:symbol
- GET /api/network/nearby
- GET /api/network/search?q=&type=&grid=&online=  (full-text address book search)
- GET /api/network/random?type=&requires_zpm=  (random eligible destination for exploration)
- POST /api/developer/apps/register  (register third-party addon integration)
- POST /api/developer/commands  (submit scoped addon command)
- GET /api/developer/commands/:command_id  (poll command status and response)
- GET /api/developer/events?cursor=  (signed event feed for addon responses)
- POST /api/developer/webhooks  (register signed webhook endpoint for async command callbacks)

Deliverables:

- JSON request and response schemas.
- Error code catalog.
- Authentication strategy for gate devices.
- Authentication and authorization strategy for third-party addon developers (scoped keys, signed requests, command allow-list).

### Phase 2: Backend and Database Foundation

Objective: create a reliable source of truth.

Tasks:

- Select the backend stack.
- Build the initial relational schema.
- Add migrations and seed data.
- Implement registration, heartbeat, and gate lookup endpoints.
- Implement address book and destination lookup endpoints.
- Store last-seen timestamps and online status.
- Add server-side validation for duplicate addresses and duplicate gate identities.

Recommended baseline stack:

- API: Node.js with Fastify or NestJS, or Python with FastAPI.
- Database: PostgreSQL.
- ORM or query layer: Prisma, Drizzle, or SQLAlchemy.
- Hosting: containerized service behind HTTPS.

Deliverables:

- Running API.
- Database schema and migrations.
- Basic gate registration and status tracking.

### Phase 3: In-world Script MVP

Objective: get a gate in OpenSimulator talking to the backend.

Tasks:

- Build a minimal gate script that can load config and call the API.
- Implement one-time registration or claim flow.
- Implement periodic heartbeat updates.
- Implement the Atlantis-style dialing state machine with light and timing control hooks.
- Implement menu-driven destination selection from a server-backed address book.
- Persist gate identity locally in the object where possible.
- Add backoff and retry logic if the API is offline.
- Make sure scripts fail safely and do not spam the server.

Deliverables:

- A deployable Stargate object.
- A successful self-registration flow.
- Live updates visible in the database and admin UI.

### Phase 4: Dialing Logic and Network Rules

Objective: move from tracking to actual network behavior.

Tasks:

- Implement local dialing session state.
- Implement chevron-by-chevron animation and symbol lock progression.
- Support both manual dialing and automated dialing from the address book menu.
- Resolve destination addresses against the backend.
- Reject offline or blocked destinations.
- Log all outgoing and incoming dial attempts.
- Add cooldowns, busy states, and failure reasons.

Deliverables:

- Functional inter-gate dialing within the same network.
- Full event logging.

### Phase 5: Hypergrid Support and ZPM Enforcement
### Phase 5: Hypergrid Support and Power Enforcement

Objective: support off-grid travel with gameplay constraints and a shared in-world power system.

Tasks:

- Define the exact distinction between local dialing and hypergrid dialing.
- Add a backend field for external grid destination records.
- Implement multi-device power aggregation: multiple ZPMs combine their charge into a single pool; charge is drawn from the highest-charged unit first.
- Implement Naquadah generator support as a lower power class: sufficient to sustain the electrical grid and local dialing but not hypergrid jumps unless a ZPM is also present.
- Record each power device (type, slot, charge/output) as a separate row in `power_devices`; report all devices per heartbeat via `POST /api/power/report`.
- Require ZPM-class total power above a configured threshold for hypergrid authorizations.
- Consume charge on wormhole completion; update `power_devices` and reflect the new aggregate in the next heartbeat.
- Build the in-world electrical grid bus: the power management script listens on a fixed region channel and answers consumer subscription requests from lights, consoles, forcefields, and other devices. Bus power level is the combined output of all attached ZPMs and Naquadah generators minus active gate drain.
- Consumers receive a power-available or power-cut broadcast when bus level crosses configurable thresholds.
- Distinguish local address book entries from hypergrid-capable destinations in the menu; show power requirement.
- Add operator controls for exempt gates, special events, or lore overrides.
- Implement graceful failure messages when total power is insufficient.

Important design points:

- Power checks must happen both in-world and server-side. Client-only enforcement can be bypassed.
- Gate dialing takes priority over electrical grid consumers. During an active wormhole, non-essential consumers are shed first.
- Naquadah generators are always-on constant-output sources, not rechargeable batteries. ZPMs are depletable batteries that recharge over time.

Deliverables:

- Multi-device power aggregation in the power management script.
- `power_devices` and `power_grid_nodes` database records.
- Server-side power-class authorization rules.
- In-world bus broadcast system for electrical consumers.
- Logged power consumption events per wormhole.

### Phase 6: Front End and Operations Tooling

Objective: provide visibility and administration.

Tasks:

- Build the public gate directory.
- Build an operator dashboard.
- Show gate last heartbeat, location, software version, and power level.
- Build address book management UI for aliases, favorites, route groups, and gate visibility rules.
- Add search by address, region, grid, or owner.
- Add filters for online, offline, local-only, and hypergrid-capable gates.
- Add audit views for registration, heartbeat, and dialing history.

Deliverables:

- Operator-ready admin UI.
- Public network directory.

### Phase 7: Hardening and Live Operations

Objective: make the system survivable in production.

Tasks:

- Add rate limiting, request signing, and token rotation.
- Add observability for API failures and missing heartbeats.
- Add backup and restore procedures.
- Add release version tracking for scripts.
- Add gate quarantine or revocation for compromised devices.
- Add testing for simulator restarts, region transfers, and duplicate object copies.

Deliverables:

- Production readiness checklist.
- Monitoring and alerting.
- Incident recovery procedures.

## Critical Design Decisions To Make Early

1. Script model: pure in-world scripts only, or scripts plus an OpenSimulator region module.
2. Canonical address model: lore address, symbolic address, UUID-based identity, or mixed model.
3. Trust model: open self-registration, approval-based registration, or signed deployment packages.
4. Hypergrid model: direct destination records, server-managed route aliases, or both.
5. ZPM model: object inventory item, linked controller object, or server-authoritative device state.
6. Animation fidelity model: single-script approximation, linked-prim lighting system, or fully modular animation controller.
7. Address book model: global directory only, per-gate favorites, per-user favorites, or a mixed approach.

## Minimum Viable Product

The first usable version should do only this:

- A gate can be deployed in-world.
- It self-registers to the API.
- It reports its region and coordinates every few minutes.
- The website shows all active gates and their last-seen time.
- The gate shows a recognizable Atlantis-style dialing sequence with chevron lighting.
- The DHD or gate menu can fetch destinations from the database-backed address book.
- A gate can dial another local gate.
- Hypergrid dialing is blocked unless a valid ZPM is attached and sufficiently charged.
- A vendor object can deliver gate, DHD, and ZPM starter kit assets in-world.

If you keep the MVP smaller than this, you will get faster validation without trapping yourself in front-end work too early.

## Recommended Implementation Order

1. Define the data model and registration protocol.
2. Build the database and API for registration plus heartbeat.
3. Build the first OpenSimulator gate scripts and animation state machine.
4. Verify automatic gate discovery via self-registration.
5. Add the database-backed address book and in-world menu flow.
6. Add the website directory and operator dashboard.
7. Add local dialing authorization.
8. Add hypergrid routing and ZPM power rules.
9. Harden authentication, logging, and operations.

## Immediate Next Actions

1. Choose the backend stack and hosting target.
2. Document the exact gate registration payload and heartbeat payload.
3. Define the gate address format, address book model, and hypergrid destination format.
4. Define the Atlantis dialing state machine, chevron lighting sequence, and failure states.
5. Define the ZPM rules in gameplay terms and in technical terms.
6. Build a thin proof-of-concept: one gate script, one registration endpoint, one heartbeat endpoint, one address book endpoint, and one web status page.

## Atlantis Dialing State Machine

This is the baseline behavior for an Atlantis-style Pegasus gate adapted to OpenSimulator.

### Primary States

- idle: gate is powered, ring is still, chevrons are dark, no active session.
- menu_active: DHD or gate menu is open and the user is browsing the address book or entering symbols.
- precheck: local gate validates busy state, local permissions, API availability, and ZPM readiness if hypergrid dialing is requested.
- dialing_start: ring lighting and startup sound begin, the gate reserves itself for the session.
- symbol_step: the next symbol in the route is selected and the ring advances.
- chevron_engage: the corresponding chevron lights and locks for that symbol.
- route_pending: all symbols are entered and the gate is waiting for backend authorization.
- wormhole_open: connection is approved, event horizon opens, kawoosh effect plays if enabled.
- active: wormhole remains stable for the configured session duration.
- shutdown: wormhole collapses, chevrons extinguish in sequence, gate returns to idle.
- failed: dialing aborts, failure lights and sounds play, state returns to idle after cooldown.

### State Transitions

1. idle -> menu_active when a player uses the DHD or gate menu.
2. menu_active -> precheck when the player chooses an address or confirms manual entry.
3. precheck -> failed if the gate is busy, destination is invalid, API cannot be reached within timeout, or ZPM rules fail.
4. precheck -> dialing_start when local validation succeeds.
5. dialing_start -> symbol_step for the first symbol.
6. symbol_step -> chevron_engage for each symbol.
7. chevron_engage -> symbol_step until the final symbol is locked.
8. final chevron_engage -> route_pending.
9. route_pending -> wormhole_open if the backend authorizes the route.
10. route_pending -> failed if the backend rejects the route.
11. wormhole_open -> active after the visual open sequence completes.
12. active -> shutdown when the timer expires, the user terminates the connection, or a fault condition occurs.
13. shutdown -> idle after all visual states are reset.

### Atlantis Sequence Timing Model

Use these as initial defaults and tune them after simulator testing:

- startup delay: 0.5 to 1.0 seconds
- time between symbol steps: 0.8 to 1.5 seconds
- chevron lock flash: 0.25 to 0.5 seconds
- final authorization wait: up to 3 seconds before local failure handling starts
- wormhole open effect: 1.5 to 3 seconds
- active session timeout: configurable, recommended 30 to 120 seconds for normal roleplay
- post-failure cooldown: 3 to 8 seconds

### Visual Control Model

- Ring motion should be a local animation concern and must not block backend communication.
- Chevron prims or linked faces should support at least these states: off, charging, locked, failure, active.
- The final chevron should have a distinct lock effect so the player can read when the gate is committing the route.
- Incoming wormholes should use a different intro path than outgoing dialing so the receiving gate does not replay a full outbound symbol sequence.

### Failure Reasons

The local scripts should expose a small fixed failure catalog:

- destination_not_found
- destination_offline
- destination_busy
- gate_busy
- local_cooldown
- authorization_timeout
- authorization_denied
- insufficient_power
- missing_zpm
- script_fault

## Address Book and Menu Behavior

The in-world menu should not be a thin cosmetic overlay. It is part of the dialing workflow.

### Menu Features

- Browse saved destinations by page.
- Filter by local network, hypergrid, favorites, nearby, and recent.
- Show online and offline status.
- Show whether a ZPM is required.
- Allow manual entry as a fallback.
- Allow owners or operators to pin favorites locally.
- Search by gate name, public address, region name, or grid — results sorted by relevance then online status.
- Random dial option — requests a random eligible online gate from the API for exploration; respects ZPM availability and access level.

### Address Book Resolution Rules

- Local cache first if the cache is fresh.
- Refresh from API when the menu opens or when the user requests reload.
- If the API is unavailable, allow dialing only from cached entries marked as cache-safe.
- Hypergrid entries must always be revalidated at authorization time even if cached locally.

### Minimum Address Book Entry Fields

- entry_id
- display_name
- public_address
- route_symbols
- destination_gate_id
- destination_type
- source_scope
- online_status
- requires_zpm
- last_verified_at
- access_level

## API Contract Draft

This is the first-pass contract for the gate package and backend.

### POST /api/gates/register

Purpose: register a newly deployed gate or rebind a copied object to a fresh identity.

Request:

```json
{
	"deployment_key": "string",
	"object_key": "uuid",
	"region_name": "Lantea Harbor",
	"grid_id": "primary-grid",
	"position": { "x": 128.5, "y": 64.0, "z": 25.2 },
	"rotation": { "x": 0, "y": 0, "z": 0.707, "w": 0.707 },
	"owner_key": "uuid",
	"owner_name": "Ancient Builder",
	"software_version": "0.1.0",
	"animation_profile": "pegasus-default",
	"capabilities": ["local_dial", "address_book", "hypergrid", "zpm_required"]
}
```

Response:

```json
{
	"gate_id": "gate_01JATLANTIS",
	"gate_token": "signed-device-token",
	"public_address": "ATL-001",
	"heartbeat_interval_seconds": 180,
	"config_ttl_seconds": 900,
	"address_book_cache_ttl_seconds": 300,
	"approved": true
}
```

### POST /api/gates/heartbeat

Purpose: keep the gate visible, report status, and update operator telemetry.

Request:

```json
{
	"gate_id": "gate_01JATLANTIS",
	"status": "idle",
	"region_name": "Lantea Harbor",
	"position": { "x": 128.5, "y": 64.0, "z": 25.2 },
	"software_version": "0.1.0",
	"animation_profile": "pegasus-default",
	"zpm": {
		"present": true,
		"charge_percent": 82,
		"tier": "standard"
	},
	"network": {
		"api_latency_ms": 240,
		"address_book_cache_age_seconds": 75
	}
}
```

Response:

```json
{
	"accepted": true,
	"server_time": "2026-04-04T12:00:00Z",
	"next_heartbeat_seconds": 180,
	"config_changed": false
}
```

### GET /api/gates/:id/address-book

Purpose: return menu-friendly dialing options for the gate.

Response:

```json
{
	"generated_at": "2026-04-04T12:00:00Z",
	"cache_ttl_seconds": 300,
	"entries": [
		{
			"entry_id": "abe_001",
			"display_name": "Atlantis Control",
			"public_address": "ATL-CTRL",
			"route_symbols": ["A", "T", "L", "A", "N", "T", "I", "S"],
			"destination_gate_id": "gate_control",
			"destination_type": "local",
			"online_status": "online",
			"requires_zpm": false,
			"access_level": "public"
		},
		{
			"entry_id": "abe_099",
			"display_name": "Midway Relay",
			"public_address": "MIDWAY",
			"route_symbols": ["M", "I", "D", "W", "A", "Y", "X", "9"],
			"destination_gate_id": "gate_midway",
			"destination_type": "hypergrid",
			"online_status": "restricted",
			"requires_zpm": true,
			"access_level": "operator"
		}
	]
}
```

### POST /api/gates/dial/authorize

Purpose: validate route, permissions, gate state, and ZPM power before the final lock.

Request:

```json
{
	"gate_id": "gate_01JATLANTIS",
	"request_mode": "address_book",
	"entry_id": "abe_001",
	"route_symbols": ["A", "T", "L", "A", "N", "T", "I", "S"],
	"destination_type": "local",
	"zpm": {
		"present": true,
		"charge_percent": 82,
		"tier": "standard"
	},
	"requested_by": {
		"avatar_key": "uuid",
		"avatar_name": "John Sheppard"
	}
}
```

Response:

```json
{
	"authorized": true,
	"session_id": "dial_01JSESSION",
	"destination_gate_id": "gate_control",
	"destination_region": "Atlantis Command",
	"open_timeout_seconds": 8,
	"max_active_seconds": 60,
	"power_cost": {
		"type": "zpm_percent",
		"value": 0
	}
}
```

### POST /api/gates/dial/report

Purpose: record state changes for audit and remote troubleshooting.

Request events:

- dialing_started
- symbol_locked
- authorization_requested
- wormhole_opened
- wormhole_closed
- dialing_failed

### Developer Add-on API (Secure Broker)

Purpose: let third-party developers build network add-ons that can issue approved commands and receive responses without direct control-plane access.

Security model:

- Add-ons never talk directly to in-world scripts or simulator chat channels.
- Every addon command goes through API-side policy validation and is transformed into internal intents.
- Each developer app gets scoped credentials with least-privilege permissions (for example: `directory:read`, `dial:request`, `events:read`).
- Sensitive actions (for example, forced shutdown, override, admin state changes) are excluded from addon scopes.
- All requests require HTTPS, request timestamp, nonce, and HMAC signature to prevent replay.
- Every command is fully audited with actor, scope, source IP, request hash, and outcome.

Primary addon endpoints:

### POST /api/developer/apps/register

Purpose: create a developer application identity and issue initial credentials for sandbox or production.

### POST /api/developer/commands

Purpose: submit a command intent through policy checks.

Example request:

```json
{
	"command_type": "dial.request",
	"target_gate_id": "gate_01JATLANTIS",
	"payload": {
		"destination_type": "local",
		"route_symbols": ["A", "T", "L", "A", "N", "T", "I", "S"],
		"requested_by": {
			"avatar_key": "uuid",
			"avatar_name": "Addon Operator"
		}
	},
	"idempotency_key": "8f10f4f2-0e2c-4c2f-b93c-b30af0f868e4"
}
```

Example response:

```json
{
	"accepted": true,
	"command_id": "cmd_01JADDON",
	"status": "queued",
	"expires_at": "2026-04-04T12:30:00Z"
}
```

### GET /api/developer/commands/:command_id

Purpose: fetch current status and final outcome.

Status lifecycle:

- queued
- running
- succeeded
- denied
- failed
- expired

### GET /api/developer/events?cursor=

Purpose: pull signed response events for addons that prefer polling streams over webhooks.

### POST /api/developer/webhooks

Purpose: register webhook callback URLs for async command results.

Webhook requirements:

- HTTPS only
- HMAC-signed payloads using per-app webhook secret
- Delivery retry with exponential backoff
- Replay protection via event id and timestamp window

## Database Shape Draft

The first relational model should include these tables beyond the baseline roadmap.

### gates

- id
- public_address
- grid_id
- region_name
- object_key
- owner_key
- owner_name
- status
- approved
- animation_profile
- supports_hypergrid
- last_seen_at

### gate_addresses

- id
- gate_id
- address_type
- display_name
- canonical_route
- is_primary
- is_public

### address_book_entries

- id
- source_scope
- source_gate_id
- source_owner_key
- target_gate_id
- alias
- sort_order
- is_favorite
- requires_zpm
- visibility

### dial_events

- id
- session_id
- source_gate_id
- destination_gate_id
- request_mode
- destination_type
- outcome
- failure_reason
- power_cost_type
- power_cost_value
- initiated_at
- closed_at

### developer_apps

- id
- owner_operator_id
- app_name
- environment
- status
- created_at
- revoked_at

### developer_api_keys

- id
- app_id
- key_id
- secret_hash
- scope_list
- rate_limit_tier
- last_used_at
- expires_at
- revoked_at

### addon_command_queue

- id
- app_id
- command_type
- target_gate_id
- payload_json
- idempotency_key
- status
- denial_reason
- response_json
- created_at
- updated_at
- completed_at

### addon_command_events

- id
- command_id
- event_type
- event_payload
- signature
- created_at

### developer_webhooks

- id
- app_id
- callback_url
- secret_hash
- status
- last_delivery_at
- last_error
- created_at

## OpenSimulator Script Breakdown

Use a multi-script object so simulator limits are easier to manage.

### Gate Controller

- Owns the authoritative local state machine.
- Starts and stops dial sessions.
- Talks to the API for authorization and reporting.
- Handles incoming wormhole events.

### Animation Controller

- Controls ring motion, chevron lighting, sounds, and event horizon effects.
- Exposes commands such as start_dial, lock_symbol, open_gate, fail_gate, and shutdown_gate.
- Does not decide whether the route is valid.

### DHD or Menu Controller

- Presents menu pages and manual input.
- Requests address book data through the local address book script.
- Sends route requests to the gate controller.

### Address Book Script

- Fetches and caches address book entries.
- Normalizes them into menu pages.
- Supports favorites, recent destinations, and nearby entries.

### Registration and Heartbeat Script

- Handles bootstrapping, registration, token refresh, and regular heartbeats.
- Keeps retry logic separate from the live dial flow.

### ZPM Script

- Tracks local ZPM attachment and charge state.
- Answers whether the requested route is currently power-eligible.
- Reports power state to the heartbeat and dial authorization flows.

## Recommended Default Decisions

Unless testing proves otherwise, use these defaults for the first implementation:

- Mixed address model: human-readable public address plus canonical route symbol array.
- Mixed address book model: global entries plus per-gate favorites.
- Animation model: modular linked-prim system with a separate animation controller script.
- Registration model: self-registration with server approval and signed gate tokens.
- Cache model: 5-minute address book cache with forced revalidation for hypergrid destinations.

---

## ESN — Exotic Stargate Network

**Project name:** ESN — Exotic Stargate Network  
**Domain:** exoticsg.net  
**Scope:** Dedicated server for the ESN API, PostgreSQL database, and web frontend only. OpenSimulator runs separately on its own existing server and connects to this API over HTTPS.

---

## Server Minimum Requirements

This server hosts only the ESN API, the database, and the web frontend. OpenSimulator is excluded.

| Resource | Minimum | Recommended |
|---|---|---|
| OS | Ubuntu 22.04 LTS 64-bit | Ubuntu 22.04 LTS 64-bit |
| CPU | 1 vCPU | 2 vCPUs |
| RAM | 1 GB | 2 GB |
| Storage | 20 GB SSD | 40 GB SSD |
| Network | 100 Mbps, static public IP | 100 Mbps, static public IP |
| IPv4 | 1 dedicated static address | 1 dedicated static address |

**Why these numbers:**

- PostgreSQL at start uses roughly 256–512 MB of RAM. It grows slowly as gate registrations, heartbeats, and dial history accumulate. With routine pruning it stays well under 1 GB for a typical network of hundreds of gates.
- The ESN API (Node.js) uses roughly 100–200 MB at idle and peaks under burst registration traffic. 1 GB RAM total is sufficient for early operation.
- Storage is dominated by PostgreSQL data and API logs. 20 GB gives several years of headroom at ESN-scale volumes. 40 GB recommended for comfort.
- A 1 vCPU shared instance (such as a $6/month DigitalOcean Droplet or Linode Nanode) is sufficient to start. Upgrade to 2 vCPU when concurrent gate connections exceed 50.

---

## Required Software Stack

### Operating System

Ubuntu 22.04 LTS is recommended because:
- Long-term support through April 2027 (ESM through 2032).
- First-class package support for Node.js 20, PostgreSQL 15, Nginx, and Certbot.
- No licensing cost.

### Runtime Dependencies

| Component | Software | Version |
|---|---|---|
| ESN API | Node.js | 20 LTS |
| Database | PostgreSQL | 15 or 16 |
| Reverse proxy | Nginx | 1.24+ |
| TLS certificates | Certbot + Let's Encrypt | latest |
| Process manager | systemd | built-in to Ubuntu |
| Optional monitoring | Prometheus + Grafana | latest |

.NET is **not** required on this server. It lives on the OpenSimulator server.

---

## Required Open Ports

Configure your firewall (ufw) and any upstream hosting firewall panel to allow only these.

| Port | Protocol | Purpose |
|---|---|---|
| 22 | TCP | SSH admin access |
| 80 | TCP | HTTP — required for Certbot domain validation, redirects to HTTPS |
| 443 | TCP | HTTPS — ESN web frontend and API |
| 5432 | TCP | PostgreSQL — **block from public**; allow only from localhost |

No OpenSimulator ports are needed on this server.

---

## DNS Configuration

At your DNS registrar or DNS provider, create these records pointing to the ESN API server IP.

```
A    exoticsg.net       <esn-server-public-ip>
A    www.exoticsg.net   <esn-server-public-ip>
A    api.exoticsg.net   <esn-server-public-ip>
```

`grid.exoticsg.net` (the OpenSimulator login URI) points to the OpenSimulator server, not this one.

If using Cloudflare, `api.exoticsg.net` should be set to **DNS-only (grey cloud)** rather than proxied. The in-world LSL scripts use `llHTTPRequest` which does not handle Cloudflare's connection fingerprinting well on some OpenSim builds.

---

## Installation Plan

### Step 0 — Provision the server

1. Spin up a VPS meeting the minimum specs above (Ubuntu 22.04 LTS image).
2. Assign or note the static public IPv4 address.
3. SSH in as root and create a non-root admin user.

```bash
adduser esnAdmin
usermod -aG sudo esnAdmin
```

4. Harden SSH: disable root login, enable key-based auth only.

---

### Step 1 — System packages and runtimes

```bash
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget gnupg2 software-properties-common unzip ufw fail2ban
```

Install Node.js 20 LTS:

```bash
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
```

Install PostgreSQL 15:

```bash
sudo apt install -y postgresql-15 postgresql-client-15
```

Install Nginx:

```bash
sudo apt install -y nginx
```

---

### Step 2 — Configure the firewall

```bash
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw deny 5432
sudo ufw enable
```

PostgreSQL listens on localhost only by default. The `deny 5432` rule is a belt-and-suspenders block at the firewall level.

---

### Step 3 — PostgreSQL database setup

```bash
sudo -u postgres psql
```

Inside psql:

```sql
CREATE USER esndb WITH PASSWORD 'choose-a-strong-password';
CREATE DATABASE esn_production OWNER esndb;
GRANT ALL PRIVILEGES ON DATABASE esn_production TO esndb;
\q
```

Do not use the default postgres superuser for application connections.

---

### Step 4 — Deploy the ESN API

Clone or copy the ESN API source to the server:

```bash
sudo mkdir /opt/esn-api && sudo chown esnAdmin:esnAdmin /opt/esn-api
cd /opt/esn-api
# copy or git clone your ESN API code here
npm install
```

Create a `.env` file (never commit this to git):

```
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://esndb:choose-a-strong-password@localhost:5432/esn_production
JWT_SECRET=generate-a-long-random-secret
GATE_TOKEN_SECRET=generate-a-second-long-random-secret
API_VERSION=1
```

Run database migrations:

```bash
npm run migrate
```

Create a systemd service for the ESN API:

```
/etc/systemd/system/esn-api.service
```

```ini
[Unit]
Description=ESN API Service
After=network.target postgresql.service

[Service]
Type=simple
User=esnAdmin
WorkingDirectory=/opt/esn-api
ExecStart=/usr/bin/node dist/server.js
EnvironmentFile=/opt/esn-api/.env
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
```

```bash
sudo systemctl daemon-reload
sudo systemctl enable esn-api
sudo systemctl start esn-api
```

---

### Step 5 — Deploy the ESN web frontend

Build your frontend and copy the output to Nginx's web root:

```bash
sudo mkdir -p /var/www/esn
# copy your built frontend files (HTML/CSS/JS) to /var/www/esn
sudo chown -R www-data:www-data /var/www/esn
```

---

### Step 6 — Configure Nginx

Create an Nginx site configuration:

```
/etc/nginx/sites-available/esn
```

```nginx
server {
    listen 80;
    server_name exoticsg.net www.exoticsg.net api.exoticsg.net;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name exoticsg.net www.exoticsg.net;

    ssl_certificate /etc/letsencrypt/live/exoticsg.net/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/exoticsg.net/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    root /var/www/esn;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

server {
    listen 443 ssl;
    server_name api.exoticsg.net;

    ssl_certificate /etc/letsencrypt/live/exoticsg.net/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/exoticsg.net/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

Enable the site:

```bash
sudo ln -s /etc/nginx/sites-available/esn /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```

---

### Step 7 — TLS certificate

Install Certbot and obtain a certificate for all three subdomains:

```bash
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d exoticsg.net -d www.exoticsg.net -d api.exoticsg.net
```

Certbot will automatically install the certificate into the Nginx config and create a renewal cron job. Test renewal without issuing a certificate:

```bash
sudo certbot renew --dry-run
```

---

### Step 8 — In-world gate configuration

Once the API is running, configure each in-world gate LSL script with the ESN API endpoint. This goes into the gate's config notecard or embedded constant:

```lsl
string ESN_API_BASE   = "https://api.exoticsg.net/api";
string ESN_GATE_ID    = "";   // filled on first registration
string ESN_GATE_TOKEN = "";   // filled on first registration
```

The gate registration script will POST to `https://api.exoticsg.net/api/gates/register` and write back the `gate_id` and `gate_token` values to the object's inventory notecard or to `llSetObjectDesc`. The OpenSimulator server address stays in its own config on the OpenSimulator server.

---

### Step 9 — Smoke test

Run these checks after installation:

1. Visit `https://exoticsg.net` — the web frontend loads with a valid TLS certificate.
2. Visit `https://api.exoticsg.net/api/network/directory` — returns a JSON response (empty array is fine on first boot).
3. From the OpenSimulator server, rez the first gate object in a region — the registration flow completes and the gate appears in the web directory.
4. Trigger a dial sequence in-world — the gate controller posts to `https://api.exoticsg.net/api/gates/dial/authorize` and receives a valid response.

---

## Storage Growth Estimates

All figures are for the ESN API server only. OpenSimulator asset storage is on the OpenSimulator server.

| Source | Growth rate | Notes |
|---|---|---|
| ESN gate registry (gates table) | Negligible — one row per gate | Hundreds of gates = kilobytes |
| ESN dial events table | ~1 MB per 10,000 dial attempts | Archive rows older than 90 days |
| ESN heartbeat events table | ~1 MB per region per week | Prune with a scheduled job; keep last 30 days |
| Nginx access logs | ~10 MB per 100,000 requests | Rotate with logrotate weekly |
| PostgreSQL WAL | 256 MB–1 GB peak | Controlled by checkpoint tuning |

Starting at 20 GB gives several years of headroom at typical ESN traffic volumes. 40 GB is recommended for comfort.

---

## Backup Minimum Requirements

Before opening ESN to the public, put these in place:

1. Daily PostgreSQL dump: `pg_dump esn_production` to a compressed file, rotated with a 14-day retention.
2. Daily copy of `/opt/esn-api/.env` and any local API config files.
3. Weekly VPS/hypervisor snapshot if your host supports it.
4. Off-site copy of critical backups — do not keep only one copy on the same server.

OpenSimulator backups are the responsibility of the OpenSimulator server.

---

## Decision Log

### 2026-04-04

- Project named ESN — Exotic Stargate Network.
- Domain confirmed as exoticsg.net.
- Scope clarified: this server hosts ESN API, PostgreSQL, and web frontend only. OpenSimulator runs on its own existing server.
- ESN API server minimum: 1 GB RAM, 1 vCPU, 20 GB SSD. Recommended: 2 GB RAM, 2 vCPU, 40 GB SSD.
- Ubuntu 22.04 LTS selected as the target OS.
- PostgreSQL 15 confirmed as the database.
- Node.js 20 LTS selected for ESN API.
- Nginx selected as reverse proxy and frontend server.
- Let's Encrypt / Certbot selected for TLS.
- DNS note: api.exoticsg.net should be DNS-only (grey cloud) in Cloudflare if Cloudflare is used, due to LSL llHTTPRequest compatibility.
