Two ways through this page
Same tool, two readings of it. You can switch at any point from the top right.
Not sure? Take the designer's route. Nothing is hidden, only reordered.
Pilot build · Windows and Linux
F4xAPI Studio is a desktop course builder that hands you the reporting. You choose what counts as done, what an attempt is worth, and what the course should do about it while the learner is still inside it. Send someone back through the practice they need. Leave the reading they understood alone.
F4xAPI Studio is a desktop course builder for people who write code. Every block emits xAPI, and every statement passes through your override before it leaves the page. Sandboxed HTML blocks, Monaco exercises, a theme layer of custom properties and classes, and an open package format.
Authoring runs offline · Windows and Linux · Build 11 September 2026
Completion tells you someone reached the end. It never tells you what the end cost them.
Every argument on this page comes back to that sentence. Peirce would call that number a sign: it constructs meaning rather than reporting it, and a course that cannot answer past it is a document with a progress bar.
The problem this exists for
Twelve chapters. A simulation you are proud of. A final assessment with a pass mark someone agreed to in a meeting. It goes out, and six weeks later the LMS says 84% completed. That number is the whole report.
Completion says a learner reached the end. It says nothing about the route. Nobody can tell you that a third of them opened the glossary four times on chapter six, that the exercise everyone passes takes eleven attempts to pass, or that the elegant interaction on page two is being skipped by anyone who finds the Continue button first.
xAPI already carries that detail. Every interaction can become a statement: who did what, to which thing, with what result. The catch is that most authoring tools decide the statements for you, and their idea of "done" is usually "seen".
F4xAPI hands the statements back. You choose which interactions count, what an attempt is worth, and what a result means. You can read those signals while the learner is still in the course and act on them: reset a section someone clearly did not absorb, score a simulation on the decisions made rather than the outcome reached, send a learner back through the practice while leaving the reading they understood intact.
Instrument a course this way and it starts answering questions about itself. Where do people stall. What gets retried. Which explanation costs three attempts and which costs none. Dewey's argument was that learning happens in the exchange between a person and a situation rather than in delivery; a course that cannot see the exchange has no way to improve it. That is a design instrument as much as a course builder, and it is why this tool exists.
Completion tells you someone reached the end. It never tells you what the end cost them.
Every feature further down this page exists to answer the second half of that sentence.
Why this, why now
Ask for a management simulation or a branching negotiation and you will have something that runs before the meeting to scope it has finished. The artefact stopped being the hard part. Whether it teaches anything is still yours — and the flight simulator was Forrester's and Sterman's idea for exactly that reason: compress the delay between a policy and its consequence until someone feels it instead of reading about it.
A designer who can hold a system in their head is worth more as the bar drops, not less: generation raises what a client will ask for, and someone has to keep the result coherent. A developer dropped into course work reinvents gates and remediation badly, because they do not look like routing and state.
Both get the same primitives. Refs, gates, resets, reveals and signals are first-class, so the designer wires logic without a build step and the developer gets the pedagogy already named. The handover becomes a file, not a document.
One course, three honest descriptions
The one holding the trunk says snake. The one at the leg says tree. The one at the ear says fan. Nobody is lying and nobody is right, because each of them has hold of a real part and none of them has hold of the animal.
"It moves, one thing after another."
The course as a learner meets it: a sequence, start to finish.
"It is solid, and it holds things up."
The course as a structure: chapters, sections, blocks, properties.
"It is wide, and it doubles back."
The course as a system: gates, loops, coupling between chapters.
Most tools hand you the structure, some add a preview, almost none show the system — which is where the interesting failures live. Three views of one file, switchable while you work.
Schön split this two ways: reflection-in-action, which happens while you are inside the work, and reflection-on-action, which happens when you step back. An override is the first. The flow and the matrix are the second. The mistake is not picking the wrong view — it is owning only one of them and calling the result a course.
Systems thinking, applied to a course
Engineering has reasoned about coupled systems for decades. Learning design has a storyboard. Two of those instruments are in the tool: a BPMN flow of how the course behaves, and a matrix of what depends on what.
Swipe the diagram →
Read the triangles. Marks below the diagonal are remediation, marks above it are escalation, and a number is how many signals share the cell. The two pulsing far from the diagonal are the ones worth arguing about: distance from the diagonal is distance travelled, and both of these move a learner across the whole course in one statement. Ackoff's point about local optimisation is the whole reason to draw this: tidying one chapter in isolation is how the next one breaks. Fold a chapter by its arrow and its signals roll up rather than disappear.
Sections are sub-processes, gates are gateways, a thin circle opens a chapter and a heavy one closes it. Dashes animate toward the arrowhead, so direction reads before the label does.
Nothing drags, nothing is measured from the DOM. Edges run in reserved channels above and below the row, so an arrow never crosses a task and a crossing hops rather than reading as a join.
View, interaction, all items, score threshold, or your own predicate. Then
show(), hide() and setRequired() let a
running course change what it asks of the learner in front of it.
Backwards is remediation: reset a block, a section or a chapter. Forwards is escalation: reveal the harder path for someone clearly ahead. A course doing both is a control system.
Two goTo calls on one branch, or a hide of the
thing you then move to: valid JavaScript, so a language service has nothing to
say. The conflict checker marks them as errors and disables Save until they
are gone, so the override never reaches the project. Two goTo
calls in opposite arms of an if are correct and are left alone.
The overview rolls feedback up to sections; open one to find the block driving it. Tabs, accordions and card decks open too.
The thing it is actually for
Ambitious courses do not die on the idea. They die on the repo, the bundler, the dev server and the two years of maintaining all three. The editor is inside the tool, so the ambitious version costs about what the ordinary one does.
Monaco with autocomplete over the block's own props and the course API. Preview is the running course, not a refresh cycle. The output is a package, not a project someone inherits.
No build step, no node_modules, and no second machine to
reproduce before a colleague can open your work.
Overrides, custom HTML and code exercises all edited where the block lives, with completion for the whole scope and five editor themes.
Desktop, tablet and phone frames, learner progress kept across edits, and a reviewer bypass that no export can contain.
H5P or SCORM, verified first, with no repository, pipeline or hosting arrangement left behind for someone to look after.
For learning designers
A loop, watched from the outside
The capstone of the built-in course is a 3D printer that runs and reports nothing. The learner has to wire up its reporting. Here is a run that goes wrong, and the rule that turns it into a lesson instead of a wall.
The commission. The machine works. Nothing it does reaches the course, so nothing the learner does is worth anything yet.
It sits in the properties panel beside the block, not buried in the content. Three failures, and the learner is returned to the exercises in the materials chapter. Only the exercises. The reading they already understood stays finished, because making someone re-read what they knew reads as punishment and they stop trusting the course.
Change 3 to 2 and the loop tightens. Change
'materials' and it points somewhere else. That is the whole
edit.
Autocomplete covers the whole scope, the built-in course walks you through this exact pattern, and a mistake here reports nothing rather than breaking the block.
Design decisions, not settings
Most of them are continuums that tooling flattens into booleans. Done, correct, ready: each one has degrees, and the degrees are where the information was.
A block can complete on sight, on any interaction, on every item being opened, on a score you set, or on a rule you write. The accordion that completes when one row opens in an onboarding course can demand all five in a compliance one, with no change to the accordion.
Keep the best attempt, keep only the first, pay less for each retry, give half marks for an answer that is one step off. Retries still report, so a learner who kept going does not look like one who walked away.
Reopen one block, one subsection, or a whole chapter — and filter by the kind of block, so a rewind can hit the exercises and leave the reading alone. Each reset reports how much it actually undid.
A learner moved without explanation assumes the course is broken. The preview shows the banner they will see, and the wording is yours: say what to review, not that they failed.
Answers changed before submitting. Rows reopened. Attempts before a pass. Blocks where progress stops and never resumes. The matching block reports how much reworking a learner needed, which is a different question from whether they got it right.
Export to H5P and every statement arrives with its verb and result. Export to SCORM and the bridge collapses it to one score and one status, because that is all SCORM defines. Knowing which you are shipping into changes what is worth measuring.
Look and feel
The theme designer sets brand colours, type, corners, spacing and a course logo. Every themed piece carries a class, so chapter titles, callouts, cards, tab headers, buttons, progress bars and feedback states move together — including the code editor a learner types into, which takes its own theme so a dark course does not end up with a white editor in the middle of it.
The five below are illustrations rather than presets, built to show how far the theme layer moves. Press one and watch both panes change together.
PETG needs a hotter nozzle than PLA. Below 230°C it will not bond, and the rig refuses to start rather than laying down a part that comes apart in the hand.
// three refusals, then send them back state.fails = (state.fails || 0) + 1; if (!statement.pass && state.fails >= 3) { course.resetChapter('materials', { type: 'exercise' }); course.goTo('materials'); state.fails = 0; }
These five are illustrations, not shipped presets — you build your own in the theme designer, from colours, type, corners and spacing. The editor themes are real: forge-light is the default, with paper, slate, midnight and contrast alongside it.
Custom interactions
A Custom HTML block takes markup, styles and script and runs them in a sealed frame. This is where a model-generated interaction goes: paste it in, give it the four calls it needs to report back, and it becomes a gradeable part of the course rather than a decoration.
The printer in the built-in course is one of these. It reports material selection, temperature changes, a refused start and a finished print as separate statements, which is why a rule could tell the difference between someone who is stuck and someone who is experimenting.
forge.complete() · forge.score(raw, max) · forge.emit(verb, result) · forge.resize(). That is the entire surface.For front-end developers
The surface area
01 · Override framework
Every component ships a default statement. A callout reports
experienced when it is seen, a question reports
answered with a score. An override replaces that default with
your own code, written in Monaco with autocomplete for the whole scope, and
attached to the block rather than forked into it. The same accordion can
complete on one interaction in one course and only after every row is read
in another.
emit, state, statement, props. State persists for the life of the block, so attempts and partial progress are countable.course.get(), uncomplete(), reset(), goTo(), resetSection(), resetChapter() — addressed by ref, so inserting a chapter never breaks a pointer.goTo calls on one branch, a hide followed by a move to the same ref — are marked as errors in the gutter and Save stays disabled until they are fixed, so they never reach a project. Ambiguous cases, like two goTo calls in opposite arms of an if, are left alone.02 · Custom HTML
A Custom HTML block runs your fragment in its own frame with a unique
opaque origin. It cannot read the page around it, touch storage or make
network calls, and it reports back through one object:
forge.complete(), forge.score(),
forge.emit(), forge.resize(). A machine
simulation can report material choice, a refused start and a finished run
as three separate statements, which is detail no host could have inferred
from outside.
The frame receives the theme as custom properties, and every themed element carries a class you can style globally.
The theme layer
A theme is a token set plus element and component overrides, adopted through a
constructable stylesheet so a component that mounts mid-session is correct on
its first paint rather than after the next panel touch. Code surfaces carry
their own palette — the exercise editor, the read-only samples and the code
frame follow the theme's editor tokens instead of a hard-coded white. Editor
themes are forge-light by default, with paper,
slate, midnight and contrast.
Values are sanitised on the way in: url(),
expression() and @import are rejected, and
structural properties — position, display,
z-index, overflow, width — are absent
from the allowlist entirely, so a theme cannot hide navigation or collapse
the page.
03 · Code exercises
A code exercise is a Monaco editor with a Validate action. Match against up to twenty accepted answers, add requirements that must appear regardless of shape, or post the submission to your own test service and score whatever comes back. Submitted code is never executed in the course.
For front-end developers
Documentation where you are typing
Each component declares a schema. That schema builds the properties panel and
feeds autocomplete inside the override editor, so props. lists
what this block actually has, with types, rather than what you remember from
the last one.
Block, ComponentKey, ProgressionConfig, AnimationConfig, XAPIStatement and the canvas message union are all declared, so an editor can tell you what a field accepts before you save.Where the code runs
Authoring is local. Untrusted markup is contained by construction rather than by a setting someone can forget to switch on.
Projects, assets, drafts and snapshots are files on your disk. Authoring works with the network off — on a plane, on an air-gapped review laptop, in a room where the content cannot leave. The network is needed for the licence check and for compilation, and what crosses it is project data you chose to export, not a mounted workspace.
The design canvas shows Custom HTML as inert source — no iframe, no Blob URL, no postMessage. Preview runs it for real in the sandbox. The exported package emits that same sandbox. Tests assert containment on both live surfaces and assert the canvas frames nothing.
Each block loads in its own frame with a unique opaque origin.
allow-same-origin is never granted beside
allow-scripts, the CSP withholds connect-src so a
block cannot phone anywhere, and message source windows are checked before a
statement is believed. A pasted fragment cannot read your page, your storage
or your cookies.
A code exercise matches a submission against accepted answers, checks it against your requirements, or posts it to a validation service you control. Nothing runs learner code in the course, so a clever answer is a marking problem rather than an execution risk.
The inline HTML sanitiser takes 24 XSS payloads in the suite — scheme
obfuscation, control-character smuggling, style-based exfiltration, handler
casing — and legitimate formatting has to survive them. Theme values get
their own: url(), expression() and
@import are rejected, and structural properties are not in the
allowlist at all.
An author editing their own project already controls it, so there is no escalation to prevent. What the runtime contains is error: a fixed four-name scope, a verb allowlist enforced at the bridge so a typo cannot reach the LMS as a novel activity type, and a throwing override that reports nothing rather than taking the block down.
A .forge5p is canonical JSON, deflated and base64url-encoded
inside a delimited text envelope, and checksummed — so corruption and
truncation surface on open instead of half-loading. That is obfuscation, not
encryption, and the API says so in as many words: two lines will decode and
inflate it. When confidentiality is the actual requirement,
serializeEncrypted() gives AES-256-GCM with a PBKDF2-derived
key, a fresh salt and nonce per export, and a wrong passphrase that fails to
decrypt rather than yielding altered content.
Client code cannot be made secret, so the client is treated as public and the machinery assumes it. Activation exchanges a key for an RS256 token carrying tier, seats, expiry and a machine fingerprint; the client verifies the signature offline against a public key compiled into the binary, so the private key never touches a machine you hand out. Authoring keeps working without a network for a thirty-day grace window, and the fingerprint is compared by similarity rather than equality, so new RAM does not cost somebody their seat.
Compilation sits behind that check, which is what stops an unlicensed copy shipping a package. It takes arbitrary author-supplied HTML and never evaluates it, payloads are size-capped and rate-limited per licence, and a compile takes a per-seat lock so one seat cannot fan out into a build farm.
Built in · not a PDF
Ten chapters, authored in F4xAPI Studio and opened from the project list. It is the thing it teaches: it gates on the work you do, marks what you write, sends you back when you have not got it, and ends with the printer from the story above. Learn the tool by being a learner in it.
Logic you would otherwise write yourself
Every one of these is a day of work and a fortnight of edge cases when you build it by hand for one course.
Tick autoplay on three videos and most tools start all three. Here they queue: only what is on screen, in document order, each waiting for the last. Finished means what the block says — a video completing on questions releases the queue when they are answered. One the learner abandoned is never restarted for them.
Questions on the timeline, each reporting answered with its
score. Done means watched, answered, or either — your choice. Captions and
poster where you need them.
Stored once locally, picked into the projects that use it, with folders and search. Imports run on a queue that outlives the panel. Images carry their bytes into the package; video is referenced by stream URL, and the card says so on its face rather than failing at compile.
Every signal a preview run would send, as the actual statement: real verb and object IRIs, the result object as it ships. Docked beside the course while editing, and absent from every export.
A reset, jump or reveal can carry a message. Being moved with no explanation reads as a broken course, so the wording is an argument to the call rather than an afterthought.
Rapid edits to one target collapse into a single entry, so typing does not leave you undoing letter by letter.
The library
Each block carries a ref, a progression rule, an entrance animation and an optional override. Tabs can hold other blocks, one level deep, which is enough for layout and not enough to lose track of.
Before it ships
Preview is the learner view under the constraints the export will meet, in a desktop, tablet or phone frame. Keep the progress from your last run while you edit, or switch on review mode to walk a long course end to end with every gate bypassed. Review mode is not a field on a block, is never written to a project file and the compiler has never heard of it, so no course can ship with its gates open.
.forge5p text file per project: content, theme and embedded assets in a delimited envelope, opened and saved locally..forge5p as readable by anyone holding it.serializeEncrypted() is AES-256-GCM with a PBKDF2-derived key, a fresh salt and nonce per export, and an auth tag — for handing a file to someone who should not be able to read it. Don't version-control that form: the random salt and IV mean the same project encrypts to different bytes every time, and the ciphertext will not compress, so the repo accumulates opaque blobs with no diff, no merge and no blame. Commit the plain archive and keep the repository private.Export
The package is a zip of content and code in an open format. Anything that supports H5P can serve, edit or extract it, and nothing downstream needs this editor.
Moodle and anything else that reads H5P receives every statement with its verb and result, per-block scores kept separate, completion for each block and for the course, and whatever your overrides emitted. A report can name the block a learner stalled on.
A bridge translates the course's own xAPI into CMI calls. SCORM has a fixed vocabulary, so per-block detail collapses into a rolled-up score, a completion status and a pass or fail. Use it when the LMS cannot take H5P, not by preference.
Getting started, writing an xAPI override, custom HTML blocks and code exercises, opened from the toolbar. The worked examples are run by the test suite rather than proofread, because an example that has drifted out of correctness is worse than none.
Who this is for
You already argue about what completion should mean. Here that argument has somewhere to live: a short rule attached to a block, separate from the content, different for the client who wants it different. You can read a model-generated interaction well enough to wire it up, and you want the course to answer back while a learner is still in it.
Monaco, an open package format, sandboxed opaque-origin frames, a theme layer built on custom properties and classes, canonical serialisation that makes an unchanged project export byte for byte, a single text file that commits and merges as text, and override parsing that runs before export. Course tooling that behaves like tooling.
If your courses are readings and quizzes, this is overhead with no return. It earns its keep when the training needs an interface that does not exist yet, and needs that interface to be assessed rather than watched. The longer version of the thinking behind all of this — Peirce, Dewey, Forrester, Ackoff, Schön — is on the Ground & Form philosophy page.
Your LMS will keep telling you they finished. Everything else is yours to instrument.
Pilot phase
F4xAPI Studio is in pilot. The download is the trial: author, preview, theme, write overrides and work through the built-in course, all of it offline. Activating a licence unlocks H5P and SCORM export. Tell us what breaks.
Download the x64 Windows installerFor an export licence, email tigerteam@groundandform.com and say a little about the course you want to ship.
.forge5p.
Changelog