diff --git a/games/common/epsilon_files.h b/games/common/epsilon_files.h
index ae81760..4e1f12c 100644
--- a/games/common/epsilon_files.h
+++ b/games/common/epsilon_files.h
@@ -114,13 +114,50 @@ static uint32_t *ef_cache(const ef_fs_t *fs, int end) {
   }
   return found;
 }
+/* Calcade, 20 octobre 2026 : le même triple, retrouvé même quand l'adresse qu'il garde ne tombe
+ * plus sur un fichier (quelqu'un a déplacé les fichiers sans l'effacer : un jeu, ou le site par
+ * l'USB). Epsilon s'y fierait sans vérifier, lirait au mauvais endroit, et la calculatrice finirait
+ * par redémarrer en effaçant tout. Vide : NULL aussi (rien à faire). */
+static uint32_t *ef_cache_any(const ef_fs_t *fs, int end) {
+  if (epsilon_is_upsilon()) return ef_cache(fs, end);
+  uint32_t *after = (uint32_t *)(void *)(fs->buf + fs->size + 4), *found = NULL;
+  uint32_t base = (uint32_t)(uintptr_t)fs->buf;
+  for (int i = 1; i < 96; i++) {
+    uint32_t *w = after + i;
+    uint32_t accessible = w[0], crc = w[1], ptr = w[2];
+    if (accessible > fs->size || (int)accessible < end + 2 || accessible < fs->size / 2) continue;
+    if (!ptr) {
+      if (crc) continue;
+    } else if (!crc || ptr < base || ptr >= base + fs->size) {
+      continue;
+    }
+    if (found) return NULL;
+    found = w;
+  }
+  return found && found[2] ? found : NULL;
+}
 #else
 static uint32_t *ef_cache(const ef_fs_t *fs, int end) {
   (void)fs, (void)end;
   return NULL;
 }
+static uint32_t *ef_cache_any(const ef_fs_t *fs, int end) {
+  (void)fs, (void)end;
+  return NULL;
+}
 #endif
 
+/* Calcade : oublie le « dernier fichier lu » d'Epsilon (toujours sans danger : il le recherche
+ * alors dans la liste). À faire avant de rendre la main à Epsilon. */
+__attribute__((unused)) static void ef_forget_cache(void) {
+  ef_fs_t fs;
+  if (!ef_open(&fs)) return;
+  int end = ef_end(&fs);
+  if (end < 0) return;
+  uint32_t *cache = ef_cache_any(&fs, end);
+  if (cache) cache[1] = cache[2] = 0;
+}
+
 /* Removes a record; true if it is gone (or was never there). */
 __attribute__((unused)) static bool ef_remove(const char *name) {
   ef_fs_t fs;
diff --git a/launcher/src/games.c b/launcher/src/games.c
index 134d881..c6fe09d 100644
--- a/launcher/src/games.c
+++ b/launcher/src/games.c
@@ -81,12 +81,15 @@ int np_game_run(int i) {
     uint32_t taille = (uint32_t)((uint8_t *)_heap_end - ram);
     memset(ram, 0, taille);
     CalcadeContexte ctx = {CALCADE_ABI, ram, taille, np_coeur_lancements(), {0, 0, 0, 0}};
-    return m->lancer(&ctx) == CALCADE_QUITTER;
+    int r = m->lancer(&ctx);
+    np_storage_heal(); /* Calcade : un jeu a pu déplacer des fichiers sans effacer le cache d'Epsilon */
+    return r == CALCADE_QUITTER;
   }
 #endif
   /* fresh RAM, as if the game had just been launched from the home screen */
   if (g->data_size) memcpy(g->data, g->data_init, g->data_size);
   if (g->bss_size) memset(g->bss, 0, g->bss_size);
   g->main();
+  np_storage_heal();
   return 0;
 }
