Export Web/SCORM : port complet du runtime jouable côté navigateur
Build and deploy / test-python (push) Successful in 9m20s
Build and deploy / test-js (push) Successful in 1m6s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

Phase 1.2 de la feuille de route produit — un paquet SCORM tourne seul
dans le LMS du client, sans serveur Forge Engine disponible. Ajoute
static/js/play/offline/ (miroir JS de screens/rendering/ et
data_actions/, sous window.FORGE_OFFLINE) pour que variables, score,
données d'objet, répéteurs et conditions de visibilité fonctionnent
entièrement en mémoire côté navigateur ; branche actions.js et
bindings.js dessus au lieu d'un fetch() serveur. Ajoute
publish/build_scorm_package.py (paquet statique + imsmanifest.xml
SCORM 1.2 + wrapper API SCORM) et le bouton "Exporter (Web/SCORM)".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-02 07:43:02 +02:00
co-authored by Claude Sonnet 5
parent 9776fbc057
commit f3b72d73c9
32 changed files with 2469 additions and 22 deletions
+31 -7
View File
@@ -252,6 +252,13 @@ function runActionNode(node) {
const ms = isNaN(seconds) ? 0 : Math.max(0, seconds) * 1000;
return new Promise(function(resolve) { setTimeout(resolve, ms); });
} else if (node.action_type === 'modifier_donnee') {
// Export Web/SCORM (voir static/js/play/offline/apply-actions.js) :
// aucun serveur disponible dans le paquet exporté — la même mutation
// s'applique directement sur gameData, en mémoire, dans le navigateur.
if (window.FORGE_OFFLINE) {
forgeApplyDataActionOffline(gameData, node);
return refreshRuntimeData();
}
const runDataUrl = FORGE_PLAY_URLS.flowNodeRunData.replace('999999', node.id);
// clicked_row_id/last_inserted_row_id : utilisés côté serveur
// SEULEMENT si ce nœud vise respectivement CLICKED_ROW_ID ou
@@ -264,6 +271,10 @@ function runActionNode(node) {
.then(r => r.json())
.then(() => refreshRuntimeData());
} else if (node.action_type === 'modifier_variable') {
if (window.FORGE_OFFLINE) {
forgeApplyVariableActionOffline(gameData, node);
return refreshRuntimeData();
}
const runVarUrl = FORGE_PLAY_URLS.flowNodeRunVariable.replace('999999', node.id);
return fetch(runVarUrl, {method: 'POST'})
.then(r => r.json())
@@ -271,22 +282,35 @@ function runActionNode(node) {
} else if (node.action_type === 'modifier_score') {
// Score/progression (voir db/scoring/, screens/data_actions/
// apply_score_action.py) : même patron que "Modifier une variable
// globale" ci-dessus, calculé et écrit entièrement côté serveur.
// globale" ci-dessus, calculé et écrit côté serveur EN LIGNE, ou
// directement en mémoire dans le paquet exporté (voir apply-actions.js).
if (window.FORGE_OFFLINE) {
forgeApplyScoreActionOffline(gameData, node);
return refreshRuntimeData();
}
const runScoreUrl = FORGE_PLAY_URLS.flowNodeRunScore.replace('999999', node.id);
return fetch(runScoreUrl, {method: 'POST'})
.then(r => r.json())
.then(() => refreshRuntimeData());
} else if (node.action_type === 'definir_statut_partie') {
if (window.FORGE_OFFLINE) {
forgeApplyStatusActionOffline(gameData, node);
return refreshRuntimeData();
}
const runStatusUrl = FORGE_PLAY_URLS.flowNodeRunStatus.replace('999999', node.id);
return fetch(runStatusUrl, {method: 'POST'})
.then(r => r.json())
.then(() => refreshRuntimeData());
} else if (node.action_type === 'ajouter_ligne') {
// Phase 4 — crée une ligne VIDE côté serveur ; son id devient
// window.lastInsertedRowId pour que les nœuds "Modifier une donnée"
// enchaînés (ciblant LAST_INSERTED_ROW_ID, "➕ Dernière ligne
// ajoutée") sachent quelle ligne renseigner. refreshRuntimeData()
// affiche la nouvelle ligne dans tout Répéteur lié dès ce round-trip.
// Phase 4 — crée une ligne VIDE (côté serveur en ligne, en mémoire
// hors ligne) ; son id devient window.lastInsertedRowId pour que les
// nœuds "Modifier une donnée" enchaînés (ciblant LAST_INSERTED_ROW_ID,
// "➕ Dernière ligne ajoutée") sachent quelle ligne renseigner.
// refreshRuntimeData() affiche la nouvelle ligne dans tout Répéteur lié.
if (window.FORGE_OFFLINE) {
window.lastInsertedRowId = forgeApplyAddRowActionOffline(gameData, node);
return refreshRuntimeData();
}
const runAddRowUrl = FORGE_PLAY_URLS.flowNodeRunAddRow.replace('999999', node.id);
return fetch(runAddRowUrl, { method: 'POST' })
.then(r => r.json())
@@ -418,6 +442,6 @@ function stopAllSpriteAnimations() {
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
resolveSpriteFrames, runSpriteAnimation, stopAllSpriteAnimations, activeSpriteAnimations,
applyObjectProperty, clampSceneObjectPosition,
applyObjectProperty, clampSceneObjectPosition, runActionNode,
};
}