about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2015-07-17 12:51:32 -0700
committerKartik K. Agaram <vc@akkartik.com>2015-07-17 12:58:37 -0700
commit32cd40ec3c9dad33738caf6f55fb742a316bd5be (patch)
treec6612fefc35741b43e1058826445d2913e94b3ba
parentfe9e53ed19f84a1771d56bfa0cf7d1d017e07559 (diff)
downloadmu-32cd40ec3c9dad33738caf6f55fb742a316bd5be.tar.gz
1799 - continue to debug memory corruption of 1795
Things I figured out:
- 'row' in render-screen doesn't perfectly track cursor-row in screen
- proximal cause was forgetting to add left:number to stop-printing
- trying to print to screen outside bounds was silently succeeding and
  corrupting simulated memory
- if we silently ignore prints outside bounds things are fine

But why are prints outside screen bounds working? We should be accessing
screen data using 'index', and that's checking its bounds.
-rw-r--r--029debug.cc29
-rw-r--r--036call_reply.cc1
-rw-r--r--043new.cc2
-rw-r--r--044space.cc1
-rw-r--r--071print.mu105
-rw-r--r--081run_interactive.cc76
-rw-r--r--edit.mu355
7 files changed, 329 insertions, 240 deletions
diff --git a/029debug.cc b/029debug.cc
index 301cd01a..90a1159e 100644
--- a/029debug.cc
+++ b/029debug.cc
@@ -103,3 +103,32 @@ case _DUMP_MEMORY: {
   dump_memory();
   break;
 }
+
+:(before "End Primitive Recipe Declarations")
+_DUMP,
+:(before "End Primitive Recipe Numbers")
+Recipe_ordinal["$dump"] = _DUMP;
+:(before "End Primitive Recipe Implementations")
+case _DUMP: {
+  reagent after_canonize = canonize(current_instruction().ingredients.at(0));
+  cerr << current_recipe_name() << ": " << current_instruction().ingredients.at(0).name << ' ' << current_instruction().ingredients.at(0).value << " => " << after_canonize.value << " => " << Memory[after_canonize.value] << '\n';
+  break;
+}
+
+:(before "End Globals")
+long long int foo = -1;
+:(before "End Primitive Recipe Declarations")
+_FOO,
+:(before "End Primitive Recipe Numbers")
+Recipe_ordinal["$foo"] = _FOO;
+:(before "End Primitive Recipe Implementations")
+case _FOO: {
+  if (current_instruction().ingredients.empty()) {
+    if (foo != -1) cerr << foo << ": " << Memory[foo] << '\n';
+    else cerr << '\n';
+  }
+  else {
+    foo = canonize(current_instruction().ingredients.at(0)).value;
+  }
+  break;
+}
diff --git a/036call_reply.cc b/036call_reply.cc
index 22eeea79..618ad651 100644
--- a/036call_reply.cc
+++ b/036call_reply.cc
@@ -51,6 +51,7 @@ case REPLY: {
         raise << current_recipe_name() << ": 'same-as-ingredient' result " << caller_instruction.products.at(i).value << " from call to " << callee << " must be location " << caller_instruction.ingredients.at(ingredient_index).value << '\n';
     }
   }
+  // End Reply
   break;  // continue to process rest of *caller* instruction
 }
 
