feat(scenario): implement the phase 6 scenario director
Add Format Specification section 21 (Scenario Model 0.1) and the runtime that realizes it: the scenario director, seven trigger classes, timelines with repeats and branches, admission and concurrency control, nested ownership with bounded cleanup, and the production GC5 resource counters. Exhibit E provides a reproducible forty-minute long-scenario reference and scenario-challenge.xzbt covers the PRD 131 cases. A standalone acceptance/soak page is built by tools/build-scenario-acceptance.mjs. 252 automated checks pass. The two-hour real-duration development soak required by the GC6 schedule remains pending. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01M7dgfQ12mpM4JjSMv3inLA
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
# XZBT Format Specification 0.1
|
||||
|
||||
**XZBT format version:** 0.1
|
||||
**Document revision:** 0.9
|
||||
**Status:** Normative specification for shared contracts (document, types, ValueSpec, ConditionSpec, resolution, bindings, and transitions); the Audio subsystem contract is complete (Phase 3a sources/control sources, Phase 3b processing/routing/components/buses, Phase 3c automation/lifecycle/protection); master-protection values remain provisional pending GC6 measurement; the Visual subsystem contract is complete (Phase 4a scene/primitives/transforms/appearance in section 17, Phase 4b components/procedural systems/behaviors/fields in section 18, and Phase 4c automation/lifecycle/camera/post-effects/ceilings in section 19); the aggregate visual ceilings of 19.5 remain provisional pending the slice 4h GC6 measurement; the Cadence and Event Subsystems contract is complete in section 20 (Phase 5); and the remaining subsystem contracts are in progress
|
||||
**Document revision:** 0.10
|
||||
**Status:** Normative specification for shared contracts (document, types, ValueSpec, ConditionSpec, resolution, bindings, and transitions); the Audio subsystem contract is complete (Phase 3a sources/control sources, Phase 3b processing/routing/components/buses, Phase 3c automation/lifecycle/protection); master-protection values remain provisional pending GC6 measurement; the Visual subsystem contract is complete (Phase 4a scene/primitives/transforms/appearance in section 17, Phase 4b components/procedural systems/behaviors/fields in section 18, and Phase 4c automation/lifecycle/camera/post-effects/ceilings in section 19); the aggregate visual ceilings of 19.5 remain provisional pending the slice 4h GC6 measurement; the Cadence and Event Subsystems contract is complete in section 20 (Phase 5); Scenario Model 0.1 is complete in section 21 (Phase 6); UI and library-hardening contracts remain in progress
|
||||
**Related resources:** [PRD](../XZBT_0-1_MVP_Product_Requirements_Document.md), [decisions](XZBT_0-1_Gap_Closure_Decisions.md), [verification](XZBT_0-1_Verification_Gates.md)
|
||||
|
||||
This document defines normative syntax and runtime semantics for XZBT 0.1 exhibits. An exhibit is a UTF-8 JSON document that configures generic procedural visual, audio, cadence, and orchestration primitives. It does not contain executable JavaScript.
|
||||
@@ -535,7 +535,7 @@ Complete shared contracts before implementing dependent subsystems. Use PRD sect
|
||||
| Audio | 34-60 | Complete recipe/component shapes, node defaults, automation timing, clamp implementation, one-shot endings and tails, unlock behavior, master protection contract | **Complete (Rev 0.4 / Phase 3a-3c):** units, graph objects, all sixteen node types, routing, modulation, graph legality, authoring limits, components, sounds/recipes, buses, automation tracks and precedence, lifecycle states and release, determinable one-shot endings, voice ceilings, unlock and pause behavior, and the master-protection contract shape. Master-protection *values* (peak ceiling, tolerance, release behavior) are provisional pending GC6 measurement |
|
||||
| Cadence | 61-68 | Exact selection/cooldown/overlap fields, clocks, pool collisions, pending audio, manual sampling isolation and continuous-sample termination | **Complete (Rev 0.9 / Phase 5):** section 20 fixes cadence classes, clocks, interval ranges, selection algorithm, cooldown, overlap policy, anti-repetition relaxation, minimum automatic gap, ambient maintenance, and manual SAMPLE PRNG stream isolation |
|
||||
| Visuals | 69-89 | Primitive/system/behavior fields, angle and motion units, transform order, depth projection, field behavior, morph compatibility, effect approximation and limits | **Complete (Rev 0.7 / Phase 4a-4c):** section 17 fixes the pipeline, canonical units and the angle convention, the `visuals` container, layers, the scene model and coordinate/fit modes, depth sign and sorting, the fourteen primitives, common properties, transform composition order, appearance and the safe blend set, paths and splines, and the once-at-instantiation resolution boundary. Section 18 fixes visual components and their `inputs` scope, particle systems and their normative integrator, the nine placement distributions, emitters and exact emission timing, repeaters and the `repeat.*` scope, the seventeen-behavior vocabulary and its channel set, the six procedural fields and their normative coherent-noise function, and trails, ribbons, and links. Section 19 fixes visual automation and its two declaration scopes and loop modes, the four visual rows it adds to the section 8.1 table and nothing beyond them, the persistent and spawned system lifecycle and its ownership, the camera matrix and both projection modes, the seven post-effects and the boundary of permitted approximation, and the centralized runtime ceilings for the whole engine. Aggregate ceiling *values* are provisional pending the slice 4h GC6 measurement |
|
||||
| Events/scenarios | 90-102 | Shared ownership, hooks/failure ordering, condition rearming, deferred ordering/expiry, dispatch limits; full trigger/timeline shapes remain for Phase 6 | **Shared lifecycle contract complete (Rev 0.3 / GC5); Event Model 0.1 complete (Rev 0.9 / Phase 5 in section 20); scenario triggers/timelines Phase 6** |
|
||||
| Events/scenarios | 90-102 | Shared ownership, hooks/failure ordering, condition rearming, deferred ordering/expiry, dispatch limits; complete trigger/timeline shapes | **Shared lifecycle contract complete (GC5); Event Model in section 20; Scenario Model in section 21 (Revision 0.10 / Phase 6)** |
|
||||
| Generated UI | 103-107 | Widget compatibility, button actions, parameter validation and override display, group/control ordering | Subsystem contract (Phase 7) |
|
||||
| Runtime/library | 108-112, 117-127 | Clock/audio synchronization, stalls, seed/stream derivation complete in GC4; import equality, update compatibility, transactions, failure recovery, persistence schema remain | **Shared clock/PRNG contract complete (Rev 0.3 / GC4); subsystem remainder Phase 1/8** |
|
||||
|
||||
@@ -3397,10 +3397,10 @@ When a class timer expires:
|
||||
* $2$ selections ago: multiplier $0.25$
|
||||
* $3$ selections ago: multiplier $0.50$
|
||||
* $4$ selections ago: multiplier $0.75$
|
||||
* Older / not in recency queue: multiplier $1.0$
|
||||
* Older / not in recency queue: multiplier `1`
|
||||
$\text{effectiveWeight} = \text{baseWeight} \times \text{multiplier}$.
|
||||
6. **Pool Relaxation (PRD 65):** If the sum of effective weights for all remaining candidates is $0$ (which occurs when all eligible sounds are penalized to zero, such as in single-sound pools):
|
||||
* Relax anti-repetition penalties: set all multipliers to $1.0$ ($\text{effectiveWeight} = \text{baseWeight}$).
|
||||
* Relax anti-repetition penalties: set all multipliers to `1` ($\text{effectiveWeight} = \text{baseWeight}$).
|
||||
* If the sum of base weights is still $0$, the class firing is skipped with no sound played.
|
||||
7. **Weighted Selection:** Draw a pseudo-random value $u \in [0, 1)$ from `rng.stream('cadence', '<class>:select:<ordinal>')` and pick the winning sound proportional to its effective weight.
|
||||
8. **Execution:** Instantiate and play the winning sound via the audio subsystem.
|
||||
@@ -3411,7 +3411,7 @@ When a class timer expires:
|
||||
|
||||
To prevent simultaneous or jarringly close auditory collisions between different automatic classes:
|
||||
* The runtime tracks $\text{lastAutomaticSoundTime}$, the logical timestamp of the most recent automatic one-shot start.
|
||||
* A class firing is permitted only if $\text{currentLogicalTime} - \text{lastAutomaticSoundTime} \ge \text{minGap}$ (default $1.5\text{s}$).
|
||||
* A class firing is permitted only if $\text{currentLogicalTime} - \text{lastAutomaticSoundTime} \ge \text{minGap}$ (default `1500ms`).
|
||||
* If multiple classes become due simultaneously or while a gap hold is in effect, they are queued and serviced in strict canonical priority order:
|
||||
$$\text{rare} > \text{occasional} > \text{intermittent} > \text{routine}$$
|
||||
* Deferred classes retain their firing opportunity: when the required $\text{minGap}$ has elapsed, the highest-priority deferred class fires immediately and resets the gap timer.
|
||||
@@ -3569,10 +3569,194 @@ Phase 5 reuses existing standard codes from the Section 7 table:
|
||||
6. **Sound Action Execution:** Sound actions validate sound references, pass inputs to recipes, check caller usage permissions, and reject unauthorized usage with `ERR_UNSUPPORTED_TARGET`.
|
||||
7. **Cadence Eligibility & Cooldown:** A sound with `when: false` or active cooldown ($\text{elapsed} < \text{cooldown}$) is excluded from selection.
|
||||
8. **Cadence Overlap Policy:** A sound with `overlap: false` is excluded from selection while any voice of that sound is active in the audio engine; a sound with `overlap: true` admits overlapping voices up to voice ceilings.
|
||||
9. **Cadence Anti-Repetition Multipliers:** Consecutive firings verify multipliers $0.0$, $0.25$, $0.50$, $0.75$, $1.0$ against the class recency history.
|
||||
9. **Cadence Anti-Repetition Multipliers:** Consecutive firings verify multipliers `0`, `0.25`, `0.50`, `0.75`, `1` against the class recency history.
|
||||
10. **Pool Relaxation:** When all eligible sounds have effective weight zero, anti-repetition penalties relax to base weights and selection succeeds rather than permanently stalling.
|
||||
11. **Cadence Intensity Scaling:** Decreasing intensity increases interval spacing ($T / \text{intensity}$); setting intensity to $0$ stops one-shot class scheduling completely.
|
||||
12. **Minimum Automatic Gap & Priority:** Simultaneous class firings enforce `minGap` (default $1.5\text{s}$) between audio starts and service deferred classes in priority order ($\text{rare} > \text{occasional} > \text{intermittent} > \text{routine}$).
|
||||
12. **Minimum Automatic Gap & Priority:** Simultaneous class firings enforce `minGap` (default `1500ms`) between audio starts and service deferred classes in priority order ($\text{rare} > \text{occasional} > \text{intermittent} > \text{routine}$).
|
||||
13. **Ambient Maintenance:** Ambient sounds (`cadence.class: "ambient"`) auto-start on audio unlock and are maintained by the runtime.
|
||||
14. **Manual SAMPLE Isolation:** Manual SAMPLE playback draws exclusively from `rng.stream('sample', ...)` and leaves cadence clocks, cooldowns, recency history, and automatic PRNG sequences byte-identical.
|
||||
14. **Manual SAMPLE Isolation:** Manual SAMPLE playback draws exclusively from the `manual-sample` domain and leaves cadence clocks, cooldowns, recency history, and automatic PRNG sequences byte-identical.
|
||||
|
||||
## 21. Scenario Model 0.1
|
||||
|
||||
This section completes PRD 92–102. Sections 9 and 10 remain authoritative for clock,
|
||||
ownership, dispatch and failure. Scenarios are finite declarative performances. Their
|
||||
director runs on the same fixed tick as cadence, after resolution and cadence and before
|
||||
visual advancement. It never installs a timer for a timeline entry. Rendering samples the
|
||||
current result and does not advance scenarios.
|
||||
|
||||
### 21.1 Definition and limits
|
||||
|
||||
`scenarios` is an ID-keyed object. Unknown fields in every object below are
|
||||
`ERR_UNKNOWN_FIELD`; wrong shapes are `ERR_SCHEMA_VALIDATION`. IDs use section 1.
|
||||
|
||||
| Field | Type | Default / requirement |
|
||||
| --- | --- | --- |
|
||||
| `name` | string | Optional display name; ID is the fallback |
|
||||
| `enabled` | boolean | `true`; affects new starts, does not cancel an active instance |
|
||||
| `priority` | integer | `50`; range 0 through 100 |
|
||||
| `group` | ID string | Optional; required with group exclusivity |
|
||||
| `trigger` | trigger object | `{ "type": "manual" }` |
|
||||
| `eligibility` | object with optional `when`, `timeout` | ConditionSpec and positive literal duration; timeout defaults to `5m` |
|
||||
| `concurrency` | object | Fields and defaults in section 21.3 |
|
||||
| `cooldown` | literal duration | `0ms`; measured from the last termination, including failure |
|
||||
| `duration` | positive literal duration | Optional upper bound; without it completion follows the last beat |
|
||||
| `onStart` | ActionSpec array | `[]` |
|
||||
| `timeline` | timeline-entry array | Required; may be empty |
|
||||
| `onComplete`, `onCancel` | restricted ActionSpec arrays | `[]`; section 10.2 |
|
||||
| `tags` | string array | Optional; at most 16 strings of at most 32 characters |
|
||||
|
||||
At most 64 definitions, 1024 entries per definition, and 1024 total occurrences of
|
||||
one repeated entry are authorable; excess is `ERR_OUT_OF_BOUNDS`. The runtime admits
|
||||
at most 16 active instances and at most one instance of each definition. Capacity is a
|
||||
start conflict and obeys the requested conflict policy. These scenario limits extend
|
||||
the centralized inventory of section 19.5 and remain provisional pending GC6.
|
||||
|
||||
Instance IDs are `instances.scenario-<definition>-<ordinal>`, with a monotonically
|
||||
increasing ordinal per definition. The director retains only live instances, bounded
|
||||
cleanup owners, and the last terminal status per definition. Completed histories are
|
||||
not retained. The lifecycle follows PRD 93: definition/eligibility/scheduling,
|
||||
`STARTING`, `ACTIVE`, `COMPLETING` or `CANCELLING`, then `COMPLETED`, `CANCELLED` or
|
||||
`FAILED`. Exactly one termination hook runs. `signals.scenario.active` is true when
|
||||
any scenario instance is starting or active.
|
||||
|
||||
### 21.2 Trigger shapes and sampling
|
||||
|
||||
| Type | Additional fields | Opportunity |
|
||||
| --- | --- | --- |
|
||||
| `manual` | none | Start API or scenario control action |
|
||||
| `once` | optional `at` (default `0ms`) | First tick at/after elapsed performance time |
|
||||
| `interval` | required positive `every` | After each interval, starting one interval after activation |
|
||||
| `random-interval` | required positive `min`, `max`, min <= max | Independently sample the first and each subsequent interval |
|
||||
| `probability` | required positive `every`, required numeric `chance` in [0,1] | One probability draw per interval |
|
||||
| `condition` | required `when`, optional nonnegative `for` (default `0ms`) | Hold/rearm state machine in section 10.3 |
|
||||
| `event` | required declared `event` ID | Notification at event invocation, before its actions |
|
||||
|
||||
Triggers use performance logical time. The first true tick starts a condition's hold
|
||||
timer; subsequent true ticks accumulate the hold. A true initial condition remains
|
||||
disarmed until a false tick. Firing consumes the edge even if admission is deferred or
|
||||
rejected. Disabled definitions still advance trigger clocks and rearming state; admission
|
||||
rechecks enabled. Intervals shorter than a tick produce at most one opportunity per tick,
|
||||
with no catch-up burst. Invalid durations use `ERR_INVALID_DURATION`, reversed ranges
|
||||
use `ERR_INVALID_RANGE_ORDER`, missing references use `ERR_INVALID_REFERENCE`.
|
||||
|
||||
Each definition has one stream `scenario / <id>:trigger:1`. Each admitted instance
|
||||
uses `scenario / <id>:<ordinal>` for its start actions, timeline timing, weighted branches,
|
||||
repeat intervals, and nested action values, in execution order. Neither manual sampling
|
||||
nor another scenario's actions consume that stream. Sound and visual instantiation use
|
||||
their own domains. Trigger event input values are copied into a deferred request once;
|
||||
they are not an authorable scenario input namespace in 0.1.
|
||||
|
||||
### 21.3 Admission, exclusivity, and controls
|
||||
|
||||
`concurrency` permits exactly `mode` (`parallel` or `exclusive`, required when the
|
||||
object is present), `scope` (`global` default, or `group`), and `policy` (`defer`
|
||||
default, `reject`, or `replace`). Parallel is the default mode. Exclusivity is symmetric:
|
||||
either participant's global exclusive scope blocks overlap; group exclusive scope blocks
|
||||
overlap with the same group. Same-definition overlap always conflicts.
|
||||
|
||||
`replace` succeeds only if the incoming priority is strictly greater than every
|
||||
conflicting instance's priority. Check eligibility and capacity before cancelling any
|
||||
victim. Equal/lower priority rejects. `reject` drops an inadmissible opportunity;
|
||||
`defer` retains it under section 10.4. Failed eligibility or cooldown obeys the same
|
||||
policy. Replacement hooks and cleanup precede the incoming start hook. Pending admission
|
||||
sorts by descending priority, creation logical time, then definition document order.
|
||||
Starts and beats consume one ordinary dispatch unit each in addition to their actions;
|
||||
even empty-start feedback is bounded by section 10.5.
|
||||
|
||||
`control` actions for this subsystem have `target: "scenarios.<id>"` and `command`
|
||||
equal to `start`, `stop`, `enable` or `disable`. Stop cancels an active instance and
|
||||
removes the definition's deferred request. Enable/disable does not reset cooldown or
|
||||
trigger state. Other scenario commands are `ERR_UNSUPPORTED_TARGET`. Global pause and
|
||||
resume are runtime controls and preserve independent user and visibility pause reasons.
|
||||
Events generated during ordinary dispatch may admit scenarios later in the same tick;
|
||||
new scenarios get their own owner. Static validation rejects event/scenario invocation
|
||||
and trigger feedback cycles with `ERR_CYCLIC_DEPENDENCY`, including indirect events and
|
||||
timeline branches. This conservative check rejects cycles even if authored conditions
|
||||
would make them unreachable. The runtime budget remains the second backstop.
|
||||
|
||||
### 21.4 Timeline, relative timing, repeats, and branches
|
||||
|
||||
An entry permits exactly `id`, `at`, `after`, `delay`, `repeat`, `actions`, `choose`.
|
||||
Use exactly one of `at` (nonnegative literal duration from instance start) or `after`
|
||||
(another entry's unique ID). `delay` belongs only to `after`, defaults to `0ms`, and
|
||||
accepts a nonnegative literal or `{ "random": { "min": "20s", "max": "50s" } }`.
|
||||
The anchor is the first occurrence's scheduled time, independent of its action outcomes
|
||||
or repeat count. Forward anchors are legal; missing anchors and cycles are rejected.
|
||||
|
||||
After successful `onStart`, resolve first-occurrence timestamps by walking entries in
|
||||
document order, resolving each anchor before its dependent and sampling each relative
|
||||
delay once. An instance keeps one mutable scheduling record per entry rather than
|
||||
expanding every repetition. Beats execute in non-decreasing scheduled time; ties use
|
||||
scenario definition document order, then entry document order. Timing is quantized only
|
||||
at dispatch: execute on the first tick at/after the scheduled timestamp. A skipped or
|
||||
zero-weight branch still counts as an occurrence and anchors dependent entries.
|
||||
|
||||
`repeat` permits exactly required integer `count` (1–1024, including the first beat)
|
||||
and required positive `every` (literal or bounded random TimeSpec). Sample the next
|
||||
interval after each occurrence and add it to the preceding scheduled timestamp.
|
||||
Choose exactly one of `actions` or nonempty `choose`. A branch permits required finite
|
||||
nonnegative numeric `weight`, optional ConditionSpec `when`, and required `actions`.
|
||||
At each occurrence evaluate branch conditions, discard ineligible/zero weights, draw
|
||||
one weighted choice and run its actions. An empty eligible pool is a no-op.
|
||||
|
||||
An explicit duration keeps the instance active even after its timeline empties. Beats
|
||||
at the duration execute before completion; later beats are cancelled. Without duration,
|
||||
completion follows the last beat, including repeats. Empty undurated scenarios finish
|
||||
in their start tick. The application exposes the director's start/cancel/status controls;
|
||||
the final schema-generated presentation belongs to Phase 7.
|
||||
|
||||
### 21.5 Resource ownership and termination
|
||||
|
||||
Section 10 governs all termination causes. Indirect event resources retain the owner,
|
||||
priority, usage authorization and stream of their caller. Sound `ownership: "scenario"`
|
||||
means that concrete caller owner; `persistent` and the existing `performance` spelling
|
||||
transfer to the performance root. A continuous sound requested before audio unlock is
|
||||
retained as a bounded logical intent (at most 16), removed if its owner terminates, and
|
||||
started when audio is available. Missed one-shots are not replayed. This does not claim
|
||||
sample-accurate reconstruction of pre-unlock oscillator phase.
|
||||
|
||||
Termination removes future timeline records before running the hook, continues past
|
||||
hook failures, and transfers both scenario-scope and duration overrides and live audio
|
||||
to a cleanup owner. Visual cleanup honors the independent originating-scenario relation
|
||||
of section 19.2. Release durations and cleanup retention are capped at five logical
|
||||
seconds. Counters include releasing audio pools, not only the actively playing set.
|
||||
Performance teardown runs hooks before disposing audio/visual engines, and force-clears
|
||||
cleanup immediately. No persistent `set` is rolled back. Persistent resources remain
|
||||
until performance teardown or explicit removal.
|
||||
|
||||
### 21.6 Required examples and verification traces
|
||||
|
||||
Minimal five-second scenario:
|
||||
|
||||
```json
|
||||
{"xzbt":"0.1","meta":{"id":"five-second","name":"Five seconds"},"scenarios":{"brief":{"trigger":{"type":"once"},"duration":"5s","timeline":[]}}}
|
||||
```
|
||||
|
||||
The composition example is `exhibits/exhibit-e.xzbt`: a 40-minute sequence with random
|
||||
trigger timing, random relative delays, repeated beats, weighted branching, persistent
|
||||
state, cadence interaction, continuous sound, visual spawning, override and recovery.
|
||||
Exhibits A and B add temporary parameter overrides. `exhibits/scenario-challenge.xzbt`
|
||||
provides short, multi-minute, condition, event, parallel, exclusive and deferred cases.
|
||||
|
||||
Invalid examples: `priority: 101` → `ERR_OUT_OF_BOUNDS`; `{ "at": "1s", "after":
|
||||
"a", "actions": [] }` → `ERR_SCHEMA_VALIDATION`; `after: "missing"` →
|
||||
`ERR_INVALID_REFERENCE`; reciprocal `after` anchors → `ERR_CYCLIC_DEPENDENCY`;
|
||||
`onCancel: [{ "type": "spawn", "target": "visuals.systems.burst" }]` →
|
||||
`ERR_UNSUPPORTED_TARGET`; `onFailure: []` → `ERR_UNKNOWN_FIELD`.
|
||||
|
||||
1. Five-second and multi-minute duration endpoints, including a beat exactly at the endpoint.
|
||||
2. Equal-time order, forward relative references, random delays and lazy finite repeats.
|
||||
3. Weighted branching, excluded branches, persistent mutations and unchanged manual PRNG stream.
|
||||
4. Once, interval, probability, random interval, manual, condition and event triggers.
|
||||
5. Startup-true condition disarming, hold cancellation and false-tick rearming (GC5).
|
||||
6. Deferred deduplication, retained expiry, eligibility/cooldown recheck, and priority ordering (GC5).
|
||||
7. Symmetric global/group exclusivity, parallel starts, higher-priority replacement and capacity.
|
||||
8. Nested event ownership using production visual and audio instances and override counters (GC5).
|
||||
9. Completion, cancellation, critical startup/action failure and hook failure preserve cleanup and prior set (GC5).
|
||||
10. Duration overrides release at owner termination, masked user edits survive, and five-second cleanup is bounded (GC5).
|
||||
11. Ordinary dispatch/depth failures preserve the independent 256-action termination budget (GC5).
|
||||
12. Repeated completion/cancellation/failure cycles return resource and scheduler counters to baseline (GC5).
|
||||
13. Forty-minute Exhibit E runs accelerated through the same fixed-step production tick; equal seeds give equal traces.
|
||||
14. Pause/resume preserves logical time; disposal leaves no owned resources or subscriptions.
|
||||
15. Two real hours on a frozen combined workload, with GC6 environment, frame intervals, resource/memory trend,
|
||||
audio peak/nonfinite measurements and listening observations. Accelerated traces cannot satisfy this gate.
|
||||
|
||||
Reference in New Issue
Block a user