DEV Community

Cover image for Granny Is Watching: A Reactive Horror Experience Using SmartPectra
Ansell Maximilian
Ansell Maximilian Subscriber

Posted on Edited on

Granny Is Watching: A Reactive Horror Experience Using SmartPectra

Presage SmartSpectra SDK Challenge Submission

This is a submission for the Presage SmartSpectra SDK Challenge

What I Built

I made a 3D horror game inspired by traditional Indonesian rural aesthetics. As such, I centered the gameplay around the traditional board game Congklak, which is played across Southeast Asia, including Indonesia.

I've modified it a bit to better fit the gameplay, but the idea is:

  • you and your opponent have two giant pits on either side.
  • between those are two sets of smaller pits on both players' sides.
  • each smaller pit has the same amount of "seeds" at the beginning.
  • each turn, you choose a smaller pit from your side and grab every seed form it into your hand.
  • then in a clockwise direction, you go through each pit, including your big one and skipping the opponent's.
  • every time you hover over a pit, you drop a seed from your hand, including your big pit.
  • if the last seed lands on your big pit, you get another turn, choosing another small pit on you side with seed(s) in it.
  • if you land on an small empty pit on your opponent's side, you end your turn.
  • if you land on a small empty pit on YOUR side, you grab that seed as well as the seeds on the opposite pit and put it in your big pit, ending your turn.
  • whoever has the most amount of seeds at the end of the game wins

For a full elaboration of the rules, check out the docs in the repo

Granny Is Watching

Obviously I didn't just make a modified version of a traditional board game. I actually have implemented 3 different endings you can get. Only one of them, though, results in your victory.

I won't spoil it here, and it's not explicitly stated in the game how you win but it should be easy enough.

Okay, so, aside from the core Congklak gameplay, I added another layer to it. You don't win by simply winning a game. Winning a game earns you the right to "burn" on of the pits, rendering them useless in the next game.

If you lose a round, granny "consumes" you. However, I have added a save progress system (which you can turn of for more difficulty). This is the first ending.

Like I said, I won't be spoiling the other two endings. One of them results in a "victory" and the other in a victory.

Using Smart Spectra for Horror

To add way more suspense during gameplay, I added facial analysis using the SmartSpectraSDK. If you enable this feature in the setting, you will get two extra hurdles on your turns:

Granny being impatient:

If you take too long to make a decision during your turn, Granny will become suspicious that you're NOT enjoying her game. She will get in your face and check!

Smile! Don't let granny know how terrified you are. Anything less than a happy face will make granny angry and you'll get the bad ending.

I implemented this one by using SmartSpectra to track the player's ExpressionType. If it's anything other than HAPPY, granny's angry meter will fill up; her screams building up until she lunges at you.

Granny tries to cheat:

Granny's not perfect! She's a horror villain after all! At random times, she will twist her head and stare at you as you pick out a pit on your turn. DO NOT LOOK AT HER.

Again, I used SmartSpectra to detect when the player is looking towards the screen (or camera). If you fail to look away in time, granny will take a random pit and add the seeds into her pile. Bad news!

Demo

Please forgive the terrible recording. My PC has no webcam, so I had to run the game on my laptop, and I had to record my laptop screen using my phone because my laptop is old the camera couldn't generate enough frames for facial analysis while recording with OBS.

This is a me problem though, your laptop/computer should be more than fine.

Skip to 5:10 if you want to go straight to how SmartSpectra was utilized.

If you'd like to try this on your own, make sure to go to settings -> provide your own API key -> enable facial hurdles

Code

Here's the GitHub repo if you'd like to try it out on your own. Don't forget to bring your own API key. Remember, this game tries to conservatively use your credits and not waste it when it's not using it.

https://github.com/AnsellMaximilian/granny-is-watching

How It Responds to Human Signals

Since this is a quick action game, I wanted to focus on the signal SmartSpectra can pick up the fastest, which is the Facial Analysis. Surprisingly it picks up the metrics my game requires without much warmup at all, allowing me to conservatively use the SDK (and therefore the API credits) only when I need it.

Prize Categories

Yes, I'm submitting this for the Creative or Delightful use category.

First idea that popped into my head was using your face to play Flappy Bird. That that seemed to shallow. Luckily another wacky usage came across my mind: horror! You know how in horror games you have simulated "hold your breath" or "don't look" sessions. I thought I would immerse players even more using SmartSpectra.

Consent and Data Handling

This section describes the current app and its pinned SmartSpectra SDK configuration. The game developer does not operate a server that receives your camera feed, facial results, saves, or diagnostic logs.

Camera consent and control

  • On a fresh install, facial hurdles are off and opening the title screen does not start capture. Choosing Enable camera or enabling Facial hurdles starts a video-only getUserMedia request. No microphone permission is requested.
  • The app uses these controls and your remembered facial-hurdles preference as its opt-in mechanism. It has no separate facial-analysis consent dialog. If you leave facial hurdles enabled, starting or continuing a run can reopen the camera on later launches.
  • Electron allows video permission only for this game's bundled main page and denies microphone and other permission requests. The app does not add a browser-style permission prompt in Electron; the operating system may separately prompt, remember, or block access. Browser development uses the browser's normal site permission controls.
  • Hiding/collapsing the preview changes visibility only. Disabling facial hurdles stops analysis but leaves capture active. Settings → Stop camera releases the device; closing the game window also releases capture. To prevent automatic reopening, turn facial hurdles off too. OS camera privacy settings provide a separate way to revoke access.

