about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--003trace.cc465
-rw-r--r--011load.cc16
-rw-r--r--012transform.cc4
-rw-r--r--013update_operation.cc2
-rw-r--r--019type_abbreviations.cc10
-rw-r--r--020run.cc33
-rw-r--r--021check_instruction.cc2
-rw-r--r--024jump.cc10
-rw-r--r--026call.cc24
-rw-r--r--028call_return.cc14
-rw-r--r--029tools.cc18
-rw-r--r--030container.cc16
-rw-r--r--031merge.cc6
-rw-r--r--032array.cc14
-rw-r--r--033exclusive_container.cc10
-rw-r--r--034address.cc20
-rw-r--r--035lookup.cc2
-rw-r--r--036abandon.cc6
-rw-r--r--038new_text.cc6
-rw-r--r--040brace.cc16
-rw-r--r--042name.cc8
-rw-r--r--043space.cc2
-rw-r--r--045closure_name.cc4
-rw-r--r--046check_type_by_name.cc6
-rw-r--r--050scenario.cc13
-rw-r--r--052tangle.cc6
-rw-r--r--053recipe_header.cc32
-rw-r--r--054static_dispatch.cc18
-rw-r--r--055shape_shifting_container.cc2
-rw-r--r--056shape_shifting_recipe.cc46
-rw-r--r--057immutable.cc2
-rw-r--r--060rewrite_literal_string.cc2
-rw-r--r--062convert_ingredients_to_text.cc6
-rw-r--r--069hash.cc2
-rw-r--r--072recipe.cc10
-rw-r--r--073scheduler.cc4
-rw-r--r--074wait.cc20
-rw-r--r--076continuation.cc26
-rw-r--r--085scenario_console.cc14
-rw-r--r--089scenario_filesystem.cc10
-rw-r--r--099hardware_checks.cc2
-rw-r--r--101run_sandboxed.cc12
-rw-r--r--999spaces.cc10
-rw-r--r--Readme.md14
-rw-r--r--browse_trace/Readme.md78
-rwxr-xr-xbrowse_trace/browse_trace7
-rw-r--r--browse_trace/browse_trace.cc (renamed from 100trace_browser.cc)198
-rwxr-xr-xbrowse_trace/build83
-rwxr-xr-xbrowse_trace/clean6
-rw-r--r--subx/003trace.cc429
-rw-r--r--subx/010---vm.cc30
-rw-r--r--subx/011run.cc9
-rw-r--r--subx/012elf.cc13
-rw-r--r--subx/013direct_addressing.cc82
-rw-r--r--subx/014indirect_addressing.cc46
-rw-r--r--subx/015immediate_addressing.cc56
-rw-r--r--subx/016index_addressing.cc8
-rw-r--r--subx/017jump_disp8.cc98
-rw-r--r--subx/018jump_disp32.cc98
-rw-r--r--subx/019functions.cc15
-rw-r--r--subx/020syscalls.cc48
-rw-r--r--subx/021byte_addressing.cc18
-rw-r--r--subx/028translate.cc3
-rw-r--r--subx/030---operands.cc2
-rw-r--r--subx/031check_operands.cc2
-rw-r--r--subx/032check_operand_bounds.cc2
-rw-r--r--subx/034compute_segment_address.cc8
-rw-r--r--subx/035labels.cc6
-rw-r--r--subx/036global_variables.cc2
-rw-r--r--subx/038---literal_strings.cc2
-rw-r--r--subx/039debug.cc2
-rw-r--r--subx/040---tests.cc4
-rwxr-xr-xsubx/drun4
73 files changed, 1326 insertions, 998 deletions
diff --git a/003trace.cc b/003trace.cc
index 11b54818..3213effc 100644
--- a/003trace.cc
+++ b/003trace.cc
@@ -13,30 +13,31 @@
 //: longer valid. In both cases you end up having to reorganize code as well as
 //: tests, an error-prone activity.
 //:
-//: In response, this layer introduces the notion of *domain-driven* testing.
-//: We focus on the domain of inputs the whole program needs to handle rather
-//: than the correctness of individual functions. All tests invoke the program
-//: in a single way: by calling run() with some input. As the program operates
-//: on the input, it traces out a list of _facts_ deduced about the domain:
+//: In response, this layer introduces the notion of domain-driven *white-box*
+//: testing. We focus on the domain of inputs the whole program needs to
+//: handle rather than the correctness of individual functions. All white-box
+//: tests (we call them 'scenarios') invoke the program in a single way: by
+//: calling run() with some input. As the program operates on the input, it
+//: traces out a list of _facts_ deduced about the domain:
 //:   trace("label") << "fact 1: " << val;
 //:
-//: Tests can now check these facts:
+//: Scenarios can now check these facts:
 //:   :(scenario foo)
 //:   34  # call run() with this input
 //:   +label: fact 1: 34  # 'run' should have deduced this fact
 //:   -label: fact 1: 35  # the trace should not contain such a fact
 //:
 //: Since we never call anything but the run() function directly, we never have
-//: to rewrite the tests when we reorganize the internals of the program. We
+//: to rewrite the scenarios when we reorganize the internals of the program. We
 //: just have to make sure our rewrite deduces the same facts about the domain,
 //: and that's something we're going to have to do anyway.
 //:
 //: To avoid the combinatorial explosion of integration tests, each layer
-//: mainly logs facts to the trace with a common *label*. All tests in a layer
-//: tend to check facts with this label. Validating the facts logged with a
-//: specific label is like calling functions of that layer directly.
+//: mainly logs facts to the trace with a common *label*. All scenarios in a
+//: layer tend to check facts with this label. Validating the facts logged
+//: with a specific label is like calling functions of that layer directly.
 //:
-//: To build robust tests, trace facts about your domain rather than details of
+//: To build robust scenarios, trace facts about your domain rather than details of
 //: how you computed them.
 //:
 //: More details: http://akkartik.name/blog/tracing-tests
@@ -44,159 +45,188 @@
 //: ---
 //:
 //: Between layers and domain-driven testing, programming starts to look like a
-//: fundamentally different activity. Instead of a) superficial, b) local rules
-//: on c) code [like say http://blog.bbv.ch/2013/06/05/clean-code-cheat-sheet],
-//: we allow programmers to engage with the a) deep, b) global structure of the
-//: c) domain. If you can systematically track discontinuities in the domain,
-//: you don't care if the code used gotos as long as it passed the tests. If
-//: tests become more robust to run it becomes easier to try out radically
-//: different implementations for the same program. If code is super-easy to
-//: rewrite, it becomes less important what indentation style it uses, or that
-//: the objects are appropriately encapsulated, or that the functions are
-//: referentially transparent.
+//: fundamentally different activity. Instead of focusing on a) superficial,
+//: b) local rules on c) code [like say http://blog.bbv.ch/2013/06/05/clean-code-cheat-sheet],
+//: we allow programmers to engage with the a) deep, b) global structure of
+//: the c) domain. If you can systematically track discontinuities in the
+//: domain, you don't care if the code used gotos as long as it passed all
+//: scenarios. If scenarios become more robust to run, it becomes easier to
+//: try out radically different implementations for the same program. If code
+//: is super-easy to rewrite, it becomes less important what indentation style
+//: it uses, or that the objects are appropriately encapsulated, or that the
+//: functions are referentially transparent.
 //:
 //: Instead of plumbing, programming becomes building and gradually refining a
-//: map of the environment the program must operate under. Whether a program is
-//: 'correct' at a given point in time is a red herring; what matters is
-//: avoiding regression by monotonically nailing down the more 'eventful' parts
-//: of the terrain. It helps readers new and old, and rewards curiosity, to
-//: organize large programs in self-similar hierarchies of example scenarios
+//: map of the environment the program must operate under. Whether a program
+//: is 'correct' at a given point in time is a red herring; what matters is
+//: avoiding regression by monotonically nailing down the more 'eventful'
+//: parts of the terrain. It helps readers new and old, and rewards curiosity,
+//: to organize large programs in self-similar hierarchies of example scenarios
 //: colocated with the code that makes them work.
 //:
 //:   "Programming properly should be regarded as an activity by which
 //:   programmers form a mental model, rather than as production of a program."
 //:   -- Peter Naur (http://alistair.cockburn.us/ASD+book+extract%3A+%22Naur,+Ehn,+Musashi%22)
 
-:(before "End Types")
-struct trace_line {
-  int depth;  // optional field just to help browse traces later
-  string label;
-  string contents;
-  trace_line(string l, string c) :depth(0), label(l), contents(c) {}
-  trace_line(int d, string l, string c) :depth(d), label(l), contents(c) {}
-};
+//:: == Core data structures
 
-//: Support for tracing an entire run.
-//: Traces can have a lot of overhead, so only turn them on when asked.
-:(before "End Commandline Options(*arg)")
-else if (is_equal(*arg, "--trace")) {
-  Save_trace = true;
-}
-:(before "End Commandline Parsing")
-if (Save_trace) {
-  cerr << "initializing trace\n";
-  Trace_stream = new trace_stream;
-}
-:(code)
-void cleanup_main() {
-  if (!Trace_stream) return;
-  if (Save_trace)
-    Trace_stream->save();
-  delete Trace_stream;
-  Trace_stream = NULL;
-}
-:(before "End One-time Setup")
-atexit(cleanup_main);
+:(before "End Globals")
+trace_stream* Trace_stream = NULL;
 
 :(before "End Types")
