Learning paths
A learning path is a row of short lessons that build something in the user’s own project, step by step (“Your first quest”, “Plugins talk through messages”). This page is for authors of Pulse plugins: how a lesson is written, how the editor checks its steps, and how to keep lessons working. The lessons of Pulse Foundation itself are under Learn.
Users meet lessons in four places:
- Pulse window, tab Docs: the section LEARN lists the lessons of the plugin with their status and Start / Continue.
- Pulse window, button Guide in the head of a plugin: opens the lesson to continue with (the first one that is not finished). Plugins without lessons keep the guide of their four workflow steps.
- Pulse Guide panel: the started lesson, step by step. The editor checks each step while the panel is open, Show Me outlines the widget of the step and Do It does the step. Every step can be clicked; the lessons can be done in any order.
- Pulse HUB, tab Pulse Docs and the website: the same lessons as pages with Start in editor. Pulse HUB reads the progress the editor writes; it does not depend on Pulse Foundation.
Writing a lesson
A lesson is a page docs/learn/<lesson-id>.md. The file name is the lesson id: never rename it, the progress of users is stored under it. The full format is in the documentation format of PulseHUB (docs/plugin-docs-format.md in the PulseHUB repository); in short:
---
title: Your first quest
parent: Learn
nav_order: 1
lesson: true
track: blueprint # blueprint or cpp
minutes: 10
result: A quest that starts when the level begins and finishes after one objective.
builds_on: [] # lesson ids of the same plugin it uses (a hint, never a lock)
needs: [] # other plugins it needs, e.g. [PulseSave]
setup: MyPlugin.Learn.SetupFirstQuest # optional, see "Set up from here"
---
Short intro.
## Create the quest asset
{: .step id="create-quest" check="MyPlugin.Learn.HasQuestAsset" show="PulseStart:Pulse.Start.MyPlugin.Create" do="Pulse.CreateAsset(MyPlugin, Quest)" capture="learn/first-quest-create.png" }
Open **Add > Pulse > Quest > Quest** and name it `DA_FirstQuest`.

