EDI Self-Hosted Machine. It’s a template for a small website with a video player integrated with EDI and a few features like subtitle area over the video, a log panel and some floating status boxes. All EDI, VideoPlayer and Machine Features are exposed through a simple API that’s designed specifically for the IA to use.
You can build interactive video experiences integrated with FunScripts just by describing in natural language what you want, and IA will do the programming for you. I built a first experience almost without touching the code myself, even though I’m an experienced programmer — the goal was to see if, by giving instructions and asking for refinements, I could get ChatGPT to assemble the whole thing.
When running The Machine is controlled entirely with the mouse. There are a few inputs available:
- main click,
- secondary click,
- mouse wheel up,
- mouse wheel down,
- middle-wheel click,
- and double-click on either button.
To have IA build an experience for you, This JavaScript helper block into your prompt. Then ask it, using this framework/SDK, to create the experience you want on top of your video: pass the video filenames, the timestamps, what behavior you want in each section, what it should say, what it shouldn’t do with the devices, and how you want it to react.
Finally
- put the code in a new file
default-experience.jsin the same folder - Select EdiConfig.json In EDI Select Game
- Enter http://localhost:5000/Edi/Assets/index.html in your browser
Example Experience - Loked in Loop heaven - EDI Machine
Code Block for Prompt
// Mini EDI Host Machine (for AI usage)
// No implementation, only what exists and what it does.
// -----------------------------------------------------------------------------
// Core
// -----------------------------------------------------------------------------
// - <video> = master timeline (currentTime in seconds).
// - EDI plays "definitions" by name: type "gallery" | "reaction" | "filler".
// - Devices are assigned to logical channels ("stroker", "anal", "vibe"...).
// - Each channel has its own queue (gallery/filler/reaction).
// - An "experience" = module with createExperience(api) that returns handlers
// for mouse/wheel to control video + EDI.
// -----------------------------------------------------------------------------
// Types (conceptual)
// -----------------------------------------------------------------------------
// DeviceDto: {
// name, variants?, isReady?, selectedVariant?,
// channel?, min?, max? // intensity 0–100
// }
//
// DefinitionResponseDto: {
// name, type, // used in play(name)
// fileName?, startTime?, endTime?, duration?, loop?, description?
// }
// -----------------------------------------------------------------------------
// EdiRestClient (server side)
// -----------------------------------------------------------------------------
// new EdiRestClient({ baseUrl, defaultHeaders?, fetchImpl? })
// Devices:
// getDevices()
// // List devices with channel, variants and min/max intensity.
// setDeviceVariant(name, variant)
// // Change the active Script Variant of the device.
// setDeviceRange(name, { min, max })
// // Limit how much that device can go (0–100).
// setDeviceChannel(name, channel)
// // Connect device to a logical channel (stroker, anal, etc.).
// Playback (patterns):
// play(defName, { seek?, channels? }?)
// // Launch a pattern by name.
// // seek: starting point (seconds). channels: one or multiple channels.
// playSynced(defName, { channels? }?)
// // Same as play(), but uses current video time as seek.
// // Makes pattern align with video timeline.
// stop({ channels? }?)
// // Stop whatever is running (global or only on certain channels).
// setIntensity(max, { channels? }?)
// // "Master fader" 0–100 global or per channel, without changing base script.
// -----------------------------------------------------------------------------
// Host → Experience API (createExperience(api))
// -----------------------------------------------------------------------------
// Video helpers:
// api.getVideoTime() // current video time (seconds).
// api.getDuration() // total duration or NaN.
// api.getVideoElement() // direct access to <video>.
// api.onVideoTime(h) // subscribe to timeupdate ({ videoTime, ... }).
//
// api.playVideo(src?, seek?) // optionally change src, seek and play.
// api.pauseVideo() // pause.
// api.toggleVideo() // toggle play/pause.
// api.seekVideo(delta) // relative seek, clamp >= 0.
// api.setMuted(muted) // mute on/off.
// api.toggleMuted() // toggle mute state.
// api.setVideoSource(src) // change <video> src.
// api.getVideoElement() // direct access to <video> element.
// EDI passthrough (same as above):
// api.getDevices()
// api.setDeviceVariant(name, variant)
// api.setDeviceRange(name, { min, max })
// api.setDeviceChannel(name, channel)
//
// api.play(defName, { channels?, seek? }?)
// // Direct version: you choose explicit seek (not necessarily videoTime).
// api.playSynced(defName, { channels? }?)
// // Shortcut: seek = api.getVideoTime(). Ideal for video-tied events.
// api.stop({ channels? }?)
// api.setIntensity(max, { channels? }?)
// UI / debug:
// api.say(text, seconds?)
// // Show subtitle-like overlay (0/omit => until next say).
// api.log(message)
// // Write message to HUD with current time (useful for debug).
// api.setInfo(html)
// // Render HTML in info panel (controls-info).
// api.hideInfo()
// // Hide info panel.
// -----------------------------------------------------------------------------
// Experience module shape
// -----------------------------------------------------------------------------
// ctx = { videoTime, duration, rawEvent }
//
// export function createExperience(api) {
// // optional setup (subscribe to onVideoTime, configure devices, etc.)
// return {
// onMousePrimaryDown(ctx),
// onMouseSecondaryDown(ctx),
// onMousePrimaryDouble(ctx),
// onMouseSecondaryDouble(ctx),
// onMouseWheel(ctx, delta), // generic (delta = deltaY)
// onMouseWheelClick(ctx), // middle button
// onMouseWheelDown(ctx), // wheel down (deltaY > 0)
// onMouseWheelUp(ctx) // wheel up (deltaY < 0)
// };
// }
//
// URL Parameters:
// ?edi=<baseUrl> // EDI server base URL (default: 'http://localhost:5000')
// ?exp=<moduleUrl> // experience module URL (default: './default-experience.js')
//
// Global reference:
// window.ediVideoHost // EdiVideoHost instance available globally
Examples:
Simple Edge Pause with auto Resume
Prompt:
Using this SDK, create an experience that, on start, loads the video ./Cock Hero 1.mp4 (the definition named is Cock Hero 1) and begins playing it. Each time I press the primary click, all device activity should stop for 30 seconds, then resume using playSynced at 50% intensity for another 30 seconds, and finally return to 100% intensity. Throughout this cycle, the video continues playing
Result :
// Cock Hero Control Experience
// ---------------------------------------------------------------
// Behavior:
// - On start: loads and plays "Cock Hero 1.mp4".
// - On primary click:
// 1) Stop all device activity for 30 seconds.
// 2) Resume using playSynced("Cock Hero 1") at 50% intensity for 30 seconds.
// 3) Return to 100% intensity and keep playing synced.
// - Video never pauses.
export function createExperience(api) {
let running = true; // click lock to prevent re-triggering
// Simple async delay helper
const delay = ms => new Promise(res => setTimeout(res, ms));
// Start: load video and play from the beginning
api.playVideo("./Cock Hero 1.mp4", 0);
api.say("Loading Cock Hero 1…");
api.log("Experience initialized.");
// Main handler for primary click
async function handlePrimary() {
// Prevent multiple concurrent executions
if (!running) return;
running = false;
api.log("PRIMARY CLICK → Stop devices for 30s");
api.stop(); // stop all channels
api.setIntensity(0); // fully mute intensity
await delay(30000); // wait 30 seconds
api.log("PRIMARY CLICK → Resume at 50% for 30s");
api.setIntensity(50);
api.playSynced("Cock Hero 1"); // sync pattern with current video time
await delay(30000);
api.log("PRIMARY CLICK → Back to 100% intensity");
api.setIntensity(100);
api.playSynced("Cock Hero 1"); // keep aligned with video
running = true; // unlock handler
}
// Experience event map
return {
onMousePrimaryDown: ctx => handlePrimary(ctx),
onMouseSecondaryDown() {},
onMousePrimaryDouble() {},
onMouseSecondaryDouble() {},
onMouseWheel() {},
onMouseWheelClick() {},
onMouseWheelDown() {},
onMouseWheelUp() {}
};
}
Cock Hero With 3 Lifes, Ruin and Post Orgasm Alternative Endings
Prompt:
Using this SDK, create a Cock Hero experience with 3 lives and 2 alternate endings.
Load the video ./CockHero.mp4 (definition name: cockHero), which lasts 60 minutes.
When the user presses the primary click, pause all device activity for 30 seconds, then resume synced to the video and subtract 1 life.
If the player loses all lives before the 30-minute mark, jump to 60:00 where the ruined orgasm sequence starts.
If the player loses all lives after the 30-minute mark, jump to 65:00 where the full orgasm + punishment post orgasm sequence starts.
When the video reaches minute 60 during normal gameplay, stop the video so the later scenes remain hidden.
// cock-hero-lives-experience.js
// Cock Hero with 3 lives and 2 alternate endings
export function createExperience(api) {
// --- Config (seconds) ------------------------------------------------------
const VIDEO_SRC = './CockHero.mp4';
const DEF_NAME = 'cockHero';
const GAME_DURATION = 60 * 60; // 60 minutes of gameplay
const HALF_MARK = 30 * 60; // 30 minutes threshold
const RUINED_TIME = 60 * 60; // Ruined orgasm sequence
const FULL_TIME = 65 * 60; // Full orgasm + punishment sequence
const PENALTY_SECONDS = 30;
// --- State -----------------------------------------------------------------
let lives = 3;
let isGameOver = false;
let inPenalty = false;
let penaltyTimeoutId = null;
// --- Helpers ---------------------------------------------------------------
const formatLives = () => `Lives: ${'❤'.repeat(lives)}${lives === 0 ? ' (GAME OVER)' : ''}`;
function updateHud(message) {
const status = `${message}<br>${formatLives()}`;
api.say(status);
api.setInfo(status);
api.log(`[HUD] ${message} | ${formatLives()}`);
}
function clearPenaltyTimer() {
if (!penaltyTimeoutId) return;
clearTimeout(penaltyTimeoutId);
penaltyTimeoutId = null;
}
function startBasePlayback() {
api.playVideo(VIDEO_SRC, 0);
api.playSynced(DEF_NAME);
api.setIntensity(100);
updateHud('Cock Hero challenge begins (3 lives)');
}
function stopEverything(reason) {
if (isGameOver) return;
isGameOver = true;
clearPenaltyTimer();
api.stop();
api.pauseVideo();
api.log(`[END] ${reason}`);
}
function jumpToEnding(lastLossTime) {
const beforeHalf = lastLossTime < HALF_MARK;
const target = beforeHalf ? RUINED_TIME : FULL_TIME;
const label = beforeHalf ? 'Ruined orgasm 💀' : 'Full orgasm + punishment 😈';
isGameOver = true;
clearPenaltyTimer();
inPenalty = false;
api.playVideo(VIDEO_SRC, target);
api.playSynced(DEF_NAME);
api.setIntensity(100);
api.say(label, 15);
api.log(`[ENDING] Jump to ${label} at ${Math.round(target / 60)} min`);
api.setInfo(`${label}<br>You lost all your lives.`);
}
function applyPenalty(ctx) {
if (isGameOver) return;
if (inPenalty) return;
if (lives <= 0) return;
const lossTime = api.getVideoTime();
lives -= 1;
inPenalty = true;
api.stop();
updateHud(`You lost a life. ${PENALTY_SECONDS}s penalty…`);
if (lives === 0) {
penaltyTimeoutId = setTimeout(() => {
jumpToEnding(lossTime);
}, PENALTY_SECONDS * 1000);
return;
}
penaltyTimeoutId = setTimeout(() => {
inPenalty = false;
api.playSynced(DEF_NAME);
api.setIntensity(100);
updateHud('Back in action. Stay in rhythm…');
}, PENALTY_SECONDS * 1000);
}
// --- Video time listener ---------------------------------------------------
api.onVideoTime(({ videoTime }) => {
if (isGameOver) return;
if (inPenalty) return;
// If the player survives to minute 60 with lives left, stop the video.
if (videoTime >= GAME_DURATION && lives > 0) {
stopEverything('Reached minute 60 with lives remaining.');
api.say('Challenge complete. You reached 60 minutes, but no ending for you 😏', 15);
api.setInfo('Challenge complete.<br>You survived the full game but earned no ending.');
}
});
// --- Init ------------------------------------------------------------------
startBasePlayback();
// --- Event handlers --------------------------------------------------------
return {
onMousePrimaryDown(ctx) {
applyPenalty(ctx);
},
onMouseSecondaryDown(ctx) {
api.toggleVideo();
api.log('[Input] Secondary click: toggle video');
},
onMousePrimaryDouble(ctx) {
api.say(`Status — ${formatLives()}`, 3);
api.log('[Input] Primary double-click: show status');
},
onMouseSecondaryDouble(ctx) {
// Not used
},
onMouseWheelClick(ctx) {
// Not used
}
};
}