Horror sanity

Turns dread into a gauge that drains in the dark and when something hunts the player, and recovers in light and safe rooms, with stages that drive vignette, heartbeat and distortion effects.

Source: module-kits:kits/horror_sanity/usage/en.mdView Markdown source

Also read offline — CLI: grip-forge docs horror-sanityMCP tool: gripforge_docs { "topic": "horror-sanity" }

Horror sanity (horror.sanity) #

What it is for #

Turns dread into a gauge that drains in the dark and when something hunts the player, and recovers in light and safe rooms, with stages that drive vignette, heartbeat and distortion effects.

It needs survival.needs, game.engine; installing it brings them in.

Turn it on #

Install the kit in a project, then change a setting (the values shown are its defaults):

gripforge_gamekit_install   { project_id: "gkp_…", id: "horror.sanity" }
gripforge_gamekit_configure { project_id: "gkp_…", id: "horror.sanity",
                              config: { "need": "sanity", "darkBelow": 0.2 } }

First example #

<!-- test: kits/game_engine/host/tests/horror_sanity.rs::the_gauge_follows_darkness_light_and_safe_rooms --> This is the body of the passing test the_gauge_follows_darkness_light_and_safe_rooms; the helper functions it calls are defined at the top of the same test file.

#[test]
fn the_gauge_follows_darkness_light_and_safe_rooms() {
    let mut s = session();
    // In the dark (ambient 0.1 < darkBelow): 6 units a second.
    step(&mut s, 60);
    assert!((value(&mut s) - 44.0).abs() < 1e-6);
    assert_eq!(call(&mut s, "dark", json!([])), json!(true));
    assert_eq!(call(&mut s, "rate", json!([])), json!(-6));
    assert_eq!(s.host.state.get("horror.dark"), Some(&json!(true)));
    // A safe room: still dark there, but it gives back twice what the dark takes.
    go(&mut s, 20.0);
    let told = step(&mut s, 60);
    assert_eq!(
        told,
        vec![(
            "horror.safe".to_string(),
            json!({ "inside": true, "room": "e2" })
        )]
    );
    assert!((value(&mut s) - 50.0).abs() < 1e-6);
    assert_eq!(call(&mut s, "safe", json!([])), json!(true));
    // In the light, out of the room.
    go(&mut s, 40.0);
    let told = step(&mut s, 60);
    assert_eq!(
        told,
        vec![(
            "horror.safe".to_string(),
            json!({ "inside": false, "room": "e2" })
        )]
    );
    assert!((value(&mut s) - 53.0).abs() < 1e-6);
    assert_eq!(s.host.state.get("horror.rate"), Some(&json!(3)));
    // Paused gauges do not move.
    s.apply(
        &json!({ "op": "call", "service": "survival.needs", "method": "pause", "args": [true] }),
    )
    .unwrap();
    step(&mut s, 60);
    assert!((value(&mut s) - 53.0).abs() < 1e-6);
}

Settings and defaults #

Descriptions come from the kit manifest and stay in English.

SettingTypeDefaultRange or valuesMeaning
needstring"sanity"The need of survival.needs this kit drives (declare it there, with drain 0 and its stages).
darkBelownumber0.20–1Light level at the player (stealth.detection) under which it is dark.
darkDrainnumber1.50–100Units lost per second in the dark.
litAbovenumber0.50–1Light level at the player from which it is reassuring.
litRestorenumber0.40–100Units regained per second in the light.
safeRestorenumber40–100Units regained per second inside a safe room.
fallbackLightnumber0.50–1Light level without stealth.detection (a lit flashlight counts as 1).
viewFovnumber10010–360What the player sees: this cone (degrees) around where they face.
sightsarray[{"tag": "stalker", "drain": 5, "range": 20}]Entities whose sight drains: units per second while one of that tag is in view (line of sight, within range).
eventsarray[{"topic": "stalker.spotted", "amount": -10},…Bus events that change it at once (negative: a scare).
saveLabelstring"Save the game"

Services, events, variables #

  • Service horror.sanity: value(), ratio(), rate() (per second now), dark(), seeing() → entities in view that frighten, safe(), safeAt(point) → boolean, savePoints(), save(point?) → boolean, scare(amount, source?).
  • Events emitted:
    • horror.safe (inside, room) — The player entered or left a safe room.
    • horror.scare (amount, source) — An event or a script changed the gauge at once.
    • horror.saved (point, slot) — The game was saved at a save point.
  • Events listened to: interaction.requested, entity.spawned, stalker.spotted, stealth.caught
  • Variables:
    • horror.dark (boolean) — In the dark
    • horror.safe (boolean) — In a safe room
    • horror.seeing (boolean) — Sees something frightening
    • horror.rate (number) — Sanity change per second
  • Verbs of the scripting layer:
    • scare (action) — Change the gauge at once (negative: a scare).
    • in_safe_room (condition) — The player is inside a safe room.

Hooks and modding #

  • Hooks (a game or a mod can intercept them):
    • sanity.scare (fields a tap may change: amount; cancellable) — The gauge is about to change at once by amount (negative: a scare; positive: a relief): a bus event of the config (source: its topic), the scare action or the service (source: script, or the caller).
    • sanity.darken (fields a tap may change: drain; cancellable) — The player entity enters the dark (the light on it fell under darkBelow): drain is what the gauge loses per second while it stays there.
    • sanity.sight (fields a tap may change: drain; cancellable) — Something frightening (seen, an entity with the tag of a sights row) comes into the view of the player entity: drain is what the gauge loses per second while one of that tag stays in view.
    • sanity.shelter (fields a tap may change: restore; cancellable) — The player entity enters a safe room (room, its entity): restore is what the gauge regains per second inside.
  • Moddable settings: need, darkBelow, darkDrain, litAbove, litRestore, safeRestore, fallbackLight, viewFov, sights, events, saveLabel
  • Components: safe_room, save_point

Engines #

Declared targets: web, rust. gripforge kit api horror.sanity lists the exact settings and services of your version.

Versions #

Kit version 1.2.0.