PROTRAILBLAZER

Jev game director · Stochastic
Tallyjev-latest · 2 calls per second
Tension
--
Beat conf
--
Latency
--
Per hour
--

How does an AI game director read the player and choose the next encounter?

A game director does not need to generate anything. Twice a second the game sends its own telemetry as JSON state to a System One model and asks typed questions: how tense is the player, how competent, how stale is the action, and which of 14 designer-authored beats should come next. The answers return as scores and probability distributions in well under a second, and the director samples the next beat from those probabilities so no two runs pace the same way.

Arenamode: Jev director
HP 100
Ammo 60
Enemies 0
Run 0:00

Survive as long as you can

Move with the arrow keys or WASD. You fire automatically at the nearest enemy, and every shot costs ammo.

Twice a second the director sends your telemetry to a simulated Jev and picks the next beat. Watch the strip and the inspector while you play.

Press Start run. The director makes its first call half a second in.
Runswitching mode resets the run

The director asks Jev every 500 ms and samples a beat whenever the last one has finished.

Director striplast 60 s
measured tension target curve fixed script, last run hold code override
    Jev inspectorsimulated · POST /v1/systemone
    Simulated Jev responses A local mock. No request leaves this page.
    Request in flightidle

    The bar spans one 500 ms tick. The red edge is when the next request goes out.

    Four questions go out in each request: three score dials and one choice over 14 beats. Start a run to watch them update.

    Calls per second0.0
    Input tokens per call--
    Input price$0.042 / M tokens, output free
    Projected per hour of play--
    1,000 players for an hour--
    This run so far$0.00000
    Last request not sent yet
    {}
    Last response mock, documented shape
    {}
    How it workstelemetry in, typed answers out, code decides

    One call, four questions, every 500 ms

    Every tick the game packs its own numbers into a JSON state: health and how it moved in the last ten seconds, damage taken, kills, how many enemies are alive and how close they are, ammo, pickups left lying on the floor, how varied the player's movement has been, and the last six beats. It sends that state with four questions in one request. The model never sees the question ids (tension, next_beat); those keys exist for the code that reads the answers.

    every 500 ms:                                         // never blocks the frame loop
        send({ state: telemetry(), model: "jev-latest", questions })
    
    on answer(a):                                          // lands 70 to 500 ms later
        error = target_tension(run_time) - a.tension.score
        speed = a.competence.score > 2.5 ? 1.15 : 1.0
    
        if hp < 20 and a.tension.score > 3.2 and not rescue_running:
            run(sample({ medkit_drop, rescue }))           // code override, ignores the slot
        else if beat_running:
            return                                         // dials still update the chart
        else if a.next_beat.confidence < 0.35:
            log("flat distribution, holding")
        else:
            p = a.next_beat.probabilities
            if a.stagnation.score > 1.5:
                p = without(p, last_2_beats)               // renormalized
            run(sample(p))                                 // weighted random, not argmax

    Ask for dials, not verdicts

    "Is the player struggling?" is a yes or no. It flips back and forth as a fight swings, and code that branches on it slams between easy and hard. A score question returns a position on a ladder the designer wrote, five levels from "cruising, nothing threatens them" to "about to die", and the score is the probability-weighted mean of those levels. A 2.6 means a real fight leaning toward overwhelmed. That number can be compared against a target curve, plotted, and subtracted, which is what a pacing model needs. Competence and staleness come back the same way, from the same call that picks the beat.

    Why sampling beats argmax

    If the director always ran the single most likely beat, the same state would produce the same encounter every time, and players learn that pattern within two runs. Sampling from the probabilities keeps the designer's preferences (a swarm at 0.40 runs about four times in ten) while letting a lights out at 0.10 happen now and then. The confidence floor guards the other end. A flat distribution is the model saying several beats fit about equally, and rolling dice on noise is not pacing, so the director holds for a tick and asks again.

    Why code keeps the override rules

    Three rules sit between the answer and the spawn. A player under 20 HP who is close to panic always gets a medkit or a rescue, whatever the distribution says. A stale run cannot repeat either of its last two beats. A dominant player gets enemies 15 percent faster. These are the guarantees a designer has to be able to promise and test, so they live in code, not in instructions a model weighs. The model supplies judgment, the code supplies the rules, and the code has the final word.

    What a 500 ms worst case means at 2 Hz

    The director asks twice a second, so one tick is 500 ms. TypeSafe reports 70 to 500 ms end to end, so even the slowest answer lands before the next request goes out, and at most one request is ever in flight. The game never waits on it. The frame loop keeps drawing, and the answer is applied on whatever frame it lands. The inspector counts the frames drawn while each request was out. A beat that starts 300 ms late is invisible to a player; a frame that stalls for 300 ms is not.

    What it costs at game-loop rates

    Each call carries the full question set (the 14 beat descriptions are most of it) plus the telemetry, and only input is billed. The meter multiplies the measured call rate by the token count of each request and $0.042 per million input tokens. Two calls a second for an hour is 7,200 calls, which is why the numbers stay in cents rather than dollars, and why trimming the beat descriptions is the first optimization worth making.

    About this demo: every answer here comes from a local mock, and nothing on this page calls Jev, which is in early access. Tension is a pressure estimate from damage taken, enemies alive, how close they are, and health, turned into logits over the five levels. Competence comes from kills per damage taken and movement variety. Staleness comes from repeated beats and long quiet stretches. The beat logits come from a rules table keyed by rounded tension and the pacing phase, with jitter so no distribution is ever one-hot. All of them go through a softmax. Confidence is shown as 1 minus normalized entropy, an approximation of TypeSafe's unpublished formula. The state also carries the pacing phase and target tension, because a model can only weigh what it is sent. Latency is drawn uniformly from 70 to 500 ms, and token counts are the JSON length divided by 4.