Initial Commit
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Sky colour via Rayleigh/Mie scattering
|
||||
|
||||
The system SHALL compute sky colour from sun altitude, `AtmosphereConfig.rayleighCoefficient`, and `mieCoefficient`.
|
||||
|
||||
#### Scenario: Earth noon sky
|
||||
|
||||
- **WHEN** the sun is at zenith on an Earth-like planet
|
||||
- **THEN** the computed `skyColour` is deep blue (high B, moderate G, low R)
|
||||
|
||||
#### Scenario: Earth sunset sky
|
||||
|
||||
- **WHEN** the sun is near the horizon on an Earth-like planet
|
||||
- **THEN** the computed `skyColour` is orange/red (high R, moderate G, low B)
|
||||
|
||||
#### Scenario: Mars noon sky
|
||||
|
||||
- **WHEN** the sun is at zenith on a Mars-like planet with CO₂ atmosphere
|
||||
- **THEN** the computed `skyColour` is butterscotch (high R, moderate G, low B)
|
||||
|
||||
#### Scenario: Vacuum world
|
||||
|
||||
- **WHEN** the observer's planet has no `AtmosphereConfig`
|
||||
- **THEN** `skyColour` is black always
|
||||
|
||||
### Requirement: Twilight star visibility thresholds
|
||||
|
||||
The system SHALL apply magnitude limits based on sun altitude and twilight phase.
|
||||
|
||||
#### Scenario: Civil twilight limit
|
||||
|
||||
- **WHEN** sun altitude is between `0` and `-6` degrees
|
||||
- **THEN** only stars with `apparentMagnitude <= 1` are visible
|
||||
|
||||
#### Scenario: Astronomical twilight limit
|
||||
|
||||
- **WHEN** sun altitude is between `-12` and `-18` degrees
|
||||
- **THEN** stars with `apparentMagnitude <= 6` are visible
|
||||
|
||||
#### Scenario: Full dark
|
||||
|
||||
- **WHEN** sun altitude is below `-18` degrees
|
||||
- **THEN** all naked-eye stars are visible
|
||||
|
||||
#### Scenario: Vacuum collapse
|
||||
|
||||
- **WHEN** the planet has no atmosphere
|
||||
- **THEN** all stars are visible regardless of sun altitude
|
||||
|
||||
### Requirement: Multi-satellite moonlight
|
||||
|
||||
The system SHALL compute per-satellite moonlight proportional to phase illumination, angular diameter squared, and albedo, summing across all satellites.
|
||||
|
||||
#### Scenario: Single moon night
|
||||
|
||||
- **WHEN** a planet with one satellite has a full-moon phase
|
||||
- **THEN** `moonContributions` contains one entry with significant light value
|
||||
|
||||
#### Scenario: Multiple moons
|
||||
|
||||
- **WHEN** a planet with two satellites both above the horizon is evaluated
|
||||
- **THEN** `totalAmbientLight` includes the sum of both contributions
|
||||
|
||||
### Requirement: Gameplay modifiers
|
||||
|
||||
The system SHALL export `EnvironmentModifiers` with `totalAmbientLight`, `stealthBonus`, `visibilityRangeMetres`, and `npcSleepProbability` as continuous functions of ambient light.
|
||||
|
||||
#### Scenario: Stealth at midnight
|
||||
|
||||
- **WHEN** `totalAmbientLight` is near `0`
|
||||
- **THEN** `stealthBonus` is near `1`
|
||||
|
||||
#### Scenario: Visibility at noon
|
||||
|
||||
- **WHEN** `totalAmbientLight` is near `1`
|
||||
- **THEN** `visibilityRangeMetres` is at its maximum
|
||||
|
||||
### Requirement: Plain ECS component interfaces
|
||||
|
||||
The system SHALL export `DayNightClock`, `LightState`, and `DayNightAffected` as plain TypeScript interfaces with no `@suaveplan/ecs` dependency.
|
||||
|
||||
#### Scenario: Interface shapes
|
||||
|
||||
- **WHEN** the exports are inspected
|
||||
- **THEN** they are `interface` declarations, not class constructors or archetype registrations
|
||||
|
||||
### Requirement: RenderPayload shape
|
||||
|
||||
The system SHALL export `RenderPayload` as the canonical per-tick output for downstream render integrations.
|
||||
|
||||
#### Scenario: Payload completeness
|
||||
|
||||
- **WHEN** a render payload is generated at a tick
|
||||
- **THEN** it contains `skyColour`, `sunAltitude`, `sunAzimuth`, `horizonColour`, `totalAmbientLight`, `moonContributions`, `fogDensity`, `rayleighTint`, `textures`, and optional `weather`
|
||||
|
||||
### Requirement: Texture manifest pass-through
|
||||
|
||||
The system SHALL pass `PlanetTextureManifest` identifiers through verbatim without loading or storing assets.
|
||||
|
||||
#### Scenario: Texture identifiers
|
||||
|
||||
- **WHEN** a `PlanetConfig` with `textures: { diffuse: "earth-diffuse", night: "earth-night" }` is used
|
||||
- **THEN** the `RenderPayload.textures` contains the same identifiers
|
||||
- **AND** no file loading occurs
|
||||
|
||||
### Requirement: Weather state pass-through
|
||||
|
||||
The system SHALL include `WeatherState` in `RenderPayload` and `LightState` when a `WeatherProvider` is registered, without breaking when absent.
|
||||
|
||||
#### Scenario: No weather provider
|
||||
|
||||
- **WHEN** no `WeatherProvider` is registered in the underlying `UniverseClient`
|
||||
- **THEN** `RenderPayload.weather` is undefined
|
||||
- **AND** lighting computation continues without weather modulation
|
||||
@@ -0,0 +1,86 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: GamingUniverse construction
|
||||
|
||||
The system SHALL export `GamingUniverse` accepting `startTime`, `timeScale`, `UniverseClient`, and optional `clock`.
|
||||
|
||||
#### Scenario: Construction
|
||||
|
||||
- **WHEN** `new GamingUniverse({ startTime, timeScale: 3600, universe })` is called
|
||||
- **THEN** the instance is created with `getGameTime()` returning `startTime`
|
||||
|
||||
### Requirement: Time scale control
|
||||
|
||||
The system SHALL provide `setTimeScale`, `pause`, `resume`, and `isPaused`.
|
||||
|
||||
#### Scenario: Pause and resume
|
||||
|
||||
- **WHEN** `pause()` is called
|
||||
- **THEN** `isPaused()` returns `true`
|
||||
- **AND** `getGameTime()` stops advancing
|
||||
- **AND** after `resume()`, `isPaused()` returns `false`
|
||||
|
||||
### Requirement: Set game time directly
|
||||
|
||||
The system SHALL provide `setGameTime` to override the clock to a specific date.
|
||||
|
||||
#### Scenario: Direct time set
|
||||
|
||||
- **WHEN** `setGameTime(futureDate)` is called
|
||||
- **THEN** `getGameTime()` returns `futureDate`
|
||||
|
||||
### Requirement: Scene queries at game time
|
||||
|
||||
The system SHALL delegate `getScene` to `UniverseClient.getScene` using the current game time.
|
||||
|
||||
#### Scenario: Scene query
|
||||
|
||||
- **WHEN** `getScene(observer)` is called while paused
|
||||
- **THEN** the query uses the paused game time, not real time
|
||||
|
||||
### Requirement: AsyncScheduler integration
|
||||
|
||||
The system SHALL wrap `@suaveplan/timing`'s `AsyncScheduler` to drive game time ticks.
|
||||
|
||||
#### Scenario: Scheduler usage
|
||||
|
||||
- **WHEN** `GamingUniverse` is constructed
|
||||
- **THEN** it creates or references an `AsyncScheduler` from `@suaveplan/timing`
|
||||
- **AND** `dispose()` stops the scheduler
|
||||
|
||||
### Requirement: Event emission
|
||||
|
||||
The system SHALL emit events via `@suaveplan/events` for `hourChanged`, `dayChanged`, `seasonChanged`, `sunriseStarted`, `sunsetStarted`, `moonriseStarted`, `moonsetStarted`, and `eclipseStarted`.
|
||||
|
||||
#### Scenario: Day change event
|
||||
|
||||
- **WHEN** game time advances across a midnight boundary
|
||||
- **THEN** a `dayChanged` event is emitted with the new `gameTime` and `dayNumber`
|
||||
|
||||
### Requirement: Signal cancellation
|
||||
|
||||
The system SHALL pass `AbortSignal` through to `UniverseClient.getScene` and cancel in-flight queries.
|
||||
|
||||
#### Scenario: Abort in-flight
|
||||
|
||||
- **WHEN** `getScene` is called and the signal is aborted
|
||||
- **THEN** the promise rejects with an abort error
|
||||
|
||||
### Requirement: Dispose idempotency
|
||||
|
||||
The system SHALL provide `dispose()` that stops the clock and cancels all in-flight queries, callable multiple times safely.
|
||||
|
||||
#### Scenario: Double dispose
|
||||
|
||||
- **WHEN** `dispose()` is called twice
|
||||
- **THEN** no error is thrown
|
||||
- **AND** the instance is fully shut down after the first call
|
||||
|
||||
### Requirement: UniverseClient exposure
|
||||
|
||||
The system SHALL expose the underlying `UniverseClient` via a readonly `universe` property.
|
||||
|
||||
#### Scenario: Access underlying client
|
||||
|
||||
- **WHEN** `gamingUniverse.universe` is accessed
|
||||
- **THEN** it returns the `UniverseClient` passed at construction
|
||||
Reference in New Issue
Block a user