게임 이벤트 SDK · 프로토콜 v1

게임 이벤트를 Hovermate로 보내기

게임이나 모드는 처치, 보스 격파, 랩, 골 또는 자체 이벤트를 알릴 수 있습니다. Hovermate는 내장 게임 카드와 마찬가지로 타임라인 마커와 하이라이트 클립을 자동으로 생성합니다.

C#, C++, Java, Python, JavaScript, Lua 클라이언트 · 각각 파일 하나, 의존성 없음 · MIT 라이선스

작동 방식

  1. 모드가 순간을 알립니다. event("bossKill") 같은 호출 한 번이면 됩니다. 클라이언트는 게임을 막지 않고 백그라운드에서 전송합니다.
  2. Hovermate가 표시합니다. 각 이벤트는 지정한 이름과 함께 녹화 및 세션 타임라인의 마커가 됩니다.
  3. 플레이어가 하이라이트를 선택합니다. 클립에 게임 전용 카드가 생깁니다. 플레이어가 켠 유형의 이벤트는 클립으로도 저장됩니다.

SDK가 켜져 있는 동안 Hovermate는 주소, 토큰, 수신 폴더를 connection.json에 기록합니다.

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

url은 이 파일에서 읽어 적힌 그대로 사용하세요. 호스트, 포트, 경로를 코드에 고정하거나 바꾸지 마세요.

클라이언트는 %LOCALAPPDATA%\Hovermate\Sdk\connection.json을 읽습니다. 테스트나 격리된 환경에서는 HOVERMATE_SDK_CONNECTION의 경로를 사용합니다. SDK를 껐다 켜면 토큰이 바뀌므로 401 응답 후 다시 읽습니다. 파일이 없으면 Hovermate가 설치되지 않았거나 SDK가 꺼진 상태이며, 클라이언트는 아무것도 보내지 않습니다.

이벤트를 보내는 세 가지 방식

모드 환경에서 허용하는 첫 번째 방식을 사용하세요.

HTTP

모드가 로컬 네트워크 연결을 열 수 있습니다.

Authorization: Bearer <token>을 사용해 URL로 이벤트를 POST합니다. Origin 헤더가 있는 요청은 거부되므로 웹 페이지에서는 이벤트를 보낼 수 없습니다.

C#, C++, Java, Python, JavaScript를 지원하며 GDScript는 HTTPRequest을 사용할 수 있습니다.

파일

모드가 파일은 쓸 수 있지만 네트워크에 접근할 수 없습니다.

{inbox}\<game.id>.jsonl에 한 줄당 JSON 이벤트 하나를 추가합니다. 토큰은 필요 없습니다. Cyber Engine Tweaks처럼 모드 폴더에만 쓸 수 있는 환경은 그 안의 hovermate-events.jsonl에 기록하며, 플레이어가 게임 카드에 파일을 추가합니다.

모든 클라이언트는 수신 폴더로 전환할 수 있으며 Lua 클라이언트는 모드 폴더에 파일을 씁니다.

로그 줄

스크립트가 게임 자체 로그에만 출력할 수 있습니다.

[HOVERMATE] 뒤에 JSON 이벤트 객체 하나를 출력합니다. 마커 앞부분은 무시됩니다. 게임 실행 중 The Witcher 3의 scriptslog.txt, Skyrim과 Fallout 4의 Papyrus.0.log를 자동으로 읽습니다.

Witcher 3 스크립트, Papyrus 또는 스크립트 로그가 있는 게임.

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

응답: 202 {"accepted":1}.

로그 줄 예시

게임 로그의 한 줄
[12:00:01] [Script] [HOVERMATE] {"v":1,"game":{"id":"com.example.mygame","name":"My Game"},"type":"bossKill","context":"Act 2"}

다른 로그는 클립 › 게임 카드 › 파일에서 이벤트 읽기에서 추가하거나 hello로 제안할 수 있습니다.

빠른 시작

각 예제는 hello, kill, bossKill와 사용자 지정 parry 이벤트를 보냅니다. SDK의 examples 폴더에 있는 실행 가능한 예제 파일입니다.