diff --git a/043new.cc b/043new.cc
index bd5dd7bf..18d93486 100644
--- a/043new.cc
+++ b/043new.cc
@@ -242,7 +242,7 @@ if (Free_list[size]) {
   Free_list[size] = Memory[result];
   for (long long int curr = result+1; curr < result+size; ++curr)
     if (Memory[curr] != 0)
-      raise << "memory in free list was not zeroed out; somebody wrote to us after free!!!\n" << die();
+      raise << current_recipe_name() << ": memory in free list was not zeroed out: " << curr << '/' << result << "; somebody wrote to us after free!!!\n" << die();
   if (SIZE(current_instruction().ingredients) > 1)
     Memory[result] = array_length;
   else
diff --git a/044space.cc b/044space.cc
index 4469e47f..40f0f5ba 100644
--- a/044space.cc
+++ b/044space.cc
@@ -178,6 +178,7 @@ void try_reclaim_locals() {
   if (inst.name != "local-scope")
     return;
 //?   cerr << inst.to_string() << '\n'; //? 1
+//?   cerr << current_recipe_name() << ": abandon " << Current_routine->calls.front().default_space << '\n'; //? 1
   abandon(Current_routine->calls.front().default_space,
           /*array length*/1+/*number-of-locals*/Name[r][""]);
 }
diff --git a/071print.mu b/071print.mu
index f0be8cfa..e8071b6e 100644
--- a/071print.mu
+++ b/071print.mu
@@ -69,6 +69,26 @@ recipe clear-screen [
   reply x:address:screen/same-as-ingredient:0
 ]
 
+recipe fake-screen-is-clear? [
+  local-scope
+  screen:address:screen <- next-ingredient
+  reply-unless screen:address:screen, 1:literal/true
+  buf:address:array:screen-cell <- get screen:address:screen/deref, data:offset
+  i:number <- copy 0:literal
+  len:number <- length buf:address:array:screen-cell/deref
+  {
+    done?:boolean <- greater-or-equal i:number, len:number
+    break-if done?:boolean
+    curr:screen-cell <- index buf:address:array:screen-cell/deref, i:number
+    curr-contents:character <- get curr:screen-cell, contents:offset
+    i:number <- add i:number, 1:literal
+    loop-unless curr-contents:character
+    # not 0
+    reply 0:literal/false
+  }
+  reply 1:literal/true
+]
+
 recipe print-character [
   local-scope
   x:address:screen <- next-ingredient
@@ -85,15 +105,34 @@ recipe print-character [
     break-if bg-color-found?:boolean
     bg-color:number <- copy 0:literal/black
   }
+  screen-width:number <- screen-width x:address:screen
+  screen-height:number <- screen-height x:address:screen
+#?   $print [eee ] #? 1
+#?   $foo #? 1
 #?   trace [app], [print character] #? 1
   {
     # if x exists
     # (handle special cases exactly like in the real screen)
     break-unless x:address:screen
     row:address:number <- get-address x:address:screen/deref, cursor-row:offset
+#?     $dump row:address:number/deref
+    legal?:boolean <- greater-or-equal row:address:number/deref, 0:literal
+    reply-unless legal?:boolean, x:address:screen
+    assert legal?:boolean, [row too small in print-character]
+    legal?:boolean <- lesser-than row:address:number/deref, screen-height:number
+    reply-unless legal?:boolean, x:address:screen
+    assert legal?:boolean, [row too large in print-character]
     column:address:number <- get-address x:address:screen/deref, cursor-column:offset
+    legal?:boolean <- greater-or-equal column:address:number/deref, 0:literal
+    reply-unless legal?:boolean, x:address:screen
+    assert legal?:boolean, [column too small in print-character]
+    legal?:boolean <- lesser-than column:address:number/deref, screen-width:number
+    reply-unless legal?:boolean, x:address:screen
+    assert legal?:boolean, [column too large in print-character]
     width:number <- get x:address:screen/deref, num-columns:offset
     height:number <- get x:address:screen/deref, num-rows:offset
+#?   $print [fff ] #? 1
+#?   $foo #? 1
     # special-case: newline
     {
       newline?:boolean <- equal c:character, 10:literal/newline
@@ -112,11 +151,14 @@ recipe print-character [
       reply x:address:screen/same-as-ingredient:0
     }
     # save character in fake screen
-#?     $print row:address:number/deref, [, ], column:address:number/deref, [ 
-#? ] #? 1
+#?     $print [ggg ] #? 1
+#?     $foo #? 1
     index:number <- multiply row:address:number/deref, width:number
     index:number <- add index:number, column:address:number/deref
     buf:address:array:screen-cell <- get x:address:screen/deref, data:offset
+    len:number <- length buf:address:array:screen-cell/deref
+#?     $print row:address:number/deref, [, ], column:address:number/deref, [ vs ], screen-height:number, [, ], screen-width:number, [ length ], len:number, [ 
+#? ] #? 1
     # special-case: backspace
     {
       backspace?:boolean <- equal c:character, 8:literal
@@ -136,20 +178,49 @@ recipe print-character [
       }
       reply x:address:screen/same-as-ingredient:0
     }
+#?     $print [hhh ] #? 1
+#?     $foo #? 1
 #?     $print [saving character ], c:character, [ to fake screen ], cursor:address/screen, [ 
 #? ] #? 1
     cursor:address:screen-cell <- index-address buf:address:array:screen-cell/deref, index:number
+#?     $print [iii ] #? 1
+#?     $foo #? 1
     cursor-contents:address:character <- get-address cursor:address:screen-cell/deref, contents:offset
+#?     $print [jjj ] #? 1
+#?     $foo #? 1
     cursor-color:address:number <- get-address cursor:address:screen-cell/deref, color:offset
+#?     $print [kkk ] #? 1
+#?     $foo #? 1
+#?   $dump cursor-contents:address:character
     cursor-contents:address:character/deref <- copy c:character
+#?     $print [lll ] #? 1
+#?     $foo #? 1
+#?     $dump x:address:screen
+#?     $dump buf:address:array:screen-cell
+#?     $dump height:number
+#?     $dump width:number
+#?     $dump row:address:number/deref
+#?     $dump column:address:number/deref
+#?     $dump index:number
+#?     $dump len:number
     cursor-color:address:number/deref <- copy color:number
+#?     $print [mmm ] #? 1
+#?     $foo #? 1
     # increment column unless it's already all the way to the right
     {
       right:number <- subtract width:number, 1:literal
+#?     $print [nnn ] #? 1
+#?     $foo #? 1
       at-right?:boolean <- greater-or-equal column:address:number/deref, right:number
+#?     $print [ooo ] #? 1
+#?     $foo #? 1
       break-if at-right?:boolean
+#?     $print [ppp ] #? 1
+#?     $foo #? 1
       column:address:number/deref <- add column:address:number/deref, 1:literal
     }
+#?     $print [qqq ] #? 1
+#?     $foo #? 1
     reply x:address:screen/same-as-ingredient:0
   }
   # otherwise, real screen
@@ -361,12 +432,24 @@ recipe move-cursor [
   x:address:screen <- next-ingredient
   new-row:number <- next-ingredient
   new-column:number <- next-ingredient
+#?   screen-width:number <- screen-width x:address:screen
+#?   screen-height:number <- screen-height x:address:screen
   # if x exists, move cursor in fake screen
   {
     break-unless x:address:screen
     row:address:number <- get-address x:address:screen/deref, cursor-row:offset
+#?     $print row:address:number/deref, [ vs ], screen-height:number, [ 
+#? ] #? 1
+#?     legal?:boolean <- greater-or-equal row:address:number/deref, 0:literal
+#?     assert legal?:boolean, [row too small in move-cursor]
+#?     legal?:boolean <- lesser-than row:address:number/deref, screen-height:number
+#?     assert legal?:boolean, [row too large in move-cursor]
     row:address:number/deref <- copy new-row:number
     column:address:number <- get-address x:address:screen/deref, cursor-column:offset
+#?     legal?:boolean <- greater-or-equal column:address:number/deref, 0:literal
+#?     assert legal?:boolean, [column too small in move-cursor]
+#?     legal?:boolean <- lesser-than column:address:number/deref, screen-width:number
+#?     assert legal?:boolean, [column too large in move-cursor]
     column:address:number/deref <- copy new-column:number
     reply x:address:screen/same-as-ingredient:0
   }
@@ -413,10 +496,11 @@ recipe cursor-down [
   {
     break-unless x:address:screen
     {
-      # if row < height
+      # if row < height-1
       height:number <- get x:address:screen/deref, num-rows:offset
       row:address:number <- get-address x:address:screen/deref, cursor-row:offset
-      at-bottom?:boolean <- greater-or-equal row:address:number/deref, height:number
+      max:number <- subtract height:number, 1:literal
+      at-bottom?:boolean <- greater-or-equal row:address:number/deref, max:number
       break-if at-bottom?:boolean
       # row = row+1
 #?       $print [AAA: ], row:address:number, [ -> ], row:address:number/deref, [ 
@@ -440,9 +524,9 @@ recipe cursor-up [
   {
     break-unless x:address:screen
     {
-      # if row >= 0
+      # if row > 0
       row:address:number <- get-address x:address:screen/deref, cursor-row:offset
-      at-top?:boolean <- lesser-than row:address:number/deref, 0:literal
+      at-top?:boolean <- lesser-or-equal row:address:number/deref, 0:literal
       break-if at-top?:boolean
       # row = row-1
       row:address:number/deref <- subtract row:address:number/deref, 1:literal
@@ -461,10 +545,11 @@ recipe cursor-right [
   {
     break-unless x:address:screen
     {
-      # if column < width
+      # if column < width-1
       width:number <- get x:address:screen/deref, num-columns:offset
       column:address:number <- get-address x:address:screen/deref, cursor-column:offset
-      at-bottom?:boolean <- greater-or-equal column:address:number/deref, width:number
+      max:number <- subtract width:number, 1:literal
+      at-bottom?:boolean <- greater-or-equal column:address:number/deref, max:number
       break-if at-bottom?:boolean
       # column = column+1
       column:address:number/deref <- add column:address:number/deref, 1:literal
@@ -483,9 +568,9 @@ recipe cursor-left [
   {
     break-unless x:address:screen
     {
-      # if column >= 0
+      # if column > 0
       column:address:number <- get-address x:address:screen/deref, cursor-column:offset
-      at-top?:boolean <- lesser-than column:address:number/deref, 0:literal
+      at-top?:boolean <- lesser-or-equal column:address:number/deref, 0:literal
       break-if at-top?:boolean
       # column = column-1
       column:address:number/deref <- subtract column:address:number/deref, 1:literal
diff --git a/081run_interactive.cc b/081run_interactive.cc
index 1d317d4b..f54a73e3 100644
--- a/081run_interactive.cc
+++ b/081run_interactive.cc
@@ -15,8 +15,10 @@ recipe main [
 # result is null
 +mem: storing 0 in location 1
 
-//: run code in 'interactive mode', i.e. with warnings off, and recording
-//: output in case we want to print it to screen
+//: run code in 'interactive mode', i.e. with warnings off and return:
+//:   stringified output in case we want to print it to screen
+//:   any warnings encountered
+//:   simulated screen any prints went to
 :(before "End Primitive Recipe Declarations")
 RUN_INTERACTIVE,
 :(before "End Primitive Recipe Numbers")
@@ -25,11 +27,12 @@ Recipe_ordinal["run-interactive"] = RUN_INTERACTIVE;
 :(before "End Primitive Recipe Implementations")
 case RUN_INTERACTIVE: {
   assert(scalar(ingredients.at(0)));
-  products.resize(2);
+  products.resize(3);
   bool new_code_pushed_to_stack = run_interactive(ingredients.at(0).at(0));
   if (!new_code_pushed_to_stack) {
     products.at(0).push_back(0);
     products.at(1).push_back(warnings_from_trace());
+    products.at(2).push_back(0);
     clean_up_interactive();
     break;  // done with this instruction
   }
@@ -40,8 +43,10 @@ case RUN_INTERACTIVE: {
 
 :(before "End Globals")
 bool Running_interactive = false;
+long long int Old_screen = 0;  // we can support one iteration of screen inside screen
 :(before "End Setup")
 Running_interactive = false;
+Old_screen = 0;
 :(code)
 // reads a string, tries to call it as code (treating it as a test), saving
 // all warnings.
@@ -49,6 +54,8 @@ Running_interactive = false;
 bool run_interactive(long long int address) {
   if (Recipe_ordinal.find("interactive") == Recipe_ordinal.end())
     Recipe_ordinal["interactive"] = Next_recipe_ordinal++;
+  Old_screen = Memory[SCREEN];
+  cerr << "save screen: " << Old_screen << '\n'; //? 1
   // try to sandbox the run as best you can
   // todo: test this
   if (!Current_scenario) {
@@ -57,6 +64,9 @@ bool run_interactive(long long int address) {
       Memory.erase(i);
     Name[Recipe_ordinal["interactive"]].clear();
   }
+//?   cerr << "screen was at " << Name[Recipe_ordinal["interactive"]]["screen"] << '\n'; //? 1
+  Name[Recipe_ordinal["interactive"]]["screen"] = SCREEN;
+//?   cerr << "screen now at " << Name[Recipe_ordinal["interactive"]]["screen"] << '\n'; //? 1
   string command = trim(strip_comments(to_string(address)));
   if (command.empty()) return false;
   Recipe.erase(Recipe_ordinal["interactive"]);
@@ -67,7 +77,12 @@ bool run_interactive(long long int address) {
     Trace_stream->collect_layer = "warn";
   }
   // call run(string) but without the scheduling
-  load("recipe interactive [\n"+command+"\n]\n");
+  // we won't create a local scope so that we can get to the new screen after
+  // we return from 'interactive'.
+  load(string("recipe interactive [\n") +
+          "screen:address <- new-fake-screen 5, 5\n" +
+          command + "\n" +
+       "]\n");
   transform_all();
   if (trace_count("warn") > 0) return false;
   Running_interactive = true;
@@ -75,21 +90,6 @@ bool run_interactive(long long int address) {
   return true;
 }
 
-:(after "Starting Reply")
-if (current_recipe_name() == "interactive") clean_up_interactive();
-:(after "Falling Through End Of Recipe")
-if (current_recipe_name() == "interactive") clean_up_interactive();
-:(code)
-void clean_up_interactive() {
-  Hide_warnings = false;
-  Running_interactive = false;
-  // hack: assume collect_layer isn't set anywhere else
-  if (Trace_stream->collect_layer == "warn") {
-    delete Trace_stream;
-    Trace_stream = NULL;
-  }
-}
-
 :(scenario "run_interactive_returns_stringified_result")
 recipe main [
   # try to interactively add 2 and 2
@@ -157,17 +157,51 @@ void record_products(const instruction& instruction, const vector<vector<double>
 }
 :(before "Complete Call Fallthrough")
 if (current_instruction().operation == RUN_INTERACTIVE && !current_instruction().products.empty()) {
-  assert(SIZE(current_instruction().products) <= 2);
+  assert(SIZE(current_instruction().products) <= 3);
   // Send the results of the most recently executed instruction, regardless of
   // call depth, to be converted to string and potentially printed to string.
   vector<double> result;
   result.push_back(new_string(Most_recent_results));
   write_memory(current_instruction().products.at(0), result);
-  if (SIZE(current_instruction().products) == 2) {
+  if (SIZE(current_instruction().products) >= 2) {
     vector<double> warnings;
     warnings.push_back(warnings_from_trace());
     write_memory(current_instruction().products.at(1), warnings);
   }
+  if (SIZE(current_instruction().products) >= 3) {
+    vector<double> screen;
+    cerr << "returning screen " << Memory[SCREEN] << " to " << current_instruction().products.at(2).to_string() << " value " << current_instruction().products.at(2).value << '\n';
+    screen.push_back(Memory[SCREEN]);
+    write_memory(current_instruction().products.at(2), screen);
+  }
+}
+
+//: clean up reply after we've popped it off the call-stack
+//: however, we need what was on the stack to decide whether to clean up
+:(after "Starting Reply")
+bool must_clean_up_interactive = (current_recipe_name() == "interactive");
+//? cerr << "reply: " << Memory[SCREEN] << '\n'; //? 1
+//? cerr << "reply2: " << Name[Recipe_ordinal["render-sandboxes"]]["screen"] << '\n'; //? 1
+:(after "Falling Through End Of Recipe")
+bool must_clean_up_interactive = (current_recipe_name() == "interactive");
+//? cerr << "pop: " << Memory[SCREEN] << '\n'; //? 1
+//? cerr << "pop2: " << Name[Recipe_ordinal["render-sandboxes"]]["screen"] << '\n'; //? 1
+:(before "End Reply")
+if (must_clean_up_interactive) clean_up_interactive();
+:(before "Complete Call Fallthrough")
+if (must_clean_up_interactive) clean_up_interactive();
+:(code)
+void clean_up_interactive() {
+  Hide_warnings = false;
+  Running_interactive = false;
+  // hack: assume collect_layer isn't set anywhere else
+  if (Trace_stream->collect_layer == "warn") {
+    delete Trace_stream;
+    Trace_stream = NULL;
+  }
+  cerr << "restore screen: " << Memory[SCREEN] << " to " << Old_screen << '\n';
+  Memory[SCREEN] = Old_screen;
+  Old_screen = 0;
 }
 
 :(code)
diff --git a/edit.mu b/edit.mu
index 5828ebdc..4bbf47b5 100644
--- a/edit.mu
+++ b/edit.mu
@@ -3,52 +3,13 @@
 recipe main [
   local-scope
   open-console
-  initial-recipe:address:array:character <- new [ 
-# return true if a list of numbers contains the pattern 1, 5, 3
-recipe check [
-  default-space:address:array:location <- new location:type, 30:literal
-  state:number <- copy 0:literal
-  {
-    +next-number
-    curr:number, found?:boolean <- next-ingredient
-    break-unless found?:boolean
-    # if curr is 1, state = 1
-    {
-      state1:boolean <- equal curr:number, 1:literal
-      break-unless state1:boolean
-      state:number <- copy 1:literal
-      loop +next-number:label
-    }
-    # if state is 1 and curr is 5, state = 2
-    {
-      state-is-1?:boolean <- equal state:number, 1:literal
-      break-unless state-is-1?:boolean
-      state2:boolean <- equal curr:number, 5:literal
-      break-unless state2:boolean
-      state:number <- copy 2:literal
-      loop +next-number:label
-    }
-    # if state is 2 and curr is 3, return true
-    {
-      state-is-2?:boolean <- equal state:number, 2:literal
-      break-unless state-is-2?:boolean
-      state3:boolean <- equal curr:number, 3:literal
-      break-unless state3:boolean
-      reply 1:literal/found
-    }
-    # otherwise reset state
-    #state:number <- copy 0:literal
-    loop
-  }
-  reply 0:literal/not-found
+  initial-recipe:address:array:character <- new [recipe new-add [
+  x:number <- next-ingredient
+  y:number <- next-ingredient
+  z:number <- add x:number, y:number
+  reply z:number
 ]]
-#?   initial-recipe:address:array:character <- new [recipe new-add [
-#?   x:number <- next-ingredient
-#?   y:number <- next-ingredient
-#?   z:number <- add x:number, y:number
-#?   reply z:number
-#? ]]
-  initial-sandbox:address:array:character <- new []
+  initial-sandbox:address:array:character <- new [print-character screen:address, 97]
   env:address:programming-environment-data <- new-programming-environment 0:literal/screen, initial-recipe:address:array:character, initial-sandbox:address:array:character
   event-loop 0:literal/screen, 0:literal/console, env:address:programming-environment-data
 ]
@@ -302,6 +263,7 @@ recipe render [
 ]
 
 # row:number, screen:address <- render-string screen:address, s:address:array:character, left:number, right:number, color:number, row:number
+# move cursor at start of next line
 # print a string 's' to 'editor' in 'color' starting at 'row'
 # leave cursor at start of next line
 recipe render-string [
@@ -370,6 +332,76 @@ recipe render-string [
   reply row:number/same-as-ingredient:5, screen:address/same-as-ingredient:0
 ]
 
+# row:number, screen:address <- render-screen screen:address, sandbox-screen:address:screen, left:number, right:number, row:number
+# print the fake sandbox screen to 'screen' with appropriate delimiters
+# leave cursor at start of next line
+recipe render-screen [
+  local-scope
+  screen:address <- next-ingredient
+  s:address:screen <- next-ingredient
+  left:number <- next-ingredient
+  right:number <- next-ingredient
+  row:number <- next-ingredient
+  row:number <- add row:number, 1:literal
+  reply-unless s:address:screen, row:number/same-as-ingredient:4, screen:address/same-as-ingredient:0
+#?   $start-tracing #? 1
+  # print 'screen:'
+  column:number <- copy left:number
+  move-cursor screen:address, row:number, column:number
+  screen-height:number <- screen-height screen:address
+  header:address:array:character <- new [screen:]
+  row:number <- subtract row:number, 1:literal  # compensate for render-string below
+  row:number <- render-string screen:address, header:address:array:character, left:number, right:number, 245:literal/grey, row:number
+  # start printing s
+  column:number <- copy left:number
+  move-cursor screen:address, row:number, column:number
+  s-width:number <- screen-width s:address:screen
+  s-height:number <- screen-height s:address:screen
+  buf:address:array:screen-cell <- get s:address:screen/deref, data:offset
+  stop-printing:number <- add s-width:number, 3:literal
+#?   stop-printing:number <- add left:number, s-width:number, 3:literal
+  max-column:number <- min stop-printing:number, right:number
+  i:number <- copy 0:literal
+  len:number <- length buf:address:array:screen-cell/deref
+  {
+    done?:boolean <- greater-or-equal i:number, len:number
+    break-if done?:boolean
+    done?:boolean <- greater-or-equal row:number, screen-height:number
+    break-if done?:boolean
+    column:number <- copy left:number
+    move-cursor screen:address, row:number, column:number
+    # initial leader for each row: two spaces and a '.'
+    print-character screen:address, 32:literal/space, 245:literal/grey
+    print-character screen:address, 32:literal/space, 245:literal/grey
+    print-character screen:address, 46:literal/full-stop, 245:literal/grey
+    column:number <- add left:number, 3:literal
+    {
+      # print row
+      row-done?:boolean <- greater-than column:number, max-column:number
+      break-if row-done?:boolean
+      curr:screen-cell <- index buf:address:array:screen-cell/deref, i:number
+      print-character screen:address, 32:literal/space
+      column:number <- add column:number, 1:literal
+      i:number <- add i:number, 1:literal
+      loop
+    }
+    # print final '.'
+    print-character screen:address, 46:literal/full-stop, 245:literal/grey
+    column:number <- add column:number, 1:literal
+    {
+      # clear rest of current line
+      line-done?:boolean <- greater-than column:number, right:number
+      break-if line-done?:boolean
+      print-character screen:address, 32:literal/space
+      column:number <- add column:number, 1:literal
+      loop
+    }
+    row:number <- add row:number, 1:literal
+    loop
+  }
+  reply row:number/same-as-ingredient:4, screen:address/same-as-ingredient:0
+]
+
 recipe clear-line-delimited [
   local-scope
   screen:address <- next-ingredient
@@ -522,19 +554,10 @@ recipe event-loop [
         loop +next-event:label
       }
     }
-    # 'touch' event
+    # 'touch' event - send to both editors
     {
       t:address:touch-event <- maybe-convert e:event, touch:variant
       break-unless t:address:touch-event
-      # on a sandbox delete icon? process delete
-      {
-        was-delete?:boolean <- delete-sandbox t:address:touch-event/deref, env:address:programming-environment-data
-        break-unless was-delete?:boolean
-        screen:address <- render-sandbox-side screen:address, env:address:programming-environment-data, 1:literal/clear
-        update-cursor screen:address, recipes:address:editor-data, current-sandbox:address:editor-data, sandbox-in-focus?:address:boolean/deref
-        loop +next-event:label
-      }
-      # if not, send to both editors
       _ <- move-cursor-in-editor screen:address, recipes:address:editor-data, t:address:touch-event/deref
       sandbox-in-focus?:address:boolean/deref <- move-cursor-in-editor screen:address, current-sandbox:address:editor-data, t:address:touch-event/deref
       jump +continue:label
@@ -1063,7 +1086,6 @@ recipe render-sandbox-side [
   local-scope
   screen:address <- next-ingredient
   env:address:programming-environment-data <- next-ingredient
-  clear:boolean <- next-ingredient
   current-sandbox:address:editor-data <- get env:address:programming-environment-data/deref, current-sandbox:offset
   left:number <- get current-sandbox:address:editor-data/deref, left:offset
   right:number <- get current-sandbox:address:editor-data/deref, right:offset
@@ -1076,62 +1098,61 @@ recipe render-sandbox-side [
   row:number <- add row:number, 1:literal
   move-cursor screen:address, row:number, left:number
   clear-line-delimited screen:address, left:number, right:number
-  reply-unless clear:boolean, screen:address/same-as-ingredient:0
-  screen-height:number <- screen-height screen:address
-  {
-    at-bottom-of-screen?:boolean <- greater-or-equal row:number, screen-height:number
-    break-if at-bottom-of-screen?:boolean
-    move-cursor screen:address, row:number, left:number
-    clear-line-delimited screen:address, left:number, right:number
-    row:number <- add row:number, 1:literal
-    loop
-  }
   reply screen:address/same-as-ingredient:0
 ]
 
 recipe render-sandboxes [
   local-scope
-  screen:address <- next-ingredient
+  screen:address:screen <- next-ingredient
   sandbox:address:sandbox-data <- next-ingredient
   left:number <- next-ingredient
   right:number <- next-ingredient
   row:number <- next-ingredient
-  reply-unless sandbox:address:sandbox-data, row:number/same-as-ingredient:4, screen:address/same-as-ingredient:0
-  screen-height:number <- screen-width screen:address
+  reply-unless sandbox:address:sandbox-data, row:number/same-as-ingredient:4, screen:address:screen/same-as-ingredient:0
+  screen-height:number <- screen-height screen:address:screen
   at-bottom?:boolean <- greater-or-equal row:number screen-height:number
-  reply-if at-bottom?:boolean, row:number/same-as-ingredient:4, screen:address/same-as-ingredient:0
-#?   $print [rendering sandbox ], sandbox:address:sandbox-data, [ 
-#? ] #? 1
-  # render sandbox menu
-  row:number <- add row:number, 1:literal
-  move-cursor screen:address, row:number, left:number
-  clear-line-delimited screen:address, left:number, right:number
-  print-character screen:address, 120:literal/x, 245:literal/grey
-  # save menu row so we can detect clicks to it later
-  starting-row:address:number <- get-address sandbox:address:sandbox-data/deref, starting-row-on-screen:offset
-  starting-row:address:number/deref <- copy row:number
+  reply-if at-bottom?:boolean, row:number/same-as-ingredient:4, screen:address:screen/same-as-ingredient:0
   # render sandbox contents
   sandbox-data:address:array:character <- get sandbox:address:sandbox-data/deref, data:offset
-  row:number, screen:address <- render-string screen:address, sandbox-data:address:array:character, left:number, right:number, 7:literal/white, row:number
+  row:number, screen:address:screen <- render-string screen:address:screen, sandbox-data:address:array:character, left:number, right:number, 7:literal/white, row:number
   # render sandbox warnings or response, in that order
   sandbox-response:address:array:character <- get sandbox:address:sandbox-data/deref, response:offset
   sandbox-warnings:address:array:character <- get sandbox:address:sandbox-data/deref, warnings:offset
+  sandbox-screen:address:screen <- get sandbox:address:sandbox-data/deref, screen:offset
   {
     break-unless sandbox-warnings:address:array:character
-    row:number, screen:address <- render-string screen:address, sandbox-warnings:address:array:character, left:number, right:number, 1:literal/red, row:number
+    row:number, screen:address:screen <- render-string screen:address:screen, sandbox-warnings:address:array:character, left:number, right:number, 1:literal/red, row:number
   }
   {
     break-if sandbox-warnings:address:array:character
-    row:number, screen:address <- render-string screen:address, sandbox-response:address:array:character, left:number, right:number, 245:literal/grey, row:number
+    row:number, screen:address:screen <- render-string screen:address:screen, sandbox-response:address:array:character, left:number, right:number, 245:literal/grey, row:number
   }
+  # render sandbox screen if necessary
+  at-bottom?:boolean <- greater-or-equal row:number screen-height:number
+  reply-if at-bottom?:boolean, row:number/same-as-ingredient:4, screen:address:screen/same-as-ingredient:0
+  {
+    empty-screen?:boolean <- fake-screen-is-clear? sandbox-screen:address:screen
+    break-if empty-screen?:boolean
+    row:number, screen:address:screen <- render-screen screen:address:screen, sandbox-screen:address:screen, left:number, right:number, row:number
+  }
+  at-bottom?:boolean <- greater-or-equal row:number screen-height:number
+  reply-if at-bottom?:boolean, row:number/same-as-ingredient:4, screen:address:screen/same-as-ingredient:0
   # draw solid line after sandbox
-  draw-horizontal screen:address, row:number, left:number, right:number, 9473:literal/horizontal-double
+#?   $print [aaa ]
+#?   $dump screen:address:screen
+#?   $dump right:number
+#?   $foo screen:address:screen
+#?   xxx:address:array:screen-cell <- get screen:address:screen/deref, data:offset
+#?   $dump xxx:address:array:screen-cell
+#?   yyy:number <- length xxx:address:array:screen-cell/deref
+#?   $dump yyy:number
+  draw-horizontal screen:address:screen, row:number, left:number, right:number, 9473:literal/horizontal-double
+#?   $print [zzz ]
+#?   $dump screen:address:screen
   # draw next sandbox
   next-sandbox:address:sandbox-data <- get sandbox:address:sandbox-data/deref, next-sandbox:offset
-#?   $print [next sandbox is ], next-sandbox:address:sandbox-data, [ 
-#? ] #? 1
-  row:number, screen:address <- render-sandboxes screen:address, next-sandbox:address:sandbox-data, left:number, right:number, row:number
-  reply row:number/same-as-ingredient:4, screen:address/same-as-ingredient:0
+  row:number, screen:address:screen <- render-sandboxes screen:address:screen, next-sandbox:address:sandbox-data, left:number, right:number, row:number
+  reply row:number/same-as-ingredient:4, screen:address:screen/same-as-ingredient:0
 ]
 
 recipe update-cursor [
@@ -2542,13 +2563,13 @@ container sandbox-data [
   data:address:array:character
   response:address:array:character
   warnings:address:array:character
-  starting-row-on-screen:number  # to track clicks on delete
+  screen:address:screen
   next-sandbox:address:sandbox-data
 ]
 
 scenario run-and-show-results [
   $close-trace  # trace too long for github
-  assume-screen 100:literal/width, 15:literal/height
+  assume-screen 100:literal/width, 12:literal/height
   # recipe editor is empty
   1:address:array:character <- new []
   # sandbox editor contains an instruction without storing outputs
@@ -2566,7 +2587,6 @@ scenario run-and-show-results [
     .                                                                                 run (F10)          .
     .                                                  ┊                                                 .
     .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                                  ┊                                                x.
     .                                                  ┊divide-with-remainder 11:literal, 3:literal      .
     .                                                  ┊3                                                .
     .                                                  ┊2                                                .
@@ -2577,7 +2597,6 @@ scenario run-and-show-results [
     .                                                                                                    .
     .                                                                                                    .
     .                                                                                                    .
-    .                                                                                                    .
     .                                                   divide-with-remainder 11:literal, 3:literal      .
     .                                                                                                    .
     .                                                                                                    .
@@ -2588,7 +2607,6 @@ scenario run-and-show-results [
     .                                                                                                    .
     .                                                  ┊                                                 .
     .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                                  ┊                                                x.
     .                                                  ┊                                                 .
     .                                                  ┊3                                                .
     .                                                  ┊2                                                .
@@ -2609,11 +2627,9 @@ scenario run-and-show-results [
     .                                                                                 run (F10)          .
     .                                                  ┊                                                 .
     .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                                  ┊                                                x.
     .                                                  ┊add 2:literal, 2:literal                         .
     .                                                  ┊4                                                .
     .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                                  ┊                                                x.
     .                                                  ┊divide-with-remainder 11:literal, 3:literal      .
     .                                                  ┊3                                                .
     .                                                  ┊2                                                .
@@ -2648,14 +2664,15 @@ recipe run-sandboxes [
     init:address:address:duplex-list <- get-address current-sandbox:address:editor-data/deref, data:offset
     init:address:address:duplex-list/deref <- push-duplex 167:literal/§, 0:literal/tail
   }
-  # rerun other sandboxes
+  # run all sandboxes
   curr:address:sandbox-data <- get env:address:programming-environment-data/deref, sandbox:offset
   {
     break-unless curr:address:sandbox-data
     data:address:address:array:character <- get-address curr:address:sandbox-data/deref, data:offset
     response:address:address:array:character <- get-address curr:address:sandbox-data/deref, response:offset
     warnings:address:address:array:character <- get-address curr:address:sandbox-data/deref, warnings:offset
-    response:address:address:array:character/deref, warnings:address:address:array:character/deref <- run-interactive data:address:address:array:character/deref
+    fake-screen:address:address:screen <- get-address curr:address:sandbox-data/deref, screen:offset
+    response:address:address:array:character/deref, warnings:address:address:array:character/deref, fake-screen:address:address:screen/deref <- run-interactive data:address:address:array:character/deref
 #?     $print warnings:address:address:array:character/deref, [ ], warnings:address:address:array:character/deref/deref, [ 
 #? ] #? 1
     curr:address:sandbox-data <- get curr:address:sandbox-data/deref, next-sandbox:offset
@@ -2663,55 +2680,6 @@ recipe run-sandboxes [
   }
 ]
 
-# was-deleted?:boolean <- delete-sandbox t:touch-event, env:address:programming-environment-data
-recipe delete-sandbox [
-  local-scope
-  t:touch-event <- next-ingredient
-  env:address:programming-environment-data <- next-ingredient
-  click-column:number <- get t:touch-event, column:offset
-  current-sandbox:address:editor-data <- get env:address:programming-environment-data/deref, current-sandbox:offset
-  right:number <- get current-sandbox:address:editor-data/deref, right:offset
-#?   $print [comparing column ], click-column:number, [ vs ], right:number, [ 
-#? ] #? 1
-  at-right?:boolean <- equal click-column:number, right:number
-  reply-unless at-right?:boolean, 0:literal/false
-#?   $print [trying to delete
-#? ] #? 1
-  click-row:number <- get t:touch-event, row:offset
-  prev:address:address:sandbox-data <- get-address env:address:programming-environment-data/deref, sandbox:offset
-#?   $print [prev: ], prev:address:address:sandbox-data, [ -> ], prev:address:address:sandbox-data/deref, [ 
-#? ] #? 1
-  curr:address:sandbox-data <- get env:address:programming-environment-data/deref, sandbox:offset
-  {
-#?     $print [next sandbox
-#? ] #? 1
-    break-unless curr:address:sandbox-data
-    # more sandboxes to check
-    {
-#?       $print [checking
-#? ] #? 1
-      target-row:number <- get curr:address:sandbox-data/deref, starting-row-on-screen:offset
-#?       $print [comparing row ], target-row:number, [ vs ], click-row:number, [ 
-#? ] #? 1
-      delete-curr?:boolean <- equal target-row:number, click-row:number
-      break-unless delete-curr?:boolean
-#?       $print [found!
-#? ] #? 1
-      # delete this sandbox, rerender and stop
-      prev:address:address:sandbox-data/deref <- get curr:address:sandbox-data/deref, next-sandbox:offset
-#?       $print [setting prev: ], prev:address:address:sandbox-data, [ -> ], prev:address:address:sandbox-data/deref, [ 
-#? ] #? 1
-      reply 1:literal/true
-    }
-    prev:address:address:sandbox-data <- get-address curr:address:sandbox-data/deref, next-sandbox:offset
-#?     $print [prev: ], prev:address:address:sandbox-data, [ -> ], prev:address:address:sandbox-data/deref, [ 
-#? ] #? 1
-    curr:address:sandbox-data <- get curr:address:sandbox-data/deref, next-sandbox:offset
-    loop
-  }
-  reply 0:literal/false
-]
-
 scenario run-updates-results [
   $close-trace  # trace too long for github
   assume-screen 100:literal/width, 12:literal/height
@@ -2735,10 +2703,9 @@ z:number <- add 2:literal, 2:literal
     .                                                                                 run (F10)          .
     .                                                  ┊                                                 .
     .recipe foo [                                      ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .z:number <- add 2:literal, 2:literal              ┊                                                x.
-    .]                                                 ┊foo                                              .
-    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊4                                                .
-    .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .z:number <- add 2:literal, 2:literal              ┊foo                                              .
+    .]                                                 ┊4                                                .
+    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
     .                                                  ┊                                                 .
   ]
   # make a change (incrementing one of the args to 'add'), then rerun
@@ -2757,10 +2724,9 @@ z:number <- add 2:literal, 2:literal
     .                                                                                 run (F10)          .
     .                                                  ┊                                                 .
     .recipe foo [                                      ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .z:number <- add 2:literal, 3:literal              ┊                                                x.
-    .]                                                 ┊foo                                              .
-    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊5                                                .
-    .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
+    .z:number <- add 2:literal, 3:literal              ┊foo                                              .
+    .]                                                 ┊5                                                .
+    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
     .                                                  ┊                                                 .
   ]
 ]
@@ -2785,7 +2751,6 @@ scenario run-instruction-and-print-warnings [
     .                                                                                 run (F10)          .
     .                                                  ┊                                                 .
     .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                                  ┊                                                x.
     .                                                  ┊get 1234:number, foo:offset                      .
     .                                                  ┊unknown element foo in container number          .
     .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -2795,7 +2760,6 @@ scenario run-instruction-and-print-warnings [
     .                                                                                                    .
     .                                                                                                    .
     .                                                                                                    .
-    .                                                                                                    .
     .                                                   get 1234:number, foo:offset                      .
     .                                                                                                    .
     .                                                                                                    .
@@ -2806,7 +2770,6 @@ scenario run-instruction-and-print-warnings [
     .                                                                                                    .
     .                                                                                                    .
     .                                                                                                    .
-    .                                                                                                    .
     .                                                   unknown element foo in container number          .
     .                                                                                                    .
   ]
@@ -2814,7 +2777,6 @@ scenario run-instruction-and-print-warnings [
     .                                                                                                    .
     .                                                  ┊                                                 .
     .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                                  ┊                                                x.
     .                                                  ┊                                                 .
     .                                                  ┊                                                 .
     .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -2843,7 +2805,6 @@ scenario run-instruction-and-print-warnings-only-once [
     .                                                                                 run (F10)          .
     .                                                  ┊                                                 .
     .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                                  ┊                                                x.
     .                                                  ┊get 1234:number, foo:offset                      .
     .                                                  ┊unknown element foo in container number          .
     .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
@@ -2851,69 +2812,37 @@ scenario run-instruction-and-print-warnings-only-once [
   ]
 ]
 
-scenario deleting-sandboxes [
-  $close-trace  # trace too long for github
-  assume-screen 100:literal/width, 15:literal/height
+scenario run-instruction-manages-screen-per-sandbox [
+  $close-trace  # trace too long for github #? 1
+  assume-screen 100:literal/width, 20:literal/height
+  # left editor is empty
   1:address:array:character <- new []
-  2:address:array:character <- new []
+  # right editor contains an illegal instruction
+  2:address:array:character <- new [print-integer screen:address, 4]
   3:address:programming-environment-data <- new-programming-environment screen:address, 1:address:array:character, 2:address:array:character
-  # run a few commands
+  # run the code in the editor
   assume-console [
-    left-click 1, 80
-    type [divide-with-remainder 11:literal, 3:literal]
-    press 65526  # F10
-    type [add 2:literal, 2:literal]
     press 65526  # F10
   ]
   run [
     event-loop screen:address, console:address, 3:address:programming-environment-data
   ]
+  # check that it prints a little 5x5 toy screen
+  # hack: screen address is brittle
   screen-should-contain [
     .                                                                                 run (F10)          .
     .                                                  ┊                                                 .
     .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                                  ┊                                                x.
-    .                                                  ┊add 2:literal, 2:literal                         .
-    .                                                  ┊4                                                .
-    .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                                  ┊                                                x.
-    .                                                  ┊divide-with-remainder 11:literal, 3:literal      .
-    .                                                  ┊3                                                .
-    .                                                  ┊2                                                .
-    .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                                  ┊                                                 .
-  ]
-  # delete second sandbox
-  assume-console [
-    left-click 7, 99
-  ]
-  run [
-    event-loop screen:address, console:address, 3:address:programming-environment-data
-  ]
-  screen-should-contain [
-    .                                                                                 run (F10)          .
-    .                                                  ┊                                                 .
-    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                                  ┊                                                x.
-    .                                                  ┊add 2:literal, 2:literal                         .
-    .                                                  ┊4                                                .
+    .                                                  ┊print-integer screen:address, 4                  .
+    .                                                  ┊5557                                             .
+    .                                                  ┊screen:                                          .
+    .                                                  ┊  .4    .                                        .
+    .                                                  ┊  .     .                                        .
+    .                                                  ┊  .     .                                        .
+    .                                                  ┊  .     .                                        .
+    .                                                  ┊  .     .                                        .
     .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
     .                                                  ┊                                                 .
-    .                                                  ┊                                                 .
-  ]
-  # delete first sandbox
-  assume-console [
-    left-click 3, 99
-  ]
-  run [
-    event-loop screen:address, console:address, 3:address:programming-environment-data
-  ]
-  screen-should-contain [
-    .                                                                                 run (F10)          .
-    .                                                  ┊                                                 .
-    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
-    .                                                  ┊                                                 .
-    .                                                  ┊                                                 .
   ]
 ]
 
@@ -3007,12 +2936,22 @@ recipe draw-horizontal [
     break-if bg-color-found?:boolean
     bg-color:number <- copy 0:literal/black
   }
+#?   $print [bbb ], 5155:number/raw, [  #? 1
+#? ] #? 1
   move-cursor screen:address, row:number, x:number
   {
+#?   $print [ccc ], 5155:number/raw, [  #? 1
+#? ] #? 1
     continue?:boolean <- lesser-or-equal x:number, right:number  # right is inclusive, to match editor-data semantics
     break-unless continue?:boolean
+#?   $print [ddd ], 5155:number/raw, [  #? 1
+#? ] #? 1
     print-character screen:address, style:character, color:number, bg-color:number
+#?   $print [xxx ], 5155:number/raw, [  #? 1
+#? ] #? 1
     x:number <- add x:number, 1:literal
+#?   $print [yyy ], 5155:number/raw, [  #? 1
+#? ] #? 1
     loop
   }
 ]