-// Pre-define some global constants that trace_stream needs to know about.
-// Since they're in the Types section, they'll be included in any cleaved
-// compilation units. So no extern linkage.
-const int Max_depth = 9999;
-const int Error_depth = 0;  // definitely always print errors
-const int App_depth = 2;  // temporarily where all Mu code will trace to
-
 struct trace_stream {
   vector<trace_line> past_lines;
-  // accumulator for current line
-  ostringstream* curr_stream;
-  string curr_label;
-  int curr_depth;
-  int callstack_depth;
-  int collect_depth;
-  ofstream null_stream;  // never opens a file, so writes silently fail
-  trace_stream() :curr_stream(NULL), curr_depth(Max_depth), callstack_depth(0), collect_depth(Max_depth) {}
-  ~trace_stream() { if (curr_stream) delete curr_stream; }
-
-  ostream& stream(string label) {
-    return stream(Max_depth, label);
-  }
+  // End trace_stream Fields
 
-  ostream& stream(int depth, string label) {
-    if (depth > collect_depth) return null_stream;
-    curr_stream = new ostringstream;
-    curr_label = label;
-    curr_depth = depth;
-    return *curr_stream;
+  trace_stream() {
+    // End trace_stream Constructor
   }
-
-  void save() {
-    cerr << "saving trace to 'last_run'\n";
-    ofstream fout("last_run");
-    fout << readable_contents("");
-    fout.close();
+  ~trace_stream() {
+    // End trace_stream Destructor
   }
+  // End trace_stream Methods
+};
 
-  // be sure to call this before messing with curr_stream or curr_label
-  void newline();
-  // useful for debugging
-  string readable_contents(string label);  // empty label = show everything
+//:: == Adding to the trace
+
+//: Top-level method is trace() which can be used like an ostream. Usage:
+//:   trace(depth, label) << ... << end();
+//: Don't forget the 'end()' to actually append to the trace.
+:(before "End Includes")
+// No brackets around the expansion so that it prints nothing if Trace_stream
+// isn't initialized.
+#define trace(...)  !Trace_stream ? cerr : Trace_stream->stream(__VA_ARGS__)
+
+:(before "End trace_stream Fields")
+// accumulator for current trace_line
+ostringstream* curr_stream;
+string curr_label;
+int curr_depth;
+// other stuff
+int collect_depth;  // avoid tracing lower levels for speed
+ofstream null_stream;  // never opened, so writes to it silently fail
+
+//: Some constants.
+:(before "struct trace_stream")  // include constants in all cleaved compilation units
+const int Max_depth = 9999;
+// Most important traces are printed to the screen by default
+const int Error_depth = 0;
+:(before "End Globals")
+int Hide_errors = false;  // if set, don't print errors to screen
+:(before "End trace_stream Constructor")
+curr_stream = NULL;
+curr_depth = Max_depth;
+collect_depth = Max_depth;
+:(before "End Reset")
+Hide_errors = false;
+
+:(before "struct trace_stream")
+struct trace_line {
+  string contents;
+  string label;
+  int depth;  // 0 is 'sea level'; positive integers are progressively 'deeper' and lower level
+  trace_line(string c, string l) {
+    contents = c;
+    label = l;
+    depth = 0;
+  }
+  trace_line(string c, string l, int d) {
+    contents = c;
+    label = l;
+    depth = d;
+  }
 };
 
+//: Starting a new trace line.
+:(before "End trace_stream Methods")
+ostream& stream(string label) {
+  return stream(Max_depth, label);
+}
+
+ostream& stream(int depth, string label) {
+  if (depth > collect_depth) return null_stream;
+  curr_stream = new ostringstream;
+  curr_label = label;
+  curr_depth = depth;
+  return *curr_stream;
+}
+
+//: End of a trace line; append it to the trace.
+:(before "End Types")
+struct end {};
+:(code)
+ostream& operator<<(ostream& os, end /*unused*/) {
+  if (Trace_stream) Trace_stream->newline();
+  return os;
+}
+
+:(before "End trace_stream Methods")
+void newline();
 :(code)
 void trace_stream::newline() {
   if (!curr_stream) return;
   string curr_contents = curr_stream->str();
   if (!curr_contents.empty()) {
-    past_lines.push_back(trace_line(curr_depth, trim(curr_label), curr_contents));  // preserve indent in contents
-    if ((!Hide_errors && curr_label == "error")
-        || Dump_trace
-        || (!Dump_label.empty() && curr_label == Dump_label))
-      cerr << curr_label << ": " << curr_contents << '\n';
+    past_lines.push_back(trace_line(curr_contents, trim(curr_label), curr_depth));  // preserve indent in contents
+    // maybe incrementally dump trace
+    trace_line& t = past_lines.back();
+    if (!Hide_errors && curr_depth == Error_depth) {
+      cerr       << std::setw(4) << t.depth << ' ' << t.label << ": " << t.contents << '\n';
+    }
+    // End trace Commit
   }
+
+  // clean up
   delete curr_stream;
   curr_stream = NULL;
   curr_label.clear();
   curr_depth = Max_depth;
 }
 
-string trace_stream::readable_contents(string label) {
-  ostringstream output;
-  label = trim(label);
-  for (vector<trace_line>::iterator p = past_lines.begin();  p != past_lines.end();  ++p)
-    if (label.empty() || label == p->label)
-      output << std::setw(4) << p->depth << ' ' << p->label << ": " << p->contents << '\n';
-  return output.str();
-}
+//:: == Initializing the trace in scenarios
 
-:(before "End Globals")
-trace_stream* Trace_stream = NULL;
-int Trace_errors = 0;  // used only when Trace_stream is NULL
+:(before "End Includes")
+#define START_TRACING_UNTIL_END_OF_SCOPE  lease_tracer leased_tracer;
+:(before "End Test Setup")
+START_TRACING_UNTIL_END_OF_SCOPE
 
-:(before "End Globals")
-bool Hide_errors = false;  // if set, don't print even error trace lines to screen
-bool Dump_trace = false;  // if set, print trace lines to screen
-string Dump_label = "";  // if set, print trace lines matching a single label to screen
-:(before "End Reset")
-Hide_errors = false;
-Dump_trace = false;  // toggle this to print traces to screen as they are emitted
-Dump_label = "";
+//: Trace_stream is a resource, lease_tracer uses RAII to manage it.
+:(before "End Types")
+struct lease_tracer {
+  lease_tracer();
+  ~lease_tracer();
+};
+:(code)
+lease_tracer::lease_tracer() { Trace_stream = new trace_stream; }
+lease_tracer::~lease_tracer() {
+  delete Trace_stream;
+  Trace_stream = NULL;
+}
+
+//:: == Errors using traces
 
 :(before "End Includes")
-#define CLEAR_TRACE  delete Trace_stream, Trace_stream = new trace_stream;
+#define raise  (!Trace_stream ? (scroll_to_bottom_and_close_console(),++Trace_errors,cerr) /*do print*/ : Trace_stream->stream(Error_depth, "error"))
 
-// Top-level helper. IMPORTANT: can't nest
-#define trace(...)  !Trace_stream ? cerr /*print nothing*/ : Trace_stream->stream(__VA_ARGS__)
+:(before "End Globals")
+int Trace_errors = 0;  // used only when Trace_stream is NULL
 
-// Just for debugging; 'git log' should never show any calls to 'dbg'.
-#define dbg trace(0, "a")
-#define DUMP(label)  if (Trace_stream) cerr << Trace_stream->readable_contents(label);
+// Fail scenarios that displayed (unexpected) errors.
+// Expected errors should always be hidden and silently checked for.
+:(before "End Test Teardown")
+if (Passed && !Hide_errors && trace_contains_errors()) {
+  Passed = false;
+}
+:(code)
+bool trace_contains_errors() {
+  return Trace_errors > 0 || trace_count("error") > 0;
+}
 
-// Errors are a special layer.
-#define raise  (!Trace_stream ? (scroll_to_bottom_and_close_console(),++Trace_errors,cerr) /*do print*/ : Trace_stream->stream(Error_depth, "error"))
+:(before "End Includes")
 // If we aren't yet sure how to deal with some corner case, use assert_for_now
 // to indicate that it isn't an inviolable invariant.
 #define assert_for_now assert
+#define raise_for_now raise
 
 //: Automatically close the console in some situations.
 :(before "End One-time Setup")
@@ -209,57 +239,30 @@ void scroll_to_bottom_and_close_console() {
   cout << "\r\n";
   tb_shutdown();
 }
-
-// Inside tests, fail any tests that displayed (unexpected) errors.
-// Expected errors in tests should always be hidden and silently checked for.
-:(before "End Test Teardown")
-if (Passed && !Hide_errors && trace_contains_errors()) {
-  Passed = false;
-}
-:(code)
-bool trace_contains_errors() {
-  return Trace_errors > 0 || trace_count("error") > 0;
-}
-
-:(before "End Types")
-struct end {};
-:(code)
-ostream& operator<<(ostream& os, end /*unused*/) {
-  if (Trace_stream) Trace_stream->newline();
-  return os;
-}
-
-:(before "End Globals")
-bool Save_trace = false;  // if set, write out trace to disk
-
-// Trace_stream is a resource, lease_tracer uses RAII to manage it.
-:(before "End Types")
-struct lease_tracer {
-  lease_tracer();
-  ~lease_tracer();
-};
-:(code)
-lease_tracer::lease_tracer() { Trace_stream = new trace_stream; }
-lease_tracer::~lease_tracer() {
-  if (Save_trace) Trace_stream->save();
-  delete Trace_stream, Trace_stream = NULL;
-}
 :(before "End Includes")
-#define START_TRACING_UNTIL_END_OF_SCOPE  lease_tracer leased_tracer;
-:(before "End Test Setup")
-START_TRACING_UNTIL_END_OF_SCOPE
+#include "termbox/termbox.h"
+
+//:: == Other assertions on traces
+//: Primitives:
+//:   - CHECK_TRACE_CONTENTS(lines)
+//:     Assert that the trace contains the given lines (separated by newlines)
+//:     in order. There can be other intervening lines between them.
+//:   - CHECK_TRACE_DOESNT_CONTAIN(line)
+//:   - CHECK_TRACE_DOESNT_CONTAIN(label, contents)
+//:     Assert that the trace doesn't contain the given (single) line.
+//:   - CHECK_TRACE_COUNT(label, count)
+//:     Assert that the trace contains exactly 'count' lines with the given
+//:     'label'.
+//:   - CHECK_TRACE_CONTAINS_ERRORS()
+//:   - CHECK_TRACE_DOESNT_CONTAIN_ERRORS()
+//:   - trace_count_prefix(label, prefix)
+//:     Count the number of trace lines with the given 'label' that start with
+//:     the given 'prefix'.
 
 :(before "End Includes")
 #define CHECK_TRACE_CONTENTS(...)  check_trace_contents(__FUNCTION__, __FILE__, __LINE__, __VA_ARGS__)
 
-#define CHECK_TRACE_CONTAINS_ERRORS()  CHECK(trace_contains_errors())
-#define CHECK_TRACE_DOESNT_CONTAIN_ERRORS() \
-  if (Passed && trace_contains_errors()) { \
-    cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): unexpected errors\n"; \
-    DUMP("error"); \
-    Passed = false; \
-    return; \
-  }
+#define CHECK_TRACE_DOESNT_CONTAIN(...)  CHECK(trace_doesnt_contain(__VA_ARGS__))
 
 #define CHECK_TRACE_COUNT(label, count) \
   if (Passed && trace_count(label) != (count)) { \
@@ -270,7 +273,17 @@ START_TRACING_UNTIL_END_OF_SCOPE
     return;  /* Currently we stop at the very first failure. */ \
   }
 
-#define CHECK_TRACE_DOESNT_CONTAIN(...)  CHECK(trace_doesnt_contain(__VA_ARGS__))
+#define CHECK_TRACE_CONTAINS_ERRORS()  CHECK(trace_contains_errors())
+#define CHECK_TRACE_DOESNT_CONTAIN_ERRORS() \
+  if (Passed && trace_contains_errors()) { \
+    cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): unexpected errors\n"; \
+    DUMP("error"); \
+    Passed = false; \
+    return; \
+  }
+
+// Allow scenarios to ignore trace lines generated during setup.
+#define CLEAR_TRACE  delete Trace_stream, Trace_stream = new trace_stream
 
 :(code)
 bool check_trace_contents(string FUNCTION, string FILE, int LINE, string expected) {
@@ -305,25 +318,13 @@ bool check_trace_contents(string FUNCTION, string FILE, int LINE, string expecte
   return false;
 }
 
-void split_label_contents(const string& s, string* label, string* contents) {
-  static const string delim(": ");
-  size_t pos = s.find(delim);
-  if (pos == string::npos) {
-    *label = "";
-    *contents = trim(s);
-  }
-  else {
-    *label = trim(s.substr(0, pos));
-    *contents = trim(s.substr(pos+SIZE(delim)));
-  }
-}
-
-bool line_exists_anywhere(const string& label, const string& contents) {
-  for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin();  p != Trace_stream->past_lines.end();  ++p) {
-    if (label != p->label) continue;
-    if (contents == trim(p->contents)) return true;
+bool trace_doesnt_contain(string expected) {
+  vector<string> tmp = split_first(expected, ": ");
+  if (SIZE(tmp) == 1) {
+    raise << expected << ": missing label or contents in trace line\n" << end();
+    assert(false);
   }
-  return false;
+  return trace_count(tmp.at(0), tmp.at(1)) == 0;
 }
 
 int trace_count(string label) {
@@ -354,17 +355,25 @@ int trace_count_prefix(string label, string prefix) {
   return result;
 }
 
-bool trace_doesnt_contain(string label, string line) {
-  return trace_count(label, line) == 0;
+void split_label_contents(const string& s, string* label, string* contents) {
+  static const string delim(": ");
+  size_t pos = s.find(delim);
+  if (pos == string::npos) {
+    *label = "";
+    *contents = trim(s);
+  }
+  else {
+    *label = trim(s.substr(0, pos));
+    *contents = trim(s.substr(pos+SIZE(delim)));
+  }
 }
 
-bool trace_doesnt_contain(string expected) {
-  vector<string> tmp = split_first(expected, ": ");
-  if (SIZE(tmp) == 1) {
-    raise << expected << ": missing label or contents in trace line\n" << end();
-    assert(false);
+bool line_exists_anywhere(const string& label, const string& contents) {
+  for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin();  p != Trace_stream->past_lines.end();  ++p) {
+    if (label != p->label) continue;
+    if (contents == trim(p->contents)) return true;
   }
-  return trace_doesnt_contain(tmp.at(0), tmp.at(1));
+  return false;
 }
 
 vector<string> split(string s, string delim) {
@@ -391,6 +400,54 @@ vector<string> split_first(string s, string delim) {
   return result;
 }
 
+//:: == Helpers for debugging using traces
+
+:(before "End Includes")
+// To debug why a scenario is failing, dump its trace using '?'.
+#define DUMP(label)  if (Trace_stream) cerr << Trace_stream->readable_contents(label);
+
+// To add temporary prints to the trace, use 'dbg'.
+// `git log` should never show any calls to 'dbg'.
+#define dbg trace(0, "a")
+
+//: Dump the entire trace to file where it can be browsed offline.
+//: Dump the trace as it happens; that way you get something even if the
+//: program crashes.
+
+:(before "End Globals")
+ofstream Trace_file;
+:(before "End Commandline Options(*arg)")
+else if (is_equal(*arg, "--trace")) {
+  Trace_stream = new trace_stream;
+  cerr << "saving trace to 'last_run'\n";
+  Trace_file.open("last_run");
+}
+:(before "End trace Commit")
+if (Trace_file) {
+  Trace_file << std::setw(4) << t.depth << ' ' << t.label << ": " << t.contents << '\n';
+}
+:(before "End One-time Setup")
+atexit(cleanup_main);
+:(code)
+void cleanup_main() {
+  if (Trace_file) Trace_file.close();
+  // End cleanup_main
+}
+
+:(before "End trace_stream Methods")
+string readable_contents(string label) {
+  string trim(const string& s);  // prototype
+  ostringstream output;
+  label = trim(label);
+  for (vector<trace_line>::iterator p = past_lines.begin();  p != past_lines.end();  ++p)
+    if (label.empty() || label == p->label)
+      output << std::setw(4) << p->depth << ' ' << p->label << ": " << p->contents << '\n';
+  return output.str();
+}
+
+//: Miscellaneous helpers.
+
+:(code)
 string trim(const string& s) {
   string::const_iterator first = s.begin();
   while (first != s.end() && isspace(*first))
@@ -419,19 +476,3 @@ using std::ostringstream;
 #include <fstream>
 using std::ifstream;
 using std::ofstream;
-
-#include "termbox/termbox.h"
-
-:(before "End Globals")
-//: In future layers we'll use the depth field as follows:
-//:
-//: Errors will be depth 0.
-//: Mu 'applications' will be able to use depths 1-100 as they like.
-//: Primitive statements will occupy 101-9989
-extern const int Initial_callstack_depth = 101;
-extern const int Max_callstack_depth = 9989;
-//: Finally, details of primitive Mu statements will occupy depth 9990-9999
-//: (more on that later as well)
-//:
-//: This framework should help us hide some details at each level, mixing
-//: static ideas like layers with the dynamic notion of call-stack depth.
diff --git a/011load.cc b/011load.cc
index 666f1c6e..575509ea 100644
--- a/011load.cc
+++ b/011load.cc
@@ -63,13 +63,13 @@ int slurp_recipe(istream& in) {
   // End Recipe Refinements
   if (result.name.empty())
     raise << "empty result.name\n" << end();
-  trace(9991, "parse") << "--- defining " << result.name << end();
+  trace(101, "parse") << "--- defining " << result.name << end();
   if (!contains_key(Recipe_ordinal, result.name))
     put(Recipe_ordinal, result.name, Next_recipe_ordinal);
   result.ordinal = get(Recipe_ordinal, result.name);
   ++Next_recipe_ordinal;
   if (Recipe.find(get(Recipe_ordinal, result.name)) != Recipe.end()) {
-    trace(9991, "parse") << "already exists" << end();
+    trace(101, "parse") << "already exists" << end();
     if (should_check_for_redefine(result.name))
       raise << "redefining recipe " << result.name << "\n" << end();
     Recipe.erase(get(Recipe_ordinal, result.name));
@@ -90,7 +90,7 @@ void slurp_body(istream& in, recipe& result) {
   while (next_instruction(in, &curr)) {
     curr.original_string = to_original_string(curr);
     // End Rewrite Instruction(curr, recipe result)
-    trace(9992, "load") << "after rewriting: " << to_string(curr) << end();
+    trace(102, "load") << "after rewriting: " << to_string(curr) << end();
     if (!curr.is_empty()) result.steps.push_back(curr);
   }
 }
@@ -126,7 +126,7 @@ bool next_instruction(istream& in, instruction* curr) {
   if (SIZE(words) == 1 && is_label_word(words.at(0))) {
     curr->is_label = true;
     curr->label = words.at(0);
-    trace(9993, "parse") << "label: " << curr->label << end();
+    trace(103, "parse") << "label: " << curr->label << end();
     if (!has_data(in)) {
       raise << "incomplete recipe at end of file (3)\n" << end();
       return false;
@@ -151,12 +151,12 @@ bool next_instruction(istream& in, instruction* curr) {
   for (;  p != words.end();  ++p)
     curr->ingredients.push_back(reagent(*p));
 
-  trace(9993, "parse") << "instruction: " << curr->name << end();
-  trace(9993, "parse") << "  number of ingredients: " << SIZE(curr->ingredients) << end();
+  trace(103, "parse") << "instruction: " << curr->name << end();
+  trace(103, "parse") << "  number of ingredients: " << SIZE(curr->ingredients) << end();
   for (vector<reagent>::iterator p = curr->ingredients.begin();  p != curr->ingredients.end();  ++p)
-    trace(9993, "parse") << "  ingredient: " << to_string(*p) << end();
+    trace(103, "parse") << "  ingredient: " << to_string(*p) << end();
   for (vector<reagent>::iterator p = curr->products.begin();  p != curr->products.end();  ++p)
-    trace(9993, "parse") << "  product: " << to_string(*p) << end();
+    trace(103, "parse") << "  product: " << to_string(*p) << end();
   if (!has_data(in)) {
     raise << "9: unbalanced '[' for recipe\n" << end();
     return false;
diff --git a/012transform.cc b/012transform.cc
index f62349fa..758a286c 100644
--- a/012transform.cc
+++ b/012transform.cc
@@ -44,7 +44,7 @@ void initialize_transforms() {
 }
 
 void transform_all() {
-  trace(9990, "transform") << "=== transform_all()" << end();
+  trace(100, "transform") << "=== transform_all()" << end();
   // Begin transform_all
   for (int t = 0;  t < SIZE(Transform);  ++t) {
     for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin();  p != Recipe.end();  ++p) {
@@ -72,7 +72,7 @@ int Num_calls_to_transform_all = 0;
 
 :(code)
 void parse_int_reagents() {
-  trace(9991, "transform") << "--- parsing any uninitialized reagents as integers" << end();
+  trace(101, "transform") << "--- parsing any uninitialized reagents as integers" << end();
   for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin();  p != Recipe.end();  ++p) {
     recipe& r = p->second;
     if (r.steps.empty()) continue;
diff --git a/013update_operation.cc b/013update_operation.cc
index 8366439d..c5a736f6 100644
--- a/013update_operation.cc
+++ b/013update_operation.cc
@@ -6,7 +6,7 @@ Transform.push_back(update_instruction_operations);  // idempotent
 
 :(code)
 void update_instruction_operations(const recipe_ordinal r) {
-  trace(9991, "transform") << "--- compute instruction operations for recipe " << get(Recipe, r).name << end();
+  trace(101, "transform") << "--- compute instruction operations for recipe " << get(Recipe, r).name << end();
   recipe& caller = get(Recipe, r);
 //?   cerr << "--- compute instruction operations for recipe " << caller.name << '\n';
   for (int index = 0;  index < SIZE(caller.steps);  ++index) {
diff --git a/019type_abbreviations.cc b/019type_abbreviations.cc
index df797dfa..dfa214c9 100644
--- a/019type_abbreviations.cc
+++ b/019type_abbreviations.cc
@@ -50,7 +50,7 @@ void load_type_abbreviations(istream& in) {
     raise << "'type' conflict: '" << new_type_name << "' defined as both '" << names_to_string_without_quotes(get(Type_abbreviations, new_type_name)) << "' and '" << old << "'\n" << end();
     return;
   }
-  trace(9990, "type") << "alias " << new_type_name << " = " << old << end();
+  trace(100, "type") << "alias " << new_type_name << " = " << old << end();
   type_tree* old_type = new_type_tree(old);
   put(Type_abbreviations, new_type_name, old_type);
 }
@@ -168,17 +168,17 @@ void expand_type_abbreviations(const recipe_ordinal r) {
 }
 
 void expand_type_abbreviations(const recipe& caller) {
-  trace(9991, "transform") << "--- expand type abbreviations in recipe '" << caller.name << "'" << end();
+  trace(101, "transform") << "--- expand type abbreviations in recipe '" << caller.name << "'" << end();
   for (int i = 0;  i < SIZE(caller.steps);  ++i) {
     const instruction& inst = caller.steps.at(i);
-    trace(9991, "transform") << "instruction '" << to_original_string(inst) << end();
+    trace(102, "transform") << "instruction '" << to_original_string(inst) << end();
     for (long int i = 0;  i < SIZE(inst.ingredients);  ++i) {
       expand_type_abbreviations(inst.ingredients.at(i).type);
-      trace(9992, "transform") << "ingredient type after expanding abbreviations: " << names_to_string(inst.ingredients.at(i).type) << end();
+      trace(102, "transform") << "ingredient type after expanding abbreviations: " << names_to_string(inst.ingredients.at(i).type) << end();
     }
     for (long int i = 0;  i < SIZE(inst.products);  ++i) {
       expand_type_abbreviations(inst.products.at(i).type);
-      trace(9992, "transform") << "product type after expanding abbreviations: " << names_to_string(inst.products.at(i).type) << end();
+      trace(102, "transform") << "product type after expanding abbreviations: " << names_to_string(inst.products.at(i).type) << end();
     }
   }
   // End Expand Type Abbreviations(caller)
diff --git a/020run.cc b/020run.cc
index 74012216..5d466bbc 100644
--- a/020run.cc
+++ b/020run.cc
@@ -63,7 +63,7 @@ void run_current_routine() {
   while (should_continue_running(Current_routine)) {  // beware: may modify Current_routine
     // Running One Instruction
     if (current_instruction().is_label) { ++current_step_index();  continue; }
-    trace(Initial_callstack_depth + Trace_stream->callstack_depth, "run") << to_string(current_instruction()) << end();
+    trace(Callstack_depth, "run") << to_string(current_instruction()) << end();
 //?     if (Foo) cerr << "run: " << to_string(current_instruction()) << '\n';
     if (get_or_insert(Memory, 0) != 0) {
       raise << "something wrote to location 0; this should never happen\n" << end();
@@ -113,6 +113,26 @@ void run_current_routine() {
   stop_running_current_routine:;
 }
 
+//: Helpers for managing trace depths
+//:
+//: We're going to use trace depths primarily to segment code running at
+//: different frames of the call stack. This will make it easy for the trace
+//: browser to collapse over entire calls.
+//:
+//: The entire map of possible depths is as follows:
+//:
+//: Errors will be depth 0.
+//: Mu 'applications' will be able to use depths 1-99 as they like.
+//: Primitive statements will occupy 100 and up to Max_depth, organized by
+//: stack frames.
+:(before "End Globals")
+extern const int Initial_callstack_depth = 100;
+int Callstack_depth = Initial_callstack_depth;
+:(before "End Reset")
+Callstack_depth = Initial_callstack_depth;
+
+//: Other helpers for the VM.
+
 :(code)
 //: hook replaced in a later layer
 bool should_continue_running(const routine* current_routine) {
@@ -255,7 +275,7 @@ void load_file_or_directory(string filename) {
     cerr << "no such file '" << filename << "'\n" << end();  // don't raise, just warn. just in case it's just a name for a scenario to run.
     return;
   }
-  trace(9990, "load") << "=== " << filename << end();
+  trace(2, "load") << "=== " << filename << end();
   load(fin);
   fin.close();
 }
@@ -306,7 +326,7 @@ vector<double> read_memory(reagent/*copy*/ x) {
   int size = size_of(x);
   for (int offset = 0;  offset < size;  ++offset) {
     double val = get_or_insert(Memory, x.value+offset);
-    trace("mem") << "location " << x.value+offset << " is " << no_scientific(val) << end();
+    trace(Callstack_depth+1, "mem") << "location " << x.value+offset << " is " << no_scientific(val) << end();
     result.push_back(val);
   }
   return result;
@@ -333,7 +353,7 @@ void write_memory(reagent/*copy*/ x, const vector<double>& data) {
   // End write_memory(x) Special-cases
   for (int offset = 0;  offset < SIZE(data);  ++offset) {
     assert(x.value+offset > 0);
-    trace("mem") << "storing " << no_scientific(data.at(offset)) << " in location " << x.value+offset << end();
+    trace(Callstack_depth+1, "mem") << "storing " << no_scientific(data.at(offset)) << " in location " << x.value+offset << end();
 //?     if (Foo) cerr << "mem: storing " << no_scientific(data.at(offset)) << " in location " << x.value+offset << '\n';
     put(Memory, x.value+offset, data.at(offset));
   }
@@ -392,10 +412,7 @@ void run(const string& form) {
   vector<recipe_ordinal> tmp = load(form);
   transform_all();
   if (tmp.empty()) return;
-  if (trace_contains_errors()) {
-    if (Save_trace && Trace_stream) Trace_stream->save();
-    return;
-  }
+  if (trace_contains_errors()) return;
   // if a test defines main, it probably wants to start there regardless of
   // definition order
   if (contains_key(Recipe, get(Recipe_ordinal, "main")))
diff --git a/021check_instruction.cc b/021check_instruction.cc
index 6347dfb3..5bc7fa9c 100644
--- a/021check_instruction.cc
+++ b/021check_instruction.cc
@@ -13,7 +13,7 @@ Transform.push_back(check_instruction);  // idempotent
 
 :(code)
 void check_instruction(const recipe_ordinal r) {
-  trace(9991, "transform") << "--- perform checks for recipe " << get(Recipe, r).name << end();
+  trace(101, "transform") << "--- perform checks for recipe " << get(Recipe, r).name << end();
   map<string, vector<type_ordinal> > metadata;
   for (int i = 0;  i < SIZE(get(Recipe, r).steps);  ++i) {
     instruction& inst = get(Recipe, r).steps.at(i);
diff --git a/024jump.cc b/024jump.cc
index 14c14297..5b95fd19 100644
--- a/024jump.cc
+++ b/024jump.cc
@@ -33,7 +33,7 @@ case JUMP: {
 case JUMP: {
   assert(current_instruction().ingredients.at(0).initialized);
   current_step_index() += ingredients.at(0).at(0)+1;
-  trace(9998, "run") << "jumping to instruction " << current_step_index() << end();
+  trace(Callstack_depth+1, "run") << "jumping to instruction " << current_step_index() << end();
   // skip rest of this instruction
   write_products = false;
   fall_through_to_next_instruction = false;
@@ -91,11 +91,11 @@ case JUMP_IF: {
 case JUMP_IF: {
   assert(current_instruction().ingredients.at(1).initialized);
   if (!scalar_ingredient(ingredients, 0)) {
-    trace(9998, "run") << "jump-if fell through" << end();
+    trace(Callstack_depth+1, "run") << "jump-if fell through" << end();
     break;
   }
   current_step_index() += ingredients.at(1).at(0)+1;
-  trace(9998, "run") << "jumping to instruction " << current_step_index() << end();
+  trace(Callstack_depth+1, "run") << "jumping to instruction " << current_step_index() << end();
   // skip rest of this instruction
   write_products = false;
   fall_through_to_next_instruction = false;
@@ -162,11 +162,11 @@ case JUMP_UNLESS: {
 case JUMP_UNLESS: {
   assert(current_instruction().ingredients.at(1).initialized);
   if (scalar_ingredient(ingredients, 0)) {
-    trace(9998, "run") << "jump-unless fell through" << end();
+    trace(Callstack_depth+1, "run") << "jump-unless fell through" << end();
     break;
   }
   current_step_index() += ingredients.at(1).at(0)+1;
-  trace(9998, "run") << "jumping to instruction " << current_step_index() << end();
+  trace(Callstack_depth+1, "run") << "jumping to instruction " << current_step_index() << end();
   // skip rest of this instruction
   write_products = false;
   fall_through_to_next_instruction = false;
diff --git a/026call.cc b/026call.cc
index c6077cd9..1f130ef5 100644
--- a/026call.cc
+++ b/026call.cc
@@ -60,11 +60,9 @@ struct routine {
 };
 :(code)
 routine::routine(recipe_ordinal r) {
-  if (Trace_stream) {
-    ++Trace_stream->callstack_depth;
-    trace("trace") << "new routine; incrementing callstack depth to " << Trace_stream->callstack_depth << end();
-    assert(Trace_stream->callstack_depth < 9000);  // 9998-101 plus cushion
-  }
+  ++Callstack_depth;
+  trace(Callstack_depth+1, "trace") << "new routine; incrementing callstack depth to " << Callstack_depth << end();
+  assert(Callstack_depth < Max_depth);
   calls.push_front(call(r));
   // End routine Constructor
 }
@@ -151,11 +149,9 @@ if (!contains_key(Recipe, inst.operation)) {
 default: {
   if (contains_key(Recipe, current_instruction().operation)) {  // error already raised in Checks above
     // not a primitive; look up the book of recipes
-    if (Trace_stream) {
-      ++Trace_stream->callstack_depth;
-      trace("trace") << "incrementing callstack depth to " << Trace_stream->callstack_depth << end();
-      assert(Trace_stream->callstack_depth < 9000);  // 9998-101 plus cushion
-    }
+    ++Callstack_depth;
+    trace(Callstack_depth+1, "trace") << "incrementing callstack depth to " << Callstack_depth << end();
+    assert(Callstack_depth < Max_depth);
     const call& caller_frame = current_call();
     Current_routine->calls.push_front(call(to_instruction(caller_frame).operation));
     finish_call_housekeeping(to_instruction(caller_frame), ingredients);
@@ -202,11 +198,9 @@ const vector<instruction>& routine::steps() const {
 // it, and the one below that, and so on
 while (current_step_index() >= SIZE(Current_routine->steps())) {
   // Falling Through End Of Recipe
-  if (Trace_stream) {
-    trace("trace") << "fall-through: exiting " << current_recipe_name() << "; decrementing callstack depth from " << Trace_stream->callstack_depth << end();
-    --Trace_stream->callstack_depth;
-    assert(Trace_stream->callstack_depth >= 0);
-  }
+  trace(Callstack_depth+1, "trace") << "fall-through: exiting " << current_recipe_name() << "; decrementing callstack depth from " << Callstack_depth << end();
+  --Callstack_depth;
+  assert(Callstack_depth >= 0);
   Current_routine->calls.pop_front();
   if (Current_routine->calls.empty()) goto stop_running_current_routine;
   // Complete Call Fallthrough
diff --git a/028call_return.cc b/028call_return.cc
index c8c1bca6..af158884 100644
--- a/028call_return.cc
+++ b/028call_return.cc
@@ -37,19 +37,17 @@ case RETURN: {
 :(before "End Primitive Recipe Implementations")
 case RETURN: {
   // Begin Return
-  if (Trace_stream) {
-    trace("trace") << current_instruction().name << ": decrementing callstack depth from " << Trace_stream->callstack_depth << end();
-    --Trace_stream->callstack_depth;
-    if (Trace_stream->callstack_depth < 0) {
-      Current_routine->calls.clear();
-      goto stop_running_current_routine;
-    }
+  trace(Callstack_depth+1, "trace") << current_instruction().name << ": decrementing callstack depth from " << Callstack_depth << end();
+  --Callstack_depth;
+  if (Callstack_depth < 0) {
+    Current_routine->calls.clear();
+    goto stop_running_current_routine;
   }
   Current_routine->calls.pop_front();
   // just in case 'main' returns a value, drop it for now
   if (Current_routine->calls.empty()) goto stop_running_current_routine;
   for (int i = 0;  i < SIZE(ingredients);  ++i)
-    trace(9998, "run") << "result " << i << " is " << to_string(ingredients.at(i)) << end();
+    trace(Callstack_depth+1, "run") << "result " << i << " is " << to_string(ingredients.at(i)) << end();
   // make return products available to caller
   copy(ingredients.begin(), ingredients.end(), inserter(products, products.begin()));
   // End Return
diff --git a/029tools.cc b/029tools.cc
index be3c04e2..56ac861c 100644
--- a/029tools.cc
+++ b/029tools.cc
@@ -169,20 +169,6 @@ case _CLEAR_TRACE: {
   break;
 }
 
-:(before "End Primitive Recipe Declarations")
-_SAVE_TRACE,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "$save-trace", _SAVE_TRACE);
-:(before "End Primitive Recipe Checks")
-case _SAVE_TRACE: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case _SAVE_TRACE: {
-  if (Save_trace) Trace_stream->save();
-  break;
-}
-
 //:: 'cheating' by using the host system
 
 :(before "End Primitive Recipe Declarations")
@@ -197,7 +183,7 @@ case _PRINT: {
 case _PRINT: {
   for (int i = 0;  i < SIZE(ingredients);  ++i) {
     if (is_literal(current_instruction().ingredients.at(i))) {
-      trace(9998, "run") << "$print: " << current_instruction().ingredients.at(i).name << end();
+      trace(Callstack_depth+1, "run") << "$print: " << current_instruction().ingredients.at(i).name << end();
       if (!has_property(current_instruction().ingredients.at(i), "newline")) {
         cout << current_instruction().ingredients.at(i).name;
       }
@@ -210,7 +196,7 @@ case _PRINT: {
     // End $print Special-cases
     else {
       for (int j = 0;  j < SIZE(ingredients.at(i));  ++j) {
-        trace(9998, "run") << "$print: " << ingredients.at(i).at(j) << end();
+        trace(Callstack_depth+1, "run") << "$print: " << ingredients.at(i).at(j) << end();
         if (j > 0) cout << " ";
         cout << no_scientific(ingredients.at(i).at(j));
       }
diff --git a/030container.cc b/030container.cc
index aff65a1d..ca2d6743 100644
--- a/030container.cc
+++ b/030container.cc
@@ -198,11 +198,11 @@ case GET: {
   int src = base_address;
   for (int i = 0; i < offset; ++i)
     src += size_of(element_type(base.type, i));
-  trace(9998, "run") << "address to copy is " << src << end();
+  trace(Callstack_depth+1, "run") << "address to copy is " << src << end();
   //: use base.type rather than base_type because later layers will introduce compound types
   reagent/*copy*/ element = element_type(base.type, offset);
   element.set_value(src);
-  trace(9998, "run") << "its type is " << names_to_string(element.type) << end();
+  trace(Callstack_depth+1, "run") << "its type is " << names_to_string(element.type) << end();
   // Read element
   products.push_back(read_memory(element));
   break;
@@ -355,13 +355,13 @@ case PUT: {
   int address = base_address;
   for (int i = 0; i < offset; ++i)
     address += size_of(element_type(base.type, i));
-  trace(9998, "run") << "address to copy to is " << address << end();
+  trace(Callstack_depth+1, "run") << "address to copy to is " << address << end();
   // optimization: directly write the element rather than updating 'product'
   // and writing the entire container
   // Write Memory in PUT in Run
   write_products = false;
   for (int i = 0;  i < SIZE(ingredients.at(2));  ++i) {
-    trace("mem") << "storing " << no_scientific(ingredients.at(2).at(i)) << " in location " << address+i << end();
+    trace(Callstack_depth+1, "mem") << "storing " << no_scientific(ingredients.at(2).at(i)) << " in location " << address+i << end();
     put(Memory, address+i, ingredients.at(2).at(i));
   }
   break;
@@ -452,12 +452,12 @@ void insert_container(const string& command, kind_of_type kind, istream& in) {
     return;
   }
   // End container Name Refinements
-  trace(9991, "parse") << "--- defining " << command << ' ' << name << end();
+  trace(101, "parse") << "--- defining " << command << ' ' << name << end();
   if (!contains_key(Type_ordinal, name)
       || get(Type_ordinal, name) == 0) {
     put(Type_ordinal, name, Next_type_ordinal++);
   }
-  trace("parse") << "type number: " << get(Type_ordinal, name) << end();
+  trace(102, "parse") << "type number: " << get(Type_ordinal, name) << end();
   skip_bracket(in, "'"+command+"' must begin with '['");
   type_info& info = get_or_insert(Type, get(Type_ordinal, name));
   if (info.Num_calls_to_transform_all_at_first_definition == -1) {
@@ -492,7 +492,7 @@ void insert_container(const string& command, kind_of_type kind, istream& in) {
     info.elements.push_back(reagent(element));
     expand_type_abbreviations(info.elements.back().type);  // todo: use abbreviation before declaration
     replace_unknown_types_with_unique_ordinals(info.elements.back().type, info);
-    trace(9993, "parse") << "  element: " << to_string(info.elements.back()) << end();
+    trace(103, "parse") << "  element: " << to_string(info.elements.back()) << end();
     // End Load Container Element Definition
   }
 }
@@ -602,7 +602,7 @@ Transform.push_back(check_or_set_invalid_types);  // idempotent
 :(code)
 void check_or_set_invalid_types(const recipe_ordinal r) {
   recipe& caller = get(Recipe, r);
-  trace(9991, "transform") << "--- check for invalid types in recipe " << caller.name << end();
+  trace(101, "transform") << "--- check for invalid types in recipe " << caller.name << end();
   for (int index = 0;  index < SIZE(caller.steps);  ++index) {
     instruction& inst = caller.steps.at(index);
     for (int i = 0;  i < SIZE(inst.ingredients);  ++i)
diff --git a/031merge.cc b/031merge.cc
index c56579a5..99b6846a 100644
--- a/031merge.cc
+++ b/031merge.cc
@@ -114,7 +114,7 @@ Transform.push_back(check_merge_calls);  // idempotent
 :(code)
 void check_merge_calls(const recipe_ordinal r) {
   const recipe& caller = get(Recipe, r);
-  trace(9991, "transform") << "--- type-check merge instructions in recipe " << caller.name << end();
+  trace(101, "transform") << "--- type-check merge instructions in recipe " << caller.name << end();
   for (int i = 0;  i < SIZE(caller.steps);  ++i) {
     const instruction& inst = caller.steps.at(i);
     if (inst.name != "merge") continue;
@@ -145,7 +145,7 @@ void check_merge_call(const vector<reagent>& ingredients, const reagent& product
   state.data.push(merge_check_point(product, 0));
   while (true) {
     assert(!state.data.empty());
-    trace("transform") << ingredient_index << " vs " << SIZE(ingredients) << end();
+    trace(102, "transform") << ingredient_index << " vs " << SIZE(ingredients) << end();
     if (ingredient_index >= SIZE(ingredients)) {
       raise << maybe(caller.name) << "too few ingredients in '" << to_original_string(inst) << "'\n" << end();
       return;
@@ -161,7 +161,7 @@ void check_merge_call(const vector<reagent>& ingredients, const reagent& product
         if (state.data.top().container_element_index == 0 && types_coercible(container, inst.ingredients.at(ingredient_index)))
           return;
         const reagent& expected_ingredient = element_type(container.type, state.data.top().container_element_index);
-        trace("transform") << "checking container " << to_string(container) << " || " << to_string(expected_ingredient) << " vs ingredient " << ingredient_index << end();
+        trace(102, "transform") << "checking container " << to_string(container) << " || " << to_string(expected_ingredient) << " vs ingredient " << ingredient_index << end();
         // if the current element is the ingredient we expect, move on to the next element/ingredient
         if (types_coercible(expected_ingredient, ingredients.at(ingredient_index))) {
           ++ingredient_index;
diff --git a/032array.cc b/032array.cc
index 25d91b60..7f36411f 100644
--- a/032array.cc
+++ b/032array.cc
@@ -57,10 +57,10 @@ case CREATE_ARRAY: {
     array_length_from_type = array_length_from_type->left;
   int array_length = to_integer(array_length_from_type->name);
   // initialize array length, so that size_of will work
-  trace("mem") << "storing " << array_length << " in location " << base_address << end();
+  trace(Callstack_depth+1, "mem") << "storing " << array_length << " in location " << base_address << end();
   put(Memory, base_address, array_length);  // in array elements
   int size = size_of(product);  // in locations
-  trace(9998, "run") << "creating array from " << size << " locations" << end();
+  trace(Callstack_depth+1, "run") << "creating array from " << size << " locations" << end();
   // initialize array
   for (int i = 1;  i <= size_of(product);  ++i)
     put(Memory, base_address+i, 0);
@@ -274,7 +274,7 @@ case INDEX: {
   reagent/*copy*/ base = current_instruction().ingredients.at(0);
   // Update INDEX base in Run
   int base_address = base.value;
-  trace(9998, "run") << "base address is " << base_address << end();
+  trace(Callstack_depth+1, "run") << "base address is " << base_address << end();
   if (base_address == 0) {
     raise << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_original_string(current_instruction()) << "'\n" << end();
     break;
@@ -288,8 +288,8 @@ case INDEX: {
   }
   reagent/*local*/ element(copy_array_element(base.type));
   element.set_value(base_address + /*skip length*/1 + index_val.at(0)*size_of(element.type));
-  trace(9998, "run") << "address to copy is " << element.value << end();
-  trace(9998, "run") << "its type is " << to_string(element.type) << end();
+  trace(Callstack_depth+1, "run") << "address to copy is " << element.value << end();
+  trace(Callstack_depth+1, "run") << "its type is " << to_string(element.type) << end();
   // Read element
   products.push_back(read_memory(element));
   break;
@@ -451,14 +451,14 @@ case PUT_INDEX: {
     break;
   }
   int address = base_address + /*skip length*/1 + index_val.at(0)*size_of(array_element(base.type));
-  trace(9998, "run") << "address to copy to is " << address << end();
+  trace(Callstack_depth+1, "run") << "address to copy to is " << address << end();
   // optimization: directly write the element rather than updating 'product'
   // and writing the entire array
   write_products = false;
   vector<double> value = read_memory(current_instruction().ingredients.at(2));
   // Write Memory in PUT_INDEX in Run
   for (int i = 0;  i < SIZE(value);  ++i) {
-    trace("mem") << "storing " << no_scientific(value.at(i)) << " in location " << address+i << end();
+    trace(Callstack_depth+1, "mem") << "storing " << no_scientific(value.at(i)) << " in location " << address+i << end();
     put(Memory, address+i, value.at(i));
   }
   break;
diff --git a/033exclusive_container.cc b/033exclusive_container.cc
index c2d69f60..5c57972e 100644
--- a/033exclusive_container.cc
+++ b/033exclusive_container.cc
@@ -150,19 +150,19 @@ case MAYBE_CONVERT: {
   write_products = false;
   if (tag == static_cast<int>(get_or_insert(Memory, base_address))) {
     const reagent& variant = variant_type(base, tag);
-    trace("mem") << "storing 1 in location " << status.value << end();
+    trace(Callstack_depth+1, "mem") << "storing 1 in location " << status.value << end();
     put(Memory, status.value, 1);
     if (!is_dummy(product)) {
       // Write Memory in Successful MAYBE_CONVERT in Run
       for (int i = 0;  i < size_of(variant);  ++i) {
         double val = get_or_insert(Memory, base_address+/*skip tag*/1+i);
-        trace("mem") << "storing " << no_scientific(val) << " in location " << product.value+i << end();
+        trace(Callstack_depth+1, "mem") << "storing " << no_scientific(val) << " in location " << product.value+i << end();
         put(Memory, product.value+i, val);
       }
     }
   }
   else {
-    trace("mem") << "storing 0 in location " << status.value << end();
+    trace(Callstack_depth+1, "mem") << "storing 0 in location " << status.value << end();
     put(Memory, status.value, 0);
   }
   break;
@@ -305,7 +305,7 @@ $error: 0
 :(before "End check_merge_call Special-cases")
 case EXCLUSIVE_CONTAINER: {
   assert(state.data.top().container_element_index == 0);
-  trace("transform") << "checking exclusive container " << to_string(container) << " vs ingredient " << ingredient_index << end();
+  trace(102, "transform") << "checking exclusive container " << to_string(container) << " vs ingredient " << ingredient_index << end();
   // easy case: exact match
   if (types_strictly_match(container, inst.ingredients.at(ingredient_index)))
     return;
@@ -320,7 +320,7 @@ case EXCLUSIVE_CONTAINER: {
     return;
   }
   const reagent& variant = variant_type(container, ingredient.value);
-  trace("transform") << "tag: " << ingredient.value << end();
+  trace(102, "transform") << "tag: " << ingredient.value << end();
   // replace union with its variant
   state.data.pop();
   state.data.push(merge_check_point(variant, 0));
diff --git a/034address.cc b/034address.cc
index a71f5194..af98fc14 100644
--- a/034address.cc
+++ b/034address.cc
@@ -215,7 +215,7 @@ Transform.push_back(transform_new_to_allocate);  // idempotent
 
 :(code)
 void transform_new_to_allocate(const recipe_ordinal r) {
-  trace(9991, "transform") << "--- convert 'new' to 'allocate' for recipe " << get(Recipe, r).name << end();
+  trace(101, "transform") << "--- convert 'new' to 'allocate' for recipe " << get(Recipe, r).name << end();
   for (int i = 0;  i < SIZE(get(Recipe, r).steps);  ++i) {
     instruction& inst = get(Recipe, r).steps.at(i);
     // Convert 'new' To 'allocate'
@@ -224,7 +224,7 @@ void transform_new_to_allocate(const recipe_ordinal r) {
       inst.operation = ALLOCATE;
       type_tree* type = new_type_tree(inst.ingredients.at(0).name);
       inst.ingredients.at(0).set_value(size_of(type));
-      trace(9992, "new") << "size of '" << inst.ingredients.at(0).name << "' is " << inst.ingredients.at(0).value << end();
+      trace(102, "new") << "size of '" << inst.ingredients.at(0).name << "' is " << inst.ingredients.at(0).value << end();
       delete type;
     }
   }
@@ -245,7 +245,7 @@ int alloc, alloc_max;
 alloc = Memory_allocated_until;
 Memory_allocated_until += Initial_memory_per_routine;
 alloc_max = Memory_allocated_until;
-trace("new") << "routine allocated memory from " << alloc << " to " << alloc_max << end();
+trace(Callstack_depth+1, "new") << "routine allocated memory from " << alloc << " to " << alloc_max << end();
 
 :(before "End Primitive Recipe Declarations")
 ALLOCATE,
@@ -259,16 +259,16 @@ case ALLOCATE: {
   Next_alloc_id++;
   if (SIZE(ingredients) > 1) {
     // array allocation
-    trace("mem") << "array length is " << ingredients.at(1).at(0) << end();
+    trace(Callstack_depth+1, "mem") << "array length is " << ingredients.at(1).at(0) << end();
     size = /*space for length*/1 + size*ingredients.at(1).at(0);
   }
   int result = allocate(size);
   // initialize alloc-id in payload
-  trace("mem") << "storing alloc-id " << alloc_id << " in location " << result << end();
+  trace(Callstack_depth+1, "mem") << "storing alloc-id " << alloc_id << " in location " << result << end();
   put(Memory, result, alloc_id);
   if (SIZE(current_instruction().ingredients) > 1) {
     // initialize array length
-    trace("mem") << "storing array length " << ingredients.at(1).at(0) << " in location " << result+/*skip alloc id*/1 << end();
+    trace(Callstack_depth+1, "mem") << "storing array length " << ingredients.at(1).at(0) << " in location " << result+/*skip alloc id*/1 << end();
     put(Memory, result+/*skip alloc id*/1, ingredients.at(1).at(0));
   }
   products.resize(1);
@@ -280,7 +280,7 @@ case ALLOCATE: {
 int allocate(int size) {
   // include space for alloc id
   ++size;
-  trace("mem") << "allocating size " << size << end();
+  trace(Callstack_depth+1, "mem") << "allocating size " << size << end();
 //?   Total_alloc += size;
 //?   ++Num_alloc;
   // Allocate Special-cases
@@ -288,10 +288,10 @@ int allocate(int size) {
   // really crappy at the moment
   ensure_space(size);
   const int result = Current_routine->alloc;
-  trace("mem") << "new alloc: " << result << end();
+  trace(Callstack_depth+1, "mem") << "new alloc: " << result << end();
   // initialize allocated space
   for (int address = result;  address < result+size;  ++address) {
-    trace("mem") << "storing 0 in location " << address << end();
+    trace(Callstack_depth+1, "mem") << "storing 0 in location " << address << end();
     put(Memory, address, 0);
   }
   Current_routine->alloc += size;
@@ -325,7 +325,7 @@ void ensure_space(int size) {
     Current_routine->alloc = Memory_allocated_until;
     Memory_allocated_until += Initial_memory_per_routine;
     Current_routine->alloc_max = Memory_allocated_until;
-    trace("new") << "routine allocated memory from " << Current_routine->alloc << " to " << Current_routine->alloc_max << end();
+    trace(Callstack_depth+1, "new") << "routine allocated memory from " << Current_routine->alloc << " to " << Current_routine->alloc_max << end();
   }
 }
 
diff --git a/035lookup.cc b/035lookup.cc
index 02c2ab3f..b7c05201 100644
--- a/035lookup.cc
+++ b/035lookup.cc
@@ -88,7 +88,7 @@ void lookup_memory(reagent& x) {
 void lookup_memory_core(reagent& x, bool check_for_null) {
   double address = x.value + /*skip alloc id in address*/1;
   double new_value = get_or_insert(Memory, address);
-  trace("mem") << "location " << address << " contains " << no_scientific(new_value) << end();
+  trace(Callstack_depth+1, "mem") << "location " << address << " contains " << no_scientific(new_value) << end();
   // check for null
   if (check_for_null && new_value == 0) {
     if (Current_routine) {
diff --git a/036abandon.cc b/036abandon.cc
index c2b9125e..4dcf572f 100644
--- a/036abandon.cc
+++ b/036abandon.cc
@@ -67,7 +67,7 @@ void abandon(int address, int payload_size) {
   for (int curr = address+1;  curr < address+payload_size;  ++curr)
     put(Memory, curr, 0);
   // append existing free list to address
-  trace("abandon") << "saving " << address << " in free-list of size " << payload_size << end();
+  trace(Callstack_depth+1, "abandon") << "saving " << address << " in free-list of size " << payload_size << end();
   put(Memory, address+/*skip invalid alloc-id*/1, get_or_insert(Current_routine->free_list, payload_size));
   put(Current_routine->free_list, payload_size, address);
 }
@@ -80,9 +80,9 @@ int payload_size(reagent/*copy*/ x) {
 
 :(after "Allocate Special-cases")
 if (get_or_insert(Current_routine->free_list, size)) {
-  trace("abandon") << "picking up space from free-list of size " << size << end();
+  trace(Callstack_depth+1, "abandon") << "picking up space from free-list of size " << size << end();
   int result = get_or_insert(Current_routine->free_list, size);
-  trace("mem") << "new alloc from free list: " << result << end();
+  trace(Callstack_depth+1, "mem") << "new alloc from free list: " << result << end();
   put(Current_routine->free_list, size, get_or_insert(Memory, result+/*skip alloc id*/1));
   // clear 'deleted' tag
   put(Memory, result, 0);
diff --git a/038new_text.cc b/038new_text.cc
index 7e3c02f6..10b819a3 100644
--- a/038new_text.cc
+++ b/038new_text.cc
@@ -31,7 +31,7 @@ if (inst.name == "new" && !inst.ingredients.empty() && is_literal_text(inst.ingr
     products.resize(1);
     products.at(0).push_back(/*alloc id*/0);
     products.at(0).push_back(new_mu_text(current_instruction().ingredients.at(0).name));
-    trace("mem") << "new string alloc: " << products.at(0).at(0) << end();
+    trace(Callstack_depth+1, "mem") << "new string alloc: " << products.at(0).at(0) << end();
     break;
   }
 
@@ -44,7 +44,7 @@ int new_mu_text(const string& contents) {
   int result = allocate(/*array length*/1 + string_length);
   int curr_address = result;
   ++curr_address;  // skip alloc id
-  trace("mem") << "storing string length " << string_length << " in location " << curr_address << end();
+  trace(Callstack_depth+1, "mem") << "storing string length " << string_length << " in location " << curr_address << end();
   put(Memory, curr_address, string_length);
   ++curr_address;  // skip length
   int curr = 0;
@@ -53,7 +53,7 @@ int new_mu_text(const string& contents) {
     uint32_t curr_character;
     assert(curr < SIZE(contents));
     tb_utf8_char_to_unicode(&curr_character, &raw_contents[curr]);
-    trace("mem") << "storing string character " << curr_character << " in location " << curr_address << end();
+    trace(Callstack_depth+1, "mem") << "storing string character " << curr_character << " in location " << curr_address << end();
     put(Memory, curr_address, curr_character);
     curr += tb_utf8_char_length(raw_contents[curr]);
     ++curr_address;
diff --git a/040brace.cc b/040brace.cc
index b3006456..8eead4ab 100644
--- a/040brace.cc
+++ b/040brace.cc
@@ -39,15 +39,15 @@ void transform_braces(const recipe_ordinal r) {
   const bool OPEN = false, CLOSE = true;
   // use signed integer for step index because we'll be doing arithmetic on it
   list<pair<bool/*OPEN/CLOSE*/, /*step*/int> > braces;
-  trace(9991, "transform") << "--- transform braces for recipe " << get(Recipe, r).name << end();
+  trace(101, "transform") << "--- transform braces for recipe " << get(Recipe, r).name << end();
   for (int index = 0;  index < SIZE(get(Recipe, r).steps);  ++index) {
     const instruction& inst = get(Recipe, r).steps.at(index);
     if (inst.label == "{") {
-      trace(9993, "transform") << maybe(get(Recipe, r).name) << "push (open, " << index << ")" << end();
+      trace(103, "transform") << maybe(get(Recipe, r).name) << "push (open, " << index << ")" << end();
       braces.push_back(pair<bool,int>(OPEN, index));
     }
     if (inst.label == "}") {
-      trace(9993, "transform") << "push (close, " << index << ")" << end();
+      trace(103, "transform") << "push (close, " << index << ")" << end();
       braces.push_back(pair<bool,int>(CLOSE, index));
     }
   }
@@ -73,7 +73,7 @@ void transform_braces(const recipe_ordinal r) {
          && inst.name != "break"
          && inst.name != "break-if"
          && inst.name != "break-unless") {
-      trace(9992, "transform") << inst.name << " ..." << end();
+      trace(102, "transform") << inst.name << " ..." << end();
       continue;
     }
     // check for errors
@@ -101,14 +101,14 @@ void transform_braces(const recipe_ordinal r) {
     if (inst.name.find("-if") != string::npos || inst.name.find("-unless") != string::npos) {
       // conditional branches check arg 1
       if (SIZE(inst.ingredients) > 1 && is_literal(inst.ingredients.at(1))) {
-        trace(9992, "transform") << inst.name << ' ' << inst.ingredients.at(1).name << ":offset" << end();
+        trace(102, "transform") << inst.name << ' ' << inst.ingredients.at(1).name << ":offset" << end();
         continue;
       }
     }
     else {
       // unconditional branches check arg 0
       if (!inst.ingredients.empty() && is_literal(inst.ingredients.at(0))) {
-        trace(9992, "transform") << "jump " << inst.ingredients.at(0).name << ":offset" << end();
+        trace(102, "transform") << "jump " << inst.ingredients.at(0).name << ":offset" << end();
         continue;
       }
     }
@@ -124,9 +124,9 @@ void transform_braces(const recipe_ordinal r) {
     inst.ingredients.push_back(target);
     // log computed target
     if (inst.name == "jump")
-      trace(9992, "transform") << "jump " << no_scientific(target.value) << ":offset" << end();
+      trace(102, "transform") << "jump " << no_scientific(target.value) << ":offset" << end();
     else
-      trace(9992, "transform") << inst.name << ' ' << inst.ingredients.at(0).name << ", " << no_scientific(target.value) << ":offset" << end();
+      trace(102, "transform") << inst.name << ' ' << inst.ingredients.at(0).name << ", " << no_scientific(target.value) << ":offset" << end();
   }
 }
 
diff --git a/042name.cc b/042name.cc
index 88343495..5846b5b4 100644
--- a/042name.cc
+++ b/042name.cc
@@ -36,7 +36,7 @@ Name = Name_snapshot;
 :(code)
 void transform_names(const recipe_ordinal r) {
   recipe& caller = get(Recipe, r);
-  trace(9991, "transform") << "--- transform names for recipe " << caller.name << end();
+  trace(101, "transform") << "--- transform names for recipe " << caller.name << end();
   bool names_used = false;
   bool numeric_locations_used = false;
   map<string, int>& names = Name[r];
@@ -78,7 +78,7 @@ void transform_names(const recipe_ordinal r) {
       if (is_named_location(product)) names_used = true;
       if (is_integer(product.name)) continue;
       if (names.find(product.name) == names.end()) {
-        trace(9993, "name") << "assign " << product.name << " " << curr_idx << end();
+        trace(103, "name") << "assign " << product.name << " " << curr_idx << end();
         names[product.name] = curr_idx;
         curr_idx += size_of(product);
       }
@@ -282,7 +282,7 @@ if (inst.name == "get" || inst.name == "get-location" || inst.name == "put") {
     type_ordinal base_type = skip_addresses(inst.ingredients.at(0).type);
     if (contains_key(Type, base_type)) {  // otherwise we'll raise an error elsewhere
       inst.ingredients.at(1).set_value(find_element_offset(base_type, inst.ingredients.at(1).name, get(Recipe, r).name));
-      trace(9993, "name") << "element " << inst.ingredients.at(1).name << " of type " << get(Type, base_type).name << " is at offset " << no_scientific(inst.ingredients.at(1).value) << end();
+      trace(103, "name") << "element " << inst.ingredients.at(1).name << " of type " << get(Type, base_type).name << " is at offset " << no_scientific(inst.ingredients.at(1).value) << end();
     }
   }
 }
@@ -330,7 +330,7 @@ if (inst.name == "maybe-convert") {
     type_ordinal base_type = skip_addresses(inst.ingredients.at(0).type);
     if (contains_key(Type, base_type)) {  // otherwise we'll raise an error elsewhere
       inst.ingredients.at(1).set_value(find_element_offset(base_type, inst.ingredients.at(1).name, get(Recipe, r).name));
-      trace(9993, "name") << "variant " << inst.ingredients.at(1).name << " of type " << get(Type, base_type).name << " has tag " << no_scientific(inst.ingredients.at(1).value) << end();
+      trace(103, "name") << "variant " << inst.ingredients.at(1).name << " of type " << get(Type, base_type).name << " has tag " << no_scientific(inst.ingredients.at(1).value) << end();
     }
   }
 }
diff --git a/043space.cc b/043space.cc
index d348f736..6028ef0d 100644
--- a/043space.cc
+++ b/043space.cc
@@ -264,7 +264,7 @@ void check_default_space(const recipe_ordinal r) {
   // End check_default_space Special-cases
   // assume recipes with only numeric addresses know what they're doing (usually tests)
   if (!contains_non_special_name(r)) return;
-  trace(9991, "transform") << "--- check that recipe " << caller.name << " sets default-space" << end();
+  trace(101, "transform") << "--- check that recipe " << caller.name << " sets default-space" << end();
   if (caller.steps.empty()) return;
   if (!starts_by_setting_default_space(caller))
     raise << caller.name << " does not seem to start with 'local-scope' or 'default-space'\n" << end();
diff --git a/045closure_name.cc b/045closure_name.cc
index 6b2defd3..b4a8ea24 100644
--- a/045closure_name.cc
+++ b/045closure_name.cc
@@ -42,7 +42,7 @@ Transform.push_back(collect_surrounding_spaces);  // idempotent
 
 :(code)
 void collect_surrounding_spaces(const recipe_ordinal r) {
-  trace(9991, "transform") << "--- collect surrounding spaces for recipe " << get(Recipe, r).name << end();
+  trace(101, "transform") << "--- collect surrounding spaces for recipe " << get(Recipe, r).name << end();
   for (int i = 0;  i < SIZE(get(Recipe, r).steps);  ++i) {
     const instruction& inst = get(Recipe, r).steps.at(i);
     if (inst.is_label) continue;
@@ -69,7 +69,7 @@ void collect_surrounding_spaces(const recipe_ordinal r) {
         raise << "recipe '" << get(Recipe, r).name << "' can have only one 'surrounding' recipe but has '" << get(Recipe, get(Surrounding_space, r)).name << "' and '" << surrounding_recipe_name << "'\n" << end();
         continue;
       }
-      trace(9993, "name") << "lexically surrounding space for recipe " << get(Recipe, r).name << " comes from " << surrounding_recipe_name << end();
+      trace(103, "name") << "lexically surrounding space for recipe " << get(Recipe, r).name << " comes from " << surrounding_recipe_name << end();
       if (!contains_key(Recipe_ordinal, surrounding_recipe_name)) {
         raise << "can't find recipe providing surrounding space for '" << get(Recipe, r).name << "'; looking for '" << surrounding_recipe_name << "'\n" << end();
         continue;
diff --git a/046check_type_by_name.cc b/046check_type_by_name.cc
index ba40f521..6042068a 100644
--- a/046check_type_by_name.cc
+++ b/046check_type_by_name.cc
@@ -34,7 +34,7 @@ struct name_lt {
 :(code)
 void check_or_set_types_by_name(const recipe_ordinal r) {
   recipe& caller = get(Recipe, r);
-  trace(9991, "transform") << "--- deduce types for recipe " << caller.name << end();
+  trace(101, "transform") << "--- deduce types for recipe " << caller.name << end();
   for (int i = 0;  i < SIZE(caller.steps);  ++i) {
     instruction& inst = caller.steps.at(i);
     for (int in = 0;  in < SIZE(inst.ingredients);  ++in)
@@ -62,7 +62,7 @@ void deduce_missing_type(set<reagent, name_lt>& known_types, reagent& x, const r
   if (known_types.find(x) == known_types.end()) return;
   const reagent& exemplar = *known_types.find(x);
   x.type = new type_tree(*exemplar.type);
-  trace(9992, "transform") << x.name << " <= " << names_to_string(x.type) << end();
+  trace(102, "transform") << x.name << " <= " << names_to_string(x.type) << end();
   // spaces are special; their type includes their /names property
   if (is_mu_space(x) && !has_property(x, "names")) {
     if (!has_property(exemplar, "names")) {
@@ -82,7 +82,7 @@ void check_type(set<reagent, name_lt>& known_types, const reagent& x, const reci
     return;
   }
   if (known_types.find(x) == known_types.end()) {
-    trace(9992, "transform") << x.name << " => " << names_to_string(x.type) << end();
+    trace(102, "transform") << x.name << " => " << names_to_string(x.type) << end();
     known_types.insert(x);
   }
   if (!types_strictly_match(known_types.find(x)->type, x.type)) {
diff --git a/050scenario.cc b/050scenario.cc
index 791ac38a..4a4c9315 100644
--- a/050scenario.cc
+++ b/050scenario.cc
@@ -225,7 +225,6 @@ void run_mu_scenario(const scenario& s) {
   if (!Hide_errors && trace_contains_errors() && !Scenario_testing_scenario)
     Passed = false;
   if (not_already_inside_test && Trace_stream) {
-    if (Save_trace) Trace_stream->save();
     delete Trace_stream;
     Trace_stream = NULL;
   }
@@ -423,7 +422,7 @@ void check_memory(const string& s) {
     double value = to_double(rhs);
     if (contains_key(locations_checked, address))
       raise << maybe(current_recipe_name()) << "duplicate expectation for location '" << address << "'\n" << end();
-    trace("run") << "checking location " << address << end();
+    trace(Callstack_depth+1, "run") << "checking location " << address << end();
     if (get_or_insert(Memory, address) != value) {
       if (!Hide_errors) cerr << '\n';
       raise << "F - " << maybe(current_recipe_name()) << "expected location '" << address << "' to contain " << no_scientific(value) << " but saw " << no_scientific(get_or_insert(Memory, address)) << '\n' << end();
@@ -469,7 +468,7 @@ void check_type(const string& lhs, istream& in) {
 }
 
 void check_mu_text(int start, const string& literal) {
-  trace("run") << "checking text length at " << start << end();
+  trace(Callstack_depth+1, "run") << "checking text length at " << start << end();
   int array_length = static_cast<int>(get_or_insert(Memory, start));
   if (array_length != SIZE(literal)) {
     if (!Hide_errors) cerr << '\n';
@@ -479,7 +478,7 @@ void check_mu_text(int start, const string& literal) {
   }
   int curr = start+1;  // now skip length
   for (int i = 0;  i < SIZE(literal);  ++i) {
-    trace("run") << "checking location " << curr+i << end();
+    trace(Callstack_depth+1, "run") << "checking location " << curr+i << end();
     if (get_or_insert(Memory, curr+i) != literal.at(i)) {
       if (!Hide_errors) cerr << '\n';
       raise << "F - " << maybe(current_recipe_name()) << "expected location " << (curr+i) << " to contain " << literal.at(i) << " but saw " << no_scientific(get_or_insert(Memory, curr+i)) << '\n' << end();
@@ -620,16 +619,18 @@ void check_trace(const string& expected) {
 vector<trace_line> parse_trace(const string& expected) {
   vector<string> buf = split(expected, "\n");
   vector<trace_line> result;
+  const string separator = ": ";
   for (int i = 0;  i < SIZE(buf);  ++i) {
     buf.at(i) = trim(buf.at(i));
     if (buf.at(i).empty()) continue;
-    int delim = buf.at(i).find(": ");
+    int delim = buf.at(i).find(separator);
     if (delim == -1) {
       raise << maybe(current_recipe_name()) << "lines in 'trace-should-contain' should be of the form <label>: <contents>. Both parts are required.\n" << end();
       result.clear();
       return result;
     }
-    result.push_back(trace_line(trim(buf.at(i).substr(0, delim)),  trim(buf.at(i).substr(delim+2))));
+    result.push_back(trace_line(/*contents*/  trim(buf.at(i).substr(delim+SIZE(separator)        )),
+                                /*label*/     trim(buf.at(i).substr(0,                      delim))));
   }
   return result;
 }
diff --git a/052tangle.cc b/052tangle.cc
index 909fc9f7..d7d1ccc3 100644
--- a/052tangle.cc
+++ b/052tangle.cc
@@ -90,7 +90,7 @@ void insert_fragments(const recipe_ordinal r) {
 }
 
 void insert_fragments(recipe& r) {
-  trace(9991, "transform") << "--- insert fragments into recipe " << r.name << end();
+  trace(101, "transform") << "--- insert fragments into recipe " << r.name << end();
   bool made_progress = true;
   int pass = 0;
   while (made_progress) {
@@ -111,12 +111,12 @@ void insert_fragments(recipe& r) {
       // ok to use contains_key even though Before_fragments uses [],
       // because appending an empty recipe is a noop
       if (contains_key(Before_fragments, inst.label)) {
-        trace(9992, "transform") << "insert fragments before label " << inst.label << end();
+        trace(102, "transform") << "insert fragments before label " << inst.label << end();
         append_fragment(result, Before_fragments[inst.label].steps, prefix.str());
       }
       result.push_back(inst);
       if (contains_key(After_fragments, inst.label)) {
-        trace(9992, "transform") << "insert fragments after label " << inst.label << end();
+        trace(102, "transform") << "insert fragments after label " << inst.label << end();
         append_fragment(result, After_fragments[inst.label].steps, prefix.str());
       }
     }
diff --git a/053recipe_header.cc b/053recipe_header.cc
index e299d3c0..5102f690 100644
--- a/053recipe_header.cc
+++ b/053recipe_header.cc
@@ -24,7 +24,7 @@ has_header = false;
 
 :(before "End Recipe Refinements")
 if (in.peek() != '[') {
-  trace("parse") << "recipe has a header; parsing" << end();
+  trace(101, "parse") << "recipe has a header; parsing" << end();
   load_recipe_header(in, result);
 }
 
@@ -42,7 +42,7 @@ void load_recipe_header(istream& in, recipe& result) {
       raise << "recipe " << result.name << " should say '->' and not '<-'\n" << end();
     if (s == "->") break;
     result.ingredients.push_back(reagent(s));
-    trace("parse") << "header ingredient: " << result.ingredients.back().original_string << end();
+    trace(101, "parse") << "header ingredient: " << result.ingredients.back().original_string << end();
     skip_whitespace_but_not_newline(in);
   }
   while (has_data(in) && in.peek() != '[' && in.peek() != '\n') {
@@ -53,7 +53,7 @@ void load_recipe_header(istream& in, recipe& result) {
       return;
     }
     result.products.push_back(reagent(s));
-    trace("parse") << "header product: " << result.products.back().original_string << end();
+    trace(101, "parse") << "header product: " << result.products.back().original_string << end();
     skip_whitespace_but_not_newline(in);
   }
   // End Load Recipe Header(result)
@@ -149,7 +149,7 @@ if (!result.has_header) {
   }
 }
 if (result.has_header) {
-  trace("parse") << "recipe " << result.name << " has a header" << end();
+  trace(101, "parse") << "recipe " << result.name << " has a header" << end();
 }
 
 //: Support type abbreviations in headers.
@@ -288,7 +288,7 @@ Transform.push_back(check_calls_against_header);  // idempotent
 :(code)
 void check_calls_against_header(const recipe_ordinal r) {
   const recipe& caller = get(Recipe, r);
-  trace(9991, "transform") << "--- type-check calls inside recipe " << caller.name << end();
+  trace(101, "transform") << "--- type-check calls inside recipe " << caller.name << end();
   for (int i = 0;  i < SIZE(caller.steps);  ++i) {
     const instruction& inst = caller.steps.at(i);
     if (is_primitive(inst.operation)) continue;
@@ -332,7 +332,7 @@ Transform.push_back(check_return_instructions_against_header);  // idempotent
 void check_return_instructions_against_header(const recipe_ordinal r) {
   const recipe& caller_recipe = get(Recipe, r);
   if (!caller_recipe.has_header) return;
-  trace(9991, "transform") << "--- checking return instructions against header for " << caller_recipe.name << end();
+  trace(101, "transform") << "--- checking return instructions against header for " << caller_recipe.name << end();
   for (int i = 0;  i < SIZE(caller_recipe.steps);  ++i) {
     const instruction& inst = caller_recipe.steps.at(i);
     if (inst.name != "reply" && inst.name != "return") continue;
@@ -403,7 +403,7 @@ Transform.push_back(check_header_ingredients);  // idempotent
 void check_header_ingredients(const recipe_ordinal r) {
   recipe& caller_recipe = get(Recipe, r);
   caller_recipe.ingredient_index.clear();
-  trace(9991, "transform") << "--- checking return instructions against header for " << caller_recipe.name << end();
+  trace(101, "transform") << "--- checking return instructions against header for " << caller_recipe.name << end();
   for (int i = 0;  i < SIZE(caller_recipe.ingredients);  ++i) {
     if (caller_recipe.ingredients.at(i).type == NULL)
       raise << maybe(caller_recipe.name) << "ingredient '" << caller_recipe.ingredients.at(i).name << "' has no type\n" << end();
@@ -435,37 +435,37 @@ Transform.push_back(deduce_types_from_header);  // idempotent
 void deduce_types_from_header(const recipe_ordinal r) {
   recipe& caller_recipe = get(Recipe, r);
   if (caller_recipe.products.empty()) return;
-  trace(9991, "transform") << "--- deduce types from header for " << caller_recipe.name << end();
+  trace(101, "transform") << "--- deduce types from header for " << caller_recipe.name << end();
   map<string, const type_tree*> header_type;
   for (int i = 0;  i < SIZE(caller_recipe.ingredients);  ++i) {
     if (!caller_recipe.ingredients.at(i).type) continue;  // error handled elsewhere
     put(header_type, caller_recipe.ingredients.at(i).name, caller_recipe.ingredients.at(i).type);
-    trace(9993, "transform") << "type of " << caller_recipe.ingredients.at(i).name << " is " << names_to_string(caller_recipe.ingredients.at(i).type) << end();
+    trace(103, "transform") << "type of " << caller_recipe.ingredients.at(i).name << " is " << names_to_string(caller_recipe.ingredients.at(i).type) << end();
   }
   for (int i = 0;  i < SIZE(caller_recipe.products);  ++i) {
     if (!caller_recipe.products.at(i).type) continue;  // error handled elsewhere
     put(header_type, caller_recipe.products.at(i).name, caller_recipe.products.at(i).type);
-    trace(9993, "transform") << "type of " << caller_recipe.products.at(i).name << " is " << names_to_string(caller_recipe.products.at(i).type) << end();
+    trace(103, "transform") << "type of " << caller_recipe.products.at(i).name << " is " << names_to_string(caller_recipe.products.at(i).type) << end();
   }
   for (int i = 0;  i < SIZE(caller_recipe.steps);  ++i) {
     instruction& inst = caller_recipe.steps.at(i);
-    trace(9992, "transform") << "instruction: " << to_string(inst) << end();
+    trace(102, "transform") << "instruction: " << to_string(inst) << end();
     for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
       if (inst.ingredients.at(i).type) continue;
       if (header_type.find(inst.ingredients.at(i).name) == header_type.end())
         continue;
       if (!contains_key(header_type, inst.ingredients.at(i).name)) continue;  // error handled elsewhere
       inst.ingredients.at(i).type = new type_tree(*get(header_type, inst.ingredients.at(i).name));
-      trace(9993, "transform") << "type of " << inst.ingredients.at(i).name << " is " << names_to_string(inst.ingredients.at(i).type) << end();
+      trace(103, "transform") << "type of " << inst.ingredients.at(i).name << " is " << names_to_string(inst.ingredients.at(i).type) << end();
     }
     for (int i = 0;  i < SIZE(inst.products);  ++i) {
-      trace(9993, "transform") << "  product: " << to_string(inst.products.at(i)) << end();
+      trace(103, "transform") << "  product: " << to_string(inst.products.at(i)) << end();
       if (inst.products.at(i).type) continue;
       if (header_type.find(inst.products.at(i).name) == header_type.end())
         continue;
       if (!contains_key(header_type, inst.products.at(i).name)) continue;  // error handled elsewhere
       inst.products.at(i).type = new type_tree(*get(header_type, inst.products.at(i).name));
-      trace(9993, "transform") << "type of " << inst.products.at(i).name << " is " << names_to_string(inst.products.at(i).type) << end();
+      trace(103, "transform") << "type of " << inst.products.at(i).name << " is " << names_to_string(inst.products.at(i).type) << end();
     }
   }
 }
@@ -491,7 +491,7 @@ Transform.push_back(fill_in_return_ingredients);  // idempotent
 :(code)
 void fill_in_return_ingredients(const recipe_ordinal r) {
   recipe& caller_recipe = get(Recipe, r);
-  trace(9991, "transform") << "--- fill in return ingredients from header for recipe " << caller_recipe.name << end();
+  trace(101, "transform") << "--- fill in return ingredients from header for recipe " << caller_recipe.name << end();
   if (!caller_recipe.has_header) return;
   for (int i = 0;  i < SIZE(caller_recipe.steps);  ++i) {
     instruction& inst = caller_recipe.steps.at(i);
@@ -624,7 +624,7 @@ Transform.push_back(check_recipe_header_constraints);
 void check_recipe_header_constraints(const recipe_ordinal r) {
   const recipe& caller = get(Recipe, r);
   if (caller.name != "main") return;
-  trace(9992, "transform") << "check recipe header constraints for recipe " << caller.name << end();
+  trace(102, "transform") << "check recipe header constraints for recipe " << caller.name << end();
   if (!caller.has_header) return;
   reagent/*local*/ expected_ingredient("x:address:array:character");
   for (int i = 0; i < SIZE(caller.ingredients); ++i) {
diff --git a/054static_dispatch.cc b/054static_dispatch.cc
index 17ffc755..cdd23e4f 100644
--- a/054static_dispatch.cc
+++ b/054static_dispatch.cc
@@ -41,7 +41,7 @@ if (result.name != "main" && contains_key(Recipe_ordinal, result.name)) {
       put(Recipe_ordinal, new_name, Next_recipe_ordinal++);
       get_or_insert(Recipe_variants, result.name).push_back(get(Recipe_ordinal, new_name));
     }
-    trace("load") << "switching " << result.name << " to " << new_name << end();
+    trace(101, "load") << "switching " << result.name << " to " << new_name << end();
     result.name = new_name;
     result.is_autogenerated = true;
   }
@@ -162,7 +162,7 @@ list<call> Resolve_stack;
 :(code)
 void resolve_ambiguous_calls(const recipe_ordinal r) {
   recipe& caller_recipe = get(Recipe, r);
-  trace(9991, "transform") << "--- resolve ambiguous calls for recipe " << caller_recipe.name << end();
+  trace(101, "transform") << "--- resolve ambiguous calls for recipe " << caller_recipe.name << end();
   for (int index = 0;  index < SIZE(caller_recipe.steps);  ++index) {
     instruction& inst = caller_recipe.steps.at(index);
     if (inst.is_label) continue;
@@ -173,7 +173,7 @@ void resolve_ambiguous_calls(const recipe_ordinal r) {
 void resolve_ambiguous_call(const recipe_ordinal r, int index, instruction& inst, const recipe& caller_recipe) {
   // End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases
   if (non_ghost_size(get_or_insert(Recipe_variants, inst.name)) == 0) return;
-  trace(9992, "transform") << "instruction " << to_original_string(inst) << end();
+  trace(102, "transform") << "instruction " << to_original_string(inst) << end();
   Resolve_stack.push_front(call(r, index));
   string new_name = best_variant(inst, caller_recipe);
   if (!new_name.empty())
@@ -241,7 +241,7 @@ vector<recipe_ordinal> strictly_matching_variants(const instruction& inst, const
   vector<recipe_ordinal> result;
   for (int i = 0;  i < SIZE(variants);  ++i) {
     if (variants.at(i) == -1) continue;
-    trace(9992, "transform") << "checking variant (strict) " << i << ": " << header_label(variants.at(i)) << end();
+    trace(102, "transform") << "checking variant (strict) " << i << ": " << header_label(variants.at(i)) << end();
     if (all_header_reagents_strictly_match(inst, get(Recipe, variants.at(i))))
       result.push_back(variants.at(i));
   }
@@ -251,14 +251,14 @@ vector<recipe_ordinal> strictly_matching_variants(const instruction& inst, const
 bool all_header_reagents_strictly_match(const instruction& inst, const recipe& variant) {
   for (int i = 0;  i < min(SIZE(inst.ingredients), SIZE(variant.ingredients));  ++i) {
     if (!types_strictly_match(variant.ingredients.at(i), inst.ingredients.at(i))) {
-      trace(9993, "transform") << "strict match failed: ingredient " << i << end();
+      trace(103, "transform") << "strict match failed: ingredient " << i << end();
       return false;
     }
   }
   for (int i = 0;  i < min(SIZE(inst.products), SIZE(variant.products));  ++i) {
     if (is_dummy(inst.products.at(i))) continue;
     if (!types_strictly_match(variant.products.at(i), inst.products.at(i))) {
-      trace(9993, "transform") << "strict match failed: product " << i << end();
+      trace(103, "transform") << "strict match failed: product " << i << end();
       return false;
     }
   }
@@ -270,7 +270,7 @@ vector<recipe_ordinal> matching_variants(const instruction& inst, const vector<r
   vector<recipe_ordinal> result;
   for (int i = 0;  i < SIZE(variants);  ++i) {
     if (variants.at(i) == -1) continue;
-    trace(9992, "transform") << "checking variant " << i << ": " << header_label(variants.at(i)) << end();
+    trace(102, "transform") << "checking variant " << i << ": " << header_label(variants.at(i)) << end();
     if (all_header_reagents_match(inst, get(Recipe, variants.at(i))))
       result.push_back(variants.at(i));
   }
@@ -280,14 +280,14 @@ vector<recipe_ordinal> matching_variants(const instruction& inst, const vector<r
 bool all_header_reagents_match(const instruction& inst, const recipe& variant) {
   for (int i = 0;  i < min(SIZE(inst.ingredients), SIZE(variant.ingredients));  ++i) {
     if (!types_match(variant.ingredients.at(i), inst.ingredients.at(i))) {
-      trace(9993, "transform") << "match failed: ingredient " << i << end();
+      trace(103, "transform") << "match failed: ingredient " << i << end();
       return false;
     }
   }
   for (int i = 0;  i < min(SIZE(variant.products), SIZE(inst.products));  ++i) {
     if (is_dummy(inst.products.at(i))) continue;
     if (!types_match(variant.products.at(i), inst.products.at(i))) {
-      trace(9993, "transform") << "match failed: product " << i << end();
+      trace(103, "transform") << "match failed: product " << i << end();
       return false;
     }
   }
diff --git a/055shape_shifting_container.cc b/055shape_shifting_container.cc
index c3d0246f..1fb5f176 100644
--- a/055shape_shifting_container.cc
+++ b/055shape_shifting_container.cc
@@ -132,7 +132,7 @@ if (!info.type_ingredient_names.empty()) continue;
 
 :(before "End container Name Refinements")
 if (name.find(':') != string::npos) {
-  trace("parse") << "container has type ingredients; parsing" << end();
+  trace(101, "parse") << "container has type ingredients; parsing" << end();
   if (!read_type_ingredients(name, command)) {
     // error; skip rest of the container definition and continue
     slurp_balanced_bracket(in);
diff --git a/056shape_shifting_recipe.cc b/056shape_shifting_recipe.cc
index cb1a40b9..7eb5f0e3 100644
--- a/056shape_shifting_recipe.cc
+++ b/056shape_shifting_recipe.cc
@@ -50,10 +50,10 @@ if (contains_type_ingredient_name(to)) return false;
 candidates = strictly_matching_shape_shifting_variants(inst, variants);
 if (!candidates.empty()) {
   recipe_ordinal exemplar = best_shape_shifting_variant(inst, candidates);
-  trace(9992, "transform") << "found variant to specialize: " << exemplar << ' ' << get(Recipe, exemplar).name << end();
+  trace(102, "transform") << "found variant to specialize: " << exemplar << ' ' << get(Recipe, exemplar).name << end();
   string new_recipe_name = insert_new_variant(exemplar, inst, caller_recipe);
   if (new_recipe_name != "") {
-    trace(9992, "transform") << "new specialization: " << new_recipe_name << end();
+    trace(102, "transform") << "new specialization: " << new_recipe_name << end();
     return new_recipe_name;
   }
 }
@@ -84,14 +84,14 @@ vector<recipe_ordinal> strictly_matching_shape_shifting_variants(const instructi
 bool all_concrete_header_reagents_strictly_match(const instruction& inst, const recipe& variant) {
   for (int i = 0;  i < min(SIZE(inst.ingredients), SIZE(variant.ingredients));  ++i) {
     if (!concrete_type_names_strictly_match(variant.ingredients.at(i), inst.ingredients.at(i))) {
-      trace(9993, "transform") << "concrete-type match failed: ingredient " << i << end();
+      trace(103, "transform") << "concrete-type match failed: ingredient " << i << end();
       return false;
     }
   }
   for (int i = 0;  i < min(SIZE(inst.products), SIZE(variant.products));  ++i) {
     if (is_dummy(inst.products.at(i))) continue;
     if (!concrete_type_names_strictly_match(variant.products.at(i), inst.products.at(i))) {
-      trace(9993, "transform") << "concrete-type match failed: product " << i << end();
+      trace(103, "transform") << "concrete-type match failed: product " << i << end();
       return false;
     }
   }
@@ -250,10 +250,10 @@ string insert_new_variant(recipe_ordinal exemplar, const instruction& inst, cons
   new_recipe.name = new_name;
   new_recipe.ordinal = new_recipe_ordinal;
   new_recipe.is_autogenerated = true;
-  trace(9993, "transform") << "switching " << inst.name << " to specialized " << header_label(new_recipe) << end();
+  trace(103, "transform") << "switching " << inst.name << " to specialized " << header_label(new_recipe) << end();
 
-  trace(9992, "transform") << "transforming new specialization: " << new_recipe.name << end();
-  trace(9992, "transform") << new_recipe.name << ": performing transforms until check_or_set_types_by_name" << end();
+  trace(102, "transform") << "transforming new specialization: " << new_recipe.name << end();
+  trace(102, "transform") << new_recipe.name << ": performing transforms until check_or_set_types_by_name" << end();
   int transform_index = 0;
   for (transform_index = 0;  transform_index < SIZE(Transform);  ++transform_index) {
     if (Transform.at(transform_index) == check_or_set_types_by_name) break;
@@ -261,11 +261,11 @@ string insert_new_variant(recipe_ordinal exemplar, const instruction& inst, cons
   }
   new_recipe.transformed_until = transform_index-1;
 
-  trace(9992, "transform") << new_recipe.name << ": performing type-ingredient-aware version of transform check_or_set_types_by_name" << end();
+  trace(102, "transform") << new_recipe.name << ": performing type-ingredient-aware version of transform check_or_set_types_by_name" << end();
   compute_type_names(new_recipe);
   new_recipe.transformed_until++;
 
-  trace(9992, "transform") << new_recipe.name << ": replacing type ingredients" << end();
+  trace(102, "transform") << new_recipe.name << ": replacing type ingredients" << end();
   {
     map<string, const type_tree*> mappings;
     bool error = false;
@@ -278,9 +278,9 @@ string insert_new_variant(recipe_ordinal exemplar, const instruction& inst, cons
   }
   ensure_all_concrete_types(new_recipe, get(Recipe, exemplar));
 
-  trace(9992, "transform") << new_recipe.name << ": recording the new variant before recursively calling resolve_ambiguous_calls" << end();
+  trace(102, "transform") << new_recipe.name << ": recording the new variant before recursively calling resolve_ambiguous_calls" << end();
   get(Recipe_variants, inst.name).push_back(new_recipe_ordinal);
-  trace(9992, "transform") << new_recipe.name << ": performing remaining transforms (including resolve_ambiguous_calls)" << end();
+  trace(102, "transform") << new_recipe.name << ": performing remaining transforms (including resolve_ambiguous_calls)" << end();
   for (/*nada*/;  transform_index < SIZE(Transform);  ++transform_index)
     (*Transform.at(transform_index))(new_recipe_ordinal);
   new_recipe.transformed_until = SIZE(Transform)-1;
@@ -288,7 +288,7 @@ string insert_new_variant(recipe_ordinal exemplar, const instruction& inst, cons
 }
 
 void compute_type_names(recipe& variant) {
-  trace(9993, "transform") << "-- compute type names: " << variant.name << end();
+  trace(103, "transform") << "-- compute type names: " << variant.name << end();
   map<string, type_tree*> type_names;
   for (int i = 0;  i < SIZE(variant.ingredients);  ++i)
     save_or_deduce_type_name(variant.ingredients.at(i), type_names, variant, "");
@@ -296,7 +296,7 @@ void compute_type_names(recipe& variant) {
     save_or_deduce_type_name(variant.products.at(i), type_names, variant, "");
   for (int i = 0;  i < SIZE(variant.steps);  ++i) {
     instruction& inst = variant.steps.at(i);
-    trace(9993, "transform") << "  instruction: " << to_string(inst) << end();
+    trace(103, "transform") << "  instruction: " << to_string(inst) << end();
     for (int in = 0;  in < SIZE(inst.ingredients);  ++in)
       save_or_deduce_type_name(inst.ingredients.at(in), type_names, variant, " in '" + to_original_string(inst) + "'");
     for (int out = 0;  out < SIZE(inst.products);  ++out)
@@ -305,10 +305,10 @@ void compute_type_names(recipe& variant) {
 }
 
 void save_or_deduce_type_name(reagent& x, map<string, type_tree*>& type, const recipe& variant, const string& context) {
-  trace(9994, "transform") << "    checking " << to_string(x) << ": " << names_to_string(x.type) << end();
+  trace(104, "transform") << "    checking " << to_string(x) << ": " << names_to_string(x.type) << end();
   if (!x.type && contains_key(type, x.name)) {
     x.type = new type_tree(*get(type, x.name));
-    trace(9994, "transform") << "    deducing type to " << names_to_string(x.type) << end();
+    trace(104, "transform") << "    deducing type to " << names_to_string(x.type) << end();
     return;
   }
   // Type Check in Type-ingredient-aware check_or_set_types_by_name
@@ -322,7 +322,7 @@ void save_or_deduce_type_name(reagent& x, map<string, type_tree*>& type, const r
   if (contains_key(type, x.name)) return;
   if (x.type->name == "offset" || x.type->name == "variant") return;  // special-case for container-access instructions
   put(type, x.name, x.type);
-  trace(9993, "transform") << "type of '" << x.name << "' is " << names_to_string(x.type) << end();
+  trace(103, "transform") << "type of '" << x.name << "' is " << names_to_string(x.type) << end();
 }
 
 void compute_type_ingredient_mappings(const recipe& exemplar, const instruction& inst, map<string, const type_tree*>& mappings, const recipe& caller_recipe, bool* error) {
@@ -372,7 +372,7 @@ void accumulate_type_ingredients(const type_tree* exemplar_type, const type_tree
         curr_refinement_type = new type_tree(*refinement_type->left);
       }
       if (!contains_key(mappings, exemplar_type->name)) {
-        trace(9993, "transform") << "adding mapping from " << exemplar_type->name << " to " << to_string(curr_refinement_type) << end();
+        trace(103, "transform") << "adding mapping from " << exemplar_type->name << " to " << to_string(curr_refinement_type) << end();
         put(mappings, exemplar_type->name, new type_tree(*curr_refinement_type));
       }
       else {
@@ -399,16 +399,16 @@ void accumulate_type_ingredients(const type_tree* exemplar_type, const type_tree
 void replace_type_ingredients(recipe& new_recipe, const map<string, const type_tree*>& mappings) {
   // update its header
   if (mappings.empty()) return;
-  trace(9993, "transform") << "replacing in recipe header ingredients" << end();
+  trace(103, "transform") << "replacing in recipe header ingredients" << end();
   for (int i = 0;  i < SIZE(new_recipe.ingredients);  ++i)
     replace_type_ingredients(new_recipe.ingredients.at(i), mappings, new_recipe);
-  trace(9993, "transform") << "replacing in recipe header products" << end();
+  trace(103, "transform") << "replacing in recipe header products" << end();
   for (int i = 0;  i < SIZE(new_recipe.products);  ++i)
     replace_type_ingredients(new_recipe.products.at(i), mappings, new_recipe);
   // update its body
   for (int i = 0;  i < SIZE(new_recipe.steps);  ++i) {
     instruction& inst = new_recipe.steps.at(i);
-    trace(9993, "transform") << "replacing in instruction '" << to_string(inst) << "'" << end();
+    trace(103, "transform") << "replacing in instruction '" << to_string(inst) << "'" << end();
     for (int j = 0;  j < SIZE(inst.ingredients);  ++j)
       replace_type_ingredients(inst.ingredients.at(j), mappings, new_recipe);
     for (int j = 0;  j < SIZE(inst.products);  ++j)
@@ -425,7 +425,7 @@ void replace_type_ingredients(recipe& new_recipe, const map<string, const type_t
 
 void replace_type_ingredients(reagent& x, const map<string, const type_tree*>& mappings, const recipe& caller) {
   string before = to_string(x);
-  trace(9993, "transform") << "replacing in ingredient " << x.original_string << end();
+  trace(103, "transform") << "replacing in ingredient " << x.original_string << end();
   if (!x.type) {
     raise << "specializing " << caller.original_name << ": missing type for '" << x.original_string << "'\n" << end();
     return;
@@ -449,7 +449,7 @@ void replace_type_ingredients(type_tree* type, const map<string, const type_tree
   if (!contains_key(mappings, type->name))
     return;
   const type_tree* replacement = get(mappings, type->name);
-  trace(9993, "transform") << type->name << " => " << names_to_string(replacement) << end();
+  trace(103, "transform") << type->name << " => " << names_to_string(replacement) << end();
   if (replacement->atom) {
     if (!contains_key(Type_ordinal, replacement->name)) {
       // error in program; should be reported elsewhere
@@ -510,7 +510,7 @@ void dump_inspect(const type_tree* x, ostream& out) {
 }
 
 void ensure_all_concrete_types(/*const*/ recipe& new_recipe, const recipe& exemplar) {
-  trace(9993, "transform") << "-- ensure all concrete types in recipe " << new_recipe.name << end();
+  trace(103, "transform") << "-- ensure all concrete types in recipe " << new_recipe.name << end();
   for (int i = 0;  i < SIZE(new_recipe.ingredients);  ++i)
     ensure_all_concrete_types(new_recipe.ingredients.at(i), exemplar);
   for (int i = 0;  i < SIZE(new_recipe.products);  ++i)
diff --git a/057immutable.cc b/057immutable.cc
index d1f1df71..e1feac7a 100644
--- a/057immutable.cc
+++ b/057immutable.cc
@@ -344,7 +344,7 @@ void check_immutable_ingredients(const recipe_ordinal r) {
   //   b) we never call 'put' or 'put-index' on it, and
   //   c) any non-primitive recipe calls in the body aren't returning it as a product
   const recipe& caller = get(Recipe, r);
-  trace(9991, "transform") << "--- check mutability of ingredients in recipe " << caller.name << end();
+  trace(101, "transform") << "--- check mutability of ingredients in recipe " << caller.name << end();
   if (!caller.has_header) return;  // skip check for old-style recipes calling next-ingredient directly
   for (int i = 0;  i < SIZE(caller.ingredients);  ++i) {
     const reagent& current_ingredient = caller.ingredients.at(i);
diff --git a/060rewrite_literal_string.cc b/060rewrite_literal_string.cc
index 95e38924..7bfc45cb 100644
--- a/060rewrite_literal_string.cc
+++ b/060rewrite_literal_string.cc
@@ -38,7 +38,7 @@ void initialize_transform_rewrite_literal_string_to_text() {
 
 void rewrite_literal_string_to_text(const recipe_ordinal r) {
   recipe& caller = get(Recipe, r);
-  trace(9991, "transform") << "--- rewrite literal strings in recipe " << caller.name << end();
+  trace(101, "transform") << "--- rewrite literal strings in recipe " << caller.name << end();
   if (contains_numeric_locations(caller)) return;
   vector<instruction> new_instructions;
   for (int i = 0;  i < SIZE(caller.steps);  ++i) {
diff --git a/062convert_ingredients_to_text.cc b/062convert_ingredients_to_text.cc
index d3d3291a..8739f47b 100644
--- a/062convert_ingredients_to_text.cc
+++ b/062convert_ingredients_to_text.cc
@@ -59,7 +59,7 @@ Transform.push_back(convert_ingredients_to_text);  // idempotent
 :(code)
 void convert_ingredients_to_text(const recipe_ordinal r) {
   recipe& caller = get(Recipe, r);
-  trace(9991, "transform") << "--- convert some ingredients to text in recipe " << caller.name << end();
+  trace(101, "transform") << "--- convert some ingredients to text in recipe " << caller.name << end();
   // in recipes without named locations, 'stash' is still not extensible
   if (contains_numeric_locations(caller)) return;
   convert_ingredients_to_text(caller);
@@ -100,7 +100,7 @@ void convert_ingredients_to_text(recipe& caller) {
         }
       }
     }
-    trace(9993, "transform") << to_string(inst) << end();
+    trace(103, "transform") << to_string(inst) << end();
     new_instructions.push_back(inst);
   }
   caller.steps.swap(new_instructions);
@@ -125,7 +125,7 @@ void convert_ingredient_to_text(reagent& r, vector<instruction>& out, const stri
     def.ingredients.push_back(r);
   }
   def.products.push_back(reagent(tmp_var));
-  trace(9993, "transform") << to_string(def) << end();
+  trace(103, "transform") << to_string(def) << end();
   out.push_back(def);
   r.clear();  // reclaim old memory
   r = reagent(tmp_var);
diff --git a/069hash.cc b/069hash.cc
index c810f98b..3a371233 100644
--- a/069hash.cc
+++ b/069hash.cc
@@ -55,7 +55,7 @@ size_t hash_mu_scalar(size_t h, const reagent& r) {
 
 size_t hash_mu_address(size_t h, reagent& r) {
   if (r.value == 0) return 0;
-  trace("mem") << "location " << r.value << " is " << no_scientific(get_or_insert(Memory, r.value)) << end();
+  trace(Callstack_depth+1, "mem") << "location " << r.value << " is " << no_scientific(get_or_insert(Memory, r.value)) << end();
   r.set_value(get_or_insert(Memory, r.value));
   drop_from_type(r, "address");
   return hash(h, r);
diff --git a/072recipe.cc b/072recipe.cc
index 439b2000..43b1d013 100644
--- a/072recipe.cc
+++ b/072recipe.cc
@@ -79,11 +79,9 @@ case CALL: {
 :(before "End Primitive Recipe Implementations")
 case CALL: {
   // Begin Call
-  if (Trace_stream) {
-    ++Trace_stream->callstack_depth;
-    trace("trace") << "indirect 'call': incrementing callstack depth to " << Trace_stream->callstack_depth << end();
-    assert(Trace_stream->callstack_depth < 9000);  // 9998-101 plus cushion
-  }
+  trace(Callstack_depth+1, "trace") << "indirect 'call': incrementing callstack depth to " << Callstack_depth << end();
+  ++Callstack_depth;
+  assert(Callstack_depth < Max_depth);
   if (!ingredients.at(0).at(0)) {
     raise << maybe(current_recipe_name()) << "tried to call empty recipe in '" << to_string(current_instruction()) << "'" << end();
     break;
@@ -224,7 +222,7 @@ void resolve_indirect_ambiguous_call(const recipe_ordinal r, int index, instruct
 Transform.push_back(check_indirect_calls_against_header);  // idempotent
 :(code)
 void check_indirect_calls_against_header(const recipe_ordinal r) {
-  trace(9991, "transform") << "--- type-check 'call' instructions inside recipe " << get(Recipe, r).name << end();
+  trace(101, "transform") << "--- type-check 'call' instructions inside recipe " << get(Recipe, r).name << end();
   const recipe& caller = get(Recipe, r);
   for (int i = 0;  i < SIZE(caller.steps);  ++i) {
     const instruction& inst = caller.steps.at(i);
diff --git a/073scheduler.cc b/073scheduler.cc
index aa22bcfb..4270ecb1 100644
--- a/073scheduler.cc
+++ b/073scheduler.cc
@@ -69,7 +69,7 @@ void run(routine* rr) {
     skip_to_next_routine();
     assert(Current_routine);
     assert(Current_routine->state == RUNNING);
-    trace(9990, "schedule") << current_routine_label() << end();
+    trace(100, "schedule") << current_routine_label() << end();
     run_current_routine();
     // Scheduler State Transitions
     if (Current_routine->completed())
@@ -465,7 +465,7 @@ DISCONTINUED,
 :(before "End Scheduler State Transitions")
 if (Current_routine->limit >= 0) {
   if (Current_routine->limit <= Scheduling_interval) {
-    trace("schedule") << "discontinuing routine " << Current_routine->id << end();
+    trace(100, "schedule") << "discontinuing routine " << Current_routine->id << end();
     Current_routine->state = DISCONTINUED;
     Current_routine->limit = 0;
   }
diff --git a/074wait.cc b/074wait.cc
index aa3b59af..0a32c994 100644
--- a/074wait.cc
+++ b/074wait.cc
@@ -89,13 +89,13 @@ case WAIT_FOR_RESET_THEN_SET: {
 :(before "End Primitive Recipe Implementations")
 case WAIT_FOR_RESET_THEN_SET: {
   int loc = static_cast<int>(ingredients.at(0).at(0));
-  trace(9998, "run") << "wait: *" << loc << " = " << get_or_insert(Memory, loc) << end();
+  trace(Callstack_depth+1, "run") << "wait: *" << loc << " = " << get_or_insert(Memory, loc) << end();
   if (get_or_insert(Memory, loc) == 0) {
-    trace(9998, "run") << "location " << loc << " is already 0; setting" << end();
+    trace(Callstack_depth+1, "run") << "location " << loc << " is already 0; setting" << end();
     put(Memory, loc, 1);
     break;
   }
-  trace(9998, "run") << "waiting for location " << loc << " to reset" << end();
+  trace(Callstack_depth+1, "run") << "waiting for location " << loc << " to reset" << end();
   Current_routine->state = WAITING;
   Current_routine->waiting_on_location = loc;
   break;
@@ -121,7 +121,7 @@ case RESET: {
 case RESET: {
   int loc = static_cast<int>(ingredients.at(0).at(0));
   put(Memory, loc, 0);
-  trace(9998, "run") << "reset: *" << loc << " = " << get_or_insert(Memory, loc) << end();
+  trace(Callstack_depth+1, "run") << "reset: *" << loc << " = " << get_or_insert(Memory, loc) << end();
   break;
 }
 
@@ -132,7 +132,7 @@ for (int i = 0;  i < SIZE(Routines);  ++i) {
   if (Routines.at(i)->state != WAITING) continue;
   int loc = Routines.at(i)->waiting_on_location;
   if (loc && get_or_insert(Memory, loc) == 0) {
-    trace("schedule") << "waking up routine " << Routines.at(i)->id << end();
+    trace(100, "schedule") << "waking up routine " << Routines.at(i)->id << end();
     put(Memory, loc, 1);
     Routines.at(i)->state = RUNNING;
     Routines.at(i)->waiting_on_location = 0;
@@ -212,7 +212,7 @@ case GET_LOCATION: {
   int result = base_address;
   for (int i = 0;  i < offset;  ++i)
     result += size_of(element_type(base.type, i));
-  trace(9998, "run") << "address to copy is " << result << end();
+  trace(Callstack_depth+1, "run") << "address to copy is " << result << end();
   products.resize(1);
   products.at(0).push_back(result);
   break;
@@ -338,7 +338,7 @@ case WAIT_FOR_ROUTINE: {
   }
   Current_routine->state = WAITING;
   Current_routine->waiting_on_routine = ingredients.at(0).at(0);
-  trace(9998, "run") << "waiting for routine " << ingredients.at(0).at(0) << end();
+  trace(Callstack_depth+1, "run") << "waiting for routine " << ingredients.at(0).at(0) << end();
   break;
 }
 
@@ -356,7 +356,7 @@ for (int i = 0;  i < SIZE(Routines);  ++i) {
     const routine* waitee = Routines.at(j);
     if (waitee->id == id && waitee->state != RUNNING && waitee->state != WAITING) {
       // routine is COMPLETED or DISCONTINUED
-      trace("schedule") << "waking up routine " << waiter->id << end();
+      trace(100, "schedule") << "waking up routine " << waiter->id << end();
       waiter->state = RUNNING;
       waiter->waiting_on_routine = 0;
     }
@@ -500,7 +500,7 @@ case WAIT_FOR_ROUTINE_TO_BLOCK: {
   }
   Current_routine->state = WAITING;
   Current_routine->waiting_on_routine_to_block = ingredients.at(0).at(0);
-  trace(9998, "run") << "waiting for routine " << ingredients.at(0).at(0) << " to block" << end();
+  trace(Callstack_depth+1, "run") << "waiting for routine " << ingredients.at(0).at(0) << " to block" << end();
   break;
 }
 
@@ -516,7 +516,7 @@ for (int i = 0;  i < SIZE(Routines);  ++i) {
     const routine* waitee = Routines.at(j);
     if (waitee->id != id) continue;
     if (waitee->state != RUNNING || waitee->blocked) {
-      trace("schedule") << "waking up routine " << waiter->id << " because routine " << waitee->id << " is blocked" << end();
+      trace(100, "schedule") << "waking up routine " << waiter->id << " because routine " << waitee->id << " is blocked" << end();
       waiter->state = RUNNING;
       waiter->waiting_on_routine_to_block = 0;
     }
diff --git a/076continuation.cc b/076continuation.cc
index 7a328a11..49916ef2 100644
--- a/076continuation.cc
+++ b/076continuation.cc
@@ -132,11 +132,9 @@ case CALL_WITH_CONTINUATION_MARK: {
 case CALL_WITH_CONTINUATION_MARK: {
   // like call, but mark the current call as a 'base of continuation' call
   // before pushing the next one on it
-  if (Trace_stream) {
-    ++Trace_stream->callstack_depth;
-    trace("trace") << "delimited continuation; incrementing callstack depth to " << Trace_stream->callstack_depth << end();
-    assert(Trace_stream->callstack_depth < 9000);  // 9998-101 plus cushion
-  }
+  trace(Callstack_depth+1, "trace") << "delimited continuation; incrementing callstack depth to " << Callstack_depth << end();
+  ++Callstack_depth;
+  assert(Callstack_depth < Max_depth);
   instruction/*copy*/ caller_instruction = current_instruction();
   Current_routine->calls.front().continuation_mark_tag = current_instruction().ingredients.at(0).value;
   Current_routine->calls.push_front(call(ingredients.at(1).at(0)));
@@ -213,13 +211,11 @@ case RETURN_CONTINUATION_UNTIL_MARK: {
       raise << maybe(current_recipe_name()) << "  " << get(Recipe, p->running_recipe).name << '\n' << end();
     break;
   }
-  trace("run") << "creating continuation " << Next_delimited_continuation_id << end();
+  trace(Callstack_depth+1, "run") << "creating continuation " << Next_delimited_continuation_id << end();
   put(Delimited_continuation, Next_delimited_continuation_id, delimited_continuation(Current_routine->calls.begin(), base));
   while (Current_routine->calls.begin() != base) {
-    if (Trace_stream) {
-      --Trace_stream->callstack_depth;
-      assert(Trace_stream->callstack_depth >= 0);
-    }
+    --Callstack_depth;
+    assert(Callstack_depth >= 0);
     Current_routine->calls.pop_front();
   }
   // return it as the result of the marked call
@@ -243,17 +239,15 @@ call_stack::iterator find_base_of_continuation(call_stack& c, int mark_tag) {
 if (is_mu_continuation(current_instruction().ingredients.at(0))) {
   // copy multiple calls on to current call stack
   assert(scalar(ingredients.at(0)));
-  trace("run") << "calling continuation " << ingredients.at(0).at(0) << end();
+  trace(Callstack_depth+1, "run") << "calling continuation " << ingredients.at(0).at(0) << end();
   if (!contains_key(Delimited_continuation, ingredients.at(0).at(0)))
     raise << maybe(current_recipe_name()) << "no such delimited continuation " << current_instruction().ingredients.at(0).original_string << '\n' << end();
   const call_stack& new_frames = get(Delimited_continuation, ingredients.at(0).at(0)).frames;
   for (call_stack::const_reverse_iterator p = new_frames.rbegin(); p != new_frames.rend(); ++p)
     Current_routine->calls.push_front(*p);
-  if (Trace_stream) {
-    Trace_stream->callstack_depth += SIZE(new_frames);
-    trace("trace") << "calling delimited continuation; growing callstack depth to " << Trace_stream->callstack_depth << end();
-    assert(Trace_stream->callstack_depth < 9000);  // 9998-101 plus cushion
-  }
+  trace(Callstack_depth+1, "trace") << "calling delimited continuation; growing callstack depth to " << Callstack_depth+SIZE(new_frames) << end();
+  Callstack_depth += SIZE(new_frames);
+  assert(Callstack_depth < Max_depth);
   // no call housekeeping; continuations don't support next-ingredient
   copy(/*drop continuation*/++ingredients.begin(), ingredients.end(), inserter(products, products.begin()));
   break;  // record results of resuming 'return-continuation-until-mark' instruction
diff --git a/085scenario_console.cc b/085scenario_console.cc
index 31aa4fe7..af6e44c2 100644
--- a/085scenario_console.cc
+++ b/085scenario_console.cc
@@ -66,7 +66,7 @@ case ASSUME_CONSOLE: {
   for (int i = 0;  i < SIZE(r.steps);  ++i) {
     const instruction& inst = r.steps.at(i);
     if (inst.name == "left-click") {
-      trace("mem") << "storing 'left-click' event starting at " << Current_routine->alloc << end();
+      trace(Callstack_depth+1, "mem") << "storing 'left-click' event starting at " << Current_routine->alloc << end();
       put(Memory, curr_address, /*tag for 'touch-event' variant of 'event' exclusive-container*/2);
       put(Memory, curr_address+/*skip tag*/1+/*offset of 'type' in 'mouse-event'*/0, TB_KEY_MOUSE_LEFT);
       put(Memory, curr_address+/*skip tag*/1+/*offset of 'row' in 'mouse-event'*/1, to_integer(inst.ingredients.at(0).name));
@@ -74,7 +74,7 @@ case ASSUME_CONSOLE: {
       curr_address += size_of_event();
     }
     else if (inst.name == "press") {
-      trace("mem") << "storing 'press' event starting at " << curr_address << end();
+      trace(Callstack_depth+1, "mem") << "storing 'press' event starting at " << curr_address << end();
       string key = inst.ingredients.at(0).name;
       if (is_integer(key))
         put(Memory, curr_address+1, to_integer(key));
@@ -95,18 +95,18 @@ case ASSUME_CONSOLE: {
     else {
       // keyboard input
       assert(inst.name == "type");
-      trace("mem") << "storing 'type' event starting at " << curr_address << end();
+      trace(Callstack_depth+1, "mem") << "storing 'type' event starting at " << curr_address << end();
       const string& contents = inst.ingredients.at(0).name;
       const char* raw_contents = contents.c_str();
       int num_keyboard_events = unicode_length(contents);
       int curr = 0;
       for (int i = 0;  i < num_keyboard_events;  ++i) {
-        trace("mem") << "storing 'text' tag at " << curr_address << end();
+        trace(Callstack_depth+1, "mem") << "storing 'text' tag at " << curr_address << end();
         put(Memory, curr_address, /*tag for 'text' variant of 'event' exclusive-container*/0);
         uint32_t curr_character;
         assert(curr < SIZE(contents));
         tb_utf8_char_to_unicode(&curr_character, &raw_contents[curr]);
-        trace("mem") << "storing character " << curr_character << " at " << curr_address+/*skip exclusive container tag*/1 << end();
+        trace(Callstack_depth+1, "mem") << "storing character " << curr_character << " at " << curr_address+/*skip exclusive container tag*/1 << end();
         put(Memory, curr_address+/*skip exclusive container tag*/1, curr_character);
         curr += tb_utf8_char_length(raw_contents[curr]);
         curr_address += size_of_event();
@@ -116,9 +116,9 @@ case ASSUME_CONSOLE: {
   assert(curr_address == event_data_address+/*skip alloc id*/1+size);
   // wrap the array of events in a console object
   int console_address = allocate(size_of_console());
-  trace("mem") << "storing console in " << console_address << end();
+  trace(Callstack_depth+1, "mem") << "storing console in " << console_address << end();
   put(Memory, CONSOLE+/*skip alloc id*/1, console_address);
-  trace("mem") << "storing console data in " << console_address+/*offset of 'data' in container 'events'*/1 << end();
+  trace(Callstack_depth+1, "mem") << "storing console data in " << console_address+/*offset of 'data' in container 'events'*/1 << end();
   put(Memory, console_address+/*skip alloc id*/1+/*offset of 'data' in container 'events'*/1+/*skip alloc id of 'data'*/1, event_data_address);
   break;
 }
diff --git a/089scenario_filesystem.cc b/089scenario_filesystem.cc
index bacb61be..b0429ea7 100644
--- a/089scenario_filesystem.cc
+++ b/089scenario_filesystem.cc
@@ -208,24 +208,24 @@ void construct_resources_object(const map<string, string>& contents) {
   for (map<string, string>::const_iterator p = contents.begin();  p != contents.end();  ++p) {
     ++curr;  // skip alloc id of resource.name
     put(Memory, curr, new_mu_text(p->first));
-    trace("mem") << "storing file name " << get(Memory, curr) << " in location " << curr << end();
+    trace(Callstack_depth+1, "mem") << "storing file name " << get(Memory, curr) << " in location " << curr << end();
     ++curr;
     ++curr;  // skip alloc id of resource.contents
     put(Memory, curr, new_mu_text(p->second));
-    trace("mem") << "storing file contents " << get(Memory, curr) << " in location " << curr << end();
+    trace(Callstack_depth+1, "mem") << "storing file contents " << get(Memory, curr) << " in location " << curr << end();
     ++curr;
   }
   curr = resources_data_address + /*skip alloc id of resources.data*/1;
   put(Memory, curr, SIZE(contents));  // array length
-  trace("mem") << "storing resources size " << get(Memory, curr) << " in location " << curr << end();
+  trace(Callstack_depth+1, "mem") << "storing resources size " << get(Memory, curr) << " in location " << curr << end();
   // wrap the resources data in a 'resources' object
   int resources_address = allocate(size_of_resources());
   curr = resources_address+/*alloc id*/1+/*offset of 'data' element*/1+/*skip alloc id of 'data' element*/1;
   put(Memory, curr, resources_data_address);
-  trace("mem") << "storing resources data address " << resources_data_address << " in location " << curr << end();
+  trace(Callstack_depth+1, "mem") << "storing resources data address " << resources_data_address << " in location " << curr << end();
   // save in product
   put(Memory, RESOURCES+/*skip alloc id*/1, resources_address);
-  trace("mem") << "storing resources address " << resources_address << " in location " << RESOURCES << end();
+  trace(Callstack_depth+1, "mem") << "storing resources address " << resources_address << " in location " << RESOURCES << end();
 }
 
 int size_of_resources() {
diff --git a/099hardware_checks.cc b/099hardware_checks.cc
index cee68bdb..f84837d2 100644
--- a/099hardware_checks.cc
+++ b/099hardware_checks.cc
@@ -35,7 +35,7 @@ void check_for_misuse_of_real_hardware(const recipe_ordinal r) {
   const recipe& caller = get(Recipe, r);
   if (caller.name == "main") return;
   if (starts_with(caller.name, "scenario_")) return;
-  trace(9991, "transform") << "--- check if recipe " << caller.name << " has any dependency-injection mistakes" << end();
+  trace(101, "transform") << "--- check if recipe " << caller.name << " has any dependency-injection mistakes" << end();
   for (int index = 0;  index < SIZE(caller.steps);  ++index) {
     const instruction& inst = caller.steps.at(index);
     if (is_primitive(inst.operation)) continue;
diff --git a/101run_sandboxed.cc b/101run_sandboxed.cc
index 99310219..5a9db896 100644
--- a/101run_sandboxed.cc
+++ b/101run_sandboxed.cc
@@ -91,6 +91,7 @@ Most_recent_products = "";
 :(before "End Globals")
 trace_stream* Save_trace_stream = NULL;
 string Save_trace_file;
+int Save_callstack_depth = 0;
 :(code)
 // reads a string, tries to call it as code (treating it as a test), saving
 // all errors.
@@ -127,9 +128,9 @@ bool run_interactive(int address) {
   // now call 'sandbox' which will run 'interactive' in a separate routine,
   // and wait for it
   if (Save_trace_stream) {
-    ++Save_trace_stream->callstack_depth;
-    trace(9999, "trace") << "run-sandboxed: incrementing callstack depth to " << Save_trace_stream->callstack_depth << end();
-    assert(Save_trace_stream->callstack_depth < 9000);  // 9998-101 plus cushion
+    ++Save_callstack_depth;
+    trace(Save_callstack_depth+1, "trace") << "run-sandboxed: incrementing callstack depth to " << Save_callstack_depth << end();
+    assert(Save_callstack_depth < Max_depth);
   }
   Current_routine->calls.push_front(call(get(Recipe_ordinal, "sandbox")));
   return true;
@@ -149,6 +150,9 @@ map<string, type_tree*> Type_abbreviations_snapshot_stash;
 vector<scenario> Scenarios_snapshot_stash;
 set<string> Scenario_names_snapshot_stash;
 
+:(before "End Types")  //: include in all cleaved compilation units
+const int App_depth = 1;  // where all Mu code will trace to by default
+
 :(code)
 void run_code_begin(bool should_stash_snapshots) {
   // stuff to undo later, in run_code_end()
@@ -159,6 +163,7 @@ void run_code_begin(bool should_stash_snapshots) {
   if (should_stash_snapshots)
     stash_snapshots();
   Save_trace_stream = Trace_stream;
+  Save_callstack_depth = Callstack_depth;
   Trace_stream = new trace_stream;
   Trace_stream->collect_depth = App_depth;
 }
@@ -175,6 +180,7 @@ void run_code_end() {
   Trace_stream = Save_trace_stream;
   Save_trace_stream = NULL;
   Save_trace_file.clear();
+  Save_callstack_depth = 0;
   Recipe.erase(get(Recipe_ordinal, "interactive"));  // keep past sandboxes from inserting errors
   if (!Recipe_snapshot_stash.empty())
     unstash_snapshots();
diff --git a/999spaces.cc b/999spaces.cc
index 219e695d..adbbf8a9 100644
--- a/999spaces.cc
+++ b/999spaces.cc
@@ -25,12 +25,10 @@ assert(Next_recipe_ordinal == 1000);
 
 //:: Depths for tracing
 //:
-//: 0 - unused
-//: 1-100 - app-level trace statements in Mu
-//: 101-9989 - call-stack statements (mostly label run)
-assert(Initial_callstack_depth == 101);
-assert(Max_callstack_depth == 9989);
-//: 9990-9999 - intra-instruction lines (mostly label mem)
+//: 0 - errors
+//: 1-99 - app-level trace statements in Mu
+//: 100-9999 - call-stack statements (mostly label run)
+assert(Initial_callstack_depth == 100);
 
 //:: Summary of transforms and their dependencies
 //: begin transforms
diff --git a/Readme.md b/Readme.md
index 54af1049..f1dd1be6 100644
--- a/Readme.md
+++ b/Readme.md
@@ -403,7 +403,19 @@ c) Try running the tests:
 d) Check out [the programming environment](https://github.com/akkartik/mu/tree/master/edit#readme),
 the largest app built so far in Mu.
 
-e) Look at the `build` scripts. Mu's compilation process is itself designed to
+e) Check out the tracing infrastructure which gives you a maps-like zoomable
+UI for browsing Mu's traces:
+
+  ```shell
+  $ ./mu --trace nqueens.mu  # just an example
+  saving trace to 'last_run'
+  $ ./browse_trace/browse_trace last_run
+  # hit 'q' to exit
+  ```
+
+For more details see the [Readme](browse_trace/Readme.md).
+
+f) Look at the `build` scripts. Mu's compilation process is itself designed to
 support staged learning. Each of the scripts (`build0`, `build1`, `build2`,
 etc.) is self-contained and can compile the project by itself. Successive
 versions add new features and configurability -- and complexity -- to the
diff --git a/browse_trace/Readme.md b/browse_trace/Readme.md
new file mode 100644
index 00000000..ccfbdca7
--- /dev/null
+++ b/browse_trace/Readme.md
@@ -0,0 +1,78 @@
+### A debugging helper that lets you zoom in/out on a trace.
+
+To try it out, first create an example trace (from the top-level `mu/`
+directory):
+
+  ```shell
+  ./mu --trace nqueens.mu
+  ```
+
+This command will save a trace of its execution in a file called `last_run`.
+The trace consists of a series of lines, each starting with an integer depth
+and a single-word 'label', followed by a colon and whitespace.
+
+Now browse this trace:
+
+  ```shell
+  ./browse_trace/browse_trace last_run
+  ```
+
+You should now find yourself in a UI showing a subsequence of lines from the
+trace, each line starting with a numeric depth, and ending with a parenthetical
+count of trace lines hidden after it with greater depths.
+
+For example, this line:
+
+  ```
+  2 app: line1 (30)
+  ```
+
+indicates that it was logged with depth 2, and that 30 following lines have
+been hidden at a depth greater than 2.
+
+(As an experiment, hidden counts of 1000 or more are highlighted in red.)
+
+The UI provides the following hotkeys:
+
+* `q` or `ctrl-c`: Quit.
+
+* `Enter`: 'Zoom into' this line. Expand lines hidden after it that were at
+  the next higher level.
+
+* `Backspace`: 'Zoom out' on a line after zooming in, collapsing lines below
+  expanded by some series of `Enter` commands.
+
+* `j` or `down-arrow`: Move/scroll cursor down one line.
+* `k` or `up-arrow`: Move/scroll cursor up one line.
+* `J` or `ctrl-f` or `page-down`: Scroll cursor down one page.
+* `K` or `ctrl-b` or `page-up`: Scroll cursor up one page.
+* `h` or `left-arrow`: Scroll cursor left one character.
+* `l` or `right-arrow`: Scroll cursor right one character.
+* `H`: Scroll cursor left one screen-width.
+* `L`: Scroll cursor right one screen-width.
+
+* `g` or `home`: Move cursor to start of trace.
+* `G` or `end`: Move cursor to end of trace.
+
+* `t`: Move cursor to top line on screen.
+* `c`: Move cursor to center line on screen.
+* `b`: Move cursor to bottom line on screen.
+* `T`: Scroll line at cursor to top of screen.
+
+* `/`: Search forward for a pattern.
+* '?': Search backward for a pattern.
+* `n`: Repeat the previous '/' or '?'.
+* `N`: Repeat the previous '/' or '?' in the opposite direction.
+
+After hitting `/`, the mini-editor on the bottom-most line supports the
+following hotkeys:
+* ascii characters: add the key to the pattern.
+* `Enter`: search for the pattern.
+* `Esc` or `ctrl-c`: cancel the current search, setting the screen back
+  to its state before the search.
+* `left-arrow`: move cursor left.
+* `right-arrow`: move cursor right.
+* `ctrl-a` or `home`: move cursor to start of search pattern.
+* `ctrl-e` or `end`: move cursor to end of search pattern.
+* `ctrl-u`: clear search pattern before cursor
+* `ctrl-k`: clear search pattern at and after cursor
diff --git a/browse_trace/browse_trace b/browse_trace/browse_trace
new file mode 100755
index 00000000..f80ea659
--- /dev/null
+++ b/browse_trace/browse_trace
@@ -0,0 +1,7 @@
+#!/bin/sh
+set -e
+
+cd `dirname $0`
+./build
+cd -
+`dirname $0`/browse_trace_bin $*
diff --git a/100trace_browser.cc b/browse_trace/browse_trace.cc
index 8b0f949e..cc03f5ce 100644
--- a/100trace_browser.cc
+++ b/browse_trace/browse_trace.cc
@@ -1,105 +1,113 @@
-//: A debugging helper that lets you zoom in/out on a trace.
-//: Warning: this tool has zero automated tests.
-//:
-//: To try it out, first create an example trace:
-//:   mu --trace nqueens.mu
-//: Then to browse the trace, which was stored in a file called 'last_run':
-//:   mu browse-trace last_run
-//:
-//: You should now find yourself in a UI showing a subsequence of lines from
-//: the trace, each line starting with a numeric depth, and ending with a
-//: parenthetical count of trace lines hidden after it with greater depths.
-//:
-//: For example, this line:
-//:   2 app: line1 (30)
-//: indicates that it was logged with depth 2, and that 30 following lines
-//: have been hidden at a depth greater than 2.
-//:
-//: As an experiment, hidden counts of 1000 or more are in red to highlight
-//: where you might be particularly interested in expanding.
-//:
-//: The UI provides the following hotkeys:
-//:
-//:   `q` or `ctrl-c`: Quit.
-//:
-//:   `Enter`: 'Zoom into' this line. Expand some or all of the hidden lines
-//:   at the next higher level, updating parenthetical counts of hidden lines.
-//:
-//:   `Backspace`: 'Zoom out' on a line after zooming in, collapsing expanded
-//:   lines below by some series of <Enter> commands.
-//:
-//:   `j` or `down-arrow`: Move/scroll cursor down one line.
-//:   `k` or `up-arrow`: Move/scroll cursor up one line.
-//:   `J` or `ctrl-f` or `page-down`: Scroll cursor down one page.
-//:   `K` or `ctrl-b` or `page-up`: Scroll cursor up one page.
-//:   `h` or `left-arrow`: Scroll cursor left one character.
-//:   `l` or `right-arrow`: Scroll cursor right one character.
-//:   `H`: Scroll cursor left one screen-width.
-//:   `L`: Scroll cursor right one screen-width.
-//:
-//:   `g` or `home`: Move cursor to start of trace.
-//:   `G` or `end`: Move cursor to end of trace.
-//:
-//:   `t`: Move cursor to top line on screen.
-//:   `c`: Move cursor to center line on screen.
-//:   `b`: Move cursor to bottom line on screen.
-//:   `T`: Scroll line at cursor to top of screen.
-//:
-//:   `/`: Search forward for a pattern.
-//:   '?': Search backward for a pattern.
-//:   `n`: Repeat the previous '/' or '?'.
-//:   `N`: Repeat the previous '/' or '?' in the opposite direction.
-//:
-//:   After hitting `/`, a small editor on the bottom-most line supports the
-//:   following hotkeys:
-//:     ascii characters: add the key to the pattern.
-//:     `Enter`: search for the pattern.
-//:     `Esc` or `ctrl-c`: cancel the current search, setting the screen back
-//:       to its state before the search.
-//:     `left-arrow`: move cursor left.
-//:     `right-arrow`: move cursor right.
-//:     `ctrl-a` or `home`: move cursor to start of search pattern.
-//:     `ctrl-e` or `end`: move cursor to end of search pattern.
-//:     `ctrl-u`: clear search pattern before cursor
-//:     `ctrl-k`: clear search pattern at and after cursor
+// Warning: zero automated tests
 
-:(before "End Primitive Recipe Declarations")
-_BROWSE_TRACE,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "$browse-trace", _BROWSE_TRACE);
-:(before "End Primitive Recipe Checks")
-case _BROWSE_TRACE: {
-  break;
+// Includes
+#include "../termbox/termbox.h"
+
+#include <stdlib.h>
+#define SIZE(X) (assert((X).size() < (1LL<<(sizeof(int)*8-2))), static_cast<int>((X).size()))
+
+#include <assert.h>
+#include <iostream>
+using std::istream;
+using std::ostream;
+using std::iostream;
+using std::cin;
+using std::cout;
+using std::cerr;
+#include <iomanip>
+#include <string.h>
+#include <string>
+using std::string;
+#include <vector>
+using std::vector;
+#include <set>
+using std::set;
+#include <sstream>
+using std::ostringstream;
+#include <fstream>
+using std::ifstream;
+using std::ofstream;
+#include <map>
+using std::map;
+#include <utility>
+using std::pair;
+// End Includes
+
+// Types
+struct trace_line {
+  string contents;
+  string label;
+  int depth;  // 0 is 'sea level'; positive integers are progressively 'deeper' and lower level
+  trace_line(string c, string l, int d) {
+    contents = c;
+    label = l;
+    depth = d;
+  }
+};
+
+struct trace_stream {
+  vector<trace_line> past_lines;
+};
+
+enum search_direction { FORWARD, BACKWARD };
+// End Types
+
+// Function prototypes are auto-generated in the 'build*' scripts; define your
+// functions in any order. Just be sure to declare each function header all on
+// one line, ending with the '{'. Our auto-generation scripts are too minimal
+// and simple-minded to handle anything else.
+#include "function_list"  // by convention, files ending with '_list' are auto-generated
+
+// from http://stackoverflow.com/questions/152643/idiomatic-c-for-reading-from-a-const-map
+template<typename T> typename T::mapped_type& get(T& map, typename T::key_type const& key) {
+  typename T::iterator iter(map.find(key));
+  assert(iter != map.end());
+  return iter->second;
 }
-:(before "End Primitive Recipe Implementations")
-case _BROWSE_TRACE: {
-  start_trace_browser();
-  break;
+template<typename T> typename T::mapped_type const& get(const T& map, typename T::key_type const& key) {
+  typename T::const_iterator iter(map.find(key));
+  assert(iter != map.end());
+  return iter->second;
 }
-
-//: browse a trace loaded from a file
-:(after "Commandline Parsing")
-if (argc == 3 && is_equal(argv[1], "browse-trace")) {
-  load_trace(argv[2]);
-  start_trace_browser();
-  return 0;
+template<typename T> typename T::mapped_type const& put(T& map, typename T::key_type const& key, typename T::mapped_type const& value) {
+  // map[key] requires mapped_type to have a zero-arg (default) constructor
+  map.insert(std::make_pair(key, value)).first->second = value;
+  return value;
+}
+template<typename T> bool contains_key(T& map, typename T::key_type const& key) {
+  return map.find(key) != map.end();
 }
+template<typename T> typename T::mapped_type& get_or_insert(T& map, typename T::key_type const& key) {
+  return map[key];
+}
+
+// Globals
+trace_stream* Trace_stream = NULL;
 
-:(before "End Globals")
+ofstream Trace_file;
+int Display_row = 0;
+int Display_column = 0;
 set<int> Visible;
 int Top_of_screen = 0;
 int Left_of_screen = 0;
 int Last_printed_row = 0;
 map<int, int> Trace_index;  // screen row -> trace index
+
 string Current_search_pattern = "";
-:(before "End Types")
-enum search_direction { FORWARD, BACKWARD };
-:(before "End Globals")
 search_direction Current_search_direction = FORWARD;
+// End Globals
+
+bool has_data(istream& in) {
+  return in && !in.eof();
+}
 
-:(code)
-void start_trace_browser() {
-  if (!Trace_stream) return;
+int main(int argc, char* argv[]) {
+  if (argc != 2) {
+    cerr << "Usage: browse_trace <trace file>\n";
+    return 1;
+  }
+  load_trace(argv[1]);
+  if (!Trace_stream) return 1;
   cerr << "computing min depth to display\n";
   int min_depth = 9999;
   for (int i = 0;  i < SIZE(Trace_stream->past_lines);  ++i) {
@@ -266,6 +274,7 @@ void start_trace_browser() {
     }
   }
   tb_shutdown();
+  return 0;
 }
 
 bool start_search_editor(search_direction dir) {
@@ -514,7 +523,7 @@ void load_trace(const char* filename) {
     if (*--label.end() == ':') label.erase(--label.end());
     string line;
     getline(tin, line);
-    Trace_stream->past_lines.push_back(trace_line(depth, label, line));
+    Trace_stream->past_lines.push_back(trace_line(line, label, depth));
   }
   cerr << "lines read: " << Trace_stream->past_lines.size() << '\n';
 }
@@ -526,3 +535,12 @@ int read_key() {
   } while (event.type != TB_EVENT_KEY);
   return event.key ? event.key : event.ch;
 }
+
+void skip_whitespace_but_not_newline(istream& in) {
+  while (true) {
+    if (!has_data(in)) break;
+    else if (in.peek() == '\n') break;
+    else if (isspace(in.peek())) in.get();
+    else break;
+  }
+}
diff --git a/browse_trace/build b/browse_trace/build
new file mode 100755
index 00000000..6047cc2b
--- /dev/null
+++ b/browse_trace/build
@@ -0,0 +1,83 @@
+#!/bin/sh
+# For details on the basic form of this script, see https://notabug.org/akkartik/basic-build.
+
+set -e  # stop immediately on error
+
+# Some environment variables that can be passed in. For example, to turn off
+# optimization:
+#   $ CFLAGS=-g ./build2
+test "$CXX" || export CXX=c++
+test "$CC" || export CC=cc
+test "$CFLAGS" || export CFLAGS="-g -O2"
+export CFLAGS="$CFLAGS -Wall -Wextra -ftrapv -fno-strict-aliasing"
+
+# there's two mechanisms for fast builds here:
+# - if a command is quick to run, always run it but update the result only on any change
+# - otherwise run it only if the output is 'older_than' the inputs
+#
+# avoid combining both mechanisms for a single file
+# otherwise you'll see spurious messages about files being updated
+# risk: a file may unnecessarily update without changes, causing unnecessary work downstream
+
+# return 1 if $1 is older than _any_ of the remaining args
+older_than() {
+  local target=$1
+  shift
+  if [ ! -e $target ]
+  then
+#?     echo "$target doesn't exist"
+    echo "updating $target" >&2
+    return 0  # success
+  fi
+  local f
+  for f in $*
+  do
+    if [ $f -nt $target ]
+    then
+      echo "updating $target" >&2
+      return 0  # success
+    fi
+  done
+  return 1  # failure
+}
+
+# redirect to $1, unless it's already identical
+update() {
+  if [ ! -e $1 ]
+  then
+    cat > $1
+  else
+    cat > $1.tmp
+    diff -q $1 $1.tmp >/dev/null  &&  rm $1.tmp  ||  mv $1.tmp $1
+  fi
+}
+
+noisy_cd() {
+  cd $1
+  echo "-- `pwd`" >&2
+}
+
+older_than browse_trace_bin browse_trace.cc *_list ../termbox/*  &&  {
+  # missing some deps here
+  noisy_cd ../termbox
+    older_than ../termbox/libtermbox.a ../termbox/*.c  &&  {
+      older_than utf8.o utf8.c && {
+        $CC $CFLAGS -c utf8.c
+      }
+      older_than termbox.o termbox.c termbox.h input.inl output.inl bytebuffer.inl && {
+        $CC $CFLAGS -c termbox.c
+      }
+      older_than libtermbox.a *.o && {
+        ar rcs libtermbox.a *.o
+      }
+    }
+  noisy_cd ../browse_trace
+
+  grep -h "^[^[:space:]#].*) {$" browse_trace.cc  |grep -v ":.*("  |sed 's/ {.*/;/'  |update function_list
+  # auto-generate list of tests to run
+  grep -h "^\s*void test_" browse_trace.cc  |sed 's/^\s*void \(.*\)() {.*/\1,/'  |update test_list
+  grep -h "^\s*void test_" browse_trace.cc  |sed 's/^\s*void \(.*\)() {.*/"\1",/'  |update test_name_list
+  $CXX $CFLAGS browse_trace.cc ../termbox/libtermbox.a -o browse_trace_bin
+}
+
+exit 0
diff --git a/browse_trace/clean b/browse_trace/clean
new file mode 100755
index 00000000..69b30478
--- /dev/null
+++ b/browse_trace/clean
@@ -0,0 +1,6 @@
+#!/bin/sh
+set -e
+
+set -v
+rm -rf browse_trace_bin* *_list
+rm -rf termbox/*.o termbox/libtermbox.a
diff --git a/subx/003trace.cc b/subx/003trace.cc
index e2080898..b794cab9 100644
--- a/subx/003trace.cc
+++ b/subx/003trace.cc
@@ -13,30 +13,31 @@
 //: longer valid. In both cases you end up having to reorganize code as well as
 //: tests, an error-prone activity.
 //:
-//: In response, this layer introduces the notion of *domain-driven* testing.
-//: We focus on the domain of inputs the whole program needs to handle rather
-//: than the correctness of individual functions. All tests invoke the program
-//: in a single way: by calling run() with some input. As the program operates
-//: on the input, it traces out a list of _facts_ deduced about the domain:
+//: In response, this layer introduces the notion of domain-driven *white-box*
+//: testing. We focus on the domain of inputs the whole program needs to
+//: handle rather than the correctness of individual functions. All white-box
+//: tests (we call them 'scenarios') invoke the program in a single way: by
+//: calling run() with some input. As the program operates on the input, it
+//: traces out a list of _facts_ deduced about the domain:
 //:   trace("label") << "fact 1: " << val;
 //:
-//: Tests can now check these facts:
+//: Scenarios can now check these facts:
 //:   :(scenario foo)
 //:   34  # call run() with this input
 //:   +label: fact 1: 34  # 'run' should have deduced this fact
 //:   -label: fact 1: 35  # the trace should not contain such a fact
 //:
 //: Since we never call anything but the run() function directly, we never have
-//: to rewrite the tests when we reorganize the internals of the program. We
+//: to rewrite the scenarios when we reorganize the internals of the program. We
 //: just have to make sure our rewrite deduces the same facts about the domain,
 //: and that's something we're going to have to do anyway.
 //:
 //: To avoid the combinatorial explosion of integration tests, each layer
-//: mainly logs facts to the trace with a common *label*. All tests in a layer
-//: tend to check facts with this label. Validating the facts logged with a
-//: specific label is like calling functions of that layer directly.
+//: mainly logs facts to the trace with a common *label*. All scenarios in a
+//: layer tend to check facts with this label. Validating the facts logged
+//: with a specific label is like calling functions of that layer directly.
 //:
-//: To build robust tests, trace facts about your domain rather than details of
+//: To build robust scenarios, trace facts about your domain rather than details of
 //: how you computed them.
 //:
 //: More details: http://akkartik.name/blog/tracing-tests
@@ -44,158 +45,183 @@
 //: ---
 //:
 //: Between layers and domain-driven testing, programming starts to look like a
-//: fundamentally different activity. Instead of a) superficial, b) local rules
-//: on c) code [like say http://blog.bbv.ch/2013/06/05/clean-code-cheat-sheet],
-//: we allow programmers to engage with the a) deep, b) global structure of the
-//: c) domain. If you can systematically track discontinuities in the domain,
-//: you don't care if the code used gotos as long as it passed the tests. If
-//: tests become more robust to run it becomes easier to try out radically
-//: different implementations for the same program. If code is super-easy to
-//: rewrite, it becomes less important what indentation style it uses, or that
-//: the objects are appropriately encapsulated, or that the functions are
-//: referentially transparent.
+//: fundamentally different activity. Instead of focusing on a) superficial,
+//: b) local rules on c) code [like say http://blog.bbv.ch/2013/06/05/clean-code-cheat-sheet],
+//: we allow programmers to engage with the a) deep, b) global structure of
+//: the c) domain. If you can systematically track discontinuities in the
+//: domain, you don't care if the code used gotos as long as it passed all
+//: scenarios. If scenarios become more robust to run, it becomes easier to
+//: try out radically different implementations for the same program. If code
+//: is super-easy to rewrite, it becomes less important what indentation style
+//: it uses, or that the objects are appropriately encapsulated, or that the
+//: functions are referentially transparent.
 //:
 //: Instead of plumbing, programming becomes building and gradually refining a
-//: map of the environment the program must operate under. Whether a program is
-//: 'correct' at a given point in time is a red herring; what matters is
-//: avoiding regression by monotonically nailing down the more 'eventful' parts
-//: of the terrain. It helps readers new and old, and rewards curiosity, to
-//: organize large programs in self-similar hierarchies of example scenarios
+//: map of the environment the program must operate under. Whether a program
+//: is 'correct' at a given point in time is a red herring; what matters is
+//: avoiding regression by monotonically nailing down the more 'eventful'
+//: parts of the terrain. It helps readers new and old, and rewards curiosity,
+//: to organize large programs in self-similar hierarchies of example scenarios
 //: colocated with the code that makes them work.
 //:
 //:   "Programming properly should be regarded as an activity by which
 //:   programmers form a mental model, rather than as production of a program."
 //:   -- Peter Naur (http://alistair.cockburn.us/ASD+book+extract%3A+%22Naur,+Ehn,+Musashi%22)
 
+//:: == Core data structures
+
+:(before "End Globals")
+trace_stream* Trace_stream = NULL;
+
 :(before "End Types")
-struct trace_line {
-  int depth;  // optional field just to help browse traces later
-  string label;
-  string contents;
-  trace_line(string l, string c) :depth(0), label(l), contents(c) {}
-  trace_line(int d, string l, string c) :depth(d), label(l), contents(c) {}
+struct trace_stream {
+  vector<trace_line> past_lines;
+  // End trace_stream Fields
+
+  trace_stream() {
+    // End trace_stream Constructor
+  }
+  ~trace_stream() {
+    // End trace_stream Destructor
+  }
+  // End trace_stream Methods
 };
 
-:(code)
-void cleanup_main() {
-  if (!Trace_stream) return;
-  delete Trace_stream;
-  Trace_stream = NULL;
-}
-:(before "End One-time Setup")
-atexit(cleanup_main);
+//:: == Adding to the trace
 
-:(before "End Types")
-// Pre-define some global constants that trace_stream needs to know about.
-// Since they're in the Types section, they'll be included in any cleaved
-// compilation units. So no extern linkage.
+//: Top-level method is trace() which can be used like an ostream. Usage:
+//:   trace(depth, label) << ... << end();
+//: Don't forget the 'end()' to actually append to the trace.
+:(before "End Includes")
+// No brackets around the expansion so that it prints nothing if Trace_stream
+// isn't initialized.
+#define trace(...)  !Trace_stream ? cerr : Trace_stream->stream(__VA_ARGS__)
+
+:(before "End trace_stream Fields")
+// accumulator for current trace_line
+ostringstream* curr_stream;
+string curr_label;
+int curr_depth;
+// other stuff
+int collect_depth;  // avoid tracing lower levels for speed
+ofstream null_stream;  // never opened, so writes to it silently fail
+
+//: Some constants.
+:(before "struct trace_stream")  // include constants in all cleaved compilation units
 const int Max_depth = 9999;
-const int Error_depth = 0;  // definitely always print errors
+// Most important traces are printed to the screen by default
+const int Error_depth = 0;
 const int Warn_depth = 1;
+:(before "End Globals")
+int Hide_errors = false;  // if set, don't print errors or warnings to screen
+int Hide_warnings = false;  // if set, don't print warnings to screen
+:(before "End trace_stream Constructor")
+curr_stream = NULL;
+curr_depth = Max_depth;
+collect_depth = Max_depth;
+:(before "End Reset")
+Hide_errors = false;
+Hide_warnings = false;
+//: Never dump warnings in scenarios
+:(before "End Test Setup")
+Hide_warnings = true;
 
-struct trace_stream {
-  vector<trace_line> past_lines;
-  // accumulator for current line
-  ostringstream* curr_stream;
-  string curr_label;
-  int curr_depth;
-  int collect_depth;
-  ofstream null_stream;  // never opens a file, so writes silently fail
-  trace_stream() :curr_stream(NULL), curr_depth(Max_depth), collect_depth(Max_depth) {}
-  ~trace_stream() { if (curr_stream) delete curr_stream; }
-
-  ostream& stream(string label) {
-    return stream(Max_depth, label);
+:(before "struct trace_stream")
+struct trace_line {
+  string contents;
+  string label;
+  int depth;  // 0 is 'sea level'; positive integers are progressively 'deeper' and lower level
+  trace_line(string c, string l) {
+    contents = c;
+    label = l;
+    depth = 0;
   }
-
-  ostream& stream(int depth, string label) {
-    if (depth > collect_depth) return null_stream;
-    curr_stream = new ostringstream;
-    curr_label = label;
-    curr_depth = depth;
-    (*curr_stream) << std::hex;
-    return *curr_stream;
+  trace_line(string c, string l, int d) {
+    contents = c;
+    label = l;
+    depth = d;
   }
-
-  // be sure to call this before messing with curr_stream or curr_label
-  void newline();
-  // useful for debugging
-  string readable_contents(string label);  // empty label = show everything
 };
 
+//: Starting a new trace line.
+:(before "End trace_stream Methods")
+ostream& stream(string label) {
+  return stream(Max_depth, label);
+}
+
+ostream& stream(int depth, string label) {
+  if (depth > collect_depth) return null_stream;
+  curr_stream = new ostringstream;
+  curr_label = label;
+  curr_depth = depth;
+  (*curr_stream) << std::hex;  // printing addresses is the common case
+  return *curr_stream;
+}
+
+//: End of a trace line; append it to the trace.
+:(before "End Types")
+struct end {};
+:(code)
+ostream& operator<<(ostream& os, end /*unused*/) {
+  if (Trace_stream) Trace_stream->newline();
+  return os;
+}
+
+:(before "End trace_stream Methods")
+void newline();
 :(code)
 void trace_stream::newline() {
   if (!curr_stream) return;
   string curr_contents = curr_stream->str();
   if (!curr_contents.empty()) {
-    past_lines.push_back(trace_line(curr_depth, trim(curr_label), curr_contents));  // preserve indent in contents
+    past_lines.push_back(trace_line(curr_contents, trim(curr_label), curr_depth));  // preserve indent in contents
+    // maybe incrementally dump trace
+    trace_line& t = past_lines.back();
     if ((!Hide_errors && curr_depth == Error_depth)
-        || (!Hide_warnings && !Hide_errors && curr_depth == Warn_depth)
-        || Dump_trace
-        || (!Dump_label.empty() && curr_label == Dump_label))
-      cerr << curr_label << ": " << curr_contents << '\n';
+        || (!Hide_warnings && !Hide_errors && curr_depth == Warn_depth)) {
+      cerr       << std::setw(4) << t.depth << ' ' << t.label << ": " << t.contents << '\n';
+    }
+    // End trace Commit
   }
+
+  // clean up
   delete curr_stream;
   curr_stream = NULL;
   curr_label.clear();
   curr_depth = Max_depth;
 }
 
-string trace_stream::readable_contents(string label) {
-  ostringstream output;
-  label = trim(label);
-  for (vector<trace_line>::iterator p = past_lines.begin();  p != past_lines.end();  ++p)
-    if (label.empty() || label == p->label)
-      output << std::setw(4) << p->depth << ' ' << p->label << ": " << p->contents << '\n';
-  return output.str();
-}
+//:: == Initializing the trace in scenarios
 
-:(before "End Globals")
-trace_stream* Trace_stream = NULL;
-int Trace_errors = 0;  // used only when Trace_stream is NULL
+:(before "End Includes")
+#define START_TRACING_UNTIL_END_OF_SCOPE  lease_tracer leased_tracer;
+:(before "End Test Setup")
+START_TRACING_UNTIL_END_OF_SCOPE
 
-//: commandline flag to print trace incrementally to stderr
-:(before "End Globals")
-bool Flag_dump_trace = false;
-:(before "End Commandline Options(*arg)")
-else if (is_equal(*arg, "--trace")) {
-  Flag_dump_trace = true;
+//: Trace_stream is a resource, lease_tracer uses RAII to manage it.
+:(before "End Types")
+struct lease_tracer {
+  lease_tracer();
+  ~lease_tracer();
+};
+:(code)
+lease_tracer::lease_tracer() { Trace_stream = new trace_stream; }
+lease_tracer::~lease_tracer() {
+  delete Trace_stream;
+  Trace_stream = NULL;
 }
 
-:(before "End Globals")
-bool Hide_errors = false;  // if set, don't print even error trace lines to screen
-bool Hide_warnings = false;  // if set, don't print warnings to screen
-bool Dump_trace = false;  // if set, print trace lines to screen
-string Dump_label = "";  // if set, print trace lines matching a single label to screen
-:(before "End Reset")
-Hide_errors = false;
-Hide_warnings = false;
-Dump_trace = Flag_dump_trace;
-Dump_label = "";
-//: Never dump warnings in scenarios
-:(before "End Test Setup")
-Hide_warnings = true;
+//:: == Errors and warnings using traces
 
 :(before "End Includes")
-#define CLEAR_TRACE  delete Trace_stream, Trace_stream = new trace_stream;
-
-// Top-level helper. IMPORTANT: can't nest
-#define trace(...)  !Trace_stream ? cerr /*print nothing*/ : Trace_stream->stream(__VA_ARGS__)
-
-// Just for debugging; 'git log' should never show any calls to 'dbg'.
-#define dbg trace(0, "a")
-#define DUMP(label)  if (Trace_stream) cerr << Trace_stream->readable_contents(label);
-
-// Errors and warnings are special layers.
 #define raise  (!Trace_stream ? (++Trace_errors,cerr) /*do print*/ : Trace_stream->stream(Error_depth, "error"))
 #define warn (!Trace_stream ? (++Trace_errors,cerr) /*do print*/ : Trace_stream->stream(Warn_depth, "warn"))
-// If we aren't yet sure how to deal with some corner case, use assert_for_now
-// to indicate that it isn't an inviolable invariant.
-#define assert_for_now assert
-#define raise_for_now raise
 
-// Inside tests, fail any tests that displayed (unexpected) errors.
-// Expected errors in tests should always be hidden and silently checked for.
+:(before "End Globals")
+int Trace_errors = 0;  // used only when Trace_stream is NULL
+
+// Fail scenarios that displayed (unexpected) errors.
+// Expected errors should always be hidden and silently checked for.
 :(before "End Test Teardown")
 if (Passed && !Hide_errors && trace_contains_errors()) {
   Passed = false;
@@ -205,41 +231,33 @@ bool trace_contains_errors() {
   return Trace_errors > 0 || trace_count("error") > 0;
 }
 
-:(before "End Types")
-struct end {};
-:(code)
-ostream& operator<<(ostream& os, end /*unused*/) {
-  if (Trace_stream) Trace_stream->newline();
-  return os;
-}
-
-// Trace_stream is a resource, lease_tracer uses RAII to manage it.
-:(before "End Types")
-struct lease_tracer {
-  lease_tracer();
-  ~lease_tracer();
-};
-:(code)
-lease_tracer::lease_tracer() { Trace_stream = new trace_stream; }
-lease_tracer::~lease_tracer() {
-  delete Trace_stream, Trace_stream = NULL;
-}
 :(before "End Includes")
-#define START_TRACING_UNTIL_END_OF_SCOPE  lease_tracer leased_tracer;
-:(before "End Test Setup")
-START_TRACING_UNTIL_END_OF_SCOPE
+// If we aren't yet sure how to deal with some corner case, use assert_for_now
+// to indicate that it isn't an inviolable invariant.
+#define assert_for_now assert
+#define raise_for_now raise
+
+//:: == Other assertions on traces
+//: Primitives:
+//:   - CHECK_TRACE_CONTENTS(lines)
+//:     Assert that the trace contains the given lines (separated by newlines)
+//:     in order. There can be other intervening lines between them.
+//:   - CHECK_TRACE_DOESNT_CONTAIN(line)
+//:   - CHECK_TRACE_DOESNT_CONTAIN(label, contents)
+//:     Assert that the trace doesn't contain the given (single) line.
+//:   - CHECK_TRACE_COUNT(label, count)
+//:     Assert that the trace contains exactly 'count' lines with the given
+//:     'label'.
+//:   - CHECK_TRACE_CONTAINS_ERRORS()
+//:   - CHECK_TRACE_DOESNT_CONTAIN_ERRORS()
+//:   - trace_count_prefix(label, prefix)
+//:     Count the number of trace lines with the given 'label' that start with
+//:     the given 'prefix'.
 
 :(before "End Includes")
 #define CHECK_TRACE_CONTENTS(...)  check_trace_contents(__FUNCTION__, __FILE__, __LINE__, __VA_ARGS__)
 
-#define CHECK_TRACE_CONTAINS_ERRORS()  CHECK(trace_contains_errors())
-#define CHECK_TRACE_DOESNT_CONTAIN_ERRORS() \
-  if (Passed && trace_contains_errors()) { \
-    cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): unexpected errors\n"; \
-    DUMP("error"); \
-    Passed = false; \
-    return; \
-  }
+#define CHECK_TRACE_DOESNT_CONTAIN(...)  CHECK(trace_doesnt_contain(__VA_ARGS__))
 
 #define CHECK_TRACE_COUNT(label, count) \
   if (Passed && trace_count(label) != (count)) { \
@@ -250,7 +268,17 @@ START_TRACING_UNTIL_END_OF_SCOPE
     return;  /* Currently we stop at the very first failure. */ \
   }
 
-#define CHECK_TRACE_DOESNT_CONTAIN(...)  CHECK(trace_doesnt_contain(__VA_ARGS__))
+#define CHECK_TRACE_CONTAINS_ERRORS()  CHECK(trace_contains_errors())
+#define CHECK_TRACE_DOESNT_CONTAIN_ERRORS() \
+  if (Passed && trace_contains_errors()) { \
+    cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): unexpected errors\n"; \
+    DUMP("error"); \
+    Passed = false; \
+    return; \
+  }
+
+// Allow scenarios to ignore trace lines generated during setup.
+#define CLEAR_TRACE  delete Trace_stream, Trace_stream = new trace_stream
 
 :(code)
 bool check_trace_contents(string FUNCTION, string FILE, int LINE, string expected) {
@@ -285,25 +313,13 @@ bool check_trace_contents(string FUNCTION, string FILE, int LINE, string expecte
   return false;
 }
 
-void split_label_contents(const string& s, string* label, string* contents) {
-  static const string delim(": ");
-  size_t pos = s.find(delim);
-  if (pos == string::npos) {
-    *label = "";
-    *contents = trim(s);
-  }
-  else {
-    *label = trim(s.substr(0, pos));
-    *contents = trim(s.substr(pos+SIZE(delim)));
-  }
-}
-
-bool line_exists_anywhere(const string& label, const string& contents) {
-  for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin();  p != Trace_stream->past_lines.end();  ++p) {
-    if (label != p->label) continue;
-    if (contents == trim(p->contents)) return true;
+bool trace_doesnt_contain(string expected) {
+  vector<string> tmp = split_first(expected, ": ");
+  if (SIZE(tmp) == 1) {
+    raise << expected << ": missing label or contents in trace line\n" << end();
+    assert(false);
   }
-  return false;
+  return trace_count(tmp.at(0), tmp.at(1)) == 0;
 }
 
 int trace_count(string label) {
@@ -334,17 +350,25 @@ int trace_count_prefix(string label, string prefix) {
   return result;
 }
 
-bool trace_doesnt_contain(string label, string line) {
-  return trace_count(label, line) == 0;
+void split_label_contents(const string& s, string* label, string* contents) {
+  static const string delim(": ");
+  size_t pos = s.find(delim);
+  if (pos == string::npos) {
+    *label = "";
+    *contents = trim(s);
+  }
+  else {
+    *label = trim(s.substr(0, pos));
+    *contents = trim(s.substr(pos+SIZE(delim)));
+  }
 }
 
-bool trace_doesnt_contain(string expected) {
-  vector<string> tmp = split_first(expected, ": ");
-  if (SIZE(tmp) == 1) {
-    raise << expected << ": missing label or contents in trace line\n" << end();
-    assert(false);
+bool line_exists_anywhere(const string& label, const string& contents) {
+  for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin();  p != Trace_stream->past_lines.end();  ++p) {
+    if (label != p->label) continue;
+    if (contents == trim(p->contents)) return true;
   }
-  return trace_doesnt_contain(tmp.at(0), tmp.at(1));
+  return false;
 }
 
 vector<string> split(string s, string delim) {
@@ -371,6 +395,53 @@ vector<string> split_first(string s, string delim) {
   return result;
 }
 
+//:: == Helpers for debugging using traces
+
+:(before "End Includes")
+// To debug why a scenario is failing, dump its trace using '?'.
+#define DUMP(label)  if (Trace_stream) cerr << Trace_stream->readable_contents(label);
+
+// To add temporary prints to the trace, use 'dbg'.
+// `git log` should never show any calls to 'dbg'.
+#define dbg trace(0, "a")
+
+//: Dump the entire trace to file where it can be browsed offline.
+//: Dump the trace as it happens; that way you get something even if the
+//: program crashes.
+
+:(before "End Globals")
+ofstream Trace_file;
+:(before "End Commandline Options(*arg)")
+else if (is_equal(*arg, "--trace")) {
+  cerr << "saving trace to 'last_run'\n";
+  Trace_file.open("last_run");
+}
+:(before "End trace Commit")
+if (Trace_file) {
+  Trace_file << std::setw(4) << t.depth << ' ' << t.label << ": " << t.contents << '\n';
+}
+:(before "End One-time Setup")
+atexit(cleanup_main);
+:(code)
+void cleanup_main() {
+  if (Trace_file) Trace_file.close();
+  // End cleanup_main
+}
+
+:(before "End trace_stream Methods")
+string readable_contents(string label) {
+  string trim(const string& s);  // prototype
+  ostringstream output;
+  label = trim(label);
+  for (vector<trace_line>::iterator p = past_lines.begin();  p != past_lines.end();  ++p)
+    if (label.empty() || label == p->label)
+      output << std::setw(4) << p->depth << ' ' << p->label << ": " << p->contents << '\n';
+  return output.str();
+}
+
+//: Miscellaneous helpers.
+
+:(code)
 string trim(const string& s) {
   string::const_iterator first = s.begin();
   while (first != s.end() && isspace(*first))
diff --git a/subx/010---vm.cc b/subx/010---vm.cc
index 0d30f3cd..4c346a3d 100644
--- a/subx/010---vm.cc
+++ b/subx/010---vm.cc
@@ -92,7 +92,7 @@ SF = ZF = OF = false;
   /* arg1 and arg2 must be signed */ \
   int64_t tmp = arg1 op arg2; \
   arg1 = arg1 op arg2; \
-  trace(90, "run") << "storing 0x" << HEXWORD << arg1 << end(); \
+  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << arg1 << end(); \
   SF = (arg1 < 0); \
   ZF = (arg1 == 0); \
   OF = (arg1 != tmp); \
@@ -103,7 +103,7 @@ SF = ZF = OF = false;
 #define BINARY_BITWISE_OP(op, arg1, arg2) { \
   /* arg1 and arg2 must be unsigned */ \
   arg1 = arg1 op arg2; \
-  trace(90, "run") << "storing 0x" << HEXWORD << arg1 << end(); \
+  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << arg1 << end(); \
   SF = (arg1 >> 31); \
   ZF = (arg1 == 0); \
   OF = false; \
@@ -296,13 +296,13 @@ inline bool already_allocated(uint32_t addr) {
 void run_one_instruction() {
   uint8_t op=0, op2=0, op3=0;
   // Run One Instruction
-  if (Dump_trace) {
+  if (Trace_file) {
     dump_registers();
     // End Dump Info for Instruction
   }
-  trace(90, "run") << "inst: 0x" << HEXWORD << EIP << end();
+  uint32_t inst_start_address = EIP;
   op = next();
-  trace(90, "run") << "opcode: " << HEXBYTE << NUM(op) << end();
+  trace(Callstack_depth, "run") << "0x" << HEXWORD << inst_start_address << " opcode: " << HEXBYTE << NUM(op) << (op == 0xe8 ? "/call" : "") << end();
   switch (op) {
   case 0xf4:  // hlt
     EIP = End_of_program;
@@ -372,7 +372,7 @@ void dump_registers() {
     out << "  " << i << ": " << std::hex << std::setw(8) << std::setfill('_') << Reg[i].u;
   }
   out << " -- SF: " << SF << "; ZF: " << ZF << "; OF: " << OF;
-  trace(90, "run") << out.str() << end();
+  trace(Callstack_depth+1, "run") << out.str() << end();
 }
 
 //: start tracking supported opcodes
@@ -409,6 +409,24 @@ if (key == "opcodes") {
 :(before "End Help Contents")
 cerr << "  opcodes\n";
 
+//: Helpers for managing trace depths
+//:
+//: We're going to use trace depths primarily to segment code running at
+//: different frames of the call stack. This will make it easy for the trace
+//: browser to collapse over entire calls.
+//:
+//: Errors will be at depth 0.
+//: Warnings will be at depth 1.
+//: SubX instructions will occupy depth 2 and up to Max_depth, organized by
+//: stack frames. Each instruction's internal details will be one level deeper
+//: than its 'main' depth. So 'call' instruction details will be at the same
+//: depth as the instructions of the function it calls.
+:(before "End Globals")
+extern const int Initial_callstack_depth = 2;
+int Callstack_depth = Initial_callstack_depth;
+:(before "End Reset")
+Callstack_depth = Initial_callstack_depth;
+
 :(before "End Includes")
 #include <iomanip>
 #define HEXBYTE  std::hex << std::setw(2) << std::setfill('0')
diff --git a/subx/011run.cc b/subx/011run.cc
index 45a6699e..90af649b 100644
--- a/subx/011run.cc
+++ b/subx/011run.cc
@@ -127,7 +127,6 @@ struct word {
 :(code)
 void parse(istream& fin, program& out) {
   vector<line> l;
-  trace(99, "parse") << "begin" << end();
   while (has_data(fin)) {
     string line_data;
     line curr;
@@ -151,7 +150,7 @@ void parse(istream& fin, program& out) {
           s.start = parse_int(segment_title);
           sanity_check_program_segment(out, s.start);
           if (trace_contains_errors()) continue;
-          trace(99, "parse") << "new segment from 0x" << HEXWORD << s.start << end();
+          trace(3, "parse") << "new segment from 0x" << HEXWORD << s.start << end();
           out.segments.push_back(s);
         }
         // End Segment Parsing Special-cases(segment_title)
@@ -226,16 +225,13 @@ vector<transform_fn> Transform;
 
 :(code)
 void transform(program& p) {
-  trace(99, "transform") << "begin" << end();
   for (int t = 0;  t < SIZE(Transform);  ++t)
     (*Transform.at(t))(p);
-  trace(99, "transform") << "done" << end();
 }
 
 //:: load
 
 void load(const program& p) {
-  trace(99, "load") << "begin" << end();
   if (p.segments.empty()) {
     raise << "no code to run\n" << end();
     return;
@@ -265,7 +261,6 @@ void load(const program& p) {
   }
   EIP = p.segments.at(0).start;
   // End Initialize EIP
-  trace(99, "load") << "done" << end();
 }
 
 uint8_t hex_byte(const string& s) {
@@ -331,7 +326,7 @@ put_new(Name, "05", "add imm32 to EAX (add)");
 :(before "End Single-Byte Opcodes")
 case 0x05: {  // add imm32 to EAX
   int32_t arg2 = next32();
-  trace(90, "run") << "add imm32 0x" << HEXWORD << arg2 << " to reg EAX" << end();
+  trace(Callstack_depth+1, "run") << "add imm32 0x" << HEXWORD << arg2 << " to reg EAX" << end();
   BINARY_ARITHMETIC_OP(+, Reg[EAX].i, arg2);
   break;
 }
diff --git a/subx/012elf.cc b/subx/012elf.cc
index 5220d18d..c9bbfc70 100644
--- a/subx/012elf.cc
+++ b/subx/012elf.cc
@@ -6,6 +6,7 @@
 assert(argc > 1);
 if (is_equal(argv[1], "run")) {
   START_TRACING_UNTIL_END_OF_SCOPE;
+  trace(2, "run") << "=== Starting to run" << end();
   assert(argc > 2);
   reset();
   cerr << std::hex;
@@ -88,8 +89,8 @@ void load_elf_contents(uint8_t* elf_contents, size_t size, int argc, char* argv[
 
 void push(uint32_t val) {
   Reg[ESP].u -= 4;
-  trace(90, "run") << "decrementing ESP to 0x" << HEXWORD << Reg[ESP].u << end();
-  trace(90, "run") << "pushing value 0x" << HEXWORD << val << end();
+  trace(Callstack_depth+1, "run") << "decrementing ESP to 0x" << HEXWORD << Reg[ESP].u << end();
+  trace(Callstack_depth+1, "run") << "pushing value 0x" << HEXWORD << val << end();
   write_mem_u32(Reg[ESP].u, val);
 }
 
@@ -142,12 +143,12 @@ const int ARGV_DATA_SEGMENT = 0x0c000000;
 :(code)
 void dump_stack() {
   ostringstream out;
-  trace(91, "run") << "stack:" << end();
+  trace(Callstack_depth+1, "run") << "stack:" << end();
   for (uint32_t a = AFTER_STACK-4;  a > Reg[ESP].u;  a -= 4)
-    trace(91, "run") << "  0x" << HEXWORD << a << " => 0x" << HEXWORD << read_mem_u32(a) << end();
-  trace(91, "run") << "  0x" << HEXWORD << Reg[ESP].u << " => 0x" << HEXWORD << read_mem_u32(Reg[ESP].u) << "  <=== ESP" << end();
+    trace(Callstack_depth+1, "run") << "  0x" << HEXWORD << a << " => 0x" << HEXWORD << read_mem_u32(a) << end();
+  trace(Callstack_depth+1, "run") << "  0x" << HEXWORD << Reg[ESP].u << " => 0x" << HEXWORD << read_mem_u32(Reg[ESP].u) << "  <=== ESP" << end();
   for (uint32_t a = Reg[ESP].u-4;  a > Reg[ESP].u-40;  a -= 4)
-    trace(91, "run") << "  0x" << HEXWORD << a << " => 0x" << HEXWORD << read_mem_u32(a) << end();
+    trace(Callstack_depth+1, "run") << "  0x" << HEXWORD << a << " => 0x" << HEXWORD << read_mem_u32(a) << end();
 }
 
 inline uint32_t u32_in(uint8_t* p) {
diff --git a/subx/013direct_addressing.cc b/subx/013direct_addressing.cc
index b7146835..40f9f52e 100644
--- a/subx/013direct_addressing.cc
+++ b/subx/013direct_addressing.cc
@@ -18,7 +18,7 @@ put_new(Name, "01", "add r32 to rm32 (add)");
 case 0x01: {  // add r32 to r/m32
   uint8_t modrm = next();
   uint8_t arg2 = (modrm>>3)&0x7;
-  trace(90, "run") << "add " << rname(arg2) << " to r/m32" << end();
+  trace(Callstack_depth+1, "run") << "add " << rname(arg2) << " to r/m32" << end();
   int32_t* arg1 = effective_address(modrm);
   BINARY_ARITHMETIC_OP(+, *arg1, Reg[arg2].i);
   break;
@@ -34,7 +34,7 @@ int32_t* effective_address(uint8_t modrm) {
   const uint8_t rm = modrm & 0x7;
   if (mod == 3) {
     // mod 3 is just register direct addressing
-    trace(90, "run") << "r/m32 is " << rname(rm) << end();
+    trace(Callstack_depth+1, "run") << "r/m32 is " << rname(rm) << end();
     return &Reg[rm].i;
   }
   return mem_addr_i32(effective_address_number(modrm));
@@ -94,7 +94,7 @@ put_new(Name, "29", "subtract r32 from rm32 (sub)");
 case 0x29: {  // subtract r32 from r/m32
   const uint8_t modrm = next();
   const uint8_t arg2 = (modrm>>3)&0x7;
-  trace(90, "run") << "subtract " << rname(arg2) << " from r/m32" << end();
+  trace(Callstack_depth+1, "run") << "subtract " << rname(arg2) << " from r/m32" << end();
   int32_t* arg1 = effective_address(modrm);
   BINARY_ARITHMETIC_OP(-, *arg1, Reg[arg2].i);
   break;
@@ -120,17 +120,17 @@ put_new(Name, "f7", "negate/multiply rm32 (with EAX if necessary) depending on s
 :(before "End Single-Byte Opcodes")
 case 0xf7: {
   const uint8_t modrm = next();
-  trace(90, "run") << "operate on r/m32" << end();
+  trace(Callstack_depth+1, "run") << "operate on r/m32" << end();
   int32_t* arg1 = effective_address(modrm);
   const uint8_t subop = (modrm>>3)&0x7;  // middle 3 'reg opcode' bits
   switch (subop) {
   case 4: {  // mul unsigned EAX by r/m32
-    trace(90, "run") << "subop: multiply EAX by r/m32" << end();
+    trace(Callstack_depth+1, "run") << "subop: multiply EAX by r/m32" << end();
     const uint64_t result = Reg[EAX].u * static_cast<uint32_t>(*arg1);
     Reg[EAX].u = result & 0xffffffff;
     Reg[EDX].u = result >> 32;
     OF = (Reg[EDX].u != 0);
-    trace(90, "run") << "storing 0x" << HEXWORD << Reg[EAX].u << end();
+    trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << Reg[EAX].u << end();
     break;
   }
   // End Op f7 Subops
@@ -161,7 +161,7 @@ put_new(Name_0f, "af", "multiply rm32 into r32 (imul)");
 case 0xaf: {  // multiply r32 into r/m32
   const uint8_t modrm = next();
   const uint8_t arg2 = (modrm>>3)&0x7;
-  trace(90, "run") << "multiply r/m32 into " << rname(arg2) << end();
+  trace(Callstack_depth+1, "run") << "multiply r/m32 into " << rname(arg2) << end();
   const int32_t* arg1 = effective_address(modrm);
   BINARY_ARITHMETIC_OP(*, Reg[arg2].i, *arg1);
   break;
@@ -182,17 +182,17 @@ case 0xaf: {  // multiply r32 into r/m32
 
 :(before "End Op f7 Subops")
 case 3: {  // negate r/m32
-  trace(90, "run") << "subop: negate" << end();
+  trace(Callstack_depth+1, "run") << "subop: negate" << end();
   // one case that can overflow
   if (static_cast<uint32_t>(*arg1) == 0x80000000) {
-    trace(90, "run") << "overflow" << end();
+    trace(Callstack_depth+1, "run") << "overflow" << end();
     SF = true;
     ZF = false;
     OF = true;
     break;
   }
   *arg1 = -(*arg1);
-  trace(90, "run") << "storing 0x" << HEXWORD << *arg1 << end();
+  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *arg1 << end();
   SF = (*arg1 >> 31);
   ZF = (*arg1 == 0);
   OF = false;
@@ -230,12 +230,12 @@ put_new(Name, "d3", "shift rm32 by CL bits depending on subop (sal/sar/shl/shr)"
 :(before "End Single-Byte Opcodes")
 case 0xd3: {
   const uint8_t modrm = next();
-  trace(90, "run") << "operate on r/m32" << end();
+  trace(Callstack_depth+1, "run") << "operate on r/m32" << end();
   int32_t* arg1 = effective_address(modrm);
   const uint8_t subop = (modrm>>3)&0x7;  // middle 3 'reg opcode' bits
   switch (subop) {
   case 4: {  // shift left r/m32 by CL
-    trace(90, "run") << "subop: shift left by CL bits" << end();
+    trace(Callstack_depth+1, "run") << "subop: shift left by CL bits" << end();
     uint8_t count = Reg[ECX].u & 0x1f;
     // OF is only defined if count is 1
     if (count == 1) {
@@ -246,7 +246,7 @@ case 0xd3: {
     *arg1 = (*arg1 << count);
     ZF = (*arg1 == 0);
     SF = (*arg1 < 0);
-    trace(90, "run") << "storing 0x" << HEXWORD << *arg1 << end();
+    trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *arg1 << end();
     break;
   }
   // End Op d3 Subops
@@ -273,14 +273,14 @@ case 0xd3: {
 
 :(before "End Op d3 Subops")
 case 7: {  // shift right r/m32 by CL, preserving sign
-  trace(90, "run") << "subop: shift right by CL bits, while preserving sign" << end();
+  trace(Callstack_depth+1, "run") << "subop: shift right by CL bits, while preserving sign" << end();
   uint8_t count = Reg[ECX].u & 0x1f;
   *arg1 = (*arg1 >> count);
   ZF = (*arg1 == 0);
   SF = (*arg1 < 0);
   // OF is only defined if count is 1
   if (count == 1) OF = false;
-  trace(90, "run") << "storing 0x" << HEXWORD << *arg1 << end();
+  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *arg1 << end();
   break;
 }
 
@@ -326,7 +326,7 @@ case 7: {  // shift right r/m32 by CL, preserving sign
 
 :(before "End Op d3 Subops")
 case 5: {  // shift right r/m32 by CL, preserving sign
-  trace(90, "run") << "subop: shift right by CL bits, while padding zeroes" << end();
+  trace(Callstack_depth+1, "run") << "subop: shift right by CL bits, while padding zeroes" << end();
   uint8_t count = Reg[ECX].u & 0x1f;
   // OF is only defined if count is 1
   if (count == 1) {
@@ -339,7 +339,7 @@ case 5: {  // shift right r/m32 by CL, preserving sign
   ZF = (*uarg1 == 0);
   // result is always positive by definition
   SF = false;
-  trace(90, "run") << "storing 0x" << HEXWORD << *arg1 << end();
+  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *arg1 << end();
   break;
 }
 
@@ -388,7 +388,7 @@ put_new(Name, "21", "rm32 = bitwise AND of r32 with rm32 (and)");
 case 0x21: {  // and r32 with r/m32
   const uint8_t modrm = next();
   const uint8_t arg2 = (modrm>>3)&0x7;
-  trace(90, "run") << "and " << rname(arg2) << " with r/m32" << end();
+  trace(Callstack_depth+1, "run") << "and " << rname(arg2) << " with r/m32" << end();
   int32_t* arg1 = effective_address(modrm);
   BINARY_BITWISE_OP(&, *arg1, Reg[arg2].u);
   break;
@@ -414,7 +414,7 @@ put_new(Name, "09", "rm32 = bitwise OR of r32 with rm32 (or)");
 case 0x09: {  // or r32 with r/m32
   const uint8_t modrm = next();
   const uint8_t arg2 = (modrm>>3)&0x7;
-  trace(90, "run") << "or " << rname(arg2) << " with r/m32" << end();
+  trace(Callstack_depth+1, "run") << "or " << rname(arg2) << " with r/m32" << end();
   int32_t* arg1 = effective_address(modrm);
   BINARY_BITWISE_OP(|, *arg1, Reg[arg2].u);
   break;
@@ -440,7 +440,7 @@ put_new(Name, "31", "rm32 = bitwise XOR of r32 with rm32 (xor)");
 case 0x31: {  // xor r32 with r/m32
   const uint8_t modrm = next();
   const uint8_t arg2 = (modrm>>3)&0x7;
-  trace(90, "run") << "xor " << rname(arg2) << " with r/m32" << end();
+  trace(Callstack_depth+1, "run") << "xor " << rname(arg2) << " with r/m32" << end();
   int32_t* arg1 = effective_address(modrm);
   BINARY_BITWISE_OP(^, *arg1, Reg[arg2].u);
   break;
@@ -461,9 +461,9 @@ case 0x31: {  // xor r32 with r/m32
 
 :(before "End Op f7 Subops")
 case 2: {  // not r/m32
-  trace(90, "run") << "subop: not" << end();
+  trace(Callstack_depth+1, "run") << "subop: not" << end();
   *arg1 = ~(*arg1);
-  trace(90, "run") << "storing 0x" << HEXWORD << *arg1 << end();
+  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *arg1 << end();
   SF = (*arg1 >> 31);
   ZF = (*arg1 == 0);
   OF = false;
@@ -490,7 +490,7 @@ put_new(Name, "39", "compare: set SF if rm32 < r32 (cmp)");
 case 0x39: {  // set SF if r/m32 < r32
   const uint8_t modrm = next();
   const uint8_t reg2 = (modrm>>3)&0x7;
-  trace(90, "run") << "compare " << rname(reg2) << " with r/m32" << end();
+  trace(Callstack_depth+1, "run") << "compare " << rname(reg2) << " with r/m32" << end();
   const int32_t* arg1 = effective_address(modrm);
   const int32_t arg2 = Reg[reg2].i;
   const int32_t tmp1 = *arg1 - arg2;
@@ -498,7 +498,7 @@ case 0x39: {  // set SF if r/m32 < r32
   ZF = (tmp1 == 0);
   const int64_t tmp2 = *arg1 - arg2;
   OF = (tmp1 != tmp2);
-  trace(90, "run") << "SF=" << SF << "; ZF=" << ZF << "; OF=" << OF << end();
+  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; OF=" << OF << end();
   break;
 }
 
@@ -543,10 +543,10 @@ put_new(Name, "89", "copy r32 to rm32 (mov)");
 case 0x89: {  // copy r32 to r/m32
   const uint8_t modrm = next();
   const uint8_t rsrc = (modrm>>3)&0x7;
-  trace(90, "run") << "copy " << rname(rsrc) << " to r/m32" << end();
+  trace(Callstack_depth+1, "run") << "copy " << rname(rsrc) << " to r/m32" << end();
   int32_t* dest = effective_address(modrm);
   *dest = Reg[rsrc].i;
-  trace(90, "run") << "storing 0x" << HEXWORD << *dest << end();
+  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *dest << end();
   break;
 }
 
@@ -571,13 +571,13 @@ put_new(Name, "87", "swap the contents of r32 and rm32 (xchg)");
 case 0x87: {  // exchange r32 with r/m32
   const uint8_t modrm = next();
   const uint8_t reg2 = (modrm>>3)&0x7;
-  trace(90, "run") << "exchange " << rname(reg2) << " with r/m32" << end();
+  trace(Callstack_depth+1, "run") << "exchange " << rname(reg2) << " with r/m32" << end();
   int32_t* arg1 = effective_address(modrm);
   const int32_t tmp = *arg1;
   *arg1 = Reg[reg2].i;
   Reg[reg2].i = tmp;
-  trace(90, "run") << "storing 0x" << HEXWORD << *arg1 << " in r/m32" << end();
-  trace(90, "run") << "storing 0x" << HEXWORD << Reg[reg2].i << " in " << rname(reg2) << end();
+  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *arg1 << " in r/m32" << end();
+  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << Reg[reg2].i << " in " << rname(reg2) << end();
   break;
 }
 
@@ -611,9 +611,9 @@ case 0x45:
 case 0x46:
 case 0x47: {  // increment r32
   const uint8_t reg = op & 0x7;
-  trace(90, "run") << "increment " << rname(reg) << end();
+  trace(Callstack_depth+1, "run") << "increment " << rname(reg) << end();
   ++Reg[reg].u;
-  trace(90, "run") << "storing value 0x" << HEXWORD << Reg[reg].u << end();
+  trace(Callstack_depth+1, "run") << "storing value 0x" << HEXWORD << Reg[reg].u << end();
   break;
 }
 
@@ -636,10 +636,10 @@ case 0xff: {
   const uint8_t subop = (modrm>>3)&0x7;  // middle 3 'reg opcode' bits
   switch (subop) {
     case 0: {  // increment r/m32
-      trace(90, "run") << "increment r/m32" << end();
+      trace(Callstack_depth+1, "run") << "increment r/m32" << end();
       int32_t* arg = effective_address(modrm);
       ++*arg;
-      trace(90, "run") << "storing value 0x" << HEXWORD << *arg << end();
+      trace(Callstack_depth+1, "run") << "storing value 0x" << HEXWORD << *arg << end();
       break;
     }
     default:
@@ -681,9 +681,9 @@ case 0x4d:
 case 0x4e:
 case 0x4f: {  // decrement r32
   const uint8_t reg = op & 0x7;
-  trace(90, "run") << "decrement " << rname(reg) << end();
+  trace(Callstack_depth+1, "run") << "decrement " << rname(reg) << end();
   --Reg[reg].u;
-  trace(90, "run") << "storing value 0x" << HEXWORD << Reg[reg].u << end();
+  trace(Callstack_depth+1, "run") << "storing value 0x" << HEXWORD << Reg[reg].u << end();
   break;
 }
 
@@ -699,10 +699,10 @@ case 0x4f: {  // decrement r32
 
 :(before "End Op ff Subops")
 case 1: {  // decrement r/m32
-  trace(90, "run") << "decrement r/m32" << end();
+  trace(Callstack_depth+1, "run") << "decrement r/m32" << end();
   int32_t* arg = effective_address(modrm);
   --*arg;
-  trace(90, "run") << "storing value 0x" << HEXWORD << *arg << end();
+  trace(Callstack_depth+1, "run") << "storing value 0x" << HEXWORD << *arg << end();
   break;
 }
 
@@ -738,7 +738,7 @@ case 0x55:
 case 0x56:
 case 0x57: {  // push r32 to stack
   uint8_t reg = op & 0x7;
-  trace(90, "run") << "push " << rname(reg) << end();
+  trace(Callstack_depth+1, "run") << "push " << rname(reg) << end();
 //?   cerr << "push: " << NUM(reg) << ": " << Reg[reg].u << " => " << Reg[ESP].u << '\n';
   push(Reg[reg].u);
   break;
@@ -779,7 +779,7 @@ case 0x5d:
 case 0x5e:
 case 0x5f: {  // pop stack into r32
   const uint8_t reg = op & 0x7;
-  trace(90, "run") << "pop into " << rname(reg) << end();
+  trace(Callstack_depth+1, "run") << "pop into " << rname(reg) << end();
 //?   cerr << "pop from " << Reg[ESP].u << '\n';
   Reg[reg].u = pop();
 //?   cerr << "=> " << NUM(reg) << ": " << Reg[reg].u << '\n';
@@ -788,8 +788,8 @@ case 0x5f: {  // pop stack into r32
 :(code)
 uint32_t pop() {
   const uint32_t result = read_mem_u32(Reg[ESP].u);
-  trace(90, "run") << "popping value 0x" << HEXWORD << result << end();
+  trace(Callstack_depth+1, "run") << "popping value 0x" << HEXWORD << result << end();
   Reg[ESP].u += 4;
-  trace(90, "run") << "incrementing ESP to 0x" << HEXWORD << Reg[ESP].u << end();
+  trace(Callstack_depth+1, "run") << "incrementing ESP to 0x" << HEXWORD << Reg[ESP].u << end();
   return result;
 }
diff --git a/subx/014indirect_addressing.cc b/subx/014indirect_addressing.cc
index cbd9afbc..448f4231 100644
--- a/subx/014indirect_addressing.cc
+++ b/subx/014indirect_addressing.cc
@@ -18,7 +18,7 @@
 case 0:  // indirect addressing
   switch (rm) {
   default:  // address in register
-    trace(90, "run") << "effective address is 0x" << HEXWORD << Reg[rm].u << " (" << rname(rm) << ")" << end();
+    trace(Callstack_depth+1, "run") << "effective address is 0x" << HEXWORD << Reg[rm].u << " (" << rname(rm) << ")" << end();
     addr = Reg[rm].u;
     break;
   // End Mod 0 Special-cases(addr)
@@ -47,7 +47,7 @@ put_new(Name, "03", "add rm32 to r32 (add)");
 case 0x03: {  // add r/m32 to r32
   const uint8_t modrm = next();
   const uint8_t arg1 = (modrm>>3)&0x7;
-  trace(90, "run") << "add r/m32 to " << rname(arg1) << end();
+  trace(Callstack_depth+1, "run") << "add r/m32 to " << rname(arg1) << end();
   const int32_t* arg2 = effective_address(modrm);
   BINARY_ARITHMETIC_OP(+, Reg[arg1].i, *arg2);
   break;
@@ -90,7 +90,7 @@ put_new(Name, "2b", "subtract rm32 from r32 (sub)");
 case 0x2b: {  // subtract r/m32 from r32
   const uint8_t modrm = next();
   const uint8_t arg1 = (modrm>>3)&0x7;
-  trace(90, "run") << "subtract r/m32 from " << rname(arg1) << end();
+  trace(Callstack_depth+1, "run") << "subtract r/m32 from " << rname(arg1) << end();
   const int32_t* arg2 = effective_address(modrm);
   BINARY_ARITHMETIC_OP(-, Reg[arg1].i, *arg2);
   break;
@@ -133,7 +133,7 @@ ff 00 00 00  # 0xff
 case 0x23: {  // and r/m32 with r32
   const uint8_t modrm = next();
   const uint8_t arg1 = (modrm>>3)&0x7;
-  trace(90, "run") << "and r/m32 with " << rname(arg1) << end();
+  trace(Callstack_depth+1, "run") << "and r/m32 with " << rname(arg1) << end();
   const int32_t* arg2 = effective_address(modrm);
   BINARY_BITWISE_OP(&, Reg[arg1].u, *arg2);
   break;
@@ -176,7 +176,7 @@ put_new(Name, "0b", "r32 = bitwise OR of r32 with rm32 (or)");
 case 0x0b: {  // or r/m32 with r32
   const uint8_t modrm = next();
   const uint8_t arg1 = (modrm>>3)&0x7;
-  trace(90, "run") << "or r/m32 with " << rname(arg1) << end();
+  trace(Callstack_depth+1, "run") << "or r/m32 with " << rname(arg1) << end();
   const int32_t* arg2 = effective_address(modrm);
   BINARY_BITWISE_OP(|, Reg[arg1].u, *arg2);
   break;
@@ -219,7 +219,7 @@ put_new(Name, "33", "r32 = bitwise XOR of r32 with rm32 (xor)");
 case 0x33: {  // xor r/m32 with r32
   const uint8_t modrm = next();
   const uint8_t arg1 = (modrm>>3)&0x7;
-  trace(90, "run") << "xor r/m32 with " << rname(arg1) << end();
+  trace(Callstack_depth+1, "run") << "xor r/m32 with " << rname(arg1) << end();
   const int32_t* arg2 = effective_address(modrm);
   BINARY_BITWISE_OP(|, Reg[arg1].u, *arg2);
   break;
@@ -303,7 +303,7 @@ put_new(Name, "3b", "compare: set SF if r32 < rm32 (cmp)");
 case 0x3b: {  // set SF if r32 < r/m32
   const uint8_t modrm = next();
   const uint8_t reg1 = (modrm>>3)&0x7;
-  trace(90, "run") << "compare r/m32 with " << rname(reg1) << end();
+  trace(Callstack_depth+1, "run") << "compare r/m32 with " << rname(reg1) << end();
   const int32_t arg1 = Reg[reg1].i;
   const int32_t* arg2 = effective_address(modrm);
   const int32_t tmp1 = arg1 - *arg2;
@@ -311,7 +311,7 @@ case 0x3b: {  // set SF if r32 < r/m32
   ZF = (tmp1 == 0);
   int64_t tmp2 = arg1 - *arg2;
   OF = (tmp1 != tmp2);
-  trace(90, "run") << "SF=" << SF << "; ZF=" << ZF << "; OF=" << OF << end();
+  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; OF=" << OF << end();
   break;
 }
 
@@ -375,10 +375,10 @@ af 00 00 00  # 0xaf
 case 0x8b: {  // copy r32 to r/m32
   const uint8_t modrm = next();
   const uint8_t rdest = (modrm>>3)&0x7;
-  trace(90, "run") << "copy r/m32 to " << rname(rdest) << end();
+  trace(Callstack_depth+1, "run") << "copy r/m32 to " << rname(rdest) << end();
   const int32_t* src = effective_address(modrm);
   Reg[rdest].i = *src;
-  trace(90, "run") << "storing 0x" << HEXWORD << *src << end();
+  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *src << end();
   break;
 }
 
@@ -394,19 +394,19 @@ case 0x8b: {  // copy r32 to r/m32
   05                              00 00 00 02
 == 0x2000  # data segment
 08 00 00 00  # 8
-+run: inst: 0x00000001
++run: 0x00000001 opcode: ff
 +run: jump to r/m32
 +run: effective address is 0x00002000 (EAX)
 +run: jumping to 0x00000008
-+run: inst: 0x00000008
--run: inst: 0x00000003
++run: 0x00000008 opcode: 05
+-run: 0x00000003 opcode: 05
 
 :(before "End Op ff Subops")
 case 4: {  // jump to r/m32
-  trace(90, "run") << "jump to r/m32" << end();
+  trace(Callstack_depth+1, "run") << "jump to r/m32" << end();
   const int32_t* arg2 = effective_address(modrm);
   EIP = *arg2;
-  trace(90, "run") << "jumping to 0x" << HEXWORD << EIP << end();
+  trace(Callstack_depth+1, "run") << "jumping to 0x" << HEXWORD << EIP << end();
   break;
 }
 
@@ -428,7 +428,7 @@ af 00 00 00  # 0xaf
 
 :(before "End Op ff Subops")
 case 6: {  // push r/m32 to stack
-  trace(90, "run") << "push r/m32" << end();
+  trace(Callstack_depth+1, "run") << "push r/m32" << end();
   const int32_t* val = effective_address(modrm);
   push(*val);
   break;
@@ -459,7 +459,7 @@ case 0x8f: {  // pop stack into r/m32
   const uint8_t subop = (modrm>>3)&0x7;
   switch (subop) {
     case 0: {
-      trace(90, "run") << "pop into r/m32" << end();
+      trace(Callstack_depth+1, "run") << "pop into r/m32" << end();
       int32_t* dest = effective_address(modrm);
       *dest = pop();
       break;
@@ -485,7 +485,7 @@ case 0x8f: {  // pop stack into r/m32
 :(before "End Mod 0 Special-cases(addr)")
 case 5:  // exception: mod 0b00 rm 0b101 => incoming disp32
   addr = next32();
-  trace(90, "run") << "effective address is 0x" << HEXWORD << addr << " (disp32)" << end();
+  trace(Callstack_depth+1, "run") << "effective address is 0x" << HEXWORD << addr << " (disp32)" << end();
   break;
 
 //:
@@ -509,13 +509,13 @@ case 1:  // indirect + disp8 addressing
   switch (rm) {
   default:
     addr = Reg[rm].u;
-    trace(90, "run") << "effective address is initially 0x" << HEXWORD << addr << " (" << rname(rm) << ")" << end();
+    trace(Callstack_depth+1, "run") << "effective address is initially 0x" << HEXWORD << addr << " (" << rname(rm) << ")" << end();
     break;
   // End Mod 1 Special-cases(addr)
   }
   if (addr > 0) {
     addr += static_cast<int8_t>(next());
-    trace(90, "run") << "effective address is 0x" << HEXWORD << addr << " (after adding disp8)" << end();
+    trace(Callstack_depth+1, "run") << "effective address is 0x" << HEXWORD << addr << " (after adding disp8)" << end();
   }
   break;
 
@@ -554,13 +554,13 @@ case 2:  // indirect + disp32 addressing
   switch (rm) {
   default:
     addr = Reg[rm].u;
-    trace(90, "run") << "effective address is initially 0x" << HEXWORD << addr << " (" << rname(rm) << ")" << end();
+    trace(Callstack_depth+1, "run") << "effective address is initially 0x" << HEXWORD << addr << " (" << rname(rm) << ")" << end();
     break;
   // End Mod 2 Special-cases(addr)
   }
   if (addr > 0) {
     addr += next32();
-    trace(90, "run") << "effective address is 0x" << HEXWORD << addr << " (after adding disp32)" << end();
+    trace(Callstack_depth+1, "run") << "effective address is 0x" << HEXWORD << addr << " (after adding disp32)" << end();
   }
   break;
 
@@ -596,7 +596,7 @@ put_new(Name, "8d", "copy address in rm32 into r32 (lea)");
 case 0x8d: {  // copy address of m32 to r32
   const uint8_t modrm = next();
   const uint8_t arg1 = (modrm>>3)&0x7;
-  trace(90, "run") << "copy address into " << rname(arg1) << end();
+  trace(Callstack_depth+1, "run") << "copy address into " << rname(arg1) << end();
   Reg[arg1].u = effective_address_number(modrm);
   break;
 }
diff --git a/subx/015immediate_addressing.cc b/subx/015immediate_addressing.cc
index 55b4fa37..93dd699b 100644
--- a/subx/015immediate_addressing.cc
+++ b/subx/015immediate_addressing.cc
@@ -17,15 +17,15 @@ put_new(Name, "81", "combine rm32 with imm32 based on subop (add/sub/and/or/xor/
 
 :(before "End Single-Byte Opcodes")
 case 0x81: {  // combine imm32 with r/m32
-  trace(90, "run") << "combine imm32 with r/m32" << end();
+  trace(Callstack_depth+1, "run") << "combine imm32 with r/m32" << end();
   const uint8_t modrm = next();
   int32_t* arg1 = effective_address(modrm);
   const int32_t arg2 = next32();
-  trace(90, "run") << "imm32 is 0x" << HEXWORD << arg2 << end();
+  trace(Callstack_depth+1, "run") << "imm32 is 0x" << HEXWORD << arg2 << end();
   const uint8_t subop = (modrm>>3)&0x7;  // middle 3 'reg opcode' bits
   switch (subop) {
   case 0:
-    trace(90, "run") << "subop add" << end();
+    trace(Callstack_depth+1, "run") << "subop add" << end();
     BINARY_ARITHMETIC_OP(+, *arg1, arg2);
     break;
   // End Op 81 Subops
@@ -68,7 +68,7 @@ put_new(Name, "2d", "subtract imm32 from EAX (sub)");
 :(before "End Single-Byte Opcodes")
 case 0x2d: {  // subtract imm32 from EAX
   const int32_t arg2 = next32();
-  trace(90, "run") << "subtract imm32 0x" << HEXWORD << arg2 << " from EAX" << end();
+  trace(Callstack_depth+1, "run") << "subtract imm32 0x" << HEXWORD << arg2 << " from EAX" << end();
   BINARY_ARITHMETIC_OP(-, Reg[EAX].i, arg2);
   break;
 }
@@ -91,7 +91,7 @@ case 0x2d: {  // subtract imm32 from EAX
 
 :(before "End Op 81 Subops")
 case 5: {
-  trace(90, "run") << "subop subtract" << end();
+  trace(Callstack_depth+1, "run") << "subop subtract" << end();
   BINARY_ARITHMETIC_OP(-, *arg1, arg2);
   break;
 }
@@ -129,12 +129,12 @@ put_new(Name, "c1", "shift rm32 by imm8 bits depending on subop (sal/sar/shl/shr
 :(before "End Single-Byte Opcodes")
 case 0xc1: {
   const uint8_t modrm = next();
-  trace(90, "run") << "operate on r/m32" << end();
+  trace(Callstack_depth+1, "run") << "operate on r/m32" << end();
   int32_t* arg1 = effective_address(modrm);
   const uint8_t subop = (modrm>>3)&0x7;  // middle 3 'reg opcode' bits
   switch (subop) {
   case 4: {  // shift left r/m32 by CL
-    trace(90, "run") << "subop: shift left by CL bits" << end();
+    trace(Callstack_depth+1, "run") << "subop: shift left by CL bits" << end();
     uint8_t count = next() & 0x1f;
     // OF is only defined if count is 1
     if (count == 1) {
@@ -145,7 +145,7 @@ case 0xc1: {
     *arg1 = (*arg1 << count);
     ZF = (*arg1 == 0);
     SF = (*arg1 < 0);
-    trace(90, "run") << "storing 0x" << HEXWORD << *arg1 << end();
+    trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *arg1 << end();
     break;
   }
   // End Op c1 Subops
@@ -171,14 +171,14 @@ case 0xc1: {
 
 :(before "End Op c1 Subops")
 case 7: {  // shift right r/m32 by CL, preserving sign
-  trace(90, "run") << "subop: shift right by CL bits, while preserving sign" << end();
+  trace(Callstack_depth+1, "run") << "subop: shift right by CL bits, while preserving sign" << end();
   uint8_t count = next() & 0x1f;
   *arg1 = (*arg1 >> count);
   ZF = (*arg1 == 0);
   SF = (*arg1 < 0);
   // OF is only defined if count is 1
   if (count == 1) OF = false;
-  trace(90, "run") << "storing 0x" << HEXWORD << *arg1 << end();
+  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *arg1 << end();
   break;
 }
 
@@ -221,7 +221,7 @@ case 7: {  // shift right r/m32 by CL, preserving sign
 
 :(before "End Op c1 Subops")
 case 5: {  // shift right r/m32 by CL, preserving sign
-  trace(90, "run") << "subop: shift right by CL bits, while padding zeroes" << end();
+  trace(Callstack_depth+1, "run") << "subop: shift right by CL bits, while padding zeroes" << end();
   uint8_t count = next() & 0x1f;
   // OF is only defined if count is 1
   if (count == 1) {
@@ -234,7 +234,7 @@ case 5: {  // shift right r/m32 by CL, preserving sign
   ZF = (*uarg1 == 0);
   // result is always positive by definition
   SF = false;
-  trace(90, "run") << "storing 0x" << HEXWORD << *arg1 << end();
+  trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *arg1 << end();
   break;
 }
 
@@ -277,7 +277,7 @@ put_new(Name, "25", "EAX = bitwise AND of imm32 with EAX (and)");
 :(before "End Single-Byte Opcodes")
 case 0x25: {  // and imm32 with EAX
   const int32_t arg2 = next32();
-  trace(90, "run") << "and imm32 0x" << HEXWORD << arg2 << " with EAX" << end();
+  trace(Callstack_depth+1, "run") << "and imm32 0x" << HEXWORD << arg2 << " with EAX" << end();
   BINARY_BITWISE_OP(&, Reg[EAX].i, arg2);
   break;
 }
@@ -300,7 +300,7 @@ ff 00 00 00  # 0xff
 
 :(before "End Op 81 Subops")
 case 4: {
-  trace(90, "run") << "subop and" << end();
+  trace(Callstack_depth+1, "run") << "subop and" << end();
   BINARY_BITWISE_OP(&, *arg1, arg2);
   break;
 }
@@ -335,7 +335,7 @@ put_new(Name, "0d", "EAX = bitwise OR of imm32 with EAX (or)");
 :(before "End Single-Byte Opcodes")
 case 0x0d: {  // or imm32 with EAX
   const int32_t arg2 = next32();
-  trace(90, "run") << "or imm32 0x" << HEXWORD << arg2 << " with EAX" << end();
+  trace(Callstack_depth+1, "run") << "or imm32 0x" << HEXWORD << arg2 << " with EAX" << end();
   BINARY_BITWISE_OP(|, Reg[EAX].i, arg2);
   break;
 }
@@ -358,7 +358,7 @@ a0 b0 c0 d0  # 0xd0c0b0a0
 
 :(before "End Op 81 Subops")
 case 1: {
-  trace(90, "run") << "subop or" << end();
+  trace(Callstack_depth+1, "run") << "subop or" << end();
   BINARY_BITWISE_OP(|, *arg1, arg2);
   break;
 }
@@ -391,7 +391,7 @@ put_new(Name, "35", "EAX = bitwise XOR of imm32 with EAX (xor)");
 :(before "End Single-Byte Opcodes")
 case 0x35: {  // xor imm32 with EAX
   const int32_t arg2 = next32();
-  trace(90, "run") << "xor imm32 0x" << HEXWORD << arg2 << " with EAX" << end();
+  trace(Callstack_depth+1, "run") << "xor imm32 0x" << HEXWORD << arg2 << " with EAX" << end();
   BINARY_BITWISE_OP(^, Reg[EAX].i, arg2);
   break;
 }
@@ -414,7 +414,7 @@ a0 b0 c0 d0  # 0xd0c0b0a0
 
 :(before "End Op 81 Subops")
 case 6: {
-  trace(90, "run") << "subop xor" << end();
+  trace(Callstack_depth+1, "run") << "subop xor" << end();
   BINARY_BITWISE_OP(^, *arg1, arg2);
   break;
 }
@@ -448,13 +448,13 @@ put_new(Name, "3d", "compare: set SF if EAX < imm32 (cmp)");
 case 0x3d: {  // compare EAX with imm32
   const int32_t arg1 = Reg[EAX].i;
   const int32_t arg2 = next32();
-  trace(90, "run") << "compare EAX and imm32 0x" << HEXWORD << arg2 << end();
+  trace(Callstack_depth+1, "run") << "compare EAX and imm32 0x" << HEXWORD << arg2 << end();
   const int32_t tmp1 = arg1 - arg2;
   SF = (tmp1 < 0);
   ZF = (tmp1 == 0);
   const int64_t tmp2 = arg1 - arg2;
   OF = (tmp1 != tmp2);
-  trace(90, "run") << "SF=" << SF << "; ZF=" << ZF << "; OF=" << OF << end();
+  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; OF=" << OF << end();
   break;
 }
 
@@ -489,13 +489,13 @@ case 0x3d: {  // compare EAX with imm32
 
 :(before "End Op 81 Subops")
 case 7: {
-  trace(90, "run") << "subop compare" << end();
+  trace(Callstack_depth+1, "run") << "subop compare" << end();
   const int32_t tmp1 = *arg1 - arg2;
   SF = (tmp1 < 0);
   ZF = (tmp1 == 0);
   const int64_t tmp2 = *arg1 - arg2;
   OF = (tmp1 != tmp2);
-  trace(90, "run") << "SF=" << SF << "; ZF=" << ZF << "; OF=" << OF << end();
+  trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; OF=" << OF << end();
   break;
 }
 
@@ -590,7 +590,7 @@ case 0xbe:
 case 0xbf: {  // copy imm32 to r32
   const uint8_t rdest = op & 0x7;
   const int32_t src = next32();
-  trace(90, "run") << "copy imm32 0x" << HEXWORD << src << " to " << rname(rdest) << end();
+  trace(Callstack_depth+1, "run") << "copy imm32 0x" << HEXWORD << src << " to " << rname(rdest) << end();
   Reg[rdest].i = src;
   break;
 }
@@ -613,7 +613,7 @@ put_new(Name, "c7", "copy imm32 to rm32 (mov)");
 :(before "End Single-Byte Opcodes")
 case 0xc7: {  // copy imm32 to r32
   const uint8_t modrm = next();
-  trace(90, "run") << "copy imm32 to r/m32" << end();
+  trace(Callstack_depth+1, "run") << "copy imm32 to r/m32" << end();
   const uint8_t subop = (modrm>>3)&0x7;  // middle 3 'reg opcode' bits
   if (subop != 0) {
     cerr << "unrecognized subop for opcode c7: " << NUM(subop) << " (only 0/copy currently implemented)\n";
@@ -621,7 +621,7 @@ case 0xc7: {  // copy imm32 to r32
   }
   int32_t* dest = effective_address(modrm);
   const int32_t src = next32();
-  trace(90, "run") << "imm32 is 0x" << HEXWORD << src << end();
+  trace(Callstack_depth+1, "run") << "imm32 is 0x" << HEXWORD << src << end();
   *dest = src;
   break;
 }
@@ -643,10 +643,10 @@ put_new(Name, "68", "push imm32 to stack (push)");
 :(before "End Single-Byte Opcodes")
 case 0x68: {
   const uint32_t val = static_cast<uint32_t>(next32());
-  trace(90, "run") << "push imm32 0x" << HEXWORD << val << end();
+  trace(Callstack_depth+1, "run") << "push imm32 0x" << HEXWORD << val << end();
 //?   cerr << "push: " << val << " => " << Reg[ESP].u << '\n';
   push(val);
-  trace(90, "run") << "ESP is now 0x" << HEXWORD << Reg[ESP].u << end();
-  trace(90, "run") << "contents at ESP: 0x" << HEXWORD << read_mem_u32(Reg[ESP].u) << end();
+  trace(Callstack_depth+1, "run") << "ESP is now 0x" << HEXWORD << Reg[ESP].u << end();
+  trace(Callstack_depth+1, "run") << "contents at ESP: 0x" << HEXWORD << read_mem_u32(Reg[ESP].u) << end();
   break;
 }
diff --git a/subx/016index_addressing.cc b/subx/016index_addressing.cc
index 0fe2aa71..9fb3e9bb 100644
--- a/subx/016index_addressing.cc
+++ b/subx/016index_addressing.cc
@@ -26,22 +26,22 @@ uint32_t effective_address_from_sib(uint8_t mod) {
   uint32_t addr = 0;
   if (base != EBP || mod != 0) {
     addr = Reg[base].u;
-    trace(90, "run") << "effective address is initially 0x" << HEXWORD << addr << " (" << rname(base) << ")" << end();
+    trace(Callstack_depth+1, "run") << "effective address is initially 0x" << HEXWORD << addr << " (" << rname(base) << ")" << end();
   }
   else {
     // base == EBP && mod == 0
     addr = next32();  // ignore base
-    trace(90, "run") << "effective address is initially 0x" << HEXWORD << addr << " (disp32)" << end();
+    trace(Callstack_depth+1, "run") << "effective address is initially 0x" << HEXWORD << addr << " (disp32)" << end();
   }
   const uint8_t index = (sib>>3)&0x7;
   if (index == ESP) {
     // ignore index and scale
-    trace(90, "run") << "effective address is 0x" << HEXWORD << addr << end();
+    trace(Callstack_depth+1, "run") << "effective address is 0x" << HEXWORD << addr << end();
   }
   else {
     const uint8_t scale = (1 << (sib>>6));
     addr += Reg[index].i*scale;  // treat index register as signed. Maybe base as well? But we'll always ensure it's non-negative.
-    trace(90, "run") << "effective address is 0x" << HEXWORD << addr << " (after adding " << rname(index) << "*" << NUM(scale) << ")" << end();
+    trace(Callstack_depth+1, "run") << "effective address is 0x" << HEXWORD << addr << " (after adding " << rname(index) << "*" << NUM(scale) << ")" << end();
   }
   return addr;
 }
diff --git a/subx/017jump_disp8.cc b/subx/017jump_disp8.cc
index b7bb9300..24467f5c 100644
--- a/subx/017jump_disp8.cc
+++ b/subx/017jump_disp8.cc
@@ -11,15 +11,15 @@ put_new(Name, "eb", "jump disp8 bytes away (jmp)");
   eb                05                        # skip 1 instruction
   05                              00 00 00 01
   05                              00 00 00 02
-+run: inst: 0x00000001
++run: 0x00000001 opcode: eb
 +run: jump 5
-+run: inst: 0x00000008
--run: inst: 0x00000003
++run: 0x00000008 opcode: 05
+-run: 0x00000003 opcode: 05
 
 :(before "End Single-Byte Opcodes")
 case 0xeb: {  // jump rel8
   int8_t offset = static_cast<int>(next());
-  trace(90, "run") << "jump " << NUM(offset) << end();
+  trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
   EIP += offset;
   break;
 }
@@ -36,16 +36,16 @@ put_new(Name, "74", "jump disp8 bytes away if equal, if ZF is set (jcc/jz/je)");
   74                05                        # skip 1 instruction
   05                              00 00 00 01
   05                              00 00 00 02
-+run: inst: 0x00000001
++run: 0x00000001 opcode: 74
 +run: jump 5
-+run: inst: 0x00000008
--run: inst: 0x00000003
++run: 0x00000008 opcode: 05
+-run: 0x00000003 opcode: 05
 
 :(before "End Single-Byte Opcodes")
 case 0x74: {  // jump rel8 if ZF
   const int8_t offset = static_cast<int>(next());
   if (ZF) {
-    trace(90, "run") << "jump " << NUM(offset) << end();
+    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
     EIP += offset;
   }
   break;
@@ -58,9 +58,9 @@ case 0x74: {  // jump rel8 if ZF
   74                05                        # skip 1 instruction
   05                              00 00 00 01
   05                              00 00 00 02
-+run: inst: 0x00000001
-+run: inst: 0x00000003
-+run: inst: 0x00000008
++run: 0x00000001 opcode: 74
++run: 0x00000003 opcode: 05
++run: 0x00000008 opcode: 05
 -run: jump 5
 
 //:: jump if not equal/not zero
@@ -75,16 +75,16 @@ put_new(Name, "75", "jump disp8 bytes away if not equal, if ZF is not set (jcc/j
   75                05                        # skip 1 instruction
   05                              00 00 00 01
   05                              00 00 00 02
-+run: inst: 0x00000001
++run: 0x00000001 opcode: 75
 +run: jump 5
-+run: inst: 0x00000008
--run: inst: 0x00000003
++run: 0x00000008 opcode: 05
+-run: 0x00000003 opcode: 05
 
 :(before "End Single-Byte Opcodes")
 case 0x75: {  // jump rel8 unless ZF
   const int8_t offset = static_cast<int>(next());
   if (!ZF) {
-    trace(90, "run") << "jump " << NUM(offset) << end();
+    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
     EIP += offset;
   }
   break;
@@ -97,9 +97,9 @@ case 0x75: {  // jump rel8 unless ZF
   75                05                        # skip 1 instruction
   05                              00 00 00 01
   05                              00 00 00 02
-+run: inst: 0x00000001
-+run: inst: 0x00000003
-+run: inst: 0x00000008
++run: 0x00000001 opcode: 75
++run: 0x00000003 opcode: 05
++run: 0x00000008 opcode: 05
 -run: jump 5
 
 //:: jump if greater
@@ -116,16 +116,16 @@ put_new(Name, "7f", "jump disp8 bytes away if greater, if ZF is unset and SF ==
   7f                05                        # skip 1 instruction
   05                              00 00 00 01
   05                              00 00 00 02
-+run: inst: 0x00000001
++run: 0x00000001 opcode: 7f
 +run: jump 5
-+run: inst: 0x00000008
--run: inst: 0x00000003
++run: 0x00000008 opcode: 05
+-run: 0x00000003 opcode: 05
 
 :(before "End Single-Byte Opcodes")
 case 0x7f: {  // jump rel8 if !SF and !ZF
   const int8_t offset = static_cast<int>(next());
   if (!ZF && SF == OF) {
-    trace(90, "run") << "jump " << NUM(offset) << end();
+    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
     EIP += offset;
   }
   break;
@@ -140,9 +140,9 @@ case 0x7f: {  // jump rel8 if !SF and !ZF
   7f                05                        # skip 1 instruction
   05                              00 00 00 01
   05                              00 00 00 02
-+run: inst: 0x00000001
-+run: inst: 0x00000003
-+run: inst: 0x00000008
++run: 0x00000001 opcode: 7f
++run: 0x00000003 opcode: 05
++run: 0x00000008 opcode: 05
 -run: jump 5
 
 //:: jump if greater or equal
@@ -158,16 +158,16 @@ put_new(Name, "7d", "jump disp8 bytes away if greater or equal, if SF == OF (jcc
   7d                05                        # skip 1 instruction
   05                              00 00 00 01
   05                              00 00 00 02
-+run: inst: 0x00000001
++run: 0x00000001 opcode: 7d
 +run: jump 5
-+run: inst: 0x00000008
--run: inst: 0x00000003
++run: 0x00000008 opcode: 05
+-run: 0x00000003 opcode: 05
 
 :(before "End Single-Byte Opcodes")
 case 0x7d: {  // jump rel8 if !SF
   const int8_t offset = static_cast<int>(next());
   if (SF == OF) {
-    trace(90, "run") << "jump " << NUM(offset) << end();
+    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
     EIP += offset;
   }
   break;
@@ -181,9 +181,9 @@ case 0x7d: {  // jump rel8 if !SF
   7d                05                        # skip 1 instruction
   05                              00 00 00 01
   05                              00 00 00 02
-+run: inst: 0x00000001
-+run: inst: 0x00000003
-+run: inst: 0x00000008
++run: 0x00000001 opcode: 7d
++run: 0x00000003 opcode: 05
++run: 0x00000008 opcode: 05
 -run: jump 5
 
 //:: jump if lesser
@@ -200,16 +200,16 @@ put_new(Name, "7c", "jump disp8 bytes away if lesser, if SF != OF (jcc/jl/jnge)"
   7c                05                        # skip 1 instruction
   05                              00 00 00 01
   05                              00 00 00 02
-+run: inst: 0x00000001
++run: 0x00000001 opcode: 7c
 +run: jump 5
-+run: inst: 0x00000008
--run: inst: 0x00000003
++run: 0x00000008 opcode: 05
+-run: 0x00000003 opcode: 05
 
 :(before "End Single-Byte Opcodes")
 case 0x7c: {  // jump rel8 if SF and !ZF
   const int8_t offset = static_cast<int>(next());
   if (SF != OF) {
-    trace(90, "run") << "jump " << NUM(offset) << end();
+    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
     EIP += offset;
   }
   break;
@@ -224,9 +224,9 @@ case 0x7c: {  // jump rel8 if SF and !ZF
   7c                05                        # skip 1 instruction
   05                              00 00 00 01
   05                              00 00 00 02
-+run: inst: 0x00000001
-+run: inst: 0x00000003
-+run: inst: 0x00000008
++run: 0x00000001 opcode: 7c
++run: 0x00000003 opcode: 05
++run: 0x00000008 opcode: 05
 -run: jump 5
 
 //:: jump if lesser or equal
@@ -243,10 +243,10 @@ put_new(Name, "7e", "jump disp8 bytes away if lesser or equal, if ZF is set or S
   7e                05                        # skip 1 instruction
   05                              00 00 00 01
   05                              00 00 00 02
-+run: inst: 0x00000001
++run: 0x00000001 opcode: 7e
 +run: jump 5
-+run: inst: 0x00000008
--run: inst: 0x00000003
++run: 0x00000008 opcode: 05
+-run: 0x00000003 opcode: 05
 
 :(scenario jle_rel8_lesser)
 % ZF = false;
@@ -257,16 +257,16 @@ put_new(Name, "7e", "jump disp8 bytes away if lesser or equal, if ZF is set or S
   7e                05                        # skip 1 instruction
   05                              00 00 00 01
   05                              00 00 00 02
-+run: inst: 0x00000001
++run: 0x00000001 opcode: 7e
 +run: jump 5
-+run: inst: 0x00000008
--run: inst: 0x00000003
++run: 0x00000008 opcode: 05
+-run: 0x00000003 opcode: 05
 
 :(before "End Single-Byte Opcodes")
 case 0x7e: {  // jump rel8 if SF or ZF
   const int8_t offset = static_cast<int>(next());
   if (ZF || SF != OF) {
-    trace(90, "run") << "jump " << NUM(offset) << end();
+    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
     EIP += offset;
   }
   break;
@@ -281,7 +281,7 @@ case 0x7e: {  // jump rel8 if SF or ZF
   7e                05                        # skip 1 instruction
   05                              00 00 00 01
   05                              00 00 00 02
-+run: inst: 0x00000001
-+run: inst: 0x00000003
-+run: inst: 0x00000008
++run: 0x00000001 opcode: 7e
++run: 0x00000003 opcode: 05
++run: 0x00000008 opcode: 05
 -run: jump 5
diff --git a/subx/018jump_disp32.cc b/subx/018jump_disp32.cc
index fd0bc33d..c86cd2df 100644
--- a/subx/018jump_disp32.cc
+++ b/subx/018jump_disp32.cc
@@ -11,15 +11,15 @@ put_new(Name, "e9", "jump disp32 bytes away (jmp)");
   e9                05 00 00 00               # skip 1 instruction
   05                              00 00 00 01
   05                              00 00 00 02
-+run: inst: 0x00000001
++run: 0x00000001 opcode: e9
 +run: jump 5
-+run: inst: 0x0000000b
--run: inst: 0x00000006
++run: 0x0000000b opcode: 05
+-run: 0x00000006 opcode: 05
 
 :(before "End Single-Byte Opcodes")
 case 0xe9: {  // jump disp32
   const int32_t offset = next32();
-  trace(90, "run") << "jump " << offset << end();
+  trace(Callstack_depth+1, "run") << "jump " << offset << end();
   EIP += offset;
   break;
 }
@@ -36,16 +36,16 @@ put_new(Name_0f, "84", "jump disp32 bytes away if equal, if ZF is set (jcc/jz/je
   0f 84                 05 00 00 00               # skip 1 instruction
   05                                  00 00 00 01
   05                                  00 00 00 02
-+run: inst: 0x00000001
++run: 0x00000001 opcode: 0f
 +run: jump 5
-+run: inst: 0x0000000c
--run: inst: 0x00000007
++run: 0x0000000c opcode: 05
+-run: 0x00000007 opcode: 05
 
 :(before "End Two-Byte Opcodes Starting With 0f")
 case 0x84: {  // jump disp32 if ZF
   const int32_t offset = next32();
   if (ZF) {
-    trace(90, "run") << "jump " << NUM(offset) << end();
+    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
     EIP += offset;
   }
   break;
@@ -58,9 +58,9 @@ case 0x84: {  // jump disp32 if ZF
   0f 84                 05 00 00 00               # skip 1 instruction
   05                                  00 00 00 01
   05                                  00 00 00 02
-+run: inst: 0x00000001
-+run: inst: 0x00000007
-+run: inst: 0x0000000c
++run: 0x00000001 opcode: 0f
++run: 0x00000007 opcode: 05
++run: 0x0000000c opcode: 05
 -run: jump 5
 
 //:: jump if not equal/not zero
@@ -75,16 +75,16 @@ put_new(Name_0f, "85", "jump disp32 bytes away if not equal, if ZF is not set (j
   0f 85                 05 00 00 00               # skip 1 instruction
   05                                  00 00 00 01
   05                                  00 00 00 02
-+run: inst: 0x00000001
++run: 0x00000001 opcode: 0f
 +run: jump 5
-+run: inst: 0x0000000c
--run: inst: 0x00000007
++run: 0x0000000c opcode: 05
+-run: 0x00000007 opcode: 05
 
 :(before "End Two-Byte Opcodes Starting With 0f")
 case 0x85: {  // jump disp32 unless ZF
   const int32_t offset = next32();
   if (!ZF) {
-    trace(90, "run") << "jump " << NUM(offset) << end();
+    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
     EIP += offset;
   }
   break;
@@ -97,9 +97,9 @@ case 0x85: {  // jump disp32 unless ZF
   0f 85                 05 00 00 00               # skip 1 instruction
   05                                  00 00 00 01
   05                                  00 00 00 02
-+run: inst: 0x00000001
-+run: inst: 0x00000007
-+run: inst: 0x0000000c
++run: 0x00000001 opcode: 0f
++run: 0x00000007 opcode: 05
++run: 0x0000000c opcode: 05
 -run: jump 5
 
 //:: jump if greater
@@ -116,16 +116,16 @@ put_new(Name_0f, "8f", "jump disp32 bytes away if greater, if ZF is unset and SF
   0f 8f                 05 00 00 00               # skip 1 instruction
   05                                  00 00 00 01
   05                                  00 00 00 02
-+run: inst: 0x00000001
++run: 0x00000001 opcode: 0f
 +run: jump 5
-+run: inst: 0x0000000c
--run: inst: 0x00000007
++run: 0x0000000c opcode: 05
+-run: 0x00000007 opcode: 05
 
 :(before "End Two-Byte Opcodes Starting With 0f")
 case 0x8f: {  // jump disp32 if !SF and !ZF
   const int32_t offset = next32();
   if (!ZF && SF == OF) {
-    trace(90, "run") << "jump " << NUM(offset) << end();
+    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
     EIP += offset;
   }
   break;
@@ -140,9 +140,9 @@ case 0x8f: {  // jump disp32 if !SF and !ZF
   0f 8f                 05 00 00 00               # skip 1 instruction
   05                                  00 00 00 01
   05                                  00 00 00 02
-+run: inst: 0x00000001
-+run: inst: 0x00000007
-+run: inst: 0x0000000c
++run: 0x00000001 opcode: 0f
++run: 0x00000007 opcode: 05
++run: 0x0000000c opcode: 05
 -run: jump 5
 
 //:: jump if greater or equal
@@ -158,16 +158,16 @@ put_new(Name_0f, "8d", "jump disp32 bytes away if greater or equal, if SF == OF
   0f 8d                 05 00 00 00               # skip 1 instruction
   05                                  00 00 00 01
   05                                  00 00 00 02
-+run: inst: 0x00000001
++run: 0x00000001 opcode: 0f
 +run: jump 5
-+run: inst: 0x0000000c
--run: inst: 0x00000007
++run: 0x0000000c opcode: 05
+-run: 0x00000007 opcode: 05
 
 :(before "End Two-Byte Opcodes Starting With 0f")
 case 0x8d: {  // jump disp32 if !SF
   const int32_t offset = next32();
   if (SF == OF) {
-    trace(90, "run") << "jump " << NUM(offset) << end();
+    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
     EIP += offset;
   }
   break;
@@ -181,9 +181,9 @@ case 0x8d: {  // jump disp32 if !SF
   0f 8d                 05 00 00 00               # skip 1 instruction
   05                                  00 00 00 01
   05                                  00 00 00 02
-+run: inst: 0x00000001
-+run: inst: 0x00000007
-+run: inst: 0x0000000c
++run: 0x00000001 opcode: 0f
++run: 0x00000007 opcode: 05
++run: 0x0000000c opcode: 05
 -run: jump 5
 
 //:: jump if lesser
@@ -200,16 +200,16 @@ put_new(Name_0f, "8c", "jump disp32 bytes away if lesser, if SF != OF (jcc/jl/jn
   0f 8c                 05 00 00 00               # skip 1 instruction
   05                                  00 00 00 01
   05                                  00 00 00 02
-+run: inst: 0x00000001
++run: 0x00000001 opcode: 0f
 +run: jump 5
-+run: inst: 0x0000000c
--run: inst: 0x00000007
++run: 0x0000000c opcode: 05
+-run: 0x00000007 opcode: 05
 
 :(before "End Two-Byte Opcodes Starting With 0f")
 case 0x8c: {  // jump disp32 if SF and !ZF
   const int32_t offset = next32();
   if (SF != OF) {
-    trace(90, "run") << "jump " << NUM(offset) << end();
+    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
     EIP += offset;
   }
   break;
@@ -224,9 +224,9 @@ case 0x8c: {  // jump disp32 if SF and !ZF
   0f 8c                 05 00 00 00               # skip 1 instruction
   05                                  00 00 00 01
   05                                  00 00 00 02
-+run: inst: 0x00000001
-+run: inst: 0x00000007
-+run: inst: 0x0000000c
++run: 0x00000001 opcode: 0f
++run: 0x00000007 opcode: 05
++run: 0x0000000c opcode: 05
 -run: jump 5
 
 //:: jump if lesser or equal
@@ -243,10 +243,10 @@ put_new(Name_0f, "8e", "jump disp32 bytes away if lesser or equal, if ZF is set
   0f 8e                 05 00 00 00               # skip 1 instruction
   05                                  00 00 00 01
   05                                  00 00 00 02
-+run: inst: 0x00000001
++run: 0x00000001 opcode: 0f
 +run: jump 5
-+run: inst: 0x0000000c
--run: inst: 0x00000007
++run: 0x0000000c opcode: 05
+-run: 0x00000007 opcode: 05
 
 :(scenario jle_disp32_lesser)
 % ZF = false;
@@ -257,16 +257,16 @@ put_new(Name_0f, "8e", "jump disp32 bytes away if lesser or equal, if ZF is set
   0f 8e                 05 00 00 00               # skip 1 instruction
   05                                  00 00 00 01
   05                                  00 00 00 02
-+run: inst: 0x00000001
++run: 0x00000001 opcode: 0f
 +run: jump 5
-+run: inst: 0x0000000c
--run: inst: 0x00000007
++run: 0x0000000c opcode: 05
+-run: 0x00000007 opcode: 05
 
 :(before "End Two-Byte Opcodes Starting With 0f")
 case 0x8e: {  // jump disp32 if SF or ZF
   const int32_t offset = next32();
   if (ZF || SF != OF) {
-    trace(90, "run") << "jump " << NUM(offset) << end();
+    trace(Callstack_depth+1, "run") << "jump " << NUM(offset) << end();
     EIP += offset;
   }
   break;
@@ -281,7 +281,7 @@ case 0x8e: {  // jump disp32 if SF or ZF
   0f 8e                 05 00 00 00               # skip 1 instruction
   05                                  00 00 00 01
   05                                  00 00 00 02
-+run: inst: 0x00000001
-+run: inst: 0x00000007
-+run: inst: 0x0000000c
++run: 0x00000001 opcode: 0f
++run: 0x00000007 opcode: 05
++run: 0x0000000c opcode: 05
 -run: jump 5
diff --git a/subx/019functions.cc b/subx/019functions.cc
index bdeafad1..66cfe384 100644
--- a/subx/019functions.cc
+++ b/subx/019functions.cc
@@ -17,11 +17,12 @@ put_new(Name, "e8", "call disp32 (call)");
 :(before "End Single-Byte Opcodes")
 case 0xe8: {  // call disp32 relative to next EIP
   const int32_t offset = next32();
-  trace(90, "run") << "call imm32 0x" << HEXWORD << offset << end();
+  ++Callstack_depth;
+  trace(Callstack_depth+1, "run") << "call imm32 0x" << HEXWORD << offset << end();
 //?   cerr << "push: EIP: " << EIP << " => " << Reg[ESP].u << '\n';
   push(EIP);
   EIP += offset;
-  trace(90, "run") << "jumping to 0x" << HEXWORD << EIP << end();
+  trace(Callstack_depth+1, "run") << "jumping to 0x" << HEXWORD << EIP << end();
   break;
 }
 
@@ -42,11 +43,12 @@ case 0xe8: {  // call disp32 relative to next EIP
 
 :(before "End Op ff Subops")
 case 2: {  // call function pointer at r/m32
-  trace(90, "run") << "call to r/m32" << end();
+  trace(Callstack_depth+1, "run") << "call to r/m32" << end();
   const int32_t* offset = effective_address(modrm);
   push(EIP);
   EIP += *offset;
-  trace(90, "run") << "jumping to 0x" << HEXWORD << EIP << end();
+  trace(Callstack_depth+1, "run") << "jumping to 0x" << HEXWORD << EIP << end();
+  ++Callstack_depth;
   break;
 }
 
@@ -83,8 +85,9 @@ put_new(Name, "c3", "return from most recent unfinished call (ret)");
 
 :(before "End Single-Byte Opcodes")
 case 0xc3: {  // return from a call
-  trace(90, "run") << "return" << end();
+  trace(Callstack_depth+1, "run") << "return" << end();
+  --Callstack_depth;
   EIP = pop();
-  trace(90, "run") << "jumping to 0x" << HEXWORD << EIP << end();
+  trace(Callstack_depth+1, "run") << "jumping to 0x" << HEXWORD << EIP << end();
   break;
 }
diff --git a/subx/020syscalls.cc b/subx/020syscalls.cc
index 675fb526..a17e9525 100644
--- a/subx/020syscalls.cc
+++ b/subx/020syscalls.cc
@@ -3,7 +3,7 @@ put_new(Name, "cd", "software interrupt (int)");
 
 :(before "End Single-Byte Opcodes")
 case 0xcd: {  // int imm8 (software interrupt)
-  trace(90, "run") << "syscall" << end();
+  trace(Callstack_depth+1, "run") << "syscall" << end();
   uint8_t code = next();
   if (code != 0x80) {
     raise << "Unimplemented interrupt code " << HEXBYTE << code << '\n' << end();
@@ -21,67 +21,67 @@ void process_int80() {
     exit(/*exit code*/Reg[EBX].u);
     break;
   case 3:
-    trace(91, "run") << "read: " << Reg[EBX].u << ' ' << Reg[ECX].u << ' ' << Reg[EDX].u << end();
+    trace(Callstack_depth+1, "run") << "read: " << Reg[EBX].u << ' ' << Reg[ECX].u << ' ' << Reg[EDX].u << end();
     Reg[EAX].i = read(/*file descriptor*/Reg[EBX].u, /*memory buffer*/mem_addr_u8(Reg[ECX].u), /*size*/Reg[EDX].u);
-    trace(91, "run") << "result: " << Reg[EAX].i << end();
+    trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].i << end();
     if (Reg[EAX].i == -1) raise << strerror(errno) << '\n' << end();
     break;
   case 4:
-    trace(91, "run") << "write: " << Reg[EBX].u << ' ' << Reg[ECX].u << ' ' << Reg[EDX].u << end();
-    trace(91, "run") << Reg[ECX].u << " => " << mem_addr_string(Reg[ECX].u, Reg[EDX].u) << end();
+    trace(Callstack_depth+1, "run") << "write: " << Reg[EBX].u << ' ' << Reg[ECX].u << ' ' << Reg[EDX].u << end();
+    trace(Callstack_depth+1, "run") << Reg[ECX].u << " => " << mem_addr_string(Reg[ECX].u, Reg[EDX].u) << end();
     Reg[EAX].i = write(/*file descriptor*/Reg[EBX].u, /*memory buffer*/mem_addr_u8(Reg[ECX].u), /*size*/Reg[EDX].u);
-    trace(91, "run") << "result: " << Reg[EAX].i << end();
+    trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].i << end();
     if (Reg[EAX].i == -1) raise << strerror(errno) << '\n' << end();
     break;
   case 5: {
     check_flags(ECX);
     check_mode(EDX);
-    trace(91, "run") << "open: " << Reg[EBX].u << ' ' << Reg[ECX].u << end();
-    trace(91, "run") << Reg[EBX].u << " => " << mem_addr_kernel_string(Reg[EBX].u) << end();
+    trace(Callstack_depth+1, "run") << "open: " << Reg[EBX].u << ' ' << Reg[ECX].u << end();
+    trace(Callstack_depth+1, "run") << Reg[EBX].u << " => " << mem_addr_kernel_string(Reg[EBX].u) << end();
     Reg[EAX].i = open(/*filename*/mem_addr_kernel_string(Reg[EBX].u), /*flags*/Reg[ECX].u, /*mode*/0640);
-    trace(91, "run") << "result: " << Reg[EAX].i << end();
+    trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].i << end();
     if (Reg[EAX].i == -1) raise << strerror(errno) << '\n' << end();
     break;
   }
   case 6:
-    trace(91, "run") << "close: " << Reg[EBX].u << end();
+    trace(Callstack_depth+1, "run") << "close: " << Reg[EBX].u << end();
     Reg[EAX].i = close(/*file descriptor*/Reg[EBX].u);
-    trace(91, "run") << "result: " << Reg[EAX].i << end();
+    trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].i << end();
     if (Reg[EAX].i == -1) raise << strerror(errno) << '\n' << end();
     break;
   case 8:
     check_mode(ECX);
-    trace(91, "run") << "creat: " << Reg[EBX].u << end();
-    trace(91, "run") << Reg[EBX].u << " => " << mem_addr_kernel_string(Reg[EBX].u) << end();
+    trace(Callstack_depth+1, "run") << "creat: " << Reg[EBX].u << end();
+    trace(Callstack_depth+1, "run") << Reg[EBX].u << " => " << mem_addr_kernel_string(Reg[EBX].u) << end();
     Reg[EAX].i = creat(/*filename*/mem_addr_kernel_string(Reg[EBX].u), /*mode*/0640);
-    trace(91, "run") << "result: " << Reg[EAX].i << end();
+    trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].i << end();
     if (Reg[EAX].i == -1) raise << strerror(errno) << '\n' << end();
     break;
   case 10:
-    trace(91, "run") << "unlink: " << Reg[EBX].u << end();
-    trace(91, "run") << Reg[EBX].u << " => " << mem_addr_kernel_string(Reg[EBX].u) << end();
+    trace(Callstack_depth+1, "run") << "unlink: " << Reg[EBX].u << end();
+    trace(Callstack_depth+1, "run") << Reg[EBX].u << " => " << mem_addr_kernel_string(Reg[EBX].u) << end();
     Reg[EAX].i = unlink(/*filename*/mem_addr_kernel_string(Reg[EBX].u));
-    trace(91, "run") << "result: " << Reg[EAX].i << end();
+    trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].i << end();
     if (Reg[EAX].i == -1) raise << strerror(errno) << '\n' << end();
     break;
   case 38:
-    trace(91, "run") << "rename: " << Reg[EBX].u << " -> " << Reg[ECX].u << end();
-    trace(91, "run") << Reg[EBX].u << " => " << mem_addr_kernel_string(Reg[EBX].u) << end();
-    trace(91, "run") << Reg[ECX].u << " => " << mem_addr_kernel_string(Reg[ECX].u) << end();
+    trace(Callstack_depth+1, "run") << "rename: " << Reg[EBX].u << " -> " << Reg[ECX].u << end();
+    trace(Callstack_depth+1, "run") << Reg[EBX].u << " => " << mem_addr_kernel_string(Reg[EBX].u) << end();
+    trace(Callstack_depth+1, "run") << Reg[ECX].u << " => " << mem_addr_kernel_string(Reg[ECX].u) << end();
     Reg[EAX].i = rename(/*old filename*/mem_addr_kernel_string(Reg[EBX].u), /*new filename*/mem_addr_kernel_string(Reg[ECX].u));
-    trace(91, "run") << "result: " << Reg[EAX].i << end();
+    trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].i << end();
     if (Reg[EAX].i == -1) raise << strerror(errno) << '\n' << end();
     break;
   case 45:  // brk: modify size of data segment
-    trace(91, "run") << "grow data segment to " << Reg[EBX].u << end();
+    trace(Callstack_depth+1, "run") << "grow data segment to " << Reg[EBX].u << end();
     grow_data_segment(/*new end address*/Reg[EBX].u);
     break;
   case 90:  // mmap: allocate memory outside existing segment allocations
-    trace(91, "run") << "mmap: allocate new segment" << end();
+    trace(Callstack_depth+1, "run") << "mmap: allocate new segment" << end();
     // Ignore most arguments for now: address hint, protection flags, sharing flags, fd, offset.
     // We only support anonymous maps.
     Reg[EAX].u = new_segment(/*length*/read_mem_u32(Reg[EBX].u+0x4));
-    trace(91, "run") << "result: " << Reg[EAX].u << end();
+    trace(Callstack_depth+1, "run") << "result: " << Reg[EAX].u << end();
     break;
   default:
     raise << HEXWORD << EIP << ": unimplemented syscall " << Reg[EAX].u << '\n' << end();
diff --git a/subx/021byte_addressing.cc b/subx/021byte_addressing.cc
index fba9a827..c96bc9c1 100644
--- a/subx/021byte_addressing.cc
+++ b/subx/021byte_addressing.cc
@@ -23,7 +23,7 @@ uint8_t* effective_byte_address(uint8_t modrm) {
   uint8_t rm = modrm & 0x7;
   if (mod == 3) {
     // select an 8-bit register
-    trace(90, "run") << "r/m8 is " << rname_8bit(rm) << end();
+    trace(Callstack_depth+1, "run") << "r/m8 is " << rname_8bit(rm) << end();
     return reg_8bit(rm);
   }
   // the rest is as usual
@@ -58,12 +58,12 @@ f0 cc bb aa
 case 0x88: {  // copy r8 to r/m8
   const uint8_t modrm = next();
   const uint8_t rsrc = (modrm>>3)&0x7;
-  trace(90, "run") << "copy " << rname_8bit(rsrc) << " to r8/m8-at-r32" << end();
+  trace(Callstack_depth+1, "run") << "copy " << rname_8bit(rsrc) << " to r8/m8-at-r32" << end();
   // use unsigned to zero-extend 8-bit value to 32 bits
   uint8_t* dest = reinterpret_cast<uint8_t*>(effective_byte_address(modrm));
   const uint8_t* src = reg_8bit(rsrc);
   *dest = *src;
-  trace(90, "run") << "storing 0x" << HEXBYTE << NUM(*dest) << end();
+  trace(Callstack_depth+1, "run") << "storing 0x" << HEXBYTE << NUM(*dest) << end();
   break;
 }
 
@@ -91,14 +91,14 @@ ab ff ff ff  # 0xab with more data in following bytes
 case 0x8a: {  // copy r/m8 to r8
   const uint8_t modrm = next();
   const uint8_t rdest = (modrm>>3)&0x7;
-  trace(90, "run") << "copy r8/m8-at-r32 to " << rname_8bit(rdest) << end();
+  trace(Callstack_depth+1, "run") << "copy r8/m8-at-r32 to " << rname_8bit(rdest) << end();
   // use unsigned to zero-extend 8-bit value to 32 bits
   const uint8_t* src = reinterpret_cast<uint8_t*>(effective_byte_address(modrm));
   uint8_t* dest = reg_8bit(rdest);
-  trace(90, "run") << "storing 0x" << HEXBYTE << NUM(*src) << end();
+  trace(Callstack_depth+1, "run") << "storing 0x" << HEXBYTE << NUM(*src) << end();
   *dest = *src;
   const uint8_t rdest_32bit = rdest & 0x3;
-  trace(90, "run") << rname(rdest_32bit) << " now contains 0x" << HEXWORD << Reg[rdest_32bit].u << end();
+  trace(Callstack_depth+1, "run") << rname(rdest_32bit) << " now contains 0x" << HEXWORD << Reg[rdest_32bit].u << end();
   break;
 }
 
@@ -137,8 +137,8 @@ f0 cc bb aa
 case 0xc6: {  // copy imm8 to r/m8
   const uint8_t modrm = next();
   const uint8_t src = next();
-  trace(90, "run") << "copy imm8 to r8/m8-at-r32" << end();
-  trace(90, "run") << "imm8 is 0x" << HEXWORD << src << end();
+  trace(Callstack_depth+1, "run") << "copy imm8 to r8/m8-at-r32" << end();
+  trace(Callstack_depth+1, "run") << "imm8 is 0x" << HEXWORD << src << end();
   const uint8_t subop = (modrm>>3)&0x7;  // middle 3 'reg opcode' bits
   if (subop != 0) {
     cerr << "unrecognized subop for opcode c6: " << NUM(subop) << " (only 0/copy currently implemented)\n";
@@ -147,6 +147,6 @@ case 0xc6: {  // copy imm8 to r/m8
   // use unsigned to zero-extend 8-bit value to 32 bits
   uint8_t* dest = reinterpret_cast<uint8_t*>(effective_byte_address(modrm));
   *dest = src;
-  trace(90, "run") << "storing 0x" << HEXBYTE << NUM(*dest) << end();
+  trace(Callstack_depth+1, "run") << "storing 0x" << HEXBYTE << NUM(*dest) << end();
   break;
 }
diff --git a/subx/028translate.cc b/subx/028translate.cc
index d4b1a3a5..42b94632 100644
--- a/subx/028translate.cc
+++ b/subx/028translate.cc
@@ -34,6 +34,7 @@ if (is_equal(argv[1], "translate")) {
       output_filename = argv[i];
     }
     else {
+      trace(2, "parse") << argv[i] << end();
       ifstream fin(argv[i]);
       if (!fin) {
         cerr << "could not open " << argv[i] << '\n';
@@ -53,8 +54,10 @@ if (is_equal(argv[1], "translate")) {
     cerr << "must provide a filename to write to using '-o'\n";
     exit(1);
   }
+  trace(2, "transform") << "begin" << end();
   transform(p);
   if (trace_contains_errors()) return 1;
+  trace(2, "translate") << "begin" << end();
   save_elf(p, output_filename);
   if (trace_contains_errors()) {
     unlink(output_filename.c_str());
diff --git a/subx/030---operands.cc b/subx/030---operands.cc
index 6d156035..085cf1b2 100644
--- a/subx/030---operands.cc
+++ b/subx/030---operands.cc
@@ -156,7 +156,7 @@ void pack_operands(program& p) {
   if (p.segments.empty()) return;
   segment& code = p.segments.at(0);
   // Pack Operands(segment code)
-  trace(99, "transform") << "-- pack operands" << end();
+  trace(3, "transform") << "-- pack operands" << end();
   for (int i = 0;  i < SIZE(code.lines);  ++i) {
     line& inst = code.lines.at(i);
     if (all_hex_bytes(inst)) continue;
diff --git a/subx/031check_operands.cc b/subx/031check_operands.cc
index 3f3b780a..bc76fca5 100644
--- a/subx/031check_operands.cc
+++ b/subx/031check_operands.cc
@@ -13,7 +13,7 @@ if (trace_contains_errors()) return;
 
 :(code)
 void check_operands(const segment& code) {
-  trace(99, "transform") << "-- check operands" << end();
+  trace(3, "transform") << "-- check operands" << end();
   for (int i = 0;  i < SIZE(code.lines);  ++i) {
     check_operands(code.lines.at(i));
     if (trace_contains_errors()) return;  // stop at the first mal-formed instruction
diff --git a/subx/032check_operand_bounds.cc b/subx/032check_operand_bounds.cc
index b2276a8c..58b7c7b9 100644
--- a/subx/032check_operand_bounds.cc
+++ b/subx/032check_operand_bounds.cc
@@ -27,7 +27,7 @@ check_operand_bounds(code);
 if (trace_contains_errors()) return;
 :(code)
 void check_operand_bounds(const segment& code) {
-  trace(99, "transform") << "-- check operand bounds" << end();
+  trace(3, "transform") << "-- check operand bounds" << end();
   for (int i = 0;  i < SIZE(code.lines);  ++i) {
     const line& inst = code.lines.at(i);
     for (int j = first_operand(inst);  j < SIZE(inst.words);  ++j)
diff --git a/subx/034compute_segment_address.cc b/subx/034compute_segment_address.cc
index 2798e040..79c6e45d 100644
--- a/subx/034compute_segment_address.cc
+++ b/subx/034compute_segment_address.cc
@@ -31,7 +31,7 @@ Currently_parsing_segment_index = -1;
 if (!starts_with(segment_title, "0x")) {
   Currently_parsing_named_segment = true;
   if (!contains_key(Segment_index, segment_title)) {
-    trace(99, "parse") << "new segment '" << segment_title << "'" << end();
+    trace(3, "parse") << "new segment '" << segment_title << "'" << end();
     if (out.segments.empty() && segment_title != "code") {
       raise << "first segment must be 'code' but is '" << segment_title << "'\n" << end();
       return;
@@ -44,7 +44,7 @@ if (!starts_with(segment_title, "0x")) {
     out.segments.push_back(segment());
   }
   else {
-    trace(99, "parse") << "appending to segment '" << segment_title << "'" << end();
+    trace(3, "parse") << "appending to segment '" << segment_title << "'" << end();
   }
   Currently_parsing_segment_index = get(Segment_index, segment_title);
 }
@@ -52,7 +52,7 @@ if (!starts_with(segment_title, "0x")) {
 :(before "End flush(p, lines) Special-cases")
 if (Currently_parsing_named_segment) {
   assert(!p.segments.empty());
-  trace(99, "parse") << "flushing segment" << end();
+  trace(3, "parse") << "flushing segment" << end();
   vector<line>& curr_segment_data = p.segments.at(Currently_parsing_segment_index).lines;
   curr_segment_data.insert(curr_segment_data.end(), lines.begin(), lines.end());
   lines.clear();
@@ -107,7 +107,7 @@ Transform.push_back(compute_segment_starts);
 
 :(code)
 void compute_segment_starts(program& p) {
-  trace(99, "transform") << "-- compute segment addresses" << end();
+  trace(3, "transform") << "-- compute segment addresses" << end();
   uint32_t p_offset = /*size of ehdr*/0x34 + SIZE(p.segments)*0x20/*size of each phdr*/;
   for (size_t i = 0;  i < p.segments.size();  ++i) {
     segment& curr = p.segments.at(i);
diff --git a/subx/035labels.cc b/subx/035labels.cc
index e86441cf..4c5ea641 100644
--- a/subx/035labels.cc
+++ b/subx/035labels.cc
@@ -26,8 +26,8 @@
 05 0x0d0c0b0a/imm32
 Entry:
 05 0x0d0c0b0a/imm32
-+run: inst: 0x00000006
--run: inst: 0x00000001
++run: 0x00000006 opcode: 05
+-run: 0x00000001 opcode: 05
 
 :(before "End Globals")
 uint32_t Entry_address = 0;
@@ -97,7 +97,7 @@ loop:
 Transform.push_back(rewrite_labels);
 :(code)
 void rewrite_labels(program& p) {
-  trace(99, "transform") << "-- rewrite labels" << end();
+  trace(3, "transform") << "-- rewrite labels" << end();
   if (p.segments.empty()) return;
   segment& code = p.segments.at(0);
   map<string, int32_t> byte_index;  // values are unsigned, but we're going to do subtractions on them so they need to fit in 31 bits
diff --git a/subx/036global_variables.cc b/subx/036global_variables.cc
index 78824262..2d20ca43 100644
--- a/subx/036global_variables.cc
+++ b/subx/036global_variables.cc
@@ -18,7 +18,7 @@ x:
 Transform.push_back(rewrite_global_variables);
 :(code)
 void rewrite_global_variables(program& p) {
-  trace(99, "transform") << "-- rewrite global variables" << end();
+  trace(3, "transform") << "-- rewrite global variables" << end();
   // Begin rewrite_global_variables
   map<string, uint32_t> address;
   compute_addresses_for_global_variables(p, address);
diff --git a/subx/038---literal_strings.cc b/subx/038---literal_strings.cc
index 7077a685..ce46a119 100644
--- a/subx/038---literal_strings.cc
+++ b/subx/038---literal_strings.cc
@@ -23,7 +23,7 @@ Transform.push_back(transform_literal_strings);
 int Next_auto_global = 1;
 :(code)
 void transform_literal_strings(program& p) {
-  trace(99, "transform") << "-- move literal strings to data segment" << end();
+  trace(3, "transform") << "-- move literal strings to data segment" << end();
   if (p.segments.empty()) return;
   segment& code = p.segments.at(0);
   segment data;
diff --git a/subx/039debug.cc b/subx/039debug.cc
index 4b7e3363..9078acf2 100644
--- a/subx/039debug.cc
+++ b/subx/039debug.cc
@@ -22,7 +22,7 @@ void load_map(const string& map_filename) {
 
 :(after "Run One Instruction")
 if (contains_key(Symbol_name, EIP))
-  trace(90, "run") << "== label " << get(Symbol_name, EIP) << end();
+  trace(Callstack_depth, "run") << "== label " << get(Symbol_name, EIP) << end();
 
 //: If a label starts with '$watch-', make a note of the effective address
 //: computed by the next instruction. Start dumping out its contents to the
diff --git a/subx/040---tests.cc b/subx/040---tests.cc
index 24819993..e6f2f489 100644
--- a/subx/040---tests.cc
+++ b/subx/040---tests.cc
@@ -26,13 +26,13 @@ test-foo:  # offset 7
   c3/return
 
 # check that code in test-foo ran (implicitly called by run-tests)
-+run: inst: 0x00000007
++run: 0x00000007 opcode: 01
 
 :(code)
 void create_test_function(program& p) {
   if (p.segments.empty()) return;
   segment& code = p.segments.at(0);
-  trace(99, "transform") << "-- create 'run-tests'" << end();
+  trace(3, "transform") << "-- create 'run-tests'" << end();
   vector<line> new_insts;
   for (int i = 0;  i < SIZE(code.lines);  ++i) {
     line& inst = code.lines.at(i);
diff --git a/subx/drun b/subx/drun
index 82584772..15c7f747 100755
--- a/subx/drun
+++ b/subx/drun
@@ -12,9 +12,9 @@ fi
 
 if [[ $1 == 'ex'* ]]
 then
-  CFLAGS=-g ./subx --map --trace run examples/$* 2>x
+  CFLAGS=-g ./subx --map --trace run examples/$*
   exit $?
 fi
 
-CFLAGS=-g ./subx --map --trace run apps/$* 2>x
+CFLAGS=-g ./subx --map --trace run apps/$*
 exit $?