Initial Commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-05-05
|
||||
93
openspec/changes/enterprise-gaming-universe/design.md
Normal file
93
openspec/changes/enterprise-gaming-universe/design.md
Normal file
@@ -0,0 +1,93 @@
|
||||
## Context
|
||||
|
||||
The existing `@suaveplan/day-night` package conflates gaming concerns (ECS components, gameplay modifiers, in-game clock) with pseudo-astronomy (hardcoded sinusoidal sun arc, 30-day moon cycle, no lat/lon). The astronomical computation layer is now provided by `@suaveplan/universe` and its provider plugins (`solar-system`, `stars`, `tides`). This design defines the two gaming-layer packages that consume that core.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- Wrap `UniverseClient` with a configurable N× game clock that drives scene queries at game time.
|
||||
- Emit semantically meaningful game events (sunrise, season change, eclipse) via `@suaveplan/events`.
|
||||
- Replace hardcoded lighting presets with physically-derived sky colour and gameplay modifiers.
|
||||
- Export plain TypeScript interfaces for ECS component shapes so consumers can wire into any ECS.
|
||||
- Lock in `RenderPayload` as the canonical per-tick output for downstream render integrations.
|
||||
- Achieve 100% test coverage, Zod schemas for all public options, OTel instrumentation, and `AbortSignal` on every async method.
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- Astronomical calculations — delegated to `@suaveplan/universe` and its providers.
|
||||
- R3F / Three.js rendering integration.
|
||||
- Multiplayer synchronisation of game time across clients.
|
||||
- `@suaveplan/weather` integration (locked interface but no implementation).
|
||||
- Bundling texture assets — only identifiers pass through.
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. Two-package split: clock vs lighting
|
||||
|
||||
`gaming-universe` owns time and events. `gaming-environment-lighting` owns sky colour and modifiers. This separation allows consumers to use the clock without the lighting system, or replace lighting with a custom renderer while keeping the clock.
|
||||
|
||||
**Rationale**: Single-responsibility. The clock is a scheduler concern; lighting is a render-data concern.
|
||||
|
||||
### 2. `GamingUniverse` wraps `@suaveplan/timing`'s `AsyncScheduler`
|
||||
|
||||
Rather than inventing a scheduler, `GamingUniverse` applies a time-scale multiplier to `timing`'s ticks.
|
||||
|
||||
**Rationale**: Uniform cancellation, clock injection, and AbortSignal drain with the rest of the monorepo. `@suaveplan/game-loop` becomes the natural tick integration point.
|
||||
|
||||
### 3. Plain TypeScript interfaces for ECS component shapes
|
||||
|
||||
`gaming-environment-lighting` exports plain interfaces (`DayNightClock`, `LightState`, `DayNightAffected`) rather than registering ECS archetypes.
|
||||
|
||||
**Rationale**: Framework-agnostic. Consumers using bitecs, miniplex, or custom ECS map fields to their own storage. No `@suaveplan/ecs` dependency.
|
||||
|
||||
### 4. `RenderPayload` as canonical render output
|
||||
|
||||
A single `RenderPayload` interface is published each tick containing sky colour, lighting, atmosphere, textures, and weather.
|
||||
|
||||
**Rationale**: Prevents churn when R3F/Three.js integrations land. Downstream renderers depend on a stable data shape, not on the lighting package's internals.
|
||||
|
||||
### 5. Feature-module directory layout — Pattern B
|
||||
|
||||
Every feature module uses a dedicated folder containing a main implementation file, a barrel `index.ts` with explicit named re-exports, sibling `.md` docs, and co-located tests.
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
src/lighting/
|
||||
├── index.ts
|
||||
├── lighting.ts
|
||||
├── lighting.test.ts
|
||||
└── lighting.md
|
||||
```
|
||||
|
||||
The `package.json` exports map uses the repeated-name form (`./lighting/lighting`) pointing to the main implementation file, not to `index.ts`.
|
||||
|
||||
**Rationale**: The barrel is reviewable and explicit. The main file name makes stack traces and import paths self-describing. Tests and docs are co-located so they stay in sync.
|
||||
|
||||
### 6. Physically-derived everything
|
||||
|
||||
Sky colour uses Rayleigh/Mie scattering from `AtmosphereConfig`. Star visibility uses sun-altitude twilight thresholds. Moonlight uses phase, angular diameter, and albedo. Gameplay modifiers are continuous functions of `totalAmbientLight`.
|
||||
|
||||
**Rationale**: No hardcoded tables means the same code works for Earth, Mars, and game-defined planets without preset authoring.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
| Risk | Mitigation |
|
||||
|---|---|
|
||||
| Rayleigh/Mie model is simplified (no spectral sampling) | Documented as approximate. Consumers needing spectral accuracy can override `skyColour` in `RenderPayload`. |
|
||||
| NPC sleep probability sigmoid may not match game design | Exposed as a configurable function in advanced options, with a sensible default. |
|
||||
| `gaming-universe` depends on `@suaveplan/timing` scheduler internals | `timing` is a foundation package with stable API. If scheduler changes, `gaming-universe` is a known dependent. |
|
||||
| Two packages to migrate from one `day-night` | Migration guide documents which `day-night` exports map to which new package. |
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Land `gaming-universe` after `universe` is complete.
|
||||
2. Land `gaming-environment-lighting` after `gaming-universe` is complete.
|
||||
3. Update `STATUS.md` and `SUMMARY.md` as each package completes.
|
||||
4. Archive `@suaveplan/day-night` after `gaming-environment-lighting` ships.
|
||||
5. Publish migration guide mapping `day-night` exports to new package exports.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- None — all deferred items are explicitly scoped out with locked interfaces.
|
||||
30
openspec/changes/enterprise-gaming-universe/proposal.md
Normal file
30
openspec/changes/enterprise-gaming-universe/proposal.md
Normal file
@@ -0,0 +1,30 @@
|
||||
## Why
|
||||
|
||||
The existing `@suaveplan/day-night` package uses hardcoded sinusoidal sun arcs, 30-day moon cycles, and preset lighting tables. It is not physically derived, not extensible to other planets, and conflates game-clock concerns with rendering concerns. This change introduces two gaming-layer packages that consume the astronomical core from `@suaveplan/universe` to provide a configurable game clock and physically-derived environment lighting.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **Retire `@suaveplan/day-night`** — replaced by `@suaveplan/gaming-universe` (game clock + events) and `@suaveplan/gaming-environment-lighting` (sky colour + gameplay modifiers).
|
||||
- **Create `@suaveplan/gaming-universe`** — wraps `UniverseClient` with configurable N× game clock via `@suaveplan/timing`'s `AsyncScheduler`, emitting season/sunrise/eclipse events through `@suaveplan/events`.
|
||||
- **Create `@suaveplan/gaming-environment-lighting`** — replaces hardcoded day-night presets with Rayleigh/Mie scattering sky colour, twilight star visibility thresholds, multi-satellite moonlight, and continuous gameplay modifiers derived from ambient light.
|
||||
|
||||
**BREAKING**: `@suaveplan/day-night` is retired — consumers must migrate to `@suaveplan/gaming-universe` + `@suaveplan/gaming-environment-lighting`.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `gaming-universe`: Game-clock astronomical wrapper — N× time scale, event emission, and scene queries at game time.
|
||||
- `gaming-environment-lighting`: Physically-derived environment lighting — sky colour, star visibility, moonlight, and gameplay modifiers.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `gaming-day-night`: **Retired** — all requirements absorbed by `gaming-universe` and `gaming-environment-lighting`. No spec delta; capability removed on archive.
|
||||
|
||||
## Impact
|
||||
|
||||
- **New packages**: 2 packages in `universal/gaming`.
|
||||
- **Dependency graph**: `universe` → `gaming-universe` → `gaming-environment-lighting`.
|
||||
- **Prerequisite**: `@suaveplan/universe` and its downstream providers (`solar-system`, `stars`, `tides`) must be implemented first.
|
||||
- **Downstream consumers**: Any package depending on `@suaveplan/day-night` must migrate.
|
||||
- **Deferred integrations**: R3F / Three.js rendering integration, multiplayer game time sync, `@suaveplan/weather` integration are explicitly out of scope.
|
||||
@@ -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
|
||||
64
openspec/changes/enterprise-gaming-universe/tasks.md
Normal file
64
openspec/changes/enterprise-gaming-universe/tasks.md
Normal file
@@ -0,0 +1,64 @@
|
||||
|
||||
|
||||
## 0. Pre-flight (non-negotiable)
|
||||
|
||||
- [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each)
|
||||
- [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
|
||||
- [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest.
|
||||
|
||||
## 1. gaming-universe — Game Clock Wrapper
|
||||
|
||||
- [ ] 1.1 Scaffold `@suaveplan/gaming-universe` package in `packages/universal/gaming/gaming-universe` with `package.json`, `tsconfig.json`, `vitest.config.ts` (100/100/100/100 thresholds)
|
||||
- [ ] 1.2 Implement `GamingUniverseOptions` and `GamingUniverse` class
|
||||
- [ ] 1.3 Integrate `@suaveplan/timing` `AsyncScheduler` for tick driving
|
||||
- [ ] 1.4 Implement `setTimeScale`, `setGameTime`, `pause`, `resume`, `isPaused`, `getGameTime`
|
||||
- [ ] 1.5 Implement `getScene` delegating to `UniverseClient` at current game time
|
||||
- [ ] 1.6 Implement event emission via `@suaveplan/events` for `hourChanged`, `dayChanged`, `seasonChanged`, `sunriseStarted`, `sunsetStarted`, `moonriseStarted`, `moonsetStarted`, `eclipseStarted`
|
||||
- [ ] 1.7 Implement `dispose` stopping scheduler and cancelling in-flight queries (idempotent)
|
||||
- [ ] 1.8 Expose readonly `universe` property
|
||||
- [ ] 1.9 Add Zod schemas for all public option types
|
||||
- [ ] 1.10 Add OTel spans on all public entrypoints
|
||||
- [ ] 1.11 Add `AbortSignal` on `getScene`
|
||||
- [ ] 1.12 Write tests achieving 100% coverage
|
||||
- [ ] 1.13 Write sibling `.md` docs for every implementation file
|
||||
- [ ] 1.14 Write `README.md` with install, quick-start, and module links
|
||||
- [ ] 1.15 Configure explicit named barrel exports for all subpaths (no `export *`)
|
||||
- [ ] 1.16 Run `bunx turbo lint typecheck test build --filter=@suaveplan/gaming-universe` and fix all errors
|
||||
- [ ] 1.17 Run `bunx turbo test:coverage --filter=@suaveplan/gaming-universe` and verify 100%
|
||||
|
||||
## 2. gaming-environment-lighting — Physical Lighting
|
||||
|
||||
- [ ] 2.1 Scaffold `@suaveplan/gaming-environment-lighting` package in `packages/universal/gaming/gaming-environment-lighting`
|
||||
- [ ] 2.2 Implement Rayleigh/Mie scattering sky colour computation from `sunAltitude` and `AtmosphereConfig`
|
||||
- [ ] 2.3 Implement twilight star visibility thresholds (civil/nautical/astronomical/full dark)
|
||||
- [ ] 2.4 Implement vacuum collapse (no atmosphere → always full dark)
|
||||
- [ ] 2.5 Implement multi-satellite moonlight sum (`moonLight ∝ phaseIllumination × angularDiameter² × albedo`)
|
||||
- [ ] 2.6 Implement `EnvironmentModifiers`: `totalAmbientLight`, `stealthBonus`, `visibilityRangeMetres`, `npcSleepProbability`
|
||||
- [ ] 2.7 Export plain ECS component interfaces: `DayNightClock`, `LightState`, `DayNightAffected`
|
||||
- [ ] 2.8 Implement `RenderPayload` generation per tick
|
||||
- [ ] 2.9 Pass `PlanetTextureManifest` identifiers through verbatim
|
||||
- [ ] 2.10 Handle optional `WeatherState` pass-through without breaking
|
||||
- [ ] 2.11 Add Zod schemas for all public option types
|
||||
- [ ] 2.12 Add OTel spans on all public entrypoints
|
||||
- [ ] 2.13 Write tests achieving 100% coverage
|
||||
- [ ] 2.14 Write sibling `.md` docs for every implementation file
|
||||
- [ ] 2.15 Write `README.md` with install, quick-start, and module links
|
||||
- [ ] 2.16 Configure explicit named barrel exports for all subpaths (no `export *`)
|
||||
- [ ] 2.17 Run `bunx turbo lint typecheck test build --filter=@suaveplan/gaming-environment-lighting` and fix all errors
|
||||
- [ ] 2.18 Run `bunx turbo test:coverage --filter=@suaveplan/gaming-environment-lighting` and verify 100%
|
||||
|
||||
## 3. Cross-cutting and Archive
|
||||
|
||||
- [ ] 3.1 Update `STATUS.md`: mark `gaming-universe` and `gaming-environment-lighting` as completed
|
||||
- [ ] 3.2 Update `SUMMARY.md` with per-package paragraphs
|
||||
- [ ] 3.3 Update `MANIFEST.md` via `bun scripts/folder-reorg/generate-manifest.ts`
|
||||
- [ ] 3.4 Update `MISSING.yml` via `bun scripts/generate-missing.ts`
|
||||
- [ ] 3.5 Run `bunx markdownlint-cli2 --fix` on all touched root docs
|
||||
- [ ] 3.6 Run `bunx turbo lint typecheck test` across the full gaming-universe filter set
|
||||
- [ ] 3.7 Verify zero banned patterns in all new package `src/` directories
|
||||
- [ ] 3.8 Verify zero `export *` in feature barrels
|
||||
- [ ] 3.9 Verify zero direct `vitest`/`bun:test` imports in non-exempt packages
|
||||
- [ ] 3.10 Verify every `.ts` has a sibling `.md`
|
||||
- [ ] 3.11 Verify every package has a `README.md`
|
||||
- [ ] 3.12 Verify no bare version strings in any new `package.json`
|
||||
- [ ] 3.13 Archive change: move `openspec/changes/enterprise-gaming-universe` to `openspec/archive/2026-04-25-enterprise-gaming-universe`
|
||||
Reference in New Issue
Block a user