Define view-direction input values

This commit is contained in:
Meta Vens 2026-07-17 04:46:49 +01:00
commit 166d2d4c7b
14 changed files with 831 additions and 3 deletions

View file

@ -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.

View file

@ -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

View 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;
}

View file

@ -0,0 +1 @@
export {};

View file

@ -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;

View file

@ -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
}
};

View file

@ -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;

View file

@ -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)

View file

@ -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 = [];

View 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
}

View file

@ -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;

View file

@ -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);

View file

@ -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,

View file

@ -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": {