Cómo funciona
- Tu mod notifica un momento. Basta una llamada, como
event("bossKill"). El cliente lo envía en segundo plano sin bloquear el juego. - Hovermate lo marca. Cada evento se convierte en un marcador de la grabación y la sesión con tu etiqueta.
- El jugador elige los momentos destacados. Tu juego obtiene su propia ficha en Clips. Para cada tipo activado por el jugador también se guarda un clip.
Mientras el SDK está activo, Hovermate publica su dirección, un token y su carpeta de entrada en connection.json:
{
"version": 1,
"url": "http://local.hovermate.gg/hovermate/v1/events",
"token": "<random, base64url, 32 bytes>",
"inbox": "C:\\Users\\<user>\\AppData\\Local\\Hovermate\\Sdk\\Inbox"
}Lee url de este archivo y úsalo exactamente como está escrito; nunca fijes en el código ni cambies su host, puerto o ruta.
Los clientes leen %LOCALAPPDATA%\Hovermate\Sdk\connection.json, o la ruta de HOVERMATE_SDK_CONNECTION para pruebas y configuraciones aisladas. Vuelven a leerlo tras una respuesta 401, porque el token cambia al desactivar y activar el SDK. Si el archivo no existe, Hovermate no está instalado o el SDK está desactivado; los clientes permanecen en silencio.
Tres formas de enviar eventos
Usa la primera que permita el entorno de tu mod.
HTTP
Tu mod puede abrir una conexión de red local.
Envía el evento por POST a la URL con Authorization: Bearer <token>. Las solicitudes con cabecera Origin se rechazan, por lo que las páginas web no pueden enviar eventos.
C#, C++, Java, Python, JavaScript; GDScript puede usar HTTPRequest.
Archivos
Tu mod puede escribir archivos, pero no acceder a la red.
Añade un evento JSON por línea a {inbox}\<game.id>.jsonl; no hace falta token. Un entorno limitado a la carpeta del mod, como Cyber Engine Tweaks, escribe allí en hovermate-events.jsonl; el jugador añade ese archivo a la ficha del juego.
Todos los clientes recurren a la carpeta de entrada; el cliente Lua escribe en la carpeta del mod.
Líneas de registro
Tus scripts solo pueden escribir en el registro del propio juego.
Escribe [HOVERMATE] seguido de un objeto de evento JSON; se ignora todo lo anterior al marcador. scriptslog.txt de The Witcher 3 y Papyrus.0.log de Skyrim y Fallout 4 se leen automáticamente mientras el juego está abierto.
Scripts de Witcher 3, Papyrus o cualquier juego con registro de scripts.
Una solicitud HTTP
POST /hovermate/v1/events HTTP/1.1
Host: local.hovermate.gg
Authorization: Bearer <token from connection.json>
Content-Type: application/json
Content-Length: 95
{"v":1,"game":{"id":"com.example.mygame","name":"My Game"},"type":"kill","label":"Double kill"}Respuesta: 202 {"accepted":1}.
Una línea de registro
[12:00:01] [Script] [HOVERMATE] {"v":1,"game":{"id":"com.example.mygame","name":"My Game"},"type":"bossKill","context":"Act 2"}Para otro registro, el jugador lo añade en Clips › ficha del juego › Leer eventos de un archivo; también puede proponerlo tu hello.
Inicio rápido
Cada ejemplo envía hello, kill, bossKill y un evento personalizado parry. Son los archivos ejecutables de la carpeta examples del SDK.
C#
- Cliente
- HovermateEvents.cs
- Entorno
- netstandard2.0, .NET Framework 4.7.2+ y .NET, con C# 7.3: Unity, BepInEx, MelonLoader.
- Configuración
- Añade HovermateEvents.cs al proyecto de tu mod.
// Sends hello, kill, bossKill and a custom "parry" event to Hovermate (or to tools/mock-server.py).
// Run: dotnet run
using System;
using Hovermate.Sdk;
HovermateEvents.Log = Console.WriteLine;
HovermateEvents.Init("com.example.sdkdemo", "SDK Demo", "SdkDemo.exe");
HovermateEvents.Hello(new HovermateOffer[] { "kill", "bossKill", HovermateOffer.Custom("parry", "Parry") });
HovermateEvents.Event("kill", label: "Double kill", tags: new[] { "headshot" }, context: "Act 2");
HovermateEvents.Event("bossKill", label: "The Warden", context: "Act 2");
HovermateEvents.Custom("parry", "Parry", label: "Perfect parry");
Console.WriteLine(HovermateEvents.Flush(2000) ? "sent" : "timed out");
HovermateEvents.Shutdown();Ejecuta en examples/csharp (el ejemplo requiere .NET 8 o posterior):
dotnet runC++
- Cliente
- hovermate_events.h
- Entorno
- C++17 en Windows. Solo cabecera; usa WinHTTP y enlaza winhttp.lib.
- Configuración
- Incluye hovermate_events.h. Llama a hovermate::shutdown() antes de descargar tu DLL, nunca dentro de DllMain.
// Sends hello, kill, bossKill and a custom "parry" event to Hovermate (or to tools/mock-server.py).
// Build (Developer Command Prompt): cl /std:c++17 /EHsc /utf-8 /I..\..\cpp example.cpp
#include <cstdio>
#include "hovermate_events.h"
int main() {
hovermate::set_log([](const std::string& message) { std::printf("%s\n", message.c_str()); });
hovermate::init("com.example.sdkdemo", "SDK Demo", "SdkDemo.exe");
hovermate::hello({ "kill", "bossKill", hovermate::Offer::custom("parry", "Parry") });
hovermate::event("kill", { "Double kill", { "headshot" }, "Act 2" });
hovermate::event("bossKill", { "The Warden", {}, "Act 2" });
hovermate::custom("parry", "Parry", { "Perfect parry", {}, {} });
std::printf("%s\n", hovermate::flush(2000) ? "sent" : "timed out");
hovermate::shutdown();
return 0;
}Ejecuta en un símbolo del sistema para desarrolladores, desde examples/cpp:
cl /std:c++17 /EHsc /utf-8 /I..\..\cpp example.cppJava
- Cliente
- HovermateEvents.java
- Entorno
- Java 8 o posterior: ModTheSpire, Forge, Fabric.
- Configuración
- Copia HovermateEvents.java a tu mod y cambia su paquete, o reubícalo durante el shading, para evitar conflictos con copias de otros mods.
// Sends hello, kill, bossKill and a custom "parry" event to Hovermate (or to tools/mock-server.py).
// Run (Java 8+): javac -d out ../../java/HovermateEvents.java Example.java && java -cp out Example
import gg.hovermate.sdk.HovermateEvents;
import gg.hovermate.sdk.HovermateEvents.Offer;
import java.util.Arrays;
public class Example {
public static void main(String[] args) {
HovermateEvents.log = System.out::println;
HovermateEvents.init("com.example.sdkdemo", "SDK Demo", "SdkDemo.exe");
HovermateEvents.hello(Offer.of("kill"), Offer.of("bossKill"), Offer.custom("parry", "Parry"));
HovermateEvents.event("kill", "Double kill", Arrays.asList("headshot"), "Act 2");
HovermateEvents.event("bossKill", "The Warden", null, "Act 2");
HovermateEvents.custom("parry", "Parry", "Perfect parry", null, null);
System.out.println(HovermateEvents.flush(2000) ? "sent" : "timed out");
HovermateEvents.shutdown(1000);
}
}Ejecuta desde examples/java:
javac -d out ../../java/HovermateEvents.java Example.java && java -cp out ExamplePython
- Cliente
- hovermate_events.py
- Entorno
- Python 3.7 o posterior, solo biblioteca estándar.
- Configuración
- Coloca hovermate_events.py junto a tu script.
"""Sends hello, kill, bossKill and a custom "parry" event to Hovermate (or to tools/mock-server.py).
Run from any folder: python example.py
"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'python'))
import hovermate_events as hovermate # noqa: E402
hovermate.set_log(print)
hovermate.init('com.example.sdkdemo', 'SDK Demo', 'SdkDemo.exe')
hovermate.hello(['kill', 'bossKill', hovermate.custom_offer('parry', 'Parry')])
hovermate.event('kill', label='Double kill', tags=['headshot'], context='Act 2')
hovermate.event('bossKill', label='The Warden', context='Act 2')
hovermate.custom('parry', 'Parry', label='Perfect parry')
print('sent' if hovermate.flush(2.0) else 'timed out')
hovermate.shutdown()Ejecuta desde examples/python:
python example.pyJavaScript
- Cliente
- hovermate-events.mjs
- Entorno
- Node.js 18 o posterior, como módulo ES.
- Configuración
- Importa hovermate-events.mjs.
// Sends hello, kill, bossKill and a custom "parry" event to Hovermate (or to tools/mock-server.py).
// Run with Node.js 18+: node example.mjs
import * as hovermate from '../../js/hovermate-events.mjs';
hovermate.setLog(console.log);
hovermate.init('com.example.sdkdemo', 'SDK Demo', 'SdkDemo.exe');
hovermate.hello(['kill', 'bossKill', hovermate.customOffer('parry', 'Parry')]);
hovermate.event('kill', { label: 'Double kill', tags: ['headshot'], context: 'Act 2' });
hovermate.event('bossKill', { label: 'The Warden', context: 'Act 2' });
hovermate.custom('parry', 'Parry', { label: 'Perfect parry' });
console.log((await hovermate.flush(2000)) ? 'sent' : 'timed out');
await hovermate.shutdown();Ejecuta desde examples/js:
node example.mjsLua
- Cliente
- hovermate_events.lua
- Entorno
- Lua 5.1+ y LuaJIT: Cyber Engine Tweaks y otros entornos sin red.
- Configuración
- Coloca hovermate_events.lua junto a init.lua. Los eventos se guardan en hovermate-events.jsonl dentro de la carpeta del mod: indica a los jugadores que añadan ese archivo a Hovermate.
-- Sends hello, kill, bossKill and a custom "parry" event to Hovermate (or to tools/mock-server.py --watch).
-- Run from the folder that stands in for your mod folder: lua path/to/example.lua
-- It writes hovermate-events.jsonl into the current folder.
--
-- In a Cyber Engine Tweaks mod, copy hovermate_events.lua next to init.lua and call it from your events:
-- local hovermate = require("hovermate_events")
-- registerForEvent("onInit", function()
-- hovermate.init("com.example.mymod", "My Mod", "Cyberpunk2077.exe", { log = print })
-- hovermate.hello({ "kill", "bossKill", hovermate.customOffer("parry", "Parry") })
-- end)
-- Then tell your users to add <game>\bin\x64\plugins\cyber_engine_tweaks\mods\<your mod>\hovermate-events.jsonl
-- in Hovermate: Clips > the game's card > Read events from a file.
local here = (arg and arg[0] or ''):match('^(.*[\\/])') or './'
package.path = here .. '../../lua/?.lua;' .. package.path
local hovermate = require('hovermate_events')
hovermate.init('com.example.sdkdemo', 'SDK Demo', 'SdkDemo.exe', { log = print, writer = print })
print('transport', hovermate.transport())
hovermate.hello({ 'kill', 'bossKill', hovermate.customOffer('parry', 'Parry') })
hovermate.event('kill', { label = 'Double kill', tags = { 'headshot' }, context = 'Act 2' })
hovermate.event('bossKill', { label = 'The Warden', context = 'Act 2' })
hovermate.custom('parry', 'Parry', { label = 'Perfect parry' })
print('log line for script-only mods:', hovermate.logLine('kill', { label = 'Double kill' }))
hovermate.shutdown()Ejecuta en la carpeta que representa la de tu mod:
lua example.luaComportamiento de los clientes
| Llamada | Función |
|---|---|
init(gameId, gameName, exe?) | Inicia el cliente. gameId coincide con [a-z0-9._-]{3,64} y permanece estable entre versiones del mod; exe es el nombre del ejecutable del juego. |
hello(offers, log?, files?) | Anuncia los tipos de eventos disponibles y, opcionalmente, un registro o archivos del mod que leer. |
event(type, {label, tags, context}) | Envía un tipo de evento estándar. |
custom(customType, customLabel, {…}) | Envía un tipo propio; customLabel da nombre a su interruptor de momentos destacados. |
flush() / shutdown() | Espera los eventos en cola y detiene el proceso en segundo plano al descargar el mod. |
- Las llamadas regresan al instante y nunca propagan excepciones al juego.
- Un proceso en segundo plano envía eventos con un tiempo de espera de 250 ms y reintenta una vez las solicitudes fallidas.
- La cola admite 256 eventos y descarta el más antiguo.
- Si HTTP no está disponible, los eventos se envían a la carpeta de entrada durante unos segundos.
- Sin
connection.json, el cliente permanece en silencio y comprueba de nuevo cada 5 segundos. - Cada evento incluye
game.name. El texto se recorta a los límites indicados abajo y las etiquetas no válidas se descartan sin provocar un fallo.
El cliente Lua (hovermate_events.lua) escribe cada evento al instante. Prefiere hovermate-events.jsonl en la carpeta del mod, después la carpeta de entrada si se permiten rutas absolutas y, por último, líneas [HOVERMATE] mediante una función writer que proporcionas, como print. logLine(type, options) devuelve una de esas líneas para que la imprimas.
El objeto de evento
{ "v": 1,
"game": { "id": "com.example.mygame", "name": "My Game", "exe": "MyGame.exe" },
"type": "kill",
"label": "Double kill",
"tags": ["headshot"],
"context": "Act 2 · The City",
"time": "2026-11-09T21:15:03.120Z" }| Campo | Obligatorio | Regla |
|---|---|---|
v | sí | 1 |
game.id | sí | [a-z0-9._-]{3,64}: nombre DNS inverso o identificador corto, estable entre versiones del mod |
game.name | primer evento | En el primer evento o hello de la sesión; máximo 64 caracteres |
game.exe | no | Solo un nombre de archivo. Vincula los eventos a la carpeta del juego detectado y permite a ese ejecutable iniciar el búfer de repetición |
type | sí | Un tipo estándar de abajo o custom |
customType, customLabel | para custom | [a-z0-9_.-]{1,32} y un nombre de interruptor de hasta 40 caracteres |
label | no | Texto de este evento para la ayuda del marcador y el título del clip; máximo 80 caracteres |
tags | no | Hasta 8 etiquetas, cada una conforme a [a-z0-9_-]{1,24} |
context | no | Mapa, nivel o zona para títulos de momentos destacados; máximo 64 caracteres |
time | no | ISO-8601 UTC. Hovermate registra la llegada; un time a menos de 5 s precisa esa marca |
Las longitudes cuentan unidades UTF-16: un emoji ocupa 2. Los campos de texto rechazan caracteres de control. Los campos desconocidos se ignoran y las nuevas versiones solo añaden campos. No envíes datos personales: las etiquetas y el contexto pueden aparecer en títulos de clips y nombres de archivos.
Tipos estándar
29 tipos compartidos con las fichas integradas de Hovermate. La lista solo crece; los tipos estándar desconocidos se rechazan.
killdeathassistmultikillroundWonroundLostmatchWonmatchLostmvpgoalsaveobjectivemissionlapbestLappositioncrashincidentpenaltypitfinishraceWonlandingtakeofffightWonlevelUpbossKillzoneachievement
Tipos personalizados
Para momentos fuera de la lista estándar. Cada tipo tiene su propio interruptor, cuyo nombre indica customLabel; hasta 200 por juego.
{ "v": 1, "game": { "id": "com.example.mygame" },
"type": "custom", "customType": "parry", "customLabel": "Parry", "label": "Perfect parry" }hello
Opcional y recomendado. Crea la ficha del juego y sus interruptores antes del primer evento real para que el jugador pueda configurarla de antemano.
{ "v": 1,
"game": { "id": "com.example.mygame", "name": "My Game", "exe": "MyGame.exe" },
"type": "hello",
"offers": ["kill", "death", "bossKill", { "customType": "parry", "customLabel": "Parry" }],
"log": "%LOCALAPPDATA%\\MyGame\\Saved\\Logs\\MyGame.log",
"files": ["C:\\Games\\MyGame\\mods\\my_mod\\hovermate-events.jsonl"] }offersenumera tipos estándar y objetos{ "customType", "customLabel" }.logpropone un registro con líneas[HOVERMATE];filespropone archivos de eventos del mod. Hovermate pide permiso al jugador antes de leer cualquier ruta propuesta.hellofunciona con cualquier transporte, incluida una línea de registro[HOVERMATE].
Límites y respuestas
| Límite | Valor |
|---|---|
| Eventos por juego | 20 por segundo. Superarlo devuelve 429 y no se acepta nada de la solicitud; hello no cuenta |
| Lote | { "events": [ … ] } con 1–100 eventos, todo o nada. Un juego nuevo debe tener nombre en el primer evento del lote que lo mencione |
| Cuerpo HTTP | 64 KB |
| Líneas de archivos de entrada | 4 KB; las líneas más largas se descartan |
| Tipos personalizados | 200 por juego |
| Tiempos de espera del cliente | Hasta 250 ms, como máximo un reintento y una cola limitada que descarta el evento más antiguo |
| Estado | Significado |
|---|---|
202 | Aceptado: {"accepted": n} |
400 | No válido: {"error", "detail", "index"}. index indica el primer evento incorrecto del lote |
401 | Token ausente o incorrecto: vuelve a leer connection.json |
403 | La solicitud contiene una cabecera Origin |
413 | Cuerpo superior a 64 KB |
429 | Más de 20 eventos por segundo para un juego |
405, 411, 415 | Método distinto de POST, falta Content-Length o el tipo de contenido no es JSON |
Códigos de error: invalid_json, version, game, type, custom, label, tags, context, offers, log, files, batch. Las líneas incorrectas de los archivos se cuentan y aparecen como eventos no válidos en la ficha del juego; Hovermate nunca registra su contenido original.
Prueba sin Hovermate
El receptor de prueba mock-server.py (Python 3.8+, biblioteca estándar) responde exactamente como Hovermate. Crea su propio connection.json en una carpeta temporal, aplica las mismas reglas y códigos de error, sigue la carpeta de entrada y los archivos de --watch, e imprime cada evento y error.
A diferencia de Hovermate, el receptor de prueba escucha en un puerto numérico de loopback y escribe esa dirección en su connection.json. Es solo una herramienta de prueba: los clientes usan la url que contenga el archivo.
python tools/mock-server.py
# In another terminal, with the path it printed:
$env:HOVERMATE_SDK_CONNECTION = "C:\Users\you\AppData\Local\Temp\hovermate-sdk-mock-…\connection.json"
python examples/python/example.pyHovermate SDK mock listening on http://127.0.0.1:52777/hovermate/v1/events
[21:15:03.120] http com.example.sdkdemo hello offers=kill,bossKill,custom:parry
[21:15:03.131] http com.example.sdkdemo kill "Double kill" tags=headshot context="Act 2"
[21:15:03.140] http com.example.sdkdemo bossKill "The Warden" context="Act 2"
[21:15:03.148] http com.example.sdkdemo custom:parry "Parry" "Perfect parry"--watch FILEsigue un archivo del modhovermate-events.jsonlo un registro del juego con líneas[HOVERMATE].--follow-proposalslee las rutas propuestas porhello, como si el jugador las hubiera autorizado.--record FILEañade cada evento aceptado o rechazado como línea JSON para comprobaciones automáticas.
HOVERMATE_SDK_CONNECTION funciona igual con Hovermate, por lo que las pruebas nunca tocan el archivo de conexión real.
Casos de conformidad
El SDK incluye conformance/cases.json: solicitudes con las respuestas esperadas de Hovermate, desde eventos válidos hasta cada código de error. conformance/run.py las envía al receptor de prueba o a cualquier otro. Hovermate usa ese mismo archivo en sus propias pruebas.
python conformance/run.py
# or against a running receiver:
python conformance/run.py --connection path\to\connection.jsonQué ve el jugador
- El primer evento válido o
hellode un nuevogame.idcrea una ficha SDK en Clips con el nombre, la carpeta y la portada siexecoincide con un juego instalado, y un interruptor por cada tipo anunciado o recibido. - Los interruptores de momentos destacados empiezan desactivados: el jugador elige qué tipos guardan clips. Los marcadores siempre se escriben.
- Los eventos siguen el mismo recorrido que las fichas integradas: diario, marcadores de clips y sesiones, ventana de momentos destacados y notificaciones.
- Si
game.execoincide con el juego en primer plano, los eventos se atribuyen a ese juego. Si no, solo se aceptan durante una grabación o con el búfer de repetición activo. - Los eventos no válidos se cuentan en el estado de la ficha para que los jugadores puedan informar de problemas.
Protocolo y archivos
El protocolo v1 es el contrato completo: detección de conexión, cuatro formas de entrega, objeto de evento, hello y comportamiento de Hovermate.
El SDK —clientes, ejemplos, herramientas, casos de conformidad y protocolo— tiene licencia MIT, así que puedes incluir un cliente en tu mod. Hovermate en sí no está cubierto por esa licencia.