Initial commit: DeathWatch, an Elden Ring death counter for OBS
Reads the game's process memory read-only (AOB signature scanning, re-resolved every tick — never a cached pointer) and serves a live death counter as a browser-source overlay plus a status panel. Supports co-op: each player runs the program locally, one acts as hub. The peer link is authenticated (HMAC challenge/response, no token on the wire, constant-time compare) but not yet encrypted — see CLAUDE.md's TODO section for the planned TLS+pinning split. Zero external dependencies — including a hand-rolled WebSocket implementation — so the whole thing ships as one .exe.
This commit is contained in:
commit
e1f7e6f529
18 files changed
+3690
No files matched your search
+113
@@ -0,0 +1,113 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// cada test corre en su propio directorio: totals.json y offset.txt
|
||||
// se resuelven contra el ejecutable, asi que lo apuntamos al temporal.
|
||||
func fresh(t *testing.T, legacy string) {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
testExeDir = dir
|
||||
if legacy != "" {
|
||||
if err := os.WriteFile(filepath.Join(dir, "offset.txt"), []byte(legacy), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
totals = newTotalsStore()
|
||||
st = state{}
|
||||
st.init(totals)
|
||||
}
|
||||
|
||||
func total(t *testing.T) int64 { t.Helper(); return st.snapshot().Total }
|
||||
|
||||
// EL CASO DE EMMA: cambiar de personaje tiene que mover el numero YA,
|
||||
// sin esperar a que alguien se muera.
|
||||
func TestCambioDePersonajeActualizaAlInstante(t *testing.T) {
|
||||
fresh(t, "")
|
||||
|
||||
st.setCharacter(0, "emma", 40)
|
||||
st.setReading(40, false)
|
||||
if got := total(t); got != 40 {
|
||||
t.Fatalf("emma deberia arrancar con sus 40 muertes, dio %d", got)
|
||||
}
|
||||
|
||||
// se va al menu y carga otro personaje, con otro conteo propio
|
||||
st.setPlayerUnloaded("menu")
|
||||
st.setCharacter(1, "emmatherock", 7)
|
||||
if got := total(t); got != 7 {
|
||||
t.Fatalf("al cargar emmatherock el total tenia que saltar a 7 SIN morir, dio %d", got)
|
||||
}
|
||||
st.setReading(7, false)
|
||||
if got := total(t); got != 7 {
|
||||
t.Fatalf("tras la lectura deberia seguir en 7, dio %d", got)
|
||||
}
|
||||
|
||||
// y volver muestra lo que el juego lleva para emma
|
||||
st.setPlayerUnloaded("menu")
|
||||
st.setCharacter(0, "emma", 41)
|
||||
st.setReading(41, false)
|
||||
if got := total(t); got != 41 {
|
||||
t.Fatalf("volviendo a emma deberia dar 41, dio %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
// Dos personajes con el MISMO nombre no se tienen que mezclar.
|
||||
func TestHomonimosNoSeMezclan(t *testing.T) {
|
||||
fresh(t, "")
|
||||
st.setCharacter(0, "Tarnished", 100)
|
||||
st.setReading(100, false)
|
||||
st.setCharacter(3, "Tarnished", 5)
|
||||
st.setReading(5, false)
|
||||
if got := total(t); got != 5 {
|
||||
t.Fatalf("el del slot 3 deberia dar 5, dio %d", got)
|
||||
}
|
||||
if c, ok := totals.get("slot0"); !ok || c.Total != 100 {
|
||||
t.Fatalf("el del slot 0 deberia seguir en 100, dio %+v", c)
|
||||
}
|
||||
}
|
||||
|
||||
// El total es el del juego, sin importar que haya un offset.txt viejo
|
||||
// dando vueltas al lado del programa.
|
||||
func TestIgnoraElOffsetViejo(t *testing.T) {
|
||||
fresh(t, "124")
|
||||
st.setCharacter(0, "emma", 30)
|
||||
st.setReading(30, false)
|
||||
if got := total(t); got != 30 {
|
||||
t.Fatalf("deberia mostrar las 30 del juego y no las 124 del archivo viejo, dio %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
// Sin poder leer el slot, cae al nombre y sigue funcionando.
|
||||
func TestSinSlotCaeAlNombre(t *testing.T) {
|
||||
fresh(t, "")
|
||||
st.setCharacter(-1, "emma", 40)
|
||||
st.setReading(40, false)
|
||||
if got := total(t); got != 40 {
|
||||
t.Fatalf("deberia dar 40, dio %d", got)
|
||||
}
|
||||
if _, ok := totals.get("name:emma"); !ok {
|
||||
t.Fatal("deberia haberlo guardado por nombre")
|
||||
}
|
||||
}
|
||||
|
||||
// Al reabrir el programa con el juego cerrado, muestra el ultimo
|
||||
// personaje conocido en vez de un cero.
|
||||
func TestRecuerdaElUltimoAlReabrir(t *testing.T) {
|
||||
fresh(t, "")
|
||||
st.setCharacter(2, "emma", 88)
|
||||
st.setReading(88, false)
|
||||
|
||||
totals = newTotalsStore() // simula reabrir el programa
|
||||
st = state{}
|
||||
st.init(totals)
|
||||
if got := total(t); got != 88 {
|
||||
t.Fatalf("al reabrir deberia mostrar 88, dio %d", got)
|
||||
}
|
||||
if st.snapshot().CharName != "emma" {
|
||||
t.Fatalf("y recordar el nombre, dio %q", st.snapshot().CharName)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user