## Result
- A step is a
##heading with{: .step ... }on the very next line. Its first paragraph is the short text of the guide panel. Headings without.step(like “Result”) are plain sections. idis the stable step id (a-z,0-9,-); always set it.checktells whether the step is done. Without it the user ticks the step with Next.showis the widget Show Me outlines: a widget tag (FTagMetaData), optionally after the id of the tab that shows it.PulseStart:is the Pulse window: its tags are listed under Guides.dois the action of Do It.captureis the picturePulse.Docs.Capturewrites todocs/images/(see “Pictures” below).- At most 20 steps, values at most 200 characters, names only
A-Z a-z 0-9 _ . : ( ) / , -and spaces.
Checks and actions
check, do and setup name a check or an action: Name or Name(arg1, arg2); arguments are plain text without parentheses and commas. Pulse Foundation has the general ones:
| Check | Done when |
|---|---|
Pulse.HasProjectClass(<Class>) | A C++ class of the project derives from the class or implements the interface (UDataAsset or DataAsset). |
Pulse.ModuleDependsOn(<Module>) | A Build.cs of the project names the module. |
Pulse.SnapshotProviderSeen(<Category>) | A snapshot provider of that category registered in this editor session. |
Pulse.TimelineEventSeen(<Source>) | The timeline recorded an event of that source in this session. |
Pulse.MessageSeen(<Channel>) | A message on the channel (or a channel below it) was sent in this session. |
Pulse.DebuggerPageRegistered(<Id>) | A page of the Pulse Debugger has that id. |
Pulse.PluginEnabled(<Plugin>) | The plugin is enabled. |
Pulse.SetupDone(<Plugin>) | Every required setup step of the plugin is done. |
Pulse.AnySetupDone | A Pulse plugin (no bridge) has required setup steps and all of them are done. |
Pulse.HasProjectAsset(<Class>) | /Game has an asset of the class or a subclass (Blueprint for any Blueprint). |
Pulse.PlayedInEditor | Play in Editor ran since the lesson started. |
| Action | Does |
|---|---|
Pulse.NewCppClass(<Class>) | Opens the New C++ Class dialog of the engine with the class chosen. |
Pulse.CopyCode | Copies the first code block of the step. |
Pulse.OpenTab(<Id>) | Opens a tab (PulseStart is the Pulse window). |
Pulse.CreateAsset(<Plugin>, <Create entry>) | Creates an asset of a Create entry of the workflow. |
Pulse.RunFix(<Plugin>, <Step>, <Fix>) | Runs a fix of a setup step, like its button in the Pulse window. |
Pulse.OpenExampleMap(<Plugin>) | Opens the example map of the plugin. |
Pulse.PlayInEditor | Starts Play in Editor. |
The ...Seen checks read what came up while playing, by name only (see Debugging).
A plugin registers its own checks and actions as <Plugin>.Learn.<Name> in StartupModule of its editor module and removes them in ShutdownModule:
#include "Learn/PulseLearnRegistry.h"
Pulse::Editor::Learn::RegisterCheck(TEXT("MyPlugin.Learn.HasQuestAsset"), [](const FPulseLearnArgs& InArgs)
{
return !Pulse::Editor::Workflow::FindProjectAssets(TEXT("MyPlugin"), true).IsEmpty();
});
Pulse::Editor::Learn::RegisterAction(TEXT("MyPlugin.Learn.SetupFirstQuest"), [](const FPulseLearnArgs& InArgs, FText& OutError)
{
// Make the state after the first lesson in the open project; say why when it does not work
return true;
}, LOCTEXT("SetupFirstQuest", "Set up"));
// ShutdownModule
Pulse::Editor::Learn::UnregisterCheck(TEXT("MyPlugin.Learn.HasQuestAsset"));
Pulse::Editor::Learn::UnregisterAction(TEXT("MyPlugin.Learn.SetupFirstQuest"));
- A check runs twice a second while the guide shows the lesson: keep it cheap (no asset loading, no file scans of the whole project).
- An action does what the user would do by hand and returns false with a reason when it cannot. It never writes source code: C++ steps copy code (
Pulse.CopyCode) or open the engine’s dialog. FPulseLearnArgshas the arguments of the call (Get(0)), the lesson and the step.- A step whose check passed stays done, even if the check fails later.
Set up from here
Lessons can be started in any order. When a lesson builds on others whose steps are not done in the project, the guide offers Set up from here: it runs the setup action of the lesson, or without one the do actions of every step of the lessons before it, in their order, in one undo transaction. Steps whose check passes are skipped; steps without an action are listed to do by hand. Start anyway starts without it, and **Enable
Progress and starting a lesson
The editor writes the progress of the project to Saved/Pulse/Learn/progress.json after every change; Pulse HUB reads it:
{ "v": 1, "plugins": { "PulseQuest": { "first-quest": { "done": ["create-quest"], "finished": false, "updated": "2026-10-16T14:00:00Z" } } } }
Pulse.Learn.Start <Plugin> <lesson-id> ["<bundle.json>"] [--step=<step-id>] starts a lesson in the guide panel. Without a bundle it reads Resources/Docs/pulse-docs.json of the plugin (made with docs:build, see the docs rules); Pulse HUB passes the docs it shows. In C++: Pulse::Editor::Learn::StartLesson (Learn/PulseLearnGuide.h).
Keeping lessons working
The spec PulseFoundation.Editor.Learn.Drift reads docs/learn/*.md of every enabled Pulse plugin (the Markdown, so it works before docs:build) and fails when
- a
check,doorsetupnames nothing that is registered, - a
showtag of the Pulse window (PulseStart:) does not exist, for example a setup row of a step that was renamed (tags of other tabs are not checked), builds_onnames no other lesson of the plugin, a step id is used twice or invalid, or{: .step }is not right below a##heading.
Names <Plugin>.Learn.* are only warnings while the plugin registers none of that prefix, so lessons can be written before their checks, and names of a lesson whose needs are not enabled are warnings too. Run the spec after renaming setup steps, Create entries or tags. The same check is public for specs of your own: ReadLessonFolder and CheckLessonSources (Learn/PulseLearnDrift.h).
While you go through a lesson in the editor, Pulse.Learn.Check <Plugin> [<lesson-id>] logs the state of every step (done, passes, open, next, NOT REGISTERED) and Pulse.Learn.Do <call> runs an action like Do It.
Pictures
Pulse.Docs.Capture <Plugin> [<lesson-id>] writes the pictures of capture in the editor: it opens a new empty level (the open one must be saved), runs the do actions of the steps before each picture, opens the tab of show, outlines the widget and saves the window around it (at most 1600 px wide) to docs/images/<capture>. At the end it lists the pictures it wrote, the steps it skipped (no do; Pulse.PlayInEditor, Pulse.CopyCode and Pulse.NewCppClass are not run for pictures) and the widgets it did not find. It needs a renderer: under -nullrhi it only logs “skipped”. Take the pictures in a small project made for them, not in a game.