C#

클라이언트
HovermateEvents.cs
실행 환경
netstandard2.0, .NET Framework 4.7.2+ 및 .NET, C# 7.3: Unity, BepInEx, MelonLoader.
설정
모드 프로젝트에 HovermateEvents.cs를 추가합니다.
csharp/Program.cs
// 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();

examples/csharp에서 실행합니다. 예제는 .NET 8 이상을 대상으로 합니다.

dotnet run

C++

클라이언트
hovermate_events.h
실행 환경
Windows의 C++17. 헤더 전용이며 WinHTTP를 사용하고 winhttp.lib를 링크합니다.
설정
hovermate_events.h를 포함합니다. DLL이 언로드되기 전에 hovermate::shutdown()을 호출하고 DllMain 안에서는 호출하지 마세요.
cpp/example.cpp
// 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;
}

개발자 명령 프롬프트의 examples/cpp에서 실행합니다.

cl /std:c++17 /EHsc /utf-8 /I..\..\cpp example.cpp

Java

클라이언트
HovermateEvents.java
실행 환경
Java 8 이상: ModTheSpire, Forge, Fabric.
설정
HovermateEvents.java를 모드에 복사하고 패키지를 변경하거나 shading 시 재배치하여 다른 모드의 사본과 충돌하지 않게 합니다.
java/Example.java
// 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);
    }
}

examples/java에서 실행합니다.

javac -d out ../../java/HovermateEvents.java Example.java && java -cp out Example

Python