Local storage, retention, and deletion

Data What is kept and for how long How to remove it
Camera frames and facial sample values Used in memory for preview, tracking, and gameplay. The game does not record video, save images, or write landmark coordinates, expression values, or physiological values to a history. Displayed readings clear when analysis stops. Stop the camera and close the game to release its capture/SDK resources. There is no game recording to delete.
Progress and preferences progress.json in Electron's user-data directory stores settings, burned pits, and the round's clue. It has no automatic expiration. Browser development uses the local-storage key granny-is-watching:progress:v1 for that site's origin. Turning Save progress off clears only the checkpoint. To remove preferences too, close the app and delete progress.json; clear the site's storage for browser development.
Local diagnostics The desktop app writes timestamps, a random run ID, camera resolution/FPS, frame timing, SDK status/error codes, gameplay phases, signal-availability flags, and counts of stable facial samples. No raw frames, face coordinates, expression classifications, device IDs, API keys, or native message text are written by this logger. It keeps one current file and one .previous file, rotating at 5 MiB each, with no age-based expiry. Logging also occurs when no camera is running. Close the app and delete both diagnostic files listed below. They are recreated on subsequent launches. They are never uploaded automatically by the game.
API key you enter in Settings The app keeps it in memory for the current window session and does not write it to progress or diagnostic files. A key you configure yourself in .env.local or an environment variable is persistent according to that configuration. Close the game to discard an entered key. Remove your .env.local/environment configuration separately. Removing a local key does not revoke it at Presage.
Electron and SDK caches Electron/Chromium can keep its usual local profile/cache files. SmartSpectra initializes separate SDK-managed device-key/runtime cache state. The pinned binding has a fallback SmartSpectraSDK directory in the OS cache base, which can be shared with other SDK hosts. The game sets no expiry for it and does not manage its internal contents. After quitting, remove the app's profile/cache for a local reset. SDK-cache deletion is separate and may affect other applications using that shared directory; it can be created again later.

For the normal Windows setup:

  • Progress: %APPDATA%\granny-is-watching\progress.json.
  • Packaged-app diagnostics: %APPDATA%\granny-is-watching\logs\smartspectra-diagnostics.jsonl and smartspectra-diagnostics.jsonl.previous in the same folder.
  • Source-run diagnostics: .playtest/smartspectra-diagnostics.jsonl and .playtest/smartspectra-diagnostics.jsonl.previous inside your checkout. .playtest/ is Git-ignored.
  • The SDK's Windows fallback cache is %LOCALAPPDATA%\SmartSpectraSDK. Its binding also attempts an app-namespaced cache when the host exposes one. This is SDK-managed storage, separate from the game's profile; do not assume deleting progress.json or uninstalling clears it. Other systems use OS cache locations and Electron's profile paths.

Quit the game before deleting files so it cannot rewrite them. Start new game resets the run, not preferences or diagnostic logs. Windows uninstall preserves application data, including saves and logs. Removing the user-data folder is a full local reset, but source-run logs, a developer's .env.local, OS permission choices, and the separate SDK cache need separate cleanup. An explicit --user-data-dir launch option changes the profile location.

Sharing, SDK telemetry, and third parties

Facial computation runs on-device. The game does not add an upload endpoint, analytics service, cloud save, or automatic log submission. Presage's SDK policy says camera images and face-derived data stay on the device. When tracking runs, the SDK still sends credentials/device-authentication information, technical request information, and session/usage records to Presage; their servers receive your IP address. These requests can be linked to your account. See the SmartSpectra SDK Privacy Policy, sections 3–4, on Presage's policy page.

The app explicitly sets enableTelemetry: false, disabling optional SDK session/readiness telemetry. It does not call device-readiness checks. This does not disable required authentication or usage/entitlement reporting. Our local diagnostic log is separate from SDK telemetry and is not submitted to Presage. See SDK telemetry and privacy.

Insights qualification: the game never calls requestInsight, sends an LLM prompt, or requests cardio/breathing metrics. Presage's broader SDK policy nevertheless describes automatic Insights through Presage and Anthropic, independently of telemetry. The Node Insights guide requires cardio plus breathing and a stable pulse for automatic vitals Insights; that trigger is not configured here. Native SDK network traffic has not been independently audited, so telemetry-off is not a blanket guarantee against SDK service processing. See SDK policy sections 4.2 and 7.

Provider retention and deletion: Presage controls its service records. SDK policy section 10 lists up to 365 days for usage/ordinary logs, longer security/backup retention, and account-based retention for authentication and Insights. Its telemetry documentation separately says readiness reports/access logs have no automatic expiration configured. This app sends no readiness reports; it cannot promise a deletion deadline for provider-held records. Local cleanup does not delete those records. Use the policy's section 11 contact/deletion process for requests to Presage.

Implementation references: camera lifecycle, permission handling, local progress, and diagnostic filtering/rotation.

Agent Session

Unfortunately, it seems after an update on Codex, the JSONL format generated is no longer detected very well on https://dev.to/agent_sessions/new. On upload and parse, it constantly shows 0 messages. Hopefully I'll be able to fix this and upload the actual agent session.

Top comments (0)