Guide
Capabilities
Importing declares a requirement; a target declares what it provides.
Importing a module declares a requirement. A target declares what it provides. Everything interesting about how this language reaches a host follows from those two sentences.
The two prefixes
std/* is the language's own and is pure, so it is available in every host and a target may not decline it. drift/* is this engine's, and every module in it carries an effect. The prefix in the import tells you which you are looking at.
// The two prefixes, in one file.
//
// `std/math` is the language's own and is pure, so it links in any host. `drift/scene` is this
// engine's, and it carries the effect of reaching a scene. The prefix in the import is what tells
// you which of the two you are looking at.
//
// `Node` is not a language type either. It arrives with the capability, from whatever the host's
// scene is made of, which is why a consumer with no renderer still has a language.
import { clamp } from "std/math"
import { setRotation } from "drift/scene"
data Spin {
speed: f32 = 45deg
}
fn turn(spin: Spin, node: Node, dt: f32) {
let step = math.clamp(spin.speed * dt, 0, 90deg)
scene.setRotation(node, 0, 1, 0, step)
}
A module the target lacks is refused by name
Not ignored, and not stubbed. The refusal names the module, the target, and whether the capability exists in this host at all, which is a different answer from "no".
// The same source against two targets.
//
// One provides `drift/animation` and one does not, and the difference is a refusal with the
// module and the target named in it. Nothing about this file changes between the two.
//
// **A script drives a blend tree; it does not build one.** The tree is built where the skeleton
// and the clips already are, and handed to a system through `uses` — so what a script does with
// it is set its parameters and evaluate it. Both kinds of parameter are set the same way: a blend
// weight, and a clock, which is the time a `clip` node was told to read instead of the frame's.
import { blendSet, blendAt, pose } from "drift/animation"
data GaitState {
// Seconds, for anything that carries on while the character stands still.
phase: f32 = 0
// The distance the character has covered, on the gait clips' own axis.
stride: f32 = 0
}
fn step(state: mut GaitState, dt: f32, speed: f32) {
state.phase += dt
state.stride += dt * speed
}
fn drive(tree: Blend, state: GaitState, joints: u32) -> Pose {
let out = animation.pose(joints)
animation.blendSet(tree, "speed", 1.0)
animation.blendSet(tree, "gait", state.stride)
animation.blendAt(tree, state.phase, out)
return out
}
Which is what lets you write ahead
A file using a surface that has not shipped still parses and still type-checks. Only linking declines it, so the file is written once and links unchanged on the day a provider arrives.
// Written before this host provided the capability. It links now, and not a character of it moved.
//
// This is the property the whole language is arranged around, and this file is the receipt. It was
// written when `drift/ai` had no provider here: it parsed, it type-checked, and only linking
// declined it, naming the module and the target and whether the capability existed at all, which is
// a different answer from "no". Then the engine bound the module, and the file linked that day,
// unedited.
//
// So press compile and watch it link. The point is not that it works; it is that nothing about the
// file had to change for it to start working.
//
// What the script does and does not do is worth reading twice. It tells an agent that something
// happened and asks what the agent is doing. It does not receive a plan, and it never runs one:
// a model's output is not an execution path, and a tool resolves by id to something registered
// long before the model was asked.
import { agent, wake, degraded, intentId } from "drift/ai"
data Sentry {
alarmed: bool = false
lastIntent: String = ""
}
fn alarm(sentry: mut Sentry, id: String, priority: i32) {
if let found = ai.agent(id) {
ai.wake(found, "heard something", priority)
sentry.alarmed = true
}
}
// `degraded` is how a script asks whether the agent is over budget and running on its policy floor
// alone. A floor is deterministic and synchronous, so an agent with no provider still behaves, and
// the answer here is a quality signal rather than a failure.
fn observe(sentry: mut Sentry, id: String) -> bool {
if let found = ai.agent(id) {
sentry.lastIntent = ai.intentId(found)
return ai.degraded(found)
} else {
return true
}
}