Fonctionnement
- Votre mod signale un moment. Un seul appel suffit, par exemple
event("bossKill"). Le client l’envoie en arrière-plan sans bloquer le jeu. - Hovermate le marque. Chaque événement devient un marqueur de l’enregistrement et de la session, avec votre libellé.
- Le joueur choisit les moments forts. Votre jeu reçoit sa propre fiche dans Clips. Chaque type activé par le joueur enregistre aussi un clip.
Quand son SDK est activé, Hovermate publie son adresse, un jeton et son dossier de réception dans 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"
}Lisez url dans ce fichier et utilisez-le exactement tel qu’il est écrit ; ne codez jamais en dur et ne modifiez jamais son hôte, son port ou son chemin.
Les clients lisent %LOCALAPPDATA%\Hovermate\Sdk\connection.json, ou le chemin de HOVERMATE_SDK_CONNECTION pour les tests et configurations isolées. Ils le relisent après une réponse 401, car le jeton change à chaque désactivation puis réactivation du SDK. L’absence du fichier signifie que Hovermate n’est pas installé ou que le SDK est désactivé ; les clients restent silencieux.
Trois façons d’envoyer des événements
Utilisez la première autorisée par l’environnement de votre mod.
HTTP
Votre mod peut ouvrir une connexion réseau locale.
Envoyez l’événement par POST à l’URL avec Authorization: Bearer <token>. Les requêtes contenant un en-tête Origin sont refusées : les pages web ne peuvent donc pas envoyer d’événements.
C#, C++, Java, Python, JavaScript ; GDScript peut utiliser HTTPRequest.
Fichiers
Votre mod peut écrire des fichiers, mais n’a pas accès au réseau.
Ajoutez un événement JSON par ligne dans {inbox}\<game.id>.jsonl ; aucun jeton n’est nécessaire. Une sandbox limitée au dossier du mod, comme Cyber Engine Tweaks, écrit plutôt dans hovermate-events.jsonl à cet endroit ; le joueur ajoute ce fichier à la fiche du jeu.
Tous les clients se replient sur le dossier de réception ; le client Lua écrit le fichier du dossier du mod.
Lignes de journal
Vos scripts peuvent seulement écrire dans le journal du jeu.
Écrivez [HOVERMATE] , puis un objet événement JSON ; tout ce qui précède le marqueur est ignoré. scriptslog.txt de The Witcher 3 et Papyrus.0.log de Skyrim et Fallout 4 sont lus automatiquement pendant que le jeu tourne.
Scripts Witcher 3, Papyrus ou tout jeu doté d’un journal de scripts.
Une requête 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"}Réponse : 202 {"accepted":1}.
Une ligne de journal
[12:00:01] [Script] [HOVERMATE] {"v":1,"game":{"id":"com.example.mygame","name":"My Game"},"type":"bossKill","context":"Act 2"}Pour un autre journal, le joueur l’ajoute dans Clips › fiche du jeu › Lire les événements depuis un fichier, ou votre hello le propose.
Démarrage rapide
Chaque exemple envoie hello, kill, bossKill et un événement personnalisé parry. Ce sont les fichiers exécutables du dossier examples du SDK.
C#
- Client
- HovermateEvents.cs
- Environnement
- netstandard2.0, .NET Framework 4.7.2+ et .NET, avec C# 7.3 : Unity, BepInEx, MelonLoader.
- Configuration
- Ajoutez HovermateEvents.cs au projet de votre 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();Lancez dans examples/csharp (l’exemple cible .NET 8 ou ultérieur) :
dotnet runC++
- Client
- hovermate_events.h
- Environnement
- C++17 sous Windows. Fichier d’en-tête uniquement ; utilise WinHTTP et lie winhttp.lib.
- Configuration
- Incluez hovermate_events.h. Appelez hovermate::shutdown() avant le déchargement de votre DLL, jamais depuis 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;
}Lancez dans une invite de commandes développeur, depuis examples/cpp :
cl /std:c++17 /EHsc /utf-8 /I..\..\cpp example.cppJava
- Client
- HovermateEvents.java
- Environnement
- Java 8 ou ultérieur : ModTheSpire, Forge, Fabric.
- Configuration
- Copiez HovermateEvents.java dans votre mod et changez son package, ou relocalisez-le lors du shading, pour éviter les conflits avec les copies d’autres 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);
}
}Lancez depuis examples/java :
javac -d out ../../java/HovermateEvents.java Example.java && java -cp out ExamplePython
- Client
- hovermate_events.py
- Environnement
- Python 3.7 ou ultérieur, bibliothèque standard uniquement.
- Configuration
- Placez hovermate_events.py à côté de votre 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()Lancez depuis examples/python :
python example.pyJavaScript
- Client
- hovermate-events.mjs
- Environnement
- Node.js 18 ou ultérieur, en module ES.
- Configuration
- Importez 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();Lancez depuis examples/js :
node example.mjsLua
- Client
- hovermate_events.lua
- Environnement
- Lua 5.1+ et LuaJIT : Cyber Engine Tweaks et autres sandboxes sans réseau.
- Configuration
- Placez hovermate_events.lua à côté de init.lua. Les événements sont écrits dans hovermate-events.jsonl dans le dossier du mod : demandez aux joueurs d’ajouter ce fichier dans 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()Lancez dans le dossier représentant celui de votre mod :
lua example.luaComportement des clients
| Appel | Fonction |
|---|---|
init(gameId, gameName, exe?) | Démarre le client. gameId correspond à [a-z0-9._-]{3,64} et reste identique entre les versions du mod ; exe est le nom de l’exécutable du jeu. |
hello(offers, log?, files?) | Annonce les types que votre jeu peut envoyer et, éventuellement, un journal ou des fichiers du mod à lire. |
event(type, {label, tags, context}) | Envoie un type d’événement standard. |
custom(customType, customLabel, {…}) | Envoie votre propre type ; customLabel nomme son option de moments forts. |
flush() / shutdown() | Attend les événements en file et arrête le traitement en arrière-plan au déchargement du mod. |
- Les appels reviennent immédiatement et ne transmettent jamais d’exception au jeu.
- Un traitement en arrière-plan envoie les événements avec un délai maximal de 250 ms et réessaie une fois les requêtes en échec.
- La file contient 256 événements et supprime le plus ancien en cas de dépassement.
- Si HTTP est inaccessible, les événements sont envoyés au dossier de réception pendant quelques secondes.
- Sans
connection.json, le client reste silencieux et vérifie à nouveau toutes les 5 secondes. - Chaque événement contient
game.name. Le texte est tronqué aux limites ci-dessous et les tags invalides sont ignorés au lieu de provoquer un échec.
Le client Lua (hovermate_events.lua) écrit immédiatement chaque événement. Il privilégie hovermate-events.jsonl dans le dossier du mod, puis le dossier de réception si les chemins absolus sont autorisés, puis des lignes [HOVERMATE] via la fonction writer fournie, par exemple print. logLine(type, options) renvoie une telle ligne pour que vous l’affichiez.
L’objet événement
{ "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" }| Champ | Obligatoire | Règle |
|---|---|---|
v | oui | 1 |
game.id | oui | [a-z0-9._-]{3,64} : nom DNS inversé ou identifiant court, stable entre les versions du mod |
game.name | premier événement | Dans le premier événement ou hello d’une session ; 64 caractères maximum |
game.exe | non | Un nom de fichier uniquement. Associe les événements au dossier du jeu détecté et permet à cet exécutable de démarrer le tampon de replay |
type | oui | Un type standard ci-dessous ou custom |
customType, customLabel | pour custom | [a-z0-9_.-]{1,32}, et un nom d’option de 40 caractères maximum |
label | non | Texte de cet événement pour l’infobulle du marqueur et le titre du clip ; 80 caractères maximum |
tags | non | Jusqu’à 8 tags, chacun correspondant à [a-z0-9_-]{1,24} |
context | non | Carte, niveau ou zone pour les titres des moments forts ; 64 caractères maximum |
time | non | ISO-8601 UTC. Hovermate date la réception ; une valeur time à moins de 5 s affine cette date |
Les longueurs comptent les unités UTF-16 : un emoji compte pour 2. Les champs texte refusent les caractères de contrôle. Les champs inconnus sont ignorés ; les versions suivantes ajoutent seulement des champs. N’envoyez pas de données personnelles : libellés et contextes peuvent apparaître dans les titres de clips et noms de fichiers.
Types standard
29 types partagés avec les fiches de jeux intégrées de Hovermate. La liste s’allonge seulement ; un type standard inconnu est refusé.
killdeathassistmultikillroundWonroundLostmatchWonmatchLostmvpgoalsaveobjectivemissionlapbestLappositioncrashincidentpenaltypitfinishraceWonlandingtakeofffightWonlevelUpbossKillzoneachievement
Types personnalisés
Pour les moments absents de la liste standard. Chaque type a sa propre option de moments forts, nommée par customLabel ; un jeu peut en avoir jusqu’à 200.
{ "v": 1, "game": { "id": "com.example.mygame" },
"type": "custom", "customType": "parry", "customLabel": "Parry", "label": "Perfect parry" }hello
Facultatif et recommandé. Crée la fiche de votre jeu et ses options avant le premier événement réel, afin que le joueur puisse la configurer à l’avance.
{ "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"] }offersénumère les types standard et les objets{ "customType", "customLabel" }.logpropose un journal avec des lignes[HOVERMATE];filespropose des fichiers d’événements du mod. Hovermate demande l’accord du joueur avant de lire un chemin proposé.hellofonctionne avec tous les transports, y compris une ligne de journal[HOVERMATE].
Limites et réponses
| Limite | Valeur |
|---|---|
| Événements par jeu | 20 par seconde. Au-delà, réponse 429 et aucun événement de la requête n’est accepté ; hello n’est pas compté |
| Lot | { "events": [ … ] } de 1 à 100 événements, entièrement accepté ou refusé. Le premier événement mentionnant un nouveau jeu doit le nommer |
| Corps HTTP | 64 Ko |
| Lignes des fichiers d’entrée | 4 Ko ; les lignes plus longues sont ignorées |
| Types personnalisés | 200 par jeu |
| Délais des clients | 250 ms maximum, une seule nouvelle tentative, une file bornée supprimant l’événement le plus ancien |
| Statut | Signification |
|---|---|
202 | Accepté : {"accepted": n} |
400 | Invalide : {"error", "detail", "index"}. index indique le premier événement invalide du lot |
401 | Jeton manquant ou incorrect : relisez connection.json |
403 | La requête contient un en-tête Origin |
413 | Corps dépassant 64 Ko |
429 | Plus de 20 événements par seconde pour un jeu |
405, 411, 415 | Méthode autre que POST, absence de Content-Length ou contenu non JSON |
Codes d’erreur : invalid_json, version, game, type, custom, label, tags, context, offers, log, files, batch. Les lignes invalides des fichiers sont comptées et signalées dans la fiche du jeu ; Hovermate ne journalise jamais leur contenu brut.
Tester sans Hovermate
Le récepteur de test mock-server.py (Python 3.8+, bibliothèque standard) répond exactement comme Hovermate. Il crée son propre connection.json dans un dossier temporaire, applique les mêmes règles et codes d’erreur, suit le dossier d’entrée et les fichiers de --watch, puis affiche chaque événement et chaque erreur.
Contrairement à Hovermate, le récepteur de test écoute sur un port numérique de loopback et écrit cette adresse dans son connection.json. Ce n’est qu’un outil de test : les clients utilisent l’url que contient le fichier.
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 FILEsuit un fichier de modhovermate-events.jsonlou un journal contenant des lignes[HOVERMATE].--follow-proposalslit les chemins proposés parhello, comme si le joueur les avait autorisés.--record FILEajoute chaque événement accepté ou refusé sous forme de ligne JSON pour les vérifications automatiques.
HOVERMATE_SDK_CONNECTION fonctionne aussi avec Hovermate lui-même : les tests ne touchent jamais le vrai fichier de connexion.
Cas de conformité
Le SDK inclut conformance/cases.json : des requêtes et les réponses attendues de Hovermate, des événements valides à chaque code d’erreur. conformance/run.py les envoie au récepteur de test ou à tout autre récepteur. Hovermate utilise le même fichier dans ses propres tests.
python conformance/run.py
# or against a running receiver:
python conformance/run.py --connection path\to\connection.jsonCe que voit le joueur
- Le premier événement valide ou
hellod’un nouveaugame.idcrée une fiche SDK dans Clips : nom du jeu, dossier et jaquette siexecorrespond à un jeu installé, et une option pour chaque type annoncé ou reçu. - Les options de moments forts sont initialement désactivées : le joueur choisit les types qui enregistrent des clips. Les marqueurs sont toujours écrits.
- Les événements suivent le même circuit que les fiches intégrées : journal, marqueurs des clips et sessions, fenêtre de moments forts et notifications.
- Quand
game.execorrespond au jeu au premier plan, les événements lui sont attribués. Sinon, ils sont acceptés uniquement pendant un enregistrement ou l’exécution du tampon de replay. - Les événements invalides sont comptés dans l’état de la fiche pour que les joueurs puissent vous signaler un problème.
Protocole et fichiers
Le protocole v1 est le contrat complet : découverte de la connexion, quatre moyens de livraison, objet événement, hello et comportement de Hovermate.
Le SDK — clients, exemples, outils, cas de conformité et protocole — est sous licence MIT : vous pouvez intégrer un client à votre mod. Cette licence ne couvre pas Hovermate lui-même.