클라이언트
hovermate_events.py
실행 환경
Python 3.7 이상, 표준 라이브러리만 사용합니다.
설정
스크립트 옆에 hovermate_events.py를 둡니다.
python/example.py
"""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()

examples/python에서 실행합니다.

python example.py

JavaScript

클라이언트
hovermate-events.mjs
실행 환경
Node.js 18 이상, ES 모듈로 사용합니다.
설정
hovermate-events.mjs를 가져옵니다.
js/example.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();

examples/js에서 실행합니다.

node example.mjs

Lua

클라이언트
hovermate_events.lua
실행 환경
Lua 5.1+ 및 LuaJIT: Cyber Engine Tweaks와 네트워크 없는 다른 샌드박스.
설정
init.lua 옆에 hovermate_events.lua를 둡니다. 이벤트는 모드 폴더의 hovermate-events.jsonl에 기록됩니다. 플레이어에게 이 파일을 Hovermate에 추가하도록 안내하세요.
lua/example.lua
-- 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()

모드 폴더로 사용할 폴더에서 실행합니다.

lua example.lua

클라이언트 동작

호출기능
init(gameId, gameName, exe?)클라이언트를 시작합니다. gameId는 [a-z0-9._-]{3,64}에 맞아야 하고 모드 버전 간에 유지됩니다. exe는 게임 실행 파일 이름입니다.
hello(offers, log?, files?)게임이 보낼 수 있는 유형과 선택적으로 읽을 로그 또는 모드 폴더 파일을 알립니다.
event(type, {label, tags, context})표준 유형의 이벤트를 보냅니다.
custom(customType, customLabel, {…})자체 유형을 보냅니다. customLabel가 해당 하이라이트 스위치의 이름입니다.
flush() / shutdown()대기 중인 이벤트를 기다리고 모드 언로드 시 백그라운드 작업을 중지합니다.
  • 호출은 즉시 반환하고 게임에 예외를 전달하지 않습니다.
  • 백그라운드 작업이 250 ms 제한 시간으로 전송하며 실패한 요청은 한 번 재시도합니다.
  • 큐는 이벤트 256개를 보관하며 초과하면 가장 오래된 이벤트를 버립니다.
  • HTTP에 접근할 수 없으면 몇 초 동안 수신 폴더로 이벤트를 보냅니다.
  • connection.json이 없으면 아무것도 보내지 않고 5초마다 다시 확인합니다.
  • 모든 이벤트에는 game.name가 포함됩니다. 텍스트는 아래 길이로 잘리고 잘못된 태그는 오류 대신 제거됩니다.

Lua 클라이언트(hovermate_events.lua)는 각 이벤트를 즉시 기록합니다. 모드 폴더의 hovermate-events.jsonl, 절대 경로가 허용될 때 수신 폴더, 마지막으로 제공한 writer 함수(예: print)를 통한 [HOVERMATE] 줄 순으로 사용합니다. logLine(type, options)은 직접 출력할 수 있는 로그 줄을 반환합니다.

이벤트 객체

{ "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" }
필드필수규칙
v예1
game.id예[a-z0-9._-]{3,64}: 역방향 DNS 이름 또는 짧은 식별자. 모드 버전 간에 유지
game.name첫 이벤트세션의 첫 이벤트 또는 hello에 필수, 최대 64자
game.exe아니요파일 이름만 지정합니다. 감지된 게임 폴더에 이벤트를 연결하고 해당 실행 파일이 리플레이 버퍼를 시작할 수 있게 합니다
type예아래 표준 유형 또는 custom
customType, customLabelcustom인 경우[a-z0-9_.-]{1,32} 및 최대 40자의 스위치 이름
label아니요마커 도움말과 클립 제목에 사용할 이벤트 표시 문구, 최대 80자
tags아니요최대 8개 태그, 각각 [a-z0-9_-]{1,24}에 일치
context아니요하이라이트 제목에 사용할 맵, 레벨 또는 지역, 최대 64자
time아니요ISO-8601 UTC. Hovermate가 수신 시간을 기록하며 그 시간에서 5초 이내인 time로 보정합니다

길이는 UTF-16 코드 단위로 계산하므로 이모지는 2입니다. 텍스트 필드는 제어 문자를 거부합니다. 알 수 없는 필드는 무시하므로 이후 버전은 필드만 추가합니다. 개인 정보를 보내지 마세요. 이름과 문맥이 클립 제목이나 파일 이름에 나타날 수 있습니다.

표준 유형

Hovermate 내장 게임 카드와 공유하는 유형 29개입니다. 목록은 추가만 되며 알 수 없는 표준 유형은 거부합니다.

  • kill
  • death
  • assist
  • multikill
  • roundWon
  • roundLost
  • matchWon
  • matchLost
  • mvp
  • goal
  • save
  • objective
  • mission
  • lap
  • bestLap
  • position
  • crash
  • incident
  • penalty
  • pit
  • finish
  • raceWon
  • landing
  • takeoff
  • fightWon
  • levelUp
  • bossKill
  • zone
  • achievement

사용자 지정 유형

표준 목록에 없는 순간에 사용합니다. 유형마다 customLabel 이름으로 별도의 하이라이트 스위치가 생기며 게임당 최대 200개입니다.

{ "v": 1, "game": { "id": "com.example.mygame" },
  "type": "custom", "customType": "parry", "customLabel": "Parry", "label": "Perfect parry" }

hello

선택 사항이며 권장합니다. 첫 실제 이벤트 전에 게임 카드와 스위치를 만들어 플레이어가 미리 설정할 수 있게 합니다.

{ "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는 표준 유형과 { "customType", "customLabel" } 객체를 나열합니다.
  • log는 [HOVERMATE] 줄이 있는 게임 로그를, files는 모드 폴더의 이벤트 파일을 제안합니다. Hovermate는 제안된 경로를 읽기 전에 플레이어의 허락을 받습니다.
  • hello는 [HOVERMATE] 로그 줄을 비롯한 모든 전송 방식에서 작동합니다.

제한과 응답

제한값
게임당 이벤트초당 20개. 초과 시 429를 반환하고 요청 전체를 거부합니다. hello는 계산하지 않습니다
일괄 전송{ "events": [ … ] }에 이벤트 1~100개, 전부 수락하거나 전부 거부합니다. 새 게임은 일괄 요청에서 처음 언급하는 이벤트에 이름이 있어야 합니다
HTTP 본문64 KB
수신 및 파일 줄4 KB, 더 긴 줄은 버립니다
사용자 지정 유형게임당 200개
클라이언트 제한 시간최대 250 ms, 재시도는 한 번만. 크기가 제한된 큐에서 가장 오래된 이벤트를 버립니다
상태의미
202수락됨: {"accepted": n}
400잘못됨: {"error", "detail", "index"}. index는 일괄 요청에서 첫 번째 잘못된 이벤트를 가리킵니다
401토큰이 없거나 잘못됨: connection.json을 다시 읽으세요
403요청에 Origin 헤더가 있습니다
413본문이 64 KB를 넘습니다
429한 게임에서 초당 이벤트가 20개를 넘습니다
405, 411, 415POST가 아님, Content-Length 누락 또는 JSON이 아닌 콘텐츠 유형

오류 코드: invalid_json, version, game, type, custom, label, tags, context, offers, log, files, batch. 파일의 잘못된 줄은 게임 카드에 잘못된 이벤트 수로 표시됩니다. Hovermate는 그 원문을 로그에 기록하지 않습니다.

Hovermate 없이 테스트

테스트 수신기 mock-server.py(Python 3.8+, 표준 라이브러리)는 Hovermate와 똑같이 응답합니다. 임시 폴더에 자체 connection.json을 만들고 같은 규칙과 오류 코드를 적용하며 수신 폴더와 --watch로 지정한 파일을 추적하고 모든 이벤트와 오류를 출력합니다.

Hovermate와 달리 테스트 수신기는 숫자 루프백 포트에서 수신하고 그 주소를 자체 connection.json에 기록합니다. 테스트 도구일 뿐이며, 클라이언트는 파일에 있는 url을 그대로 사용합니다.

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.py
테스트 수신기 출력
Hovermate 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 FILE은 모드 폴더의 hovermate-events.jsonl 또는 [HOVERMATE] 줄이 있는 게임 로그를 추적합니다.
  • --follow-proposals은 플레이어가 허용한 것처럼 hello가 제안한 경로를 읽습니다.
  • --record FILE은 수락하거나 거부한 모든 이벤트를 JSON 한 줄씩 추가하여 자동 검사에 사용합니다.

HOVERMATE_SDK_CONNECTION은 Hovermate 본체에서도 같게 동작하므로 테스트가 실제 연결 파일을 건드리지 않습니다.

적합성 검사 사례

SDK의 conformance/cases.json에는 정상 이벤트부터 모든 오류 코드까지 요청과 Hovermate의 기대 응답이 들어 있습니다. conformance/run.py가 테스트 수신기나 다른 수신기로 보냅니다. Hovermate 자체 테스트도 같은 파일을 사용합니다.

python conformance/run.py
# or against a running receiver:
python conformance/run.py --connection path\to\connection.json

플레이어에게 보이는 화면

  • 새 game.id의 첫 정상 이벤트 또는 hello가 클립에 SDK 카드를 만듭니다. 게임 이름, exe가 설치된 게임과 일치할 때 폴더와 표지, 선언되거나 수신된 각 유형의 스위치가 표시됩니다.
  • 하이라이트 스위치는 처음에는 꺼져 있습니다. 클립을 저장할 유형은 플레이어가 선택하며 마커는 항상 기록됩니다.
  • 이벤트는 내장 카드와 같은 경로로 처리됩니다. 기록, 클립과 세션 마커, 하이라이트 창 및 알림을 사용합니다.
  • game.exe가 전면 게임과 일치하면 이벤트를 해당 게임으로 처리합니다. 그렇지 않으면 녹화나 리플레이 버퍼가 실행 중일 때만 수락합니다.
  • 잘못된 이벤트 수를 카드 상태에 표시하므로 플레이어가 문제를 알릴 수 있습니다.

프로토콜과 파일

프로토콜 v1은 연결 검색, 네 가지 전달 방식, 이벤트 객체, hello, Hovermate 동작의 전체 계약입니다.

SDK의 클라이언트, 예제, 도구, 적합성 사례 및 프로토콜에는 MIT 라이선스가 적용되므로 모드에 클라이언트를 포함할 수 있습니다. Hovermate 자체에는 적용되지 않습니다.