diff --git a/launcher/src/main.c b/launcher/src/main.c
index 2959d17..96d60ff 100644
--- a/launcher/src/main.c
+++ b/launcher/src/main.c
@@ -36,6 +36,7 @@ int main(void) {
   np_coeur_init();
 #endif
   np_session_begin();
+  np_storage_heal(); /* Calcade : un cache laissé périmé (par le site, un jeu...) est effacé d'abord */
   np_finish_pending_uninstalls();
   /* just updated? the saves come back from their copy, then the copy is refreshed */
   np_progress_restore();
diff --git a/launcher/src/np.h b/launcher/src/np.h
index 10c5c33..aba14d6 100644
--- a/launcher/src/np.h
+++ b/launcher/src/np.h
@@ -118,6 +118,7 @@ bool np_bg_save(int mode);
 /* A copy of every save in numplay_saves.py, a Python script: the only kind of
    file the NumWorks installer keeps when NumPlay is updated. */
 void np_progress_backup(void);
+void np_storage_heal(void); /* Calcade : efface le cache périmé d'Epsilon */
 void np_progress_restore(void); /* only when none of the saves are left */
 /* Matrices, the disguise: true when the secret was used (open NumPlay), false to quit */
 bool np_matrices(const np_config_t *c);
diff --git a/launcher/src/storage.c b/launcher/src/storage.c
index 53dbd53..c2e337e 100644
--- a/launcher/src/storage.c
+++ b/launcher/src/storage.c
@@ -30,6 +30,9 @@ static bool first_with_prefix(const char *p, int n, char *out, uint32_t size) {
   return false;
 }
 
+/* Calcade : voir ef_forget_cache (games/common/epsilon_files.h) */
+void np_storage_heal(void) { ef_forget_cache(); }
+
 bool np_storage_delete(const char *name) {
   int n = prefix_len(name);
   if (n < 0) return ef_remove(name);
@@ -317,14 +320,14 @@ void np_progress_backup(void) {
   if (!ef_write(COPY_NAME, NULL, total)) return;
   uint8_t *o = body(COPY_NAME, &len);
   if (!o || len != total) return;
+  uint8_t *stop = o + total - 1; /* (the last byte: the terminating zero) */
   *o++ = 0; /* not imported by the Python shell */
   for (const char *h = copy_head; *h; h++) *o++ = (uint8_t)*h;
-  uint8_t *stop = o + total;
   for (int i = 0; save_name(i, name); i++) {
     const uint8_t *d = ef_read(name, &len);
     if (!d) continue;
     uint32_t l = backup_line(name, d, len, NULL);
-    if (o + l >= stop) continue;
+    if (o + l > stop) continue;
     o += backup_line(name, ef_read(name, &len), len, o);
   }
   *o = 0;
diff --git a/launcher/src/sys.c b/launcher/src/sys.c
index 170c2a0..04b543b 100644
--- a/launcher/src/sys.c
+++ b/launcher/src/sys.c
@@ -1,4 +1,5 @@
 #include "sys.h"
+void np_storage_heal(void); /* Calcade : launcher/src/storage.c */
 
 #if PLATFORM_DEVICE && !NP_SIMULATOR
 #define USERLAND_MAGIC 0xDEC0EDFEu
@@ -81,6 +82,7 @@ void np_session_begin(void) {
 }
 
 int np_session_end(void) {
+  np_storage_heal(); /* Calcade : Epsilon reprend la main avec un cache juste */
   bool unlock = session_locked;
   for (volatile uint32_t *p = (volatile uint32_t *)(void *)_data_section_start_ram; p < (volatile uint32_t *)(void *)_heap_end; p++)
     *p = 0;
diff --git a/pack/coeur/coeur.c b/pack/coeur/coeur.c
index c7f118c..7e36634 100644
--- a/pack/coeur/coeur.c
+++ b/pack/coeur/coeur.c
@@ -86,6 +86,7 @@ static int lancer(int i) {
   memset(ram, 0, taille);
   CalcadeContexte ctx = {CALCADE_ABI, ram, taille, cc_launches(), {0, 0, 0, 0}};
   int r = m->lancer(&ctx);
+  np_storage_heal(); /* un jeu a pu déplacer des fichiers sans effacer le cache d'Epsilon */
   if (cache) memmove(ram, copie, cache);
   np_wait_release(); /* un Back qui a fini le jeu ne fait pas aussi « retour » dans le menu */
   return r;
@@ -184,6 +185,7 @@ bool coeur_message(const char *titre, const char *texte, bool fermer_par_ok) {
 /* ---------------------------------------------------------------- l'application */
 int main(void) {
   np_session_begin();
+  np_storage_heal(); /* au cas où le site ou un jeu l'aurait laissé périmé */
   decouvrir();
   cc_module_count = coeur_njeux;
   /* juste mis à jour ? les sauvegardes reviennent de leur copie, puis la copie est refaite */
