Define explicit scalar input controls

This commit is contained in:
Meta Vens 2026-07-17 05:11:52 +01:00
commit 961a5e0df2
8 changed files with 164 additions and 4 deletions

View file

@ -21,6 +21,14 @@ 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 `scalar.v1` model exposes an explicit finite numeric control rather than a
sampled device axis. `currentValue` is provider-owned state. `minimumValue`,
`maximumValue`, and `stepValue` are optional input constraints, and `unit` is an
optional semantic label. A client submits the chosen value to `payloadKey`; it
does not update `currentValue` optimistically or clamp values beyond the
advertised constraints. Providers may leave the range open when the original
control accepted arbitrary numeric input.
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`

View file

@ -1,8 +1,13 @@
export type InputValue = ({
[k: string]: any;
} & {
model: ("button-hold.v1" | "axis.v1" | "view-direction.v1");
model: ("button-hold.v1" | "axis.v1" | "scalar.v1" | "view-direction.v1");
payloadKey?: string;
currentValue?: number;
minimumValue?: number;
maximumValue?: number;
stepValue?: number;
unit?: string;
/**
* @minItems 3
* @maxItems 3

View file

@ -723,12 +723,29 @@ export declare const eveContractSchemas: {
readonly required: readonly ["model"];
readonly properties: {
readonly model: {
readonly enum: readonly ["button-hold.v1", "axis.v1", "view-direction.v1"];
readonly enum: readonly ["button-hold.v1", "axis.v1", "scalar.v1", "view-direction.v1"];
};
readonly payloadKey: {
readonly type: "string";
readonly minLength: 1;
};
readonly currentValue: {
readonly type: "number";
};
readonly minimumValue: {
readonly type: "number";
};
readonly maximumValue: {
readonly type: "number";
};
readonly stepValue: {
readonly type: "number";
readonly exclusiveMinimum: 0;
};
readonly unit: {
readonly type: "string";
readonly minLength: 1;
};
readonly payloadKeys: {
readonly type: "array";
readonly minItems: 3;
@ -755,6 +772,18 @@ export declare const eveContractSchemas: {
readonly else: {
readonly required: readonly ["payloadKey"];
};
}, {
readonly if: {
readonly properties: {
readonly model: {
readonly const: "scalar.v1";
};
};
readonly required: readonly ["model"];
};
readonly then: {
readonly required: readonly ["currentValue"];
};
}];
readonly additionalProperties: false;
};

View file

@ -874,6 +874,7 @@ export const eveContractSchemas = {
"enum": [
"button-hold.v1",
"axis.v1",
"scalar.v1",
"view-direction.v1"
]
},
@ -881,6 +882,23 @@ export const eveContractSchemas = {
"type": "string",
"minLength": 1
},
"currentValue": {
"type": "number"
},
"minimumValue": {
"type": "number"
},
"maximumValue": {
"type": "number"
},
"stepValue": {
"type": "number",
"exclusiveMinimum": 0
},
"unit": {
"type": "string",
"minLength": 1
},
"payloadKeys": {
"type": "array",
"minItems": 3,
@ -914,6 +932,23 @@ export const eveContractSchemas = {
"payloadKey"
]
}
},
{
"if": {
"properties": {
"model": {
"const": "scalar.v1"
}
},
"required": [
"model"
]
},
"then": {
"required": [
"currentValue"
]
}
}
],
"additionalProperties": false

View file

@ -3,8 +3,13 @@
export type InputValue = ({
[k: string]: any
} & {
model: ("button-hold.v1" | "axis.v1" | "view-direction.v1")
model: ("button-hold.v1" | "axis.v1" | "scalar.v1" | "view-direction.v1")
payloadKey?: string
currentValue?: number
minimumValue?: number
maximumValue?: number
stepValue?: number
unit?: string
/**
* @minItems 3
* @maxItems 3

View file

@ -874,6 +874,7 @@ export const eveContractSchemas = {
"enum": [
"button-hold.v1",
"axis.v1",
"scalar.v1",
"view-direction.v1"
]
},
@ -881,6 +882,23 @@ export const eveContractSchemas = {
"type": "string",
"minLength": 1
},
"currentValue": {
"type": "number"
},
"minimumValue": {
"type": "number"
},
"maximumValue": {
"type": "number"
},
"stepValue": {
"type": "number",
"exclusiveMinimum": 0
},
"unit": {
"type": "string",
"minLength": 1
},
"payloadKeys": {
"type": "array",
"minItems": 3,
@ -914,6 +932,23 @@ export const eveContractSchemas = {
"payloadKey"
]
}
},
{
"if": {
"properties": {
"model": {
"const": "scalar.v1"
}
},
"required": [
"model"
]
},
"then": {
"required": [
"currentValue"
]
}
}
],
"additionalProperties": false

View file

@ -105,6 +105,30 @@ test("validates scalar and view-direction input value models", () => {
inputValue: { model: "button-hold.v1", payloadKey: "active" },
}],
}).actions[0].inputValue.payloadKey, "active");
assert.deepEqual(parseEveInputCapability({
...base,
actions: [{
actionId: "equipment.0.temperature",
label: "Target Temperature",
operation: "equipment.set-temperature",
availability: "available",
inputValue: {
model: "scalar.v1",
payloadKey: "scalarValue",
currentValue: 300,
minimumValue: 0,
stepValue: 1,
unit: "kelvin",
},
}],
}).actions[0].inputValue, {
model: "scalar.v1",
payloadKey: "scalarValue",
currentValue: 300,
minimumValue: 0,
stepValue: 1,
unit: "kelvin",
});
assert.deepEqual(parseEveInputCapability({
...base,
actions: [{
@ -128,6 +152,16 @@ test("validates scalar and view-direction input value models", () => {
inputValue: { model: "view-direction.v1", payloadKey: "directionX" },
}],
}), EveContractValidationError);
assert.throws(() => parseEveInputCapability({
...base,
actions: [{
actionId: "equipment.0.temperature",
label: "Target Temperature",
operation: "equipment.set-temperature",
availability: "available",
inputValue: { model: "scalar.v1", payloadKey: "scalarValue" },
}],
}), EveContractValidationError);
});
test("validates an existing canonical Eve surface fixture", async () => {

View file

@ -48,8 +48,13 @@
"type": "object",
"required": ["model"],
"properties": {
"model": { "enum": ["button-hold.v1", "axis.v1", "view-direction.v1"] },
"model": { "enum": ["button-hold.v1", "axis.v1", "scalar.v1", "view-direction.v1"] },
"payloadKey": { "type": "string", "minLength": 1 },
"currentValue": { "type": "number" },
"minimumValue": { "type": "number" },
"maximumValue": { "type": "number" },
"stepValue": { "type": "number", "exclusiveMinimum": 0 },
"unit": { "type": "string", "minLength": 1 },
"payloadKeys": {
"type": "array",
"minItems": 3,
@ -63,6 +68,10 @@
"if": { "properties": { "model": { "const": "view-direction.v1" } }, "required": ["model"] },
"then": { "required": ["payloadKeys"] },
"else": { "required": ["payloadKey"] }
},
{
"if": { "properties": { "model": { "const": "scalar.v1" } }, "required": ["model"] },
"then": { "required": ["currentValue"] }
}
],
"additionalProperties": false