Define view-direction input values
This commit is contained in:
parent
0c5cfdf101
commit
166d2d4c7b
14 changed files with 831 additions and 3 deletions
|
|
@ -20,3 +20,9 @@ release. The `axis.v1` model writes the current normalized axis value. Clients
|
|||
send changed values through the action's advertised operation; providers own
|
||||
interpretation, persistence, receipts, and simulation. A client must not infer
|
||||
decay or repair a missed release by mutating presented state.
|
||||
|
||||
The `view-direction.v1` model is a one-shot value sampled when its gesture is
|
||||
performed. The client reads the active semantic view's normalized world-space
|
||||
forward direction and writes X, Y, and Z to the three advertised `payloadKeys`
|
||||
in that order. The provider decides what the ray means; clients do not select,
|
||||
filter, or rank gameplay targets.
|
||||
|
|
|
|||
|
|
@ -10,11 +10,13 @@ import {
|
|||
parseEveProviderAdvertisement,
|
||||
parseEveSurfaceDocument,
|
||||
parseEveCommandInvocation,
|
||||
parseEveInputCapability,
|
||||
} from "@gamecult/eve-contracts";
|
||||
|
||||
const provider = parseEveProviderAdvertisement(untrustedAdvertisement);
|
||||
const surface = parseEveSurfaceDocument(untrustedSurface);
|
||||
const command = parseEveCommandInvocation(untrustedCommand);
|
||||
const input = parseEveInputCapability(untrustedInputCapability);
|
||||
```
|
||||
|
||||
Providers own their state, surfaces, command acceptance, and receipts. This
|
||||
|
|
|
|||
51
packages/eve-contracts/dist/generated/input-capability.d.ts
vendored
Normal file
51
packages/eve-contracts/dist/generated/input-capability.d.ts
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
export type InputValue = ({
|
||||
[k: string]: any;
|
||||
} & {
|
||||
model: ("button-hold.v1" | "axis.v1" | "view-direction.v1");
|
||||
payloadKey?: string;
|
||||
/**
|
||||
* @minItems 3
|
||||
* @maxItems 3
|
||||
*/
|
||||
payloadKeys?: [string, string, string];
|
||||
});
|
||||
export interface EveInputCapability {
|
||||
schema: "gamecult.eve.input_capability.v1";
|
||||
providerId: string;
|
||||
capabilityId: string;
|
||||
version?: number;
|
||||
actions: {
|
||||
actionId: string;
|
||||
label: string;
|
||||
operation: string;
|
||||
context?: string;
|
||||
category?: string;
|
||||
iconRef?: string;
|
||||
availability: ("available" | "dormant" | "unavailable");
|
||||
sourceRef?: string;
|
||||
payload?: {
|
||||
[k: string]: string;
|
||||
};
|
||||
inputValue?: InputValue;
|
||||
}[];
|
||||
defaultProfiles: {
|
||||
profileId: string;
|
||||
deviceClass: ("keyboard-mouse" | "gamepad");
|
||||
bindings: Binding[];
|
||||
}[];
|
||||
}
|
||||
export interface Binding {
|
||||
bindingId: string;
|
||||
actionId: string;
|
||||
gesture: Gesture;
|
||||
actionBar?: boolean;
|
||||
}
|
||||
export interface Gesture {
|
||||
kind: ("direct" | "chord" | "sequence" | "axis");
|
||||
/**
|
||||
* @minItems 1
|
||||
*/
|
||||
controls: [string, ...(string)[]];
|
||||
maxStepIntervalMs?: number;
|
||||
completionControl?: string;
|
||||
}
|
||||
1
packages/eve-contracts/dist/generated/input-capability.js
vendored
Normal file
1
packages/eve-contracts/dist/generated/input-capability.js
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
export {};
|
||||
181
packages/eve-contracts/dist/generated/schemas.d.ts
vendored
181
packages/eve-contracts/dist/generated/schemas.d.ts
vendored
|
|
@ -625,5 +625,186 @@ export declare const eveContractSchemas: {
|
|||
};
|
||||
readonly additionalProperties: true;
|
||||
};
|
||||
readonly inputCapability: {
|
||||
readonly $schema: "https://json-schema.org/draft/2020-12/schema";
|
||||
readonly $id: "gamecult.eve.input_capability.v1";
|
||||
readonly title: "Eve Input Capability";
|
||||
readonly type: "object";
|
||||
readonly required: readonly ["schema", "providerId", "capabilityId", "actions", "defaultProfiles"];
|
||||
readonly properties: {
|
||||
readonly schema: {
|
||||
readonly const: "gamecult.eve.input_capability.v1";
|
||||
};
|
||||
readonly providerId: {
|
||||
readonly type: "string";
|
||||
readonly minLength: 1;
|
||||
};
|
||||
readonly capabilityId: {
|
||||
readonly type: "string";
|
||||
readonly minLength: 1;
|
||||
};
|
||||
readonly version: {
|
||||
readonly type: "integer";
|
||||
readonly minimum: 0;
|
||||
};
|
||||
readonly actions: {
|
||||
readonly type: "array";
|
||||
readonly items: {
|
||||
readonly type: "object";
|
||||
readonly required: readonly ["actionId", "label", "operation", "availability"];
|
||||
readonly properties: {
|
||||
readonly actionId: {
|
||||
readonly type: "string";
|
||||
readonly minLength: 1;
|
||||
};
|
||||
readonly label: {
|
||||
readonly type: "string";
|
||||
readonly minLength: 1;
|
||||
};
|
||||
readonly operation: {
|
||||
readonly type: "string";
|
||||
readonly minLength: 1;
|
||||
};
|
||||
readonly context: {
|
||||
readonly type: "string";
|
||||
};
|
||||
readonly category: {
|
||||
readonly type: "string";
|
||||
};
|
||||
readonly iconRef: {
|
||||
readonly type: "string";
|
||||
};
|
||||
readonly availability: {
|
||||
readonly enum: readonly ["available", "dormant", "unavailable"];
|
||||
};
|
||||
readonly sourceRef: {
|
||||
readonly type: "string";
|
||||
};
|
||||
readonly payload: {
|
||||
readonly type: "object";
|
||||
readonly additionalProperties: {
|
||||
readonly type: "string";
|
||||
};
|
||||
};
|
||||
readonly inputValue: {
|
||||
readonly $ref: "#/$defs/inputValue";
|
||||
};
|
||||
};
|
||||
readonly additionalProperties: false;
|
||||
};
|
||||
};
|
||||
readonly defaultProfiles: {
|
||||
readonly type: "array";
|
||||
readonly items: {
|
||||
readonly type: "object";
|
||||
readonly required: readonly ["profileId", "deviceClass", "bindings"];
|
||||
readonly properties: {
|
||||
readonly profileId: {
|
||||
readonly type: "string";
|
||||
readonly minLength: 1;
|
||||
};
|
||||
readonly deviceClass: {
|
||||
readonly enum: readonly ["keyboard-mouse", "gamepad"];
|
||||
};
|
||||
readonly bindings: {
|
||||
readonly type: "array";
|
||||
readonly items: {
|
||||
readonly $ref: "#/$defs/binding";
|
||||
};
|
||||
};
|
||||
};
|
||||
readonly additionalProperties: false;
|
||||
};
|
||||
};
|
||||
};
|
||||
readonly $defs: {
|
||||
readonly inputValue: {
|
||||
readonly type: "object";
|
||||
readonly required: readonly ["model"];
|
||||
readonly properties: {
|
||||
readonly model: {
|
||||
readonly enum: readonly ["button-hold.v1", "axis.v1", "view-direction.v1"];
|
||||
};
|
||||
readonly payloadKey: {
|
||||
readonly type: "string";
|
||||
readonly minLength: 1;
|
||||
};
|
||||
readonly payloadKeys: {
|
||||
readonly type: "array";
|
||||
readonly minItems: 3;
|
||||
readonly maxItems: 3;
|
||||
readonly items: {
|
||||
readonly type: "string";
|
||||
readonly minLength: 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
readonly allOf: readonly [{
|
||||
readonly if: {
|
||||
readonly properties: {
|
||||
readonly model: {
|
||||
readonly const: "view-direction.v1";
|
||||
};
|
||||
};
|
||||
readonly required: readonly ["model"];
|
||||
};
|
||||
readonly then: {
|
||||
readonly required: readonly ["payloadKeys"];
|
||||
};
|
||||
readonly else: {
|
||||
readonly required: readonly ["payloadKey"];
|
||||
};
|
||||
}];
|
||||
readonly additionalProperties: false;
|
||||
};
|
||||
readonly gesture: {
|
||||
readonly type: "object";
|
||||
readonly required: readonly ["kind", "controls"];
|
||||
readonly properties: {
|
||||
readonly kind: {
|
||||
readonly enum: readonly ["direct", "chord", "sequence", "axis"];
|
||||
};
|
||||
readonly controls: {
|
||||
readonly type: "array";
|
||||
readonly minItems: 1;
|
||||
readonly items: {
|
||||
readonly type: "string";
|
||||
readonly minLength: 1;
|
||||
};
|
||||
};
|
||||
readonly maxStepIntervalMs: {
|
||||
readonly type: "integer";
|
||||
readonly minimum: 50;
|
||||
};
|
||||
readonly completionControl: {
|
||||
readonly type: "string";
|
||||
};
|
||||
};
|
||||
readonly additionalProperties: false;
|
||||
};
|
||||
readonly binding: {
|
||||
readonly type: "object";
|
||||
readonly required: readonly ["bindingId", "actionId", "gesture"];
|
||||
readonly properties: {
|
||||
readonly bindingId: {
|
||||
readonly type: "string";
|
||||
readonly minLength: 1;
|
||||
};
|
||||
readonly actionId: {
|
||||
readonly type: "string";
|
||||
readonly minLength: 1;
|
||||
};
|
||||
readonly gesture: {
|
||||
readonly $ref: "#/$defs/gesture";
|
||||
};
|
||||
readonly actionBar: {
|
||||
readonly type: "boolean";
|
||||
};
|
||||
};
|
||||
readonly additionalProperties: false;
|
||||
};
|
||||
};
|
||||
readonly additionalProperties: false;
|
||||
};
|
||||
};
|
||||
export type EveContractSchemaName = keyof typeof eveContractSchemas;
|
||||
|
|
|
|||
229
packages/eve-contracts/dist/generated/schemas.js
vendored
229
packages/eve-contracts/dist/generated/schemas.js
vendored
|
|
@ -748,5 +748,234 @@ export const eveContractSchemas = {
|
|||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"inputCapability": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "gamecult.eve.input_capability.v1",
|
||||
"title": "Eve Input Capability",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"schema",
|
||||
"providerId",
|
||||
"capabilityId",
|
||||
"actions",
|
||||
"defaultProfiles"
|
||||
],
|
||||
"properties": {
|
||||
"schema": {
|
||||
"const": "gamecult.eve.input_capability.v1"
|
||||
},
|
||||
"providerId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"capabilityId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"version": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"actions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"actionId",
|
||||
"label",
|
||||
"operation",
|
||||
"availability"
|
||||
],
|
||||
"properties": {
|
||||
"actionId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"operation": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"context": {
|
||||
"type": "string"
|
||||
},
|
||||
"category": {
|
||||
"type": "string"
|
||||
},
|
||||
"iconRef": {
|
||||
"type": "string"
|
||||
},
|
||||
"availability": {
|
||||
"enum": [
|
||||
"available",
|
||||
"dormant",
|
||||
"unavailable"
|
||||
]
|
||||
},
|
||||
"sourceRef": {
|
||||
"type": "string"
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"inputValue": {
|
||||
"$ref": "#/$defs/inputValue"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"defaultProfiles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"profileId",
|
||||
"deviceClass",
|
||||
"bindings"
|
||||
],
|
||||
"properties": {
|
||||
"profileId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"deviceClass": {
|
||||
"enum": [
|
||||
"keyboard-mouse",
|
||||
"gamepad"
|
||||
]
|
||||
},
|
||||
"bindings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/binding"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"inputValue": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"model"
|
||||
],
|
||||
"properties": {
|
||||
"model": {
|
||||
"enum": [
|
||||
"button-hold.v1",
|
||||
"axis.v1",
|
||||
"view-direction.v1"
|
||||
]
|
||||
},
|
||||
"payloadKey": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"payloadKeys": {
|
||||
"type": "array",
|
||||
"minItems": 3,
|
||||
"maxItems": 3,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"model": {
|
||||
"const": "view-direction.v1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"model"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"payloadKeys"
|
||||
]
|
||||
},
|
||||
"else": {
|
||||
"required": [
|
||||
"payloadKey"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"gesture": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"controls"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"direct",
|
||||
"chord",
|
||||
"sequence",
|
||||
"axis"
|
||||
]
|
||||
},
|
||||
"controls": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"maxStepIntervalMs": {
|
||||
"type": "integer",
|
||||
"minimum": 50
|
||||
},
|
||||
"completionControl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"binding": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"bindingId",
|
||||
"actionId",
|
||||
"gesture"
|
||||
],
|
||||
"properties": {
|
||||
"bindingId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"actionId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"gesture": {
|
||||
"$ref": "#/$defs/gesture"
|
||||
},
|
||||
"actionBar": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
};
|
||||
|
|
|
|||
6
packages/eve-contracts/dist/index.d.ts
vendored
6
packages/eve-contracts/dist/index.d.ts
vendored
|
|
@ -2,12 +2,14 @@ import { type ErrorObject } from "ajv/dist/2020.js";
|
|||
import type { EveCommandDescriptor } from "./generated/command-descriptor.js";
|
||||
import type { EveCommandInvocation } from "./generated/command-invocation.js";
|
||||
import type { EveCommandReceipt } from "./generated/command-receipt.js";
|
||||
import type { EveInputCapability } from "./generated/input-capability.js";
|
||||
import type { EveProviderAdvertisement } from "./generated/provider-advertisement.js";
|
||||
import type { EveSurfaceDocument } from "./generated/surface.js";
|
||||
import { type EveContractSchemaName } from "./generated/schemas.js";
|
||||
export type { EveCommandDescriptor } from "./generated/command-descriptor.js";
|
||||
export type { EveCommandInvocation } from "./generated/command-invocation.js";
|
||||
export type { EveCommandReceipt } from "./generated/command-receipt.js";
|
||||
export type { EveInputCapability } from "./generated/input-capability.js";
|
||||
export type { EveProviderAdvertisement } from "./generated/provider-advertisement.js";
|
||||
export type { EveSurfaceDocument } from "./generated/surface.js";
|
||||
export { eveContractSchemas, type EveContractSchemaName } from "./generated/schemas.js";
|
||||
|
|
@ -16,12 +18,14 @@ export declare const EVE_SURFACE_SCHEMA: "gamecult.eve.surface.v1";
|
|||
export declare const EVE_COMMAND_DESCRIPTOR_SCHEMA: "gamecult.eve.command.v1";
|
||||
export declare const EVE_COMMAND_INVOCATION_SCHEMA: "gamecult.eve.command_invocation.v1";
|
||||
export declare const EVE_COMMAND_RECEIPT_SCHEMA: "gamecult.eve.command_receipt.v1";
|
||||
export declare const EVE_INPUT_CAPABILITY_SCHEMA: "gamecult.eve.input_capability.v1";
|
||||
type ContractTypes = {
|
||||
providerAdvertisement: EveProviderAdvertisement;
|
||||
surface: EveSurfaceDocument;
|
||||
commandDescriptor: EveCommandDescriptor;
|
||||
commandInvocation: EveCommandInvocation;
|
||||
commandReceipt: EveCommandReceipt;
|
||||
inputCapability: EveInputCapability;
|
||||
};
|
||||
export declare class EveContractValidationError extends TypeError {
|
||||
readonly contract: EveContractSchemaName;
|
||||
|
|
@ -35,8 +39,10 @@ export declare const isEveSurfaceDocument: (value: unknown) => value is EveSurfa
|
|||
export declare const isEveCommandDescriptor: (value: unknown) => value is EveCommandDescriptor;
|
||||
export declare const isEveCommandInvocation: (value: unknown) => value is EveCommandInvocation;
|
||||
export declare const isEveCommandReceipt: (value: unknown) => value is EveCommandReceipt;
|
||||
export declare const isEveInputCapability: (value: unknown) => value is EveInputCapability;
|
||||
export declare const parseEveProviderAdvertisement: (value: unknown) => EveProviderAdvertisement;
|
||||
export declare const parseEveSurfaceDocument: (value: unknown) => EveSurfaceDocument;
|
||||
export declare const parseEveCommandDescriptor: (value: unknown) => EveCommandDescriptor;
|
||||
export declare const parseEveCommandInvocation: (value: unknown) => EveCommandInvocation;
|
||||
export declare const parseEveCommandReceipt: (value: unknown) => EveCommandReceipt;
|
||||
export declare const parseEveInputCapability: (value: unknown) => EveInputCapability;
|
||||
|
|
|
|||
3
packages/eve-contracts/dist/index.js
vendored
3
packages/eve-contracts/dist/index.js
vendored
|
|
@ -6,6 +6,7 @@ export const EVE_SURFACE_SCHEMA = "gamecult.eve.surface.v1";
|
|||
export const EVE_COMMAND_DESCRIPTOR_SCHEMA = "gamecult.eve.command.v1";
|
||||
export const EVE_COMMAND_INVOCATION_SCHEMA = "gamecult.eve.command_invocation.v1";
|
||||
export const EVE_COMMAND_RECEIPT_SCHEMA = "gamecult.eve.command_receipt.v1";
|
||||
export const EVE_INPUT_CAPABILITY_SCHEMA = "gamecult.eve.input_capability.v1";
|
||||
const ajv = new Ajv2020({
|
||||
allErrors: true,
|
||||
allowUnionTypes: true,
|
||||
|
|
@ -40,11 +41,13 @@ export const isEveSurfaceDocument = (value) => isEveContract("surface", value);
|
|||
export const isEveCommandDescriptor = (value) => isEveContract("commandDescriptor", value);
|
||||
export const isEveCommandInvocation = (value) => isEveContract("commandInvocation", value);
|
||||
export const isEveCommandReceipt = (value) => isEveContract("commandReceipt", value);
|
||||
export const isEveInputCapability = (value) => isEveContract("inputCapability", value);
|
||||
export const parseEveProviderAdvertisement = (value) => parseEveContract("providerAdvertisement", value);
|
||||
export const parseEveSurfaceDocument = (value) => parseEveContract("surface", value);
|
||||
export const parseEveCommandDescriptor = (value) => parseEveContract("commandDescriptor", value);
|
||||
export const parseEveCommandInvocation = (value) => parseEveContract("commandInvocation", value);
|
||||
export const parseEveCommandReceipt = (value) => parseEveContract("commandReceipt", value);
|
||||
export const parseEveInputCapability = (value) => parseEveContract("inputCapability", value);
|
||||
function requiredValidator(schemaId) {
|
||||
const validator = ajv.getSchema(schemaId);
|
||||
if (!validator)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ const contracts = [
|
|||
["commandDescriptor", "EveCommandDescriptor", "command-descriptor.ts", "gamecult.eve.command.v1.schema.json"],
|
||||
["commandInvocation", "EveCommandInvocation", "command-invocation.ts", "gamecult.eve.command_invocation.v1.schema.json"],
|
||||
["commandReceipt", "EveCommandReceipt", "command-receipt.ts", "gamecult.eve.command_receipt.v1.schema.json"],
|
||||
["inputCapability", "EveInputCapability", "input-capability.ts", "gamecult.eve.input_capability.v1.schema.json"],
|
||||
];
|
||||
|
||||
const loaded = [];
|
||||
|
|
|
|||
54
packages/eve-contracts/src/generated/input-capability.ts
Normal file
54
packages/eve-contracts/src/generated/input-capability.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// Generated by scripts/generate-contracts.mjs. Do not edit by hand.
|
||||
// Source: schemas/gamecult.eve.input_capability.v1.schema.json
|
||||
export type InputValue = ({
|
||||
[k: string]: any
|
||||
} & {
|
||||
model: ("button-hold.v1" | "axis.v1" | "view-direction.v1")
|
||||
payloadKey?: string
|
||||
/**
|
||||
* @minItems 3
|
||||
* @maxItems 3
|
||||
*/
|
||||
payloadKeys?: [string, string, string]
|
||||
})
|
||||
|
||||
export interface EveInputCapability {
|
||||
schema: "gamecult.eve.input_capability.v1"
|
||||
providerId: string
|
||||
capabilityId: string
|
||||
version?: number
|
||||
actions: {
|
||||
actionId: string
|
||||
label: string
|
||||
operation: string
|
||||
context?: string
|
||||
category?: string
|
||||
iconRef?: string
|
||||
availability: ("available" | "dormant" | "unavailable")
|
||||
sourceRef?: string
|
||||
payload?: {
|
||||
[k: string]: string
|
||||
}
|
||||
inputValue?: InputValue
|
||||
}[]
|
||||
defaultProfiles: {
|
||||
profileId: string
|
||||
deviceClass: ("keyboard-mouse" | "gamepad")
|
||||
bindings: Binding[]
|
||||
}[]
|
||||
}
|
||||
export interface Binding {
|
||||
bindingId: string
|
||||
actionId: string
|
||||
gesture: Gesture
|
||||
actionBar?: boolean
|
||||
}
|
||||
export interface Gesture {
|
||||
kind: ("direct" | "chord" | "sequence" | "axis")
|
||||
/**
|
||||
* @minItems 1
|
||||
*/
|
||||
controls: [string, ...(string)[]]
|
||||
maxStepIntervalMs?: number
|
||||
completionControl?: string
|
||||
}
|
||||
|
|
@ -748,6 +748,235 @@ export const eveContractSchemas = {
|
|||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"inputCapability": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "gamecult.eve.input_capability.v1",
|
||||
"title": "Eve Input Capability",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"schema",
|
||||
"providerId",
|
||||
"capabilityId",
|
||||
"actions",
|
||||
"defaultProfiles"
|
||||
],
|
||||
"properties": {
|
||||
"schema": {
|
||||
"const": "gamecult.eve.input_capability.v1"
|
||||
},
|
||||
"providerId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"capabilityId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"version": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"actions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"actionId",
|
||||
"label",
|
||||
"operation",
|
||||
"availability"
|
||||
],
|
||||
"properties": {
|
||||
"actionId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"operation": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"context": {
|
||||
"type": "string"
|
||||
},
|
||||
"category": {
|
||||
"type": "string"
|
||||
},
|
||||
"iconRef": {
|
||||
"type": "string"
|
||||
},
|
||||
"availability": {
|
||||
"enum": [
|
||||
"available",
|
||||
"dormant",
|
||||
"unavailable"
|
||||
]
|
||||
},
|
||||
"sourceRef": {
|
||||
"type": "string"
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"inputValue": {
|
||||
"$ref": "#/$defs/inputValue"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"defaultProfiles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"profileId",
|
||||
"deviceClass",
|
||||
"bindings"
|
||||
],
|
||||
"properties": {
|
||||
"profileId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"deviceClass": {
|
||||
"enum": [
|
||||
"keyboard-mouse",
|
||||
"gamepad"
|
||||
]
|
||||
},
|
||||
"bindings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/binding"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"inputValue": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"model"
|
||||
],
|
||||
"properties": {
|
||||
"model": {
|
||||
"enum": [
|
||||
"button-hold.v1",
|
||||
"axis.v1",
|
||||
"view-direction.v1"
|
||||
]
|
||||
},
|
||||
"payloadKey": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"payloadKeys": {
|
||||
"type": "array",
|
||||
"minItems": 3,
|
||||
"maxItems": 3,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"model": {
|
||||
"const": "view-direction.v1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"model"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"payloadKeys"
|
||||
]
|
||||
},
|
||||
"else": {
|
||||
"required": [
|
||||
"payloadKey"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"gesture": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"controls"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"direct",
|
||||
"chord",
|
||||
"sequence",
|
||||
"axis"
|
||||
]
|
||||
},
|
||||
"controls": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"maxStepIntervalMs": {
|
||||
"type": "integer",
|
||||
"minimum": 50
|
||||
},
|
||||
"completionControl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"binding": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"bindingId",
|
||||
"actionId",
|
||||
"gesture"
|
||||
],
|
||||
"properties": {
|
||||
"bindingId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"actionId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"gesture": {
|
||||
"$ref": "#/$defs/gesture"
|
||||
},
|
||||
"actionBar": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
} as const;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import Ajv2020, { type ErrorObject, type ValidateFunction } from "ajv/dist/2020.
|
|||
import type { EveCommandDescriptor } from "./generated/command-descriptor.js";
|
||||
import type { EveCommandInvocation } from "./generated/command-invocation.js";
|
||||
import type { EveCommandReceipt } from "./generated/command-receipt.js";
|
||||
import type { EveInputCapability } from "./generated/input-capability.js";
|
||||
import type { EveProviderAdvertisement } from "./generated/provider-advertisement.js";
|
||||
import type { EveSurfaceDocument } from "./generated/surface.js";
|
||||
import { eveContractSchemas, type EveContractSchemaName } from "./generated/schemas.js";
|
||||
|
|
@ -9,6 +10,7 @@ import { eveContractSchemas, type EveContractSchemaName } from "./generated/sche
|
|||
export type { EveCommandDescriptor } from "./generated/command-descriptor.js";
|
||||
export type { EveCommandInvocation } from "./generated/command-invocation.js";
|
||||
export type { EveCommandReceipt } from "./generated/command-receipt.js";
|
||||
export type { EveInputCapability } from "./generated/input-capability.js";
|
||||
export type { EveProviderAdvertisement } from "./generated/provider-advertisement.js";
|
||||
export type { EveSurfaceDocument } from "./generated/surface.js";
|
||||
export { eveContractSchemas, type EveContractSchemaName } from "./generated/schemas.js";
|
||||
|
|
@ -18,6 +20,7 @@ export const EVE_SURFACE_SCHEMA = "gamecult.eve.surface.v1" as const;
|
|||
export const EVE_COMMAND_DESCRIPTOR_SCHEMA = "gamecult.eve.command.v1" as const;
|
||||
export const EVE_COMMAND_INVOCATION_SCHEMA = "gamecult.eve.command_invocation.v1" as const;
|
||||
export const EVE_COMMAND_RECEIPT_SCHEMA = "gamecult.eve.command_receipt.v1" as const;
|
||||
export const EVE_INPUT_CAPABILITY_SCHEMA = "gamecult.eve.input_capability.v1" as const;
|
||||
|
||||
type ContractTypes = {
|
||||
providerAdvertisement: EveProviderAdvertisement;
|
||||
|
|
@ -25,6 +28,7 @@ type ContractTypes = {
|
|||
commandDescriptor: EveCommandDescriptor;
|
||||
commandInvocation: EveCommandInvocation;
|
||||
commandReceipt: EveCommandReceipt;
|
||||
inputCapability: EveInputCapability;
|
||||
};
|
||||
|
||||
const ajv = new Ajv2020({
|
||||
|
|
@ -78,6 +82,8 @@ export const isEveCommandInvocation = (value: unknown): value is EveCommandInvoc
|
|||
isEveContract("commandInvocation", value);
|
||||
export const isEveCommandReceipt = (value: unknown): value is EveCommandReceipt =>
|
||||
isEveContract("commandReceipt", value);
|
||||
export const isEveInputCapability = (value: unknown): value is EveInputCapability =>
|
||||
isEveContract("inputCapability", value);
|
||||
|
||||
export const parseEveProviderAdvertisement = (value: unknown): EveProviderAdvertisement =>
|
||||
parseEveContract("providerAdvertisement", value);
|
||||
|
|
@ -89,6 +95,8 @@ export const parseEveCommandInvocation = (value: unknown): EveCommandInvocation
|
|||
parseEveContract("commandInvocation", value);
|
||||
export const parseEveCommandReceipt = (value: unknown): EveCommandReceipt =>
|
||||
parseEveContract("commandReceipt", value);
|
||||
export const parseEveInputCapability = (value: unknown): EveInputCapability =>
|
||||
parseEveContract("inputCapability", value);
|
||||
|
||||
function requiredValidator(schemaId: string): ValidateFunction {
|
||||
const validator = ajv.getSchema(schemaId);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
isEveCommandDescriptor,
|
||||
parseEveCommandInvocation,
|
||||
parseEveCommandReceipt,
|
||||
parseEveInputCapability,
|
||||
parseEveProviderAdvertisement,
|
||||
parseEveSurfaceDocument,
|
||||
} from "../dist/index.js";
|
||||
|
|
@ -86,6 +87,49 @@ test("published schema constants retain their authoritative ids", async () => {
|
|||
assert.equal(schemas.$id, "gamecult.eve.surface.v1");
|
||||
});
|
||||
|
||||
test("validates scalar and view-direction input value models", () => {
|
||||
const base = {
|
||||
schema: "gamecult.eve.input_capability.v1",
|
||||
providerId: "flight.provider",
|
||||
capabilityId: "pilot.input",
|
||||
version: 1,
|
||||
defaultProfiles: [],
|
||||
};
|
||||
assert.equal(parseEveInputCapability({
|
||||
...base,
|
||||
actions: [{
|
||||
actionId: "pilot.fire",
|
||||
label: "Fire",
|
||||
operation: "pilot.fire",
|
||||
availability: "available",
|
||||
inputValue: { model: "button-hold.v1", payloadKey: "active" },
|
||||
}],
|
||||
}).actions[0].inputValue.payloadKey, "active");
|
||||
assert.deepEqual(parseEveInputCapability({
|
||||
...base,
|
||||
actions: [{
|
||||
actionId: "pilot.target-reticle",
|
||||
label: "Target Reticle",
|
||||
operation: "pilot.target-reticle",
|
||||
availability: "available",
|
||||
inputValue: {
|
||||
model: "view-direction.v1",
|
||||
payloadKeys: ["directionX", "directionY", "directionZ"],
|
||||
},
|
||||
}],
|
||||
}).actions[0].inputValue.payloadKeys, ["directionX", "directionY", "directionZ"]);
|
||||
assert.throws(() => parseEveInputCapability({
|
||||
...base,
|
||||
actions: [{
|
||||
actionId: "pilot.target-reticle",
|
||||
label: "Target Reticle",
|
||||
operation: "pilot.target-reticle",
|
||||
availability: "available",
|
||||
inputValue: { model: "view-direction.v1", payloadKey: "directionX" },
|
||||
}],
|
||||
}), EveContractValidationError);
|
||||
});
|
||||
|
||||
test("validates an existing canonical Eve surface fixture", async () => {
|
||||
const fixturePath = resolve(
|
||||
import.meta.dirname,
|
||||
|
|
|
|||
|
|
@ -46,11 +46,24 @@
|
|||
"$defs": {
|
||||
"inputValue": {
|
||||
"type": "object",
|
||||
"required": ["model", "payloadKey"],
|
||||
"required": ["model"],
|
||||
"properties": {
|
||||
"model": { "enum": ["button-hold.v1", "axis.v1"] },
|
||||
"payloadKey": { "type": "string", "minLength": 1 }
|
||||
"model": { "enum": ["button-hold.v1", "axis.v1", "view-direction.v1"] },
|
||||
"payloadKey": { "type": "string", "minLength": 1 },
|
||||
"payloadKeys": {
|
||||
"type": "array",
|
||||
"minItems": 3,
|
||||
"maxItems": 3,
|
||||
"items": { "type": "string", "minLength": 1 }
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": { "properties": { "model": { "const": "view-direction.v1" } }, "required": ["model"] },
|
||||
"then": { "required": ["payloadKeys"] },
|
||||
"else": { "required": ["payloadKey"] }
|
||||
}
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"gesture": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue