add generic stateful input streaming
All checks were successful
build / image (push) Successful in 50s

This commit is contained in:
Syncer Deploy
2026-08-28 11:27:53 -03:00
parent 0bf2b95755
commit 96b73922cc
15 changed files with 767 additions and 81 deletions

View File

@@ -1,6 +1,7 @@
import {
createJsonCodec,
defineMultiplayerGame,
withInputStream,
} from "@syncer/engine";
import type {
FluxAuthorityEvent,
@@ -36,7 +37,7 @@ const perceptionCodec = createJsonCodec<FluxPerception>();
* a team tug-of-war: hold thrust, manage private energy, and pull the shared
* core through your team's gate.
*/
export const fluxGame = defineMultiplayerGame<FluxGameContract>({
const baseFluxGame = defineMultiplayerGame<FluxGameContract>({
clock: {
ticksPerSecond: FLUX_TICK_RATE,
snapshotsPerSecond: FLUX_SNAPSHOT_RATE,
@@ -220,6 +221,13 @@ export const fluxGame = defineMultiplayerGame<FluxGameContract>({
},
});
export const fluxGame = withInputStream(baseFluxGame, {
heartbeatRateHz: 20,
timeoutMs: 400,
inputsEqual: (left, right) => left.thrust === right.thrust,
neutralize: () => ({ thrust: false }),
});
function createAuthorityState(): FluxAuthorityState {
return {
core: 0,

View File

@@ -1,6 +1,7 @@
import {
createJsonCodec,
defineMultiplayerGame,
withInputStream,
withSpatialReplication,
} from "@syncer/engine";
import {
@@ -290,7 +291,36 @@ const baseRoyaleGame = defineMultiplayerGame<RoyaleGameContract>({
},
});
export const royaleGame = withSpatialReplication(baseRoyaleGame, {
const streamedRoyaleGame = withInputStream(baseRoyaleGame, {
heartbeatRateHz: 20,
timeoutMs: 400,
inputsEqual(left, right) {
return (
left.forward === right.forward &&
left.strafe === right.strafe &&
left.yaw === right.yaw &&
left.pitch === right.pitch &&
left.fire === right.fire &&
left.sprint === right.sprint &&
left.reload === right.reload
);
},
neutralize(lastInput) {
return {
...lastInput,
forward: 0,
strafe: 0,
fire: false,
sprint: false,
reload: false,
};
},
resume(lastInput) {
return { ...lastInput, reload: false };
},
});
export const royaleGame = withSpatialReplication(streamedRoyaleGame, {
cellSize: ROYALE_CHUNK_SIZE,
bandwidthBudgetBytesPerSecond: 30_000,
reservedBytesPerSnapshot: 300,

View File

@@ -1,6 +1,7 @@
import {
defineNetworkedGame,
deterministicHash,
withInputStream,
withLagCompensation,
withReplayTransport,
withTimeTravel,
@@ -242,7 +243,41 @@ const shooterDefinition = defineNetworkedGame<
},
});
const lagCompensatedShooterDefinition = withLagCompensation(shooterDefinition, {
const streamedShooterDefinition = withInputStream(shooterDefinition, {
heartbeatRateHz: 20,
timeoutMs: 400,
inputsEqual(left, right) {
return (
left.strafe === right.strafe &&
left.forward === right.forward &&
left.yaw === right.yaw &&
left.pitch === right.pitch &&
left.fire === right.fire &&
left.sprint === right.sprint &&
left.reload === right.reload &&
left.weapon === right.weapon
);
},
neutralize(lastInput) {
return {
...lastInput,
strafe: 0,
forward: 0,
fire: false,
sprint: false,
reload: false,
};
},
resume(lastInput) {
return {
...lastInput,
fire: lastInput.fire && WEAPONS[lastInput.weapon].automatic,
reload: false,
};
},
});
const lagCompensatedShooterDefinition = withLagCompensation(streamedShooterDefinition, {
historySeconds: 0.5,
captureState(state) {
return {

View File

@@ -22,12 +22,16 @@ test("Flux Relay is a complete game defined through the public authoring API", (
const server = fluxGame.createServer();
server.addPlayer(1);
assert.deepEqual(server.submitInput(1, createThrustPacket(1, 1)), {
const thrustPacket = createThrustPacket(1, 1);
assert.deepEqual(server.submitInput(1, thrustPacket), {
accepted: true,
});
const events = [];
for (let tick = 0; tick < 180; tick += 1) {
if (tick > 0 && tick % 6 === 0) {
assert.deepEqual(server.submitInput(1, thrustPacket), { accepted: true });
}
events.push(...server.step